Ember and Handlebars Iterate Over a Set Array - javascript

I'm working on learning Ember and am trying to do some small ideas with it. Currently, I am trying to receive text field input to filter a list and return the matching results. I have all of this working, you know, the 'hard' stuff. However, the part that isn't working is Handlebars reading the 'title' property of my array that I am returning. It's just blank.
Here is my template:
<script data-template-name="application" type="text/x-handlebars">
{{input type="text" value=searchString placeholder="Search..."}}
{{filterMovies}}
<ul>
{{#each searchResults}}
<li>{{title}}</li>
{{/each}}
</ul>
</script>
And now my controller:
App.ApplicationController = Ember.Controller.extend({
filterMovies: function() {
var self = this,
searchString = self.get('searchString'),
searchResults = [],
filterArrLength = null,
theFullMovieList,
theFilteredMovieList = [];
if (!searchString) {
return;
}
var url = 'http://www.json-generator.com/api/json/get/clVKyWQSnC';
Ember.$.getJSON(url).then(function(data) {
theFullMovieList = data;
theFullMovieList.filter(function(movie) {
if (movie.title.toLowerCase().startsWith(searchString)) {
theFilteredMovieList.push(movie);
}
});
console.log(theFilteredMovieList);
self.set('searchResults', theFilteredMovieList);
});
}.property('searchString')
});
I have tried printing using {{this}}, {{this.title}}, {{searchResults.title}}, and {{title}} with no luck. However, logging the array shows the correct values.
Any ideas? View On CodePen

Your each syntax is invalid. You have to use new syntax:
<ul>
{{#each searchResults as |movie|}}
<li>{{movie.title}}</li>
{{/each}}
</ul>
See working demo on CodePen.

Related

Ember.js route problems with filter

My aim is to pass filtered data to my controller and then to my template. I've tried not using the filter and everything works as expected. If I even try to use a filter that lets everything through, I don't get any data. I've even tried using false instead of true and fiddling with the argument list of the filter.
I'm using ember-data fixtures to test this. I'm following the name conventions so much of the work is done for me under the hood. That all seems to be working though (otherwise the first example should also have a problem).
Works (arrives in the controller and eventually gets rendered on the page):
App.DomainDirRoute = Ember.Route.extend({
model: function(params) {
return this.store.find('domain_dir');
}
});
Fails (controller gets an empty array):
App.DomainDirRoute = Ember.Route.extend({
model: function(params) {
return this.store.filter('domain_dir', function(item){
return true;
});
}
});
UPDATE (ATTEMPT 1):
Okay, so I've tried a couple of things based on Sam Selikoff's answer. I've defined 4 properties (2 filters, one map, one plain copy) in the controller and tried to display each in the mockup page. Only the property copyDomain gives a result.
Properties in controller:
filteredDomains: Ember.computed.filterBy('domain', 'domain', true),
upperCaseDomains: Ember.computed.map('domain', function(domain, index) {
return domain.toUpperCase() + '!';
}),
filteredDomains2: function() {
return this.get("model").filterBy('domain', true);
}.property('model.#each.domain'),
copyDomains: function(){
result = [];
this.forEach(function(item) {
result.pushObject(item);
})
console.log(result);
return result;
}.property('model.#each.domain')
Mockup:
<ul>
<li>filteredDomains</li>
{{#each domainDir in controller.filteredDomains}}
<li>domainDir.domain</li>
{{/each}}
</ul>
<ul>
<li>filteredDomains2</li>
{{#each domainDir in controller.filteredDomains2}}
<li>domainDir.domain</li>
{{/each}}
</ul>
<ul>
<li>upperCaseDomains</li>
{{#each domainDir in controller.upperCaseDomains}}
<li>domainDir.domain</li>
{{/each}}
</ul>
<ul>
<li>copyDomains</li>
{{#each domainDir in controller.copyDomains}}
<li>domainDir.domain</li>
{{/each}}
</ul>
Filtering is generally done at the controller/component level. store.find makes an AJAX request. Is your goal to only retrieve the filtered subset of data from the server, or to filter the data you already have at the view layer?
Typically if you're just wanting to do some live filtering, you'll do it in the controller. Leave your model hook as this.store.find('domain_dir') and add a filter in your controller:
App.DomainDirController = Ember.Controller.extend({
filteredDomains: function() {
return this.get("model").filterBy('someProp', true);
}.property('model.#each.someProp')
});
You should also check out the computed macros for some shorthands:
App.DomainDirController = Ember.Controller.extend({
filteredDomains: Ember.computed.filterBy('model', 'someProp');
});
Now in your template, you can do
{{#each domain in filteredDomains}}
...

Combining 2 Collection in 1 Template Meteor.js

I'm trying to make a category system and i cant seem to figure out how to make it work.
here's a mock js and html to demonstrate what im trying to accomplish
test.js
Categories = new Meteor.Collection('categories');
Rooms = new Meteor.Collection('rooms');
if (Meteor.isClient) {
Template.Category_System.categories = function () {
return Categories.find({});
};
Template.Category_System.rooms = function () {
return Rooms.find({}); //here i want to return the rooms and users in the Rooms collection but only the rooms that fall under the category and subcategory of the Categories collection
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
Categories.insert({category:"category1",subcategories:[{subcategory:"subcategory1",rooms:[]},{subcategory:"subcategory2",rooms:[]}]};
Categories.insert({category:"category2",subcategories:[{subcategory:"subcategory1",rooms:[]},{subcategory:"subcategory2",rooms:[]}]};
Rooms.insert({category:"category1",subcategory:"subcategory1",room:'Room_01',users:[a,b,c]});
Rooms.insert({category:"category1",subcategory:"subcategory1",room:'Room_02',users:[d,e,f,g,h]});
Rooms.insert({category:"category1",subcategory:"subcategory2",room:'Room_03',users:[i]});
Rooms.insert({category:"category2",subcategory:"subcategory1",room:'Room_01',users:[j,k]});
Rooms.insert({category:"category2",subcategory:"subcategory2",room:'Room_02',users:[l,m,n]});
Rooms.insert({category:"category2",subcategory:"subcategory2",room:'Room_03',users:[o,p,q,r]});
});
}
test.html -> just the template
<template name="Category_System">
{{#each categories}}
{{category}}
{{#each subcategories}}
{{subcategory}}
{{#each rooms}}
{{room}}{{users}}
{{/each}}
{{/each}}
{{/each}}
</template>
the outcome i'm trying to achieve
category1
-subcategory1
-Room_01 a,b,c
-Room_02 d,e,f,g,h
-subcategory2
-Room_03 i
category2
-subcategory1
-Room_01 j,k
-subcategory2
-Room_02 l,m,n
-Room_03 o,p,q,r
thanks in advance
Based on your mock code, you could do something like the following:
category.js
Categories = new Meteor.Collection('categories');
Rooms = new Meteor.Collection('rooms');
if (Meteor.isClient) {
Template.Category_System.categories = function () {
return Categories.find();
};
Template.Category_System.rooms = function ( cat ) {
var _subcat = this.subcategory,
_cat = cat;
return Rooms.find({ category: _cat, subcategory: _subcat });
};
}
if (Meteor.isServer) {
Meteor.startup(function () {
if ( !Categories.find().count() ) {
Categories.insert({category:"category1",subcategories:[{subcategory:"subcategory1",rooms:[]},{subcategory:"subcategory2",rooms:[]}]});
Categories.insert({category:"category2",subcategories:[{subcategory:"subcategory1",rooms:[]},{subcategory:"subcategory2",rooms:[]}]});
}
if ( !Rooms.find().count() ) {
Rooms.insert({category:"category1",subcategory:"subcategory1",room:'Room_01',users:["a","b","c"]});
Rooms.insert({category:"category1",subcategory:"subcategory1",room:'Room_02',users:["d","e","f","g","h"]});
Rooms.insert({category:"category1",subcategory:"subcategory2",room:'Room_03',users:["i"]});
Rooms.insert({category:"category2",subcategory:"subcategory1",room:'Room_01',users:["j","k"]});
Rooms.insert({category:"category2",subcategory:"subcategory2",room:'Room_02',users:["l","m","n"]});
Rooms.insert({category:"category2",subcategory:"subcategory2",room:'Room_03',users:["o","p","q","r"]});
}
});
}
category.html
<head>
<title>Category System Test</title>
</head>
<body>
{{> Category_System}}
</body>
<template name="Category_System">
<ul>
{{#each categories}}
<li>
{{category}}
<ul>
{{#each subcategories}}
<li>
{{subcategory}}
<ul>
{{#each rooms ../category }}
<li>{{room}} - {{users}}</li>
{{/each}}
</ul>
</li>
{{/each}}
</ul>
</li>
{{/each}}
</ul>
</template>
Key thing to note is the passing of ../category in the block helper for rooms and the template helper for rooms which accepts the category parameter and also makes use of the current subcategory data context to filter the Room collection.
If you run this under Meteor 0.8.0, you should see the following output...
You could make this easier by completing the reference to the rooms array in each of your Category docs. Either do this through a UI or capture the insertId for each Room doc and update the appropriate Category doc as you seed.
By the way, you'll probably want to throw in a check when you are seeding your collections (see the code above)...otherwise, you'll wind up with lots and lots of records every time you make a change and the app restarts.

Computed.alias not updating bind-attr

I recently started using Ember.js. In my small application I currently have problems regarding Ember.computed.alias, because an {{#if}}-section is updated properly, but the bind-attr helper in the same template is not updated accordingly.
The application controller and the action influencing the value look as follows:
App.ApplicationController = Ember.ObjectController.extend({
isEditing: false,
actions: {
toggleEdit: function() {
var a = this.get('isEditing');
this.set('isEditing', !a);
}
}
});
The controller taking care of the template causing problems:
App.CategoriesController = Ember.ArrayController.extend({
needs: ['application'],
isEditing: Ember.computed.alias('controllers.application.isEditing'),
general: function() { // example depending on the alias
var result = this.filterBy('type', 1);
if (!this.get('isEditing')) {
result = result.filterBy('isHidden', false);
}
return result;
}.property('#each.type', '#each.isHidden', 'isEditing'),
// ......
The related template:
<ul id="categories">
{{#if isEditing}}YES!{{else}}NO!{{/if}}
{{#each general}}
<li {{bind-attr class=":general isEditing:editing"}}>
{{name}}
</li>
{{/each}}
</ul>
When the action toggleEdit is triggered, the {{#if}} section is updated and swaps between YES! and NO!, but the editing class is not applied to the list element. I tried encapsulated the alias into another property of the controller depending on the alias, but without success.
I assume it's a beginners mistake, but I can't figure out what I am overlooking.
Thanking you in anticipation.
isEditing is no longer in scope, use controller.isEditing, sorry phone response
Here's an example that would keep it in scope, but I'm fully qualifying it just to show you.
{{#each item in general}}
<li {{bind-attr class=":general controller.isEditing:editing"}}>
{{item.name}}
</li>
{{/each}}

Nothing handled the event error in Emberjs

I am trying to filter a JSON response by using 'filterProperty' in Emberjs. But I am getting this error, Uncaught Error: Nothing handled the event 'last'
Here's my App.js
App = Ember.Application.create({});
App.IndexRoute = Ember.Route.extend({
renderTemplate : function(controller) {
this.render('MyApp', {
controller : controller
});
},
model : function() {
return App.MyTemplateModel.find();
}
});
App.IndexController = Ember.ArrayController.extend({
last : (function() {
this.get('content').filterProperty('last_name', 'Solow');
}).property('content.#each.type')
});
App.MyTemplateModel = Ember.Model.extend({
id : Ember.attr(),
last_name : Ember.attr(),
first_name : Ember.attr(),
suffix : Ember.attr(),
expiration : Ember.attr()
});
App.SiteController = Ember.ObjectController.extend({
});
App.MyTemplateModel.url = "http://ankur1.local/index.php/api/example/users/";
App.MyTemplateModel.adapter = Ember.RESTAdapter.create();
var existing = App.MyTemplateModel.find();
App.MyTemplateModel.camelizeKeys = true;
Here's my HTML page,
<script type="text/x-handlebars" data-template-name="MyApp">
{{#each item in content }}
<tr><td>
{{id}} <p> {{item.first_name}} {{item.expiration}}</p>
</td></tr>
{{/each}}
<button {{action last}}>filter</button>
</script>
<script type="text/x-handlebars">
<h1>Application Template</h1>
{{outlet}}
</script>
</body>
What I might be doing wrong in my App.js or should I use any other property to filter the JSON response?
You declared the property last on your IndexController as Computed Property, but if you want to use the {{action}} helper, this is not allowed. It has be a plain function. This is why Ember does not find a suitable event anywhere and complains about it.
App.IndexController = Ember.ArrayController.extend({
// for initial filling of this property, will be overridden by last action
filteredContent : Ember.computed.oneWay("content"),
last : function() {
var filtered = this.get('content').filterProperty('last_name', 'Solow');
this.set("filteredContent", filtered);
}
});
<script type="text/x-handlebars" data-template-name="MyApp">
{{#each item in filteredContent }}
<tr><td>
{{id}} <p> {{item.first_name}} {{item.expiration}}</p>
</td></tr>
{{/each}}
<button {{action last}}>filter</button>
</script>
So i did basically two things:
I changed the computed property to a plain function.
The template is iterating over filteredContent instead of content. (Note the initialization i had to do on your Controller.)
Sou the basic mechanism is to have an additional property on your Controller, which holds the filtered content. You have to expand on this, because your usecase is a bit more complex for sure. :-)

handlebars - is it possible to access parent context in a partial?

I've got a handlebar template that loads a partial for a sub-element.
I would need to access a variable from the parent context in the calling template, from within the partial. .. doesn't seem to resolve to anything inside the partial.
Simplified code goes like this:
the template
{{#each items}}
{{> item-template}}
{{/each}}
the partial
value is {{value}}
(obviously the real code is more complicated but it's the same principle, within the partial .. appears to be undefined.)
To show it's undefined, I've used a very simple helper whatis like this:
Handlebars.registerHelper('whatis', function(param) {
console.log(param);
});
and updated the above code to this:
updated template
{{#each items}}
{{whatis ..}} <-- Console shows the correct parent context
{{> item-template}}
{{/each}}
updated partial
{{whatis ..}} <-- Console shows "undefined"
value is {{value}}
Is there a way to go around that issue? Am I missing something?
EDIT: There's an open issue relating to this question on handlebars' github project
Just in case anyone stumbles across this question. This functionality exists now in Handlebars.
Do this:
{{#each items}}
{{! Will pass the current item in items to your partial }}
{{> item-template this}}
{{/each}}
Working fiddle (inspired by handlebars pull request #385 by AndrewHenderson)
http://jsfiddle.net/QV9em/4/
Handlebars.registerHelper('include', function(options) {
var context = {},
mergeContext = function(obj) {
for(var k in obj)context[k]=obj[k];
};
mergeContext(this);
mergeContext(options.hash);
return options.fn(context);
});
Here's how you'd setup the parent template:
{{#each items}}
{{#include parent=..}}
{{> item-template}}
{{/include}}
{{/each}}
And the partial:
value is {{parent}}
As of 2.0.0 partials now supports passing in values.
{{#each items}}
{{> item-template some_parent_var=../some_parent_var}}
{{/each}}
Took me awhile to find this, hope it's useful for someone else too!
The easiest way to pass the parent context to the partial is to do the loop inside the partial. This way the parent context is passed by default and when you do the loop inside the partial the {{../variable}} convention can access the parent context.
example fiddle here.
The Data
{
color: "#000"
items: [
{ title: "title one" },
{ title: "title two" },
]
}
The Template
<div class="mainTemplate">
Parent Color: {{color}}
{{> partial}}
</div>
The Partial
<div>
{{#each items}}
<div style="color:{{../color}}">
{{title}}
</div>
{{/each}}
</div>
You can use some of the proposed solutions on the comments from the link to github:
https://github.com/wycats/handlebars.js/issues/182#issuecomment-4206666
https://github.com/wycats/handlebars.js/issues/182#issuecomment-4445747
They create helpers to pass the info to the partial.
I created an each Helper function that includes the parent key/values within the subcontext under the key parentContext.
http://jsfiddle.net/AndrewHenderson/kQZpu/1/
Note: Underscore is a dependency.
Handlebars.registerHelper('eachIncludeParent', function ( context, options ) {
var fn = options.fn,
inverse = options.inverse,
ret = "",
_context = [];
$.each(context, function (index, object) {
var _object = $.extend({}, object);
_context.push(_object);
});
if ( _context && _context.length > 0 ) {
for ( var i = 0, j = _context.length; i < j; i++ ) {
_context[i]["parentContext"] = options.hash.parent;
ret = ret + fn(_context[i]);
}
} else {
ret = inverse(this);
}
return ret;
});
To be used as follows:
{{#eachIncludeParent context parent=this}}
{{> yourPartial}}
{{/eachIncludeParent}}
Access parent context values in your partial using {{parentContext.value}}
I needed dynamic form attributes for something like this...
{{#each model.questions}}
<h3>{{text}}</h3>
{{#each answers}}
{{formbuilder ../type id ../id text}}
{{/each}}
{{/each}}
and a helper like so...
Handlebars.registerHelper('formbuilder', function(type, id, qnum, text, options)
{
var q_type = options.contexts[0][type],
a_id = options.contexts[1].id,
q_number = options.contexts[0][qnum],
a_text = options.contexts[1].text;
return new Handlebars.SafeString(
'<input type=' + q_type + ' id=' + a_id + ' name=' + q_number + '>' + a_text + '</input><br/>'
);
});
Which produces...
<input type="checkbox" id="1" name="surveyQ0">First question</input>
My model is a big blob of arrays and objects mixed together. What's noteworthy is that using '../' like so '../type', passes in the parent model as the context, and without it, such as with 'id', it passes in the current model as the context.
To get specifically the parent of the partial (where you may be several partials deep) then follow the other answers like SeanWM.
If you know that the parent is the main template then you can use #root which resolves to the top-most context no matter how deep you are.
e.g. {{#root.rootObject.rootProperty}}
It is a pity that ../../.. does not go up past a partial.

Categories

Resources