I am creating an unordered list in an Ember.js view by using the each helper. I also have some Javascript that runs on the didInsertElement hook, which selects each list item. However, that selector fails to select any elements if I generate the list using the each helper. Any thoughts on how to get results from the selector?
relevant view:
<script type="text/x-handlebars" data-template-name="slider">
<ul>
{{#each slide in controller}}
<li><img src="./resource/locations/1/test.jpg"></li>
{{/each}}
</ul>
<br>
</script>
relevant JS:
App.SliderView = Ember.View.extend({
templateName: "slider",
didInsertElement: function() {
var self = this;
var items = $(">ul>li", self.$());
//...
}
});
Update:
I believe the problem arises because I am loading the models that populate my controller asynchronously. Is there any way to listen to the event that occurs when the model is first populated?
My Model Code:
App.Location = DS.Model.extend({
thenSlider: DS.hasMany("slide", {async: true})
});
App.Slide = DS.Model.extend({
//...
});
Looks good to me, you're probably facing a problem where your data is populating after didInsertElement has fired. didInsertElement only fires when Slider has been inserted initially, not necessarily when the elements inside of it are inserted/modified.
http://emberjs.jsbin.com/liluxece/1/edit
Since it's async, I'd use render and hook it up to another view, that way you can use didInsertElement on a case by case situation for each item.
{{render 'template' context}}
Here's an example
http://emberjs.jsbin.com/liluxece/5/edit
Related
Parent template onRendered function invokes before child template. How to execute parent templates function after child template rendered.
<template name="parent">
{{#each object}}
{{> child}}
{{/each}}
</template>
<template name="child">
<img src="someurl" data-src="someurl">
</template>
Now I need to execute some document ready function so
Template.parent.onRendered(function() { // doesnt invokes after child template
$("img").unveil();
$(window).trigger("lookup");
});
A combination of autorun and afterFlush is probably what you need. Give something like this a try:
Template.parent.onRendered(function() {
this.autorun(function() {
// replace the find with whatever is in your helper
// which returns the children array/cursor
if (Children.find().count()) {
// this should run after the child templates have been rerendered
Tracker.afterFlush(function() {
$('img').unveil();
$(window).trigger('lookup');
});
}
});
});
While using Tracker.afterFlush is an option that produces the needed behavior, the best way to do something like this is to just make use of the child template's onRendered function. As soon as the child template is rendered then needed code will execute.
Template.child.onRendered(function() {
this.$('img').unveil();
this.$(window).trigger('lookup');
});
This approach is more natural and allows the child template to be used in any other template as well without "breaking"
I do an extensive use of templates, and I like to use full contained templates. I mean that I want to see in the template code all the DOM elements including the root one, like this:
<script type="text/template" id="template-card">
<div class="card box" id="card-<%= id %>">
<h2><%= title %></h2>
<div><%= name %></div>
</div>
</script>
But what Backbone likes is having a template like this:
<script type="text/template" id="template-card">
<h2><%= title %></h2>
<div><%= name %></div>
</script>
And defining the root element and its attributes in the JS code. What I think is ugly and confusing.
So, any good way to avoiding my Backbone View to wrapper my template with an extra DOM element?
I have been checking this issue thread: https://github.com/documentcloud/backbone/issues/546 and I understand there is not any official way to do it.. but maybe you can recommend me a non official way.
You can take advantage of view.setElement to render a complete template and use it as the view element.
setElement view.setElement(element)
If you'd like to apply a Backbone view to a different DOM element, use setElement, which will
also create the cached $el reference and move the view's delegated
events from the old element to the new one
Two points you have to account for:
setElement calls undelegateEvents, taking care of the view events, but be careful to remove all other events you might have set yourself.
setElement doesn't inject the element into the DOM, you have to handle that yourself.
That said, your view could look like this
var FullTemplateView = Backbone.View.extend({
render: function () {
var html, $oldel = this.$el, $newel;
html = /**however you build your html : by a template, hardcoded, ... **/;
$newel = $(html);
// rebind and replace the element in the view
this.setElement($newel);
// reinject the element in the DOM
$oldel.replaceWith($newel);
return this;
}
});
And a working example to play with http://jsfiddle.net/gNBLV/7/
Now you can also define a view's tagName as a function and create a class like this:
var MyView = Backbone.View.extend({
template: '#my-template',
tagName: function() {
// inspect the template to retrieve the tag name
},
render: function() {
// render the template and append its contents to the current element
}
});
Here's a working example
Backbone.Decarative.Views provides you with an alternative way to do this, without having to rely on setElement. For more, check out my answer here.
Here's a fiddle for this question (make sure you have your console open):
Basically, I have a handlebars block view as a parent, then loop through an array of items and create child block views for each item. Later, those children are modified in some way, and I want to detect and handle that event.
Summary Code:
{{#view App.outerView}}
{{#each item in items}}
<h6>Person:</h6>
{{#view App.innerView}}
<dl>
<dt>First Name</dt><dd>{{item.first}}</dd>
<dt>Last Name</dt><dd>{{item.last}}</dd>
</dl>
{{/view}}
{{/each}}
{{/view}}
At some later point:
controller.get( 'items' ).pushObject( 'new item' );
In the jsFiddle, I am trying to keep the last child object always highlighted (active = true). When the outerView is initially inserted, we apply the highlight to the last item and it works as expected. Later, when we add a new item, our highlight method fires, but it doesn't see the new item.
2 Questions about this:
Main Question: Why doesn't the last item (Matt) get highlighted? In other words, why, after a new item has been added, does childViews.length still report the old value? Is there a better way to be doing this?
Secondary Question: Why does the observer on childViews fire twice when we add the new item? (3 times total: 1 on initial insert, 2 from addition of 1 new child item)
Edit: I've adjusted the fiddle a little to better illustrate my needs. Notice the mixed content of the outerView (HTML + innerViews). Basically, my outerView needs to accept ANY content as children, not just an array/collection of views/data.
Edit 2: Further clarification: Manipulating those child views after the fact is a different story and can be accomplished either by working with the contents of childViews (ember views) or using jQuery to manipulate HTML elements that may not be a formal ember view object, or any other means. The real goal here is simply to capture the event when ANY of the outerView content changes (with a current copy of childViews).
Edit 3: Here's an Issue on Github
You initial code looks a little tangled up. Here is a better way to achieve the same result:
http://jsfiddle.net/XaN8T/1/
You should wrap each item in a controller, which is easily done with the {{each}} helper:
<script data-template-name="application" type="text/x-handlebars">
This is a list:
<ul>
{{each controller itemController="item" itemViewClass="App.ItemView"}}
</ul>
</script>
And create a separate template for your items:
<script data-template-name="item" type="text/x-handlebars">
<h6>Person:</h6>
<div {{bindAttr class=":content active"}}>
<dl>
<dt>First Name</dt><dd>{{first}}</dd>
<dt>Last Name</dt><dd>{{last}}</dd>
</dl>
</div>
</script>
Then you can take advantage of needs and computed properties in Ember with the active property:
App.ItemController = Ember.ObjectController.extend({
needs: ['application'], //This way each ItemController can access the ApplicationController's content (i.e. the list of all items)
active: function() {
return this.get('controllers.application.lastObject') === this.get('content');
}.property('controllers.application.lastObject')
});
The App.ItemView then binds its css class to the controller's active property:
App.ItemView = Ember.View.extend({
tagName: 'li',
templateName: 'item',
classNameBindings: ['controller.active']
});
Voila! It works :)
A better way of doing what you have accomplished in the fiddle is by the use of Ember.CollectionView
and then adding a computed property which determines whether the child is the last one or not on the itemViewClass for the active class
active: function () {
return this.get("elementId") === this.get("parentView.childViews.lastObject.elementId");
}.property('parentView.childViews.length'),
Here is a Working Fiddle
Note Whenever there is a need to deal with collection of views either use CollectionView or Ember.ContainerView(This gives much more control over the child views manipulation) as per your requirements, but for most of the cases Ember.CollectionView would suffice
I seem to have a problem with the code below. I have a div element with the id='content' in my html. I wanted to replace 'body' element of el property with the div element but my hello world text doesn't when I typed el: $('div') or el:$('div#content') or el: $('#content'). I'm a beginner in backbone.js and in my understanding, I believe that this el property holds our parent tag where all our templates will be added as child elements(in this case 'body' tag being parent and 'p' tag being child).
(function($){
var ListView = Backbone.View.extend({
el: $('body'),
initialize: function(){
this.render();
},
render: function(){
(this.el).append("<p>Hello World</p>");
}
});
var listView = new ListView();
})(jQuery);
The View.el property should be defined as a jQuery selector (string), not a reference to HTML element.
From Backbone documentation:
var BodyView = Backbone.View.extend({
el: 'body'
});
Or as you wished,
el:'div#content'
When the view initializes, Backbone references the element in makes it available via the view.$elproperty, which is a cached jQuery object.
this.$el.append("<p>Hello World</p>");
The sample code you posted works, because there is always only one bodyelement, and that element already exists in the DOM when your view is rendered. So when you declare el:$('body'), you get a reference to the body element. The code in renderin works, because this.el is now a direct reference to the jQuery object:
(this.el).append("<p>Hello World</p>");
If you need to initialize a Backbone view using an existing DOM element (not a selector), Backbone documentation recommends passing it in the initialize function.
I am trying to hide an object using the jQuery method hide(), but it doesn't work.
I used console.log() to check the value of the argument in called function, so I know Im not doing right. But, i dont know how to make it work...
Here's what I have:
#------------------------Model------------------------
App.Monster = Ember.Object.extend({
name: "",
hp: 0,
isDead : false,
isDeadChanged: function(target, value){
App.monstersController.kill(target);
}.observes('isDead')
});
#------------------------Controller------------------------
App.monstersController = Ember.ArrayProxy.create({
content:[],
createMonster: function(name,hp){
var monster = App.Monster.create({name:name});
this.pushObject(monster);
},
kill: function(target){
//I Want the selected object to hide by using Jquery hide method!!
$('target').hide();
this.removeObject(target);
},
#------------------------HTML FILE------------------------
{{#each App.monstersController contentBinding="App.monstersController.content" tagName="ul"}}
<li>
{{view Ember.Checkbox checkedBinding="isDead"}}
<label for="" {{bindAttr class="isDead"}}>{{name}}</label>
</li>
{{/each}}
Generally bringing your DOM scripting patterns to Ember isn't going work, since Ember isn't about DOM manipulation it's about describing your application using objects. The DOM representation of your application will update automatically to reflect the state of its underlying data objects.
Ember.View tracks its visibility (and the visibility of its child views) with the isVisible property and will handle toggling the it's DOM representation for you. You should try to uncover the semantic object meaning of visibility for a given view context. For example, if you decided finished items in a todo list shouldn't be visible, you'd do:
{{#each item in controller}}
{{view App.TodoItemView isVisibileBinding="item.isComplete"}} <a {{action kill item}}>Done!</a>
{{/each}}
Don't quote target.
kill: function(target){
//I Want the selected object to hide by using Jquery hide method!!
$(target).hide();
this.removeObject(target);
},
$('target') looks for <target> elements.