Why is #first always false in my handlebars #each loop? - javascript

Using Ember.Handlebars, I'm trying to set up a Bootstrap carousel to showcase a selection of issues from my data store.
I want to add the active class on the first item of my carousel, but it sounds like my {{if #first}} test is never evaluated true.
My app/templates/issues-list.hbs is :
<div id="showcase-issues" class="carousel slide">
<div class="carousel-inner">
{{#each model.issues as |issue|}}
{{#if #first}}
<div class="active item">
{{#issue-item issue=issue}}{{body}}{{/issue-item}}
</div>
{{else}}
<div class="item">
{{#issue-item issue=issue}}{{body}}{{/issue-item}}
</div>
{{/if}}
{{/each}}
</div>
{{!-- Nav buttons, etc. ... --}}
</div>
My app/routes/issues-list.js is simply :
model: function() {
var issues = this.get('store').filter('issue', {
filters: { "state" : "visible" }
}, function(item) {
return (item.get('state') === "visible") ;
}).then( function(results) {
return results ;
}) ;
return Ember.RSVP.hash({issues: issues}) ;
}
Did anyone already face this problem ?
Is there a proper way to handle DS.FilteredRecordArrays in a {{#each ...}} helper ?
I'm working with :
Ember 1.13.2
Ember-data 1.13.5
Bootstrap 3.3.5
Thanks a lot for any given hand !

#index and #first isn't available in htmlbars. You can however:
{{#each model.issues as |issue index|}}
<div class="item {{unless index 'active'}}">
{{#issue-item issue=issue}}{{body}}{{/issue-item}}
</div>
{{/each}}
See: http://jsbin.com/zupezap/2/edit?html,css,js,output

Related

Conditionally hide the nth element of a v-for loop without modifying the array. vue 3 composition api search function

I have a ref variable (foxArticles ), which holds a list that contains 100 items. In a v-for loop i loop over each value. As a result, i have 100 values rendered on the page.
<template>
<div class="news_container">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput)"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
const foxArticles = ref([]);
</script>
I also have a search function, which returns the value, if it includes the passed in keyword. The function is used in the child of the v-for loop.
<div class="search_input_container">
<input
type="text"
class="search_input"
v-model="keywordInput"
/>
</div>
<script>
const keywordInput = ref("");
function containsKeyword(article, keywordInput) {
if (article.title.toLowerCase().includes(keywordInput.toLowerCase())) {
return article;
}
}
</script>
The problem is, i can't use .slice() on the foxArticles array in the v-for loop, because that screws up the search functionality, as it returns only the values from the sliced range.
How can i have the access the all of the values of the array, while not rendering all 100 of returned articles on the initial load?
Any suggestions?
I think your approach will make it incredibly complex to achieve. It would be simpler to always iterate over some set, this set is either filtered based on a search-term, or it will be the first 100 items.
I'm not very familiar yet with the Vue 3 composition api so I'll demonstrate with a regular (vue 2) component.
<template>
<div class="news_container">
<div
v-for="article in matchingArticles"
v-bind:key="article"
class="article_single_cell"
>
... news_box ...
</div>
</div>
</template>
<script>
export default {
...
computed: {
matchingArticles() {
var articles = this.foxArticles;
if (this.keywordInput) {
articles = articles.filter(article => {
return this.containsKeyword(article, this.keywordInput)
})
} else {
// we will limit the result to 100
articles = articles.slice(0, 100);
}
// you may want to always limit results to 100
// but i'll leave that up to you.
return articles;
}
},
....
}
</script>
Another benefit is that the template does not need to worry about filtering results.
ok, so i kind of came up with another solution, for which you don't have to change the script part...
instead of having one v-for loop , you can make two of them, where each one is wrapped in a v-if statement div
The first v-if statement says, If the client has not used the search (keywordInput == ''), display articles in the range of (index, index)
The second one says = If the user has written something (keywordInput != ''), return those articles.
<template>
<div class="news_container">
<!-- if no search has been done -->
<div v-if="keywordInput == ''">
<div
v-for="article in foxArticles.slice(0, 4)"
v-bind:key="article"
class="article_single_cell"
>
<div class="news_box shadow hover:bg-red-100 ">
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
<!-- if searched something -->
<div v-else-if="keywordInput != ''">
<div
v-for="article in foxArticles"
v-bind:key="article"
class="article_single_cell"
>
<div
class="news_box shadow hover:bg-red-100 "
v-if="containsKeyword(article, keywordInput) && keywordInput != ''"
>
<div class="news_box_right">
<div class="news_headline text-red-500">
<a :href="article.url" target="_blank">
{{ article.title }}
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
im not sure how this impacts performance tho, but that's a problem for another day

How to target an item inside ngFor with introjs

I need to use introjs to tour new features on an angular 6 app.
Inside the tour some step target items in ngFor in a child component of the component where I start introjs. The library is able to target child component html elements but seems to not be able to target element inside ngFor. The usage I want is simple I have a li that represent a card item I loop on as :
<div *ngFor="let source of sourcesDisplay; let idx = index" class="col-lg-5ths col-md-4 col-sm-6 col-xs-12">
<!-- item -->
<div class="item blue-light">
<div class="header">
<div class="categories">
<div class="truncat">{{source.categories.length > 0? source.categories : 'NO CATEGORY'}}</div>
</div>
<a routerLink="{{ organization?.name | routes: 'sources' : source.id }}" class="title">
<div class="truncat">{{source.name}}</div>
</a>
<div class="corner-ribbon blue-light" *ngIf="isARecentSource(source.createdAt)">New</div>
<div class="corner-fav" [class.is-active]="isSourceFavorite(source.id)"
(click)="toggleFavorite(source.id)"><em class="icon-heart-o"></em></div>
</div>
<a class="content" routerLink="{{ organization?.name | routes: 'sources' : source.id }}">
<div class="icon">
<em [ngClass]="source.icon? 'icon-'+source.icon : 'icon-cocktail3'"></em>
</div>
</a>
<div class="actions">
<ul class="buttons three">
<li class="icon-font" ><a (click)="openDashboardModal(source)"
[class.disable]="source.powerbi.length === 0" class="btn-effect"><em
class="icon-chart-bar"></em></a></li>
<li class="icon-font"><a
(click)="openDatalakeModal(source)" [class.disable]="source.datalakePath.length === 0"
class="btn-effect"><em class="icon-arrow-to-bottom"></em></a>
</li>
<li class="icon-font"><a routerLink="{{ organization?.name | routes: 'sources' : source.id }}"
class="btn-effect"><em class="icon-info-circle"></em></a></li>
</ul>
</div>
</div>
</div><!-- /item -->
And I want to target part of this card like a button for example as :
<li class="icon-font" id="step4{{idx}}">
<a (click)="openDashboardModal(source)" [class.disable]="source.powerbi.length === 0" class="btn-effect">
<em class="icon-chart-bar"></em>
</a>
</li>
and then in my component :
const intro = IntroJs.introJs();
intro.setOptions({
steps: [
{
element: document.querySelector('#step40'),
intro: 'Welcome to the Data Portal ! Explore and download data accross any division. Let\'s discover the new home page.'
}]})
intro.start();
Is there something I am doing wrong ? is introjs not able to do it at all ? is there another lib that can do it?
Thank you in advance
The sections generated by the *ngFor loop may not be rendered yet in the AfterViewInit event. To detect the presence of these elements, you should use #ViewChildren and monitor the QueryList.changes event.
In the template, define a template reference variable #step instead of id:
<li class="icon-font" #step>
<a (click)="openDashboardModal(source)" [class.disable]="source.powerbi.length === 0" class="btn-effect">
<em class="icon-chart-bar"></em>
</a>
</li>
In the code, retrieve the elements with #ViewChildren, and subscribe to the QueryList.changes event. You may have to convert the QueryList to an array to access an element with a specific index.
#ViewChildren("step") steps: QueryList<ElementRef>;
ngAfterViewInit() {
this.startIntroJs(); // Maybe already present
this.steps.changes.subscribe((list: QueryList<ElementRef>) => {
this.startIntroJs(); // Created later
});
}
private startIntroJs(): void {
if (this.steps.length > 40) {
const intro = IntroJs.introJs();
intro.setOptions({
steps: [
{
element: this.steps.toArray()[40].nativeElement,
intro: 'Welcome to the Data Portal...'
}
]
});
intro.start();
}
}

How do I make {{#each}} work with these templates?

I have been reading these tutorials on Spacebars.
Understanding Spacebars by David Burles
Meteor's Spacebars README on Github
Discover Meteor's Spacebars secrets
I think I can understand pretty good, but I am having this problem
i have the main.html template like this
<template name="main">
<div class="container">
<section id="bl-work-section">
<div class="bl-box">
<h4 class="sin">Próximo</h4>
<h2 id="titulo1" class="sin"></h2>
<h4 class="sin" id="sub1"></h4>
</div>
<div class="bl-content">
<h2>Lunes 25 de Noviembre del 2014</h2>
{{#each mostrarLunes}}
{{> Lunes}}
{{/each}}
<a></a>
</div>
<span class="bl-icon bl-icon-close"></span>
</section>
<!-- Panel items for the works -->
<div class="bl-panel-items" id="bl-panel-work-items">
{{#each mostrarLunes}}
{{showPromos}}
{{/each}}
</div>
</div>
</div><!-- /container -->
</template>
So like you guys see im calling 2 templates inside main template and that 2 templates look like this
Lunes.html Template
<template name="Lunes">
<ul id="bl-work-items">
<li data-panel="panel">
<div class="oferta">
<h3>Promocion: {{metadata.nombrePromo}} </h3><br><small>Descripcion:{{metadata.descripcionPromo}}</small>
</div></li><br><br>
</ul>
</template>
showPromos.html Template
<template name="showPromos">
<div data-panel="panel">
<div>
<h1>Estoy viendo esto en la segunda pagina </h1>
<h3>Nombre Promo {{metadata.nombrePromo}}</h3>
<p>Descripcion promo.{{metadata.descripcionPromo}}</p>
</div>
</div>
<nav>
<span class="bl-icon bl-icon-close"></span>
</nav>
</template>
So what is the problem? well if we look at the template Lunes and showPromos, i have a Data-Panel="panel", but its seems like that data panel don't work when i wrap that value on the {{each}} tags, because if i use, that {{each}} tags outside the data-panel selector it works, so its seems like its not working seems like they don't have connection.
so im asking if there is a way to connect that value? already try helper attributes like the 3rd link says, and don't work, the attributes helpers look like this
Template.main.helpers({
attributes: function () {
return {
dataPanel: "prueba",
}
}
});
Helper mostrarLunes
Template.main.helpers({
'mostrarLunes' : function(){
return Promociones.find({'metadata.diaOferta' : { $in: ['Lunes'] } });
}
});
Looks like the data context isn't set. Try creating a template helper for your other templates with the data (I don't understand Spanish so I'm assuming this is the MongoDB cursor you need...
Templates.lunes.helpers({
data: function() {
return Promociones.find({'metadata.diaOferta' : { $in: ['Lunes'] } });
}
});
Templates.showPromos.helpers({
data: function() {
return Promociones.find({'metadata.diaOferta' : { $in: ['Lunes'] } });
}
});

Ember navbar UI condition based on currentPath

I must not be doing something right. I have the following:
application.hbs
{{#view App.NavbarView}}{{/view}}
{{outlet}}
with the following template for Navbar
_navbar.hbs
<div class="toolbar">
<div class="row">
<div class="absolute top-left">
<button {{action "back"}} class="btn passive back"><i class="fa fa-play"></i></button>
</div>
{{#if hasTabs}}
<div class="small-centered columns">
<div class="tabs">
<ul>
{{#link-to 'stories' tagName="li" class="tab"}}<i class="fa fa-book"></i> Stories{{/link-to}}
{{#link-to 'mylevels' tagName="li" class="tab"}}<i class="fa fa-user"></i> My Levels{{/link-to}}
{{#link-to 'arcade.index' tagName="li" class="tab"}}<i class="fa fa-gamepad"></i> Arcade{{/link-to}}
</ul>
</div>
</div>
{{else}}
<div class="small-6 small-offset-3 columns">
<h2 class="title">{{ pageTitle App.currentPath }}</h2>
</div>
{{/if}}
{{#if currentUser.userName}}
<div class="absolute top-right">
<span class="user-hello">Welcome Back, <strong>{{ currentUser.userName }}</strong></span>
<button {{action "transitionAccount" currentUser._id}} class="square logged-in"><i class="fa fa-user"></i></button>
</div>
{{ else }}
<div class="absolute top-right">
<button {{action "transitionLogin"}} class="square logged-out"><i class="fa fa-user"></i></button>
</div>
{{/if}}
</div>
</div>
So all it is is a typical fixed navbar and in the middle of it I display what page you are on, if you happen to be on a page that has tabbed content, I show a tab container instead.
So I'm just using this.get('currentPath') in my App controller and comparing it against a group of route names to trigger true/false (I need an observer so it looks at the route change since the Navbar is in inline view at the Application level).
app.js
App.ApplicationController = Ember.ObjectController.extend({
updateCurrentPath: function() {
App.set('currentPath', this.get('currentPath'));
}.observes('currentPath'),
tabs: function() {
var route = this.get('currentPath'),
group = ['arcade.index', 'mylevels', 'stories', 'arcade', 'arcade.loading'];
console.log("ROUTE: ", route);
var tabs = group.indexOf(route) > -1 ? true : false;
return tabs;
}.observes('currentPath'),
// no idea what to do here
hasTabs: function() {
this.tabs();
}.property('tabs')
});
So, basically, no matter what, the tab UI is showing up, but I only want it to show up if that tabs observer is true. With some debugging I'm getting all the console output I would expect but I tried just doing {{#if tabs}} (just using the observer directly) and that always fires true (always shows the tabs UI). I assumed that's because it was an observer and not an actual controller property I could use in my template, so I tried just setting the hasTabs property and referencing the observer, but that doesn't seem to work. I realize I am fundamentally not understanding how this should work. Any thoughts?
If I understand your question correctly you should be able to just change your code to this (renamed tabs to hasTabs, removed previous hasTabs function. Changed from observes currentPath to be property of current path, removed the tabs variable assignment and replaced with the return, reduced the boolean conditional to the simple comparison operator). This is what I'd do, anyway. :) H2H
hasTabs: function() {
var route = this.get('currentPath'),
group = ['arcade.index', 'mylevels', 'stories', 'arcade', 'arcade.loading'];
return group.indexOf(route) > -1;
}.property('currentPath')

Pagination with Handlebars/Backbone

I want to implement a simple pagination using Handlebars. I am using bootstrap 3. This is what I have:
HTML
<div class = "categories-block">
{{#each category}}
<div class = "category">
<div class = "item">
<h2>{{itemname}}</h2>
View Category
<p class = "item-desc">{{itemndesc}}</p>
</div>
</div>
{{/each}}
</div>
<div class="pagination">
<li>Previous</li>
<li>1</li>
<li>Next</li>
</div>
MODEL
var categorymodel = Backbone.Model.extend({
url: "http://localhost/category.json",
defaults: {
}
});
Right now, everything I get from my model is displayed there. How can I paginate, display 5 items in one page.
Thanks in advance

Categories

Resources