I've been trying making a new library app based on the code-school ember tutorial (books instead products... not too complicated).
I'm using the latest ember.js stable release, 1.1.10.
using the {{#each}} in my templates,
js:
App.BooksRoute = Ember.Route.extend({
model: function () {
return this.store.findAll('book');
}
});
html:
{{#each}}
<h1>{{title}}</h1>
{{/each}}
the following warning is shown in the console
DEPRECATION: Using the context switching form of {{each}} is deprecated. Please use the keyword form (`{{#each foo in bar}}`)
So I've been trying to use the recommended syntax and I've found this, which is working
html:
{{#each book in model}}
<h1>{{book.title}}</h1>
{{/each}}
But when it comes the time to try the sortProperties in my arrayController, with my {{#each book in model}}
js:
App.BooksRoute = Ember.Route.extend({
model: function () {
return this.store.findAll('book');
}
});
App.BooksController = Ember.ArrayController.extend({
sortProperties: ['title']
});
html:
{{#each book in model}}
<h1>{{book.title}}</h1>
{{/each}}
my books are not sorted...
I've found another workaround, building a property inside my ArrayController:
js:
App.BooksRoute = Ember.Route.extend({
model: function () {
return this.store.findAll('book');
}
});
App.BooksController = Ember.ArrayController.extend({
books: function(){
return this;
}.property(),
sortProperties: ['title']
});
html:
{{#each book in books}}
<h1>{{book.title}}</h1>
{{/each}}
It's sorted!
, but I'm not satisfied...
Is there another cleanest/simplest way to use the each statement as defined in ember 1.1.10 and sort my array ?
Instead of {{#each book in model}}
use {{#each book in arrangedContent}} or {{#each book in this}}
Try using 'arrangedContent' to get your array sorted in ArrayController.
App.BooksController = Ember.ArrayController.extend({
sortProperties: ['title'],
content: function() {
return this.get('arrangedContent'); // this gives sorted array
},
});
Hope this helps
Related
I'm very new to Meteor (started yesterday) so bear with me.
I've got the following to try and get some messages from the server:
var messages = new Mongo.Collection('messages');
if (Meteor.isServer) {
//Initial first few messages
Meteor.publish("messages", function () {
return messages.find({}, {sort: {createdAt : -1}, limit: 2, reactive : false});
});
}
And then a helper for the template:
Template.card.helpers({
messages: function () {
return messages.find({});
}
});
Template:
<div class="messages-slider">
{{#each messages}}
{{> message}}
{{/each}}
</div>
I'm trying to get the first couple of messages and keep them static in the DOM, basically - however, with the above code the template updates with the collection regardless. How do I fix it?
The reactive option of find is client-only. Use it in your helper:
Session.set('ready', false);
Meteor.subscribe('messages', { onReady: function() {
Session.set('ready', true);
}});
Template.card.helpers({
messages: function () {
if (Session.get('ready')) {
return messages.find({}, {reactive: false});
}
}
});
With reference to Christian's answer:
I think you could probably solve the rendering-before-the-subscription-is-ready issue by wrapping the template in {{#if Template.subscriptionsReady}] so it won't render until the subscription is loaded. No need for Session variables in that case. For example:
Template.card.onCreated(function(){
var instance = this;
instance.autorun(function() {
instance.subscribe('messages');
});
});
Template.card.helpers({
messages: function() {
messages.find({},{reactive:false});
}
});
Template:
<template name="card">
{{#if Template.subscriptionsReady}}
<div class="messages-slider">
{{#each messages}}
{{> message}}
{{/each}}
</div>
{{/if}}
</template>
I'd like to create a template in Meteor that has a Tracker.autorun which exclusively runs when part of a document changes --- but not when other parts of the document change.
So here is sample code using a minimongo collection and template.autorun
parent.html
{{#each items}}
{{> child}}
{{/each}}
child.html
<div>{{title}}</div>
<p>{{description}}</p>
Minimongo Collection
LocalProject.findOne() output:
"items": [
{
"title": "hi",
"description": "test"
},
{
"title": "hi 2",
"description": "test 2"
},
],
"otherstuff:{//etc}
child.js
Template.child.onRendered(function(){
this.autorun(function() {
var data = Template.currentData();
doSomething(data.title,data.description)
});
});
addnewitem.js
LocalProject.update(currentEditingProjectID,{ $push: { 'items': newItem }},function(error,result){
if(error){console.log(error)}
});
The problem is, whenever I run addnewitem.js, all of my Template.child autoruns execute even though their reactive data source (Template.currentData()) has not changed unless it was the specific item I updated. Similarly if I want to update an existing item, not just add a new one to the array, all of the autoruns for each item get executed.
So is there a way, using this model, to create a dependency for autorun that is reactively granular to specific portions of a document?
I don't think the way to go is by using an autorun. I would either set up individual reactive dependencies on each item, or use observe/observeChange.
First idea
parent.html:
{{#each items}}
{{> child}}
{{/each}}
parent.js:
Template.parent.helpers({
// Returns only item ids
items: function() {
return Items.find({}, { fields: { _id: 1 } });
}
});
child.html:
{{#each items}}
{{#with item=getItem}}
<div>{{item.title}}</div>
<p>{{item.description}}</p>
{{/with}}
{{/each}}
child.js:
Template.child.helpers({
getItem: function() {
// Get the item and set up a reactive dependency on this particular item
var item = Items.find(this._id);
// After item has been displayed, do something with the dom
Tracker.afterFlush(function () {
doSomething(item.title, item.description);
});
return item;
}
});
Second idea
parent.html:
{{#each items}}
{{> child}}
{{/each}}
parent.js:
function do(item) {
Tracker.afterFlush(function () {
doSomething(item.title, item.description);
});
}
Template.parent.onCreated({
this.items = Items.find({});
this.handle = this.items.observe({
added: function(item) { do(item); },
changed: function(oldItem, newItem) { do(newItem); },
});
});
Template.parent.onDestroyed({
this.handle.stop();
});
Template.parent.helpers({
items: function() {
return Template.instance().items;
}
});
child.html:
{{#each items}}
<div>{{title}}</div>
<p>{{description}}</p>
{{/each}}
There's a tool just for this - 3stack:embox-value provides reactive isolation, and value caching.
Using your example, you could isolate changes to title/description like so:
first up, add the packages
meteor add 3stack:embox-value
meteor add ejson
Then, update your code:
Template.child.onRendered(function(){
// creates a reactive data source, that only triggers "changes"
// when the output of the function changes.
var isolatedData = this.emboxValue(function(){
var data = Template.currentData();
return {title: data.title, description: data.description}
}, {equals: EJSON.equals})
this.autorun(function() {
// This autorun will only execute when title or description changes.
var data = isolatedData()
doSomething(data.title,data.description)
});
});
According to official documentation, way to create itemcontroller is:
App.PostsController = Ember.ArrayController.extend({
itemController: 'post'
});
App.PostController = Ember.ObjectController.extend({
// the `title` property will be proxied to the underlying post.
titleLength: function() {
return this.get('title').length;
}.property('title')
});
But I'm not setting my ArrayController to App. It is set to a local variable behind a function scope. And the itemController property can only be string (according to documentation). So how do I set the itemController property?
My code looks like this:
var Channels=Ember.Object.extend({
list:Ember.ArrayController.create(
{
"model":[
{
"id":"display",
"label":"Display",
},{
"id":"social",
"label":"Social",
},{
"id":"email",
"label":"Email",
}
]
}
)
});
App.ChannelController=Ember.Controller.extend({
channels:Channels,
}));
<script type="text/x-handlebars" data-template-name='channel'>
<div>
{{#each channel in channels.list}}
{{channel.label}}
{{/each}}
</div>
</script>
I don't want to pollute App namespace with itemControllers that is to be used locally.
Update
Suppose my channels is like this:
var Channels=Ember.Object.extend({
list:Ember.ArrayController.create(
{
"model":[
{
"id":"display",
"label":"Display",
},{
"id":"social",
"label":"Social",
},{
"id":"email",
"label":"Email",
}
]
}
),
selected:"display"
});
and I want to something like this in template:
<script type="text/x-handlebars" data-template-name='channel'>
<h1>{{channels.selected}}</h1>
<div>
{{#each channel in channels.list}}
<div {{bind-attr class="channel.isselected:active:inactive"}}>{{channel.label}}</div>
{{/each}}
</div>
</script>
so that it outputs:
<h1>display</h1>
<div>
<div class="active">Display</div>
<div class="inactive">Social</div>
<div class="inactive">Email</div>
</div>
How do I do it with components?
You'll likely want to read the guide of components to get the full picture, but the gist of it is that you want to replace all item controllers with components. However, components will also replace the template inside of the each block as well. I don't entirely understand what's going on in your code, but here's an example roughly based on your code.
// Component
App.ChannelDisplayComponent = Ember.Component.extend({
channel: null,
isSelected: function() {
// Compute this however you want
// Maybe you need to pass in another property
}.property('channel')
});
{{! Component Template }}
<div {{bind-attr class="channel.isSelected:active:inactive"}}>
{{channel.label}}
</div>
{{!Channels Template}}
{{#each channel in channels.list}}
{{channel-component channel=channel}}
{{/each}}
The component is essentially your item controller, only it gets its own template as well.
You really shouldn't be worried about polluting the app namespace (unless you're having naming collisions, but that's a different issue). And as Kitler said, you should move to components instead of item controllers. But if you want to do this, the best way I can think of is overridding the (private) controllerAt hook.
var ItemController = Ember.Controller.extend({});
App.PostsController = Ember.ArrayController.extend({
controllerAt: function(idx, object, controllerClass) {
var subControllers = this._subControllers;
if (subControllers.length > idx) {
if (subControllers[idx]) {
return subControllers[idx];
}
}
var parentController = (this._isVirtual ? this.get('parentController') : this);
var controller = ItemController.create({
target: parentController,
parentController: parentController,
model: object
});
subControllers[idx] = controller;
return controller;
}
})
I've made a jsbin to illustrate my issue.
the binding seems KO with lastname property defined inside the itemController and the fullname value is not updated in my items loop.
What am I doing wrong ?
Controller for item in list is different than one you edit property lastname for, so it will never get updated. Propery lastname has to be specified as Model's property (if using Ember Data you simply don't use DS.attr for it and it won't be persisted). If you use custom library for data persistence you have to manually remove lastname property. You can use Ember Inspector extension to see that there are 5 controllers when you click on item. 4 for each item in list and one is being generated when you click. You edit property lastname for this fifth controller. To solve this you can use:
JavaScript:
App = Ember.Application.create();
App.Router.map(function() {
this.resource('items', function() {
this.resource('item', {path: '/:item_id'});
});
});
App.Model = Ember.Object.extend({
firstname: 'foo',
lastname: 'bar',
fullname: function() {
return this.get('firstname') + ' ' + this.get('lastname');
}.property('firstname', 'lastname')
});
App.ItemsRoute = Ember.Route.extend({
model: function() {
return [App.Model.create({id: 1}), App.Model.create({id: 2}), App.Model.create({id: 3}), App.Model.create({id: 4})];
}
});
App.ItemRoute = Ember.Route.extend({
model: function(params) {
return this.modelFor('items').findBy('id', +params.item_id);
}
});
Templates:
<script type="text/x-handlebars">
<h2>Welcome to Ember.js</h2>
{{link-to "items" "items"}}
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="items">
<ul>
{{#each item in model}}
<li>
{{#link-to 'item' item.id}}
{{item.fullname}} {{item.id}}
{{/link-to}}
</li>
{{/each}}
</ul>
{{outlet}}
</script>
<script type="text/x-handlebars" data-template-name="item">
{{input value=model.firstname}}
{{input value=model.lastname}}
{{model.fullname}}
</script>
Please keep in mind that ArrayController and ObjectController aren't recommended to use, because they will be deprecated in future. Demo.
I have my Route setup with models from FireBase:
App.ApplicationRoute = Ember.Route.extend({
model: function() {
return EmberFire.Array.create({
ref: new Firebase("https://some-database.firebaseio.com/shape")
});
},
setupController: function(controller, model) {
controller.set('model', model);
}
});
In the template I'm display each array object within a view with #each:
{#each controller}}
{{#view App.ColorView}}
<div>{{color}}</div>
{{/view}}
{{/each}}
And in the view I like a click action to delete the particular color:
App.ColorView = Ember.View.extend({
click: function() {
var theColor = this.get('content');
console.log(theColor);
}
});
Currently a list of colors show up int he view but I'm getting "undefined" when I try to access the model property belongs to this object. Are there additional setup that I need to do?
You can pass using contentBinding
{#each controller}}
{{#view App.ColorView contentBinding=this}}
<div>{{color}}</div>
{{/view}}
{{/each}}