Ember.Controller array content filtering - ember.js

I have a fiddle http://jsfiddle.net/kristaps_petersons/9wteJ/2/ it loads 3 objects and shows them in a view. Data is shown alright, but i can not filter it before i show it.
This
nodes: function(){
this.get('controller.content').filter(function(item, idx, en){
console.log('should log this atleast 3x')
})
return this.get('controller.content')
}.property('controller.content')
method is called when template iterates over array of values, but it never goes in to the loop and print console.log('should log this atleast 3x') why is that?

You are trying to replace controller.content while also binding to it. You need to define another property, such as filteredContent and bind it to controller.content. Take a look at how Ember.SortableMixin computes the variable arrangedContent for controllers with a sortProperties variable defined. Using that method as a template I would implement it like this:
filteredContent: Ember.computed('content', function() {
var content = this.get('content');
return this.filter(function(item, idx, en) {
console.log('should log this atleast 3x');
});
}).cacheable()
This should be implemented in the controller, not the view. The controller is the place for data manipulation, computed properties, and bindings.
Then bind the view layout to filteredContent instead of content to show the filtered data. Then both the original content and the filtered content are available.

Ok i got it working, but it feels a bit strange. First i moved method to Controller class and changed it to look like this:
nodes: function(){
console.log('BEFORE should log this atleast 3x', this.get('content.length'))
this.get('content').forEach(function(item, idx, en){
console.log('should log this atleast 3x')
})
console.log('AFTER should log this atleast 3x', this.get('content.length'))
return this.get('content')
}.property('content').cacheable()
as it should be same as buuda's recomedation, because as i understand from docs .poperty() is the same as Ember.computed. As it was still not working, i changed .property('content') to .property('content.#each') and it was working. Fiddle: http://jsfiddle.net/kristaps_petersons/9wteJ/21/ . I guess, that tempate first creates a binding to controller.content and as content itself does not change does not notify this method again, instead template pulls data as it becomes available.

Related

How can I dynamically render HTML using Meteor Spacebars templates?

So let's say I'm storing <div>{{name}}</div> and <div>{{age}}</div> in my database.
Then I want to take the first HTML string and render it in a template - {{> template1}} which just renders the first string with the {{name}} handlebar in it. Then I want to give that newly generated template/html data, so that it can fill in the handlebar with the actual name from the database, so that we would get <div>John</div>. I've tried doing
<template name="firstTemplate">
{{#with dataGetter}}
{{> template1}}
{{/with}}
</template>
Where template1 is defined as
<template name="template1">
{{{templateInfo}}}
</template>
And templateInfo is the helper that returns the aforementioned html string with the handlebar in it from the database.
dataGetter is just this (just an example, I'm working with differently named collections)
Template.firstTemplate.dataGetter = function() {
return Users.findOne({_id: Session.get("userID")});
}
I can't get the {{name}} to populate. I've tried it a couple of different ways, but it seems like Meteor doesn't understand that the handlebars in the string need to be evaluated with the data. I'm on 0.7.0 so no Blaze, I can't upgrade at the moment due to the other packages I'm using, they just don't have 0.8+ version support as of yet. Any ideas on how I can get this to work are much appreciated.
In 1.0 none of the methods described above work. I got this to work with the function below defined in the client code. The key was to pass the options { isTemplate: true} to the compile function.
var compileTemplate = function(name, html_text) {
try {
var compiled = SpacebarsCompiler.compile(html_text, { isTemplate:true });
var renderer = eval(compiled);
console.log('redered:',renderer);
//Template[name] = new Template(name,renderer);
UI.Template.__define__(name, renderer);
} catch (err){
console.log('Error compiling template:' + html_text);
console.log(err.message);
}
};
The you can call with something like this on the client:
compileTemplate('faceplate', '<span>Hello!!!!!!{{_id}}</span>');
This will render with a UI dynamic in your html
{{> Template.dynamic template='faceplate'}}
You can actually compile strings to templates yourself using the spacebars compiler.. You just have to use meteor add spacebars-compiler to add it to your project.
In projects using 0.8.x
var compiled = Spacebars.compile("<div>{{name}}</div> and <div>{{age}}</div>");
var rendered = eval(compiled);
Template["dynamicTemplate"] = UI.Component.extend({
kind: "dynamicTemplate",
render: rendered
});
In projects using 0.9.x
var compiled = SpacebarsCompiler.compile("<div>{{name}}</div> and <div>{{age}}</div>");
var renderer = eval(compiled);
Template["dynamicTemplate"] = Template.__create__("Template.dynamicTemplate", rendered);
Following #user3354036's answer :
var compileTemplate = function(name, html_text) {
try {
var compiled = SpacebarsCompiler.compile(html_text, { isTemplate:true }),
renderer = eval(compiled);
console.log('redered:',renderer);
//Template[name] = new Template(name,renderer);
UI.Template.__define__(name, renderer);
} catch (err) {
console.log('Error compiling template:' + html_text);
console.log(err.message);
}
};
1) Add this in your HTML
{{> Template.dynamic template=template}}
2) Call the compileTemplate method.
compileTemplate('faceplate', '<span>Hello!!!!!!{{_id}}</span>');
Session.set('templateName','faceplate');
Save the template name in a Session variable. The importance of this is explained in the next point.
3) Write a helper function to return the template name. I have used Session variable to do so. This is important if you are adding the dynamic content on a click event or if the parent template has already been rendered. Otherwise you will never see the dynamic template getting rendered.
'template' : function() {
return Session.get('templateName');
}
4) Write this is the rendered method of the parent template. This is to reset the Session variable.
Session.set('templateName','');
This worked for me. Hope it helps someone.
If you need to dynamically compile complex templates, I would suggest Kelly's answer.
Otherwise, you have two options:
Create every template variation, then dynamically choose the right template:
eg, create your templates
<template name="displayName">{{name}}</template>
<template name="displayAge">{{age}}</template>
And then include them dynamically with
{{> Template.dynamic template=templateName}}
Where templateName is a helper that returns "age" or "name"
If your templates are simple, just perform the substitution yourself. You can use Spacebars.SafeString to return HTML.
function simpleTemplate(template, values){
return template.replace(/{{\w+}}/g, function(sub) {
var p = sub.substr(2,sub.length-4);
if(values[p] != null) { return _.escape(values[p]); }
else { return ""; }
})
}
Template.template1.helpers({
templateInfo: function(){
// In this context this/self refers to the "user" data
var templateText = getTemplateString();
return Spacebars.SafeString(
simpleTemplate(templateText, this)
);
}
Luckily, the solution to this entire problem and any other problems like it has been provided to the Meteor API in the form of the Blaze package, which is the core Meteor package that makes reactive templates possible. If you take a look at the linked documentation, the Blaze package provides a long list of functions that allow for a wide range of solutions for programmatically creating, rendering, and removing both reactive and non-reactive content.
In order to solve the above described problem, you would need to do the following things:
First, anticipate the different HTML chunks that would need to be dynamically rendered for the application. In this case, these chunks would be <div>{{name}}</div> and <div>{{age}}</div>, but they could really be anything that is valid HTML (although it is not yet part of the public API, in the future developers will have more options for defining this content in a more dynamic way, as mentioned here in the documentation). You would put these into small template definitions like so:
<template name="nameDiv">
<div>{{name}}</div>
</template>
and
<template name="ageDiv">
<div>{{age}}</div>
</template>
Second, the definition for the firstTemplate template would need to be altered to contain an HTML node that can be referenced programmatically, like so:
<template name="firstTemplate">
<div></div>
</template>
You would then need to have logic defined for your firstTemplate template that takes advantage of some of the functions provided by the Blaze package, namely Blaze.With, Blaze.render, and Blaze.remove (although you could alter the following logic and take advantage of the Blaze.renderWithData function instead; it is all based on your personal preference for how you want to define your logic - I only provide one possible solution below for the sake of explanation).
Template.firstTemplate.onRendered(function() {
var dataContext = Template.currentData();
var unrenderedView = Blaze.With(dataContext, function() {
// Define some logic to determine if name/age template should be rendered
// Return either Template.nameDiv or Template.ageDiv
});
var currentTemplate = Template.instance();
var renderedView = Blaze.render(unrenderedView, currentTemplate.firstNode);
currentTemplate.renderedView = renderedView;
});
Template.firstTemplate.onDestroyed(function() {
var renderedView = Template.instance().renderedView;
Blaze.remove(renderedView);
});
So what we are doing here in the onRendered function for your firstTemplate template is dynamically determining which of the pieces of data that we want to render onto the page (either name or age in your case) and using the Blaze.With() function to create an unrendered view of that template using the data context of the firstTemplate template. Then, we select the firstTemplate template element node that we want the dynamically generated content to be contained in and pass both objects into the Meteor.render() function, which renders the unrendered view onto the page with the specified element node as the parent node of the rendered content.
If you read the details for the Blaze.render() function, you will see that this rendered content will remain reactive until the rendered view is removed using the Blaze.remove() function, or the specified parent node is removed from the DOM. In my example above, I am taking the reference to the rendered view that I received from the call to Blaze.render() and saving it directly on the template object. I do this so that when the template itself is destroyed, I can manually remove the rendered view in the onDestroyed() callback function and be assured that it is truly destroyed.
A very simple way is to include in the onRendered event a call to the global Blaze object.
Blaze.renderWithData(Template[template_name], data ,document.getElementById(template_id))

Force a controller to always act as a proxy to a model in Ember

I'm looping through a content of an ArrayController whose content is set to a RecordArray. Each record is DS.Model, say Client
{{# each item in controller}}
{{item.balance}}
{{/each}}
balance is a property of the Client model and a call to item.balance will fetch the property from the model directly. I want to apply some formatting to balance to display in a money format. The easy way to do this is to add a computed property, balanceMoney, to the Client object and do the formatting there:
App.Client = DS.Model({
balance: DS.attr('balance'),
balanceMoney: function() {
// format the balance property
return Money.format(this.get('balance');
}.property('balance')
});
This serves well the purpose, the right place for balanceMoney computed property though, is the client controller rather than the client model. I was under the impression that Ember lookup properties in the controller first and then tries to retrieve them in the model if nothing has been found. None of this happen here though, a call to item.balanceMoney will just be ignored and will never reach the controller.
Is it possible to configure somehow a controller to act always as a proxy to the model in all circumstances.
UPDATE - Using the latest version from emberjs master repository you can configure the array controller to resolve records' methods through a controller proxy by overriding the lookupItemController method in the ArrayController. The method should return the name of the controller without the 'controller' suffix i.e. client instead of clientController. Merely setting the itemControllerClass property in the array controller doesn't seem to work for the moment.
lookupItemController: function( object ) {
return 'client';
},
This was recently added to master: https://github.com/emberjs/ember.js/commit/2a75cacc30c8d02acc83094b47ae8a6900c0975b
As of this writing it is not in any released versions. It will mostly likely be part of 1.0.0.pre.3.
If you're only after formatting, another possibility is to make a handlebars helper. You could implement your own {{formatMoney item.balance}} helper, for instance.
For something more general, I made this one to wrap an sprintf implementation (pick one of several out there):
Ember.Handlebars.registerHelper('sprintf', function (/*arbitrary number of arguments*/) {
var options = arguments[arguments.length - 1],
fmtStr = arguments[0],
params = Array.prototype.slice.call(arguments, 1, -1);
for (var i = 0; i < params.length; i++) {
params[i] = this.get(params[i]);
}
return vsprintf(fmtStr, params);
});
And then you can do {{sprintf "$%.2f" item.balance}}.
However, the solution #luke-melia gave will be far more flexible--for example letting you calculate a balance in the controller, as opposed to simply formatting a single value.
EDIT:
A caveat I should have mentioned because it's not obvious: the above solution does not create a bound handlebars helper, so changes to the underlying model value won't be reflected. There's supposed to be a registerBoundHelper already committed to Ember.js which would fix this, but that too is not released yet.

Dynamic templates with knockout

I have a viewmodel whose template I want to change dynamically at runtime when the state of my application changes. I referred to this link
while coming up with my solution.
In my html page I have a div that is bound to a list of view models:
<div class="app"
data-bind="template: {name: templateSelector, foreach: viewModelBackStack}">
</div>
And my templateSelector method looks like this:
this.templateSelector = function(viewModel)
{
if (!_itemTemplate)
{
_itemTemplate = ko.computed(function() {return this.template();}, viewModel);
}
return _itemTemplate();
}
var _itemTemplate;
As can be seen, I am constructing a computed observable which returns viewModel's template.
My viewModel looks like this:
function MyViewModel
{
this.template = ko.observable("MyTemplate");
}
I am changing the value of template as a result of an ajax call being completed and I see that computed observable is called correctly as well (I placed an alert in there to verify it), however the bindings in html does not update the template of my viewmodel. Any help will be appreciated.
UPDATE: I found the bug that was causing it not to work. Basically I was including jquery.tmpl plugin before including knockout.js. Removing jquery.tmpl did the trick !
I don't see a problem with your code, unless it lies in the part where you update the template observable as the result of an AJAX call. Make sure that you have a reference to your view model and are setting it as an observable vm.template(newValue); and not recreating the observable.
Here is your code working: http://jsbin.com/ipijet/5/edit#javascript,html,live
One thing to note is that bindings are already executed within the context of a computed observable, so it is unnecessary to create your own within your templateSelector function.
You can simply create a function that returns your observable directly like:
this.getTemplate = function(data) {
return data.template();
};
http://jsbin.com/ipijet/3/edit#javascript,html,live
Here is an article that I wrote a while back on this topic: http://www.knockmeout.net/2011/03/quick-tip-dynamically-changing.html

Adding item to filtered result from ember-data

I have a DS.Store which uses the DS.RESTAdapter and a ChatMessage object defined as such:
App.ChatMessage = DS.Model.extend({
contents: DS.attr('string'),
roomId: DS.attr('string')
});
Note that a chat message exists in a room (not shown for simplicity), so in my chat messages controller (which extends Ember.ArrayController) I only want to load messages for the room the user is currently in:
loadMessages: function(){
var room_id = App.getPath("current_room.id");
this.set("content", App.store.find(App.ChatMessage, {room_id: room_id});
}
This sets the content to a DS.AdapterPopulatedModelArray and my view happily displays all the returned chat messages in an {{#each}} block.
Now it comes to adding a new message, I have the following in the same controller:
postMessage: function(contents) {
var room_id = App.getPath("current_room.id");
App.store.createRecord(App.ChatMessage, {
contents: contents,
room_id: room_id
});
App.store.commit();
}
This initiates an ajax request to save the message on the server, all good so far, but it doesn't update the view. This pretty much makes sense as it's a filtered result and if I remove the room_id filter on App.store.find then it updates as expected.
Trying this.pushObject(message) with the message record returned from App.store.createRecord raises an error.
How do I manually add the item to the results? There doesn't seem to be a way as far as I can tell as both DS.AdapterPopulatedModelArray and DS.FilteredModelArray are immutable.
so couple of thoughts:
(reference: https://github.com/emberjs/data/issues/190)
how to listen for new records in the datastore
a normal Model.find()/findQuery() will return you an AdapterPopulatedModelArray, but that array will stand on its own... it wont know that anything new has been loaded into the database
a Model.find() with no params (or store.findAll()) will return you ALL records a FilteredModelArray, and ember-data will "register" it into a list, and any new records loaded into the database will be added to this array.
calling Model.filter(func) will give you back a FilteredModelArray, which is also registered with the store... and any new records in the store will cause ember-data to "updateModelArrays", meaning it will call your filter function with the new record, and if you return true, then it will stick it into your existing array.
SO WHAT I ENDED UP DOING: was immediately after creating the store, I call store.findAll(), which gives me back an array of all models for a type... and I attach that to the store... then anywhere else in the code, I can addArrayObservers to those lists.. something like:
App.MyModel = DS.Model.extend()
App.store = DS.Store.create()
App.store.allMyModels = App.store.findAll(App.MyModel)
//some other place in the app... a list controller perhaps
App.store.allMyModels.addArrayObserver({
arrayWillChange: function(arr, start, removeCount, addCount) {}
arrayDidChange: function(arr, start, removeCount, addCount) {}
})
how to push a model into one of those "immutable" arrays:
First to note: all Ember-Data Model instances (records) have a clientId property... which is a unique integer that identifies the model in the datastore cache whether or not it has a real server-id yet (example: right after doing a Model.createRecord).
so the AdapterPopulatedModelArray itself has a "content" property... which is an array of these clientId's... and when you iterate over the AdapterPopulatedModelArray, the iterator loops over these clientId's and hands you back the full model instances (records) that map to each clientId.
SO WHAT I HAVE DONE
(this doesn't mean it's "right"!) is to watch those findAll arrays, and push new clientId's into the content property of the AdapterPopulatedModelArray... SOMETHING LIKE:
arrayDidChange:function(arr, start, removeCount, addCount){
if (addCount == 0) {return;} //only care about adds right now... not removes...
arr.slice(start, start+addCount).forEach(function(item) {
//push clientId of this item into AdapterPopulatedModelArray content list
self.getPath('list.content').pushObject(item.get('clientId'));
});
}
what I can say is: "its working for me" :) will it break on the next ember-data update? totally possible
For those still struggling with this, you can get yourself a dynamic DS.FilteredArray instead of a static DS.AdapterPopulatedRecordArray by using the store.filter method. It takes 3 parameters: type, query and finally a filter callback.
loadMessages: function() {
var self = this,
room_id = App.getPath('current_room.id');
this.store.filter(App.ChatMessage, {room_id: room_id}, function (msg) {
return msg.get('roomId') === room_id;
})
// set content only after promise has resolved
.then(function (messages) {
self.set('content', messages);
});
}
You could also do this in the model hook without the extra clutter, because the model hook will accept a promise directly:
model: function() {
var self = this,
room_id = App.getPath("current_room.id");
return this.store.filter(App.ChatMessage, {room_id: room_id}, function (msg) {
return msg.get('roomId') === room_id;
});
}
My reading of the source (DS.Store.find) shows that what you'd actually be receiving in this instance is an AdapterPopulatedModelArray. A FilteredModelArray would auto-update as you create records. There are passing tests for this behaviour.
As of ember.data 1.13 store.filter was marked for removal, see the following ember blog post.
The feature was made available as a mixin. The GitHub page contains the following note
We recommend that you refactor away from using this addon. Below is a short guide for the three filter use scenarios and how to best refactor each.
Why? Simply put, it's far more performant (and not a memory leak) for you to manage filtering yourself via a specialized computed property tailored specifically for your needs

Proper design of REST-powered list in Ember.js

I'm having difficulty wrapping my head around the following:
There's a view that displays the list of items
I take the list of items from the backend via RESTful interface in JSON using ember-data and hand-crafted adapter
In my view I do something like this:
{{#collection contentBinding="App.recentAdditionsController"}}
...
{{/collection}}
App.recentAdditionsController is defined like this:
App.recentAdditionsController = Em.ArrayController.create({
refresh: function(query) {
var items = App.store.findAll(App.Item);
this.set('content', items);
}
});
And... this doesn't work. The reason being App.store.findAll() returning ModelArray which is much like ArrayController itself.
I saw people doing something like this:
App.recentAdditions = App.store.findAll(App.Item);
I could imagine doing it like that, but how would I refresh the list at will (checking if there's anything new).
Hope all is clear more or less.
I've verified that you can use a ModelArray inside an ArrayController. Here's a jsFiddle example: http://jsfiddle.net/ebryn/VkKX2/
"Now the question is how to make the list update itself if there are new objects in the backend?"
Use App.Model.filter to keep your recordArray in sync. Add the query hash when the filter is invoked to ensure than an initial query was made.
model: ->
App.Model.filter {page: 1}, (data) ->
data
edit: Just saw how old the question was, but leaving it here in case it helps someone.