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
Related
I am currently writing a Nightwatch test to select a new document from a list. And I will need to be able to select the next in the list. Is there a way to manually override the child number that needs selecting?
For example the current selector being used is :
<ul class="dv-packdocs">
<li class="dv-packdoc"<div class="icon-todo"></li>
<li class="dv-packdoc"<div class="icon-todo"></li>
<li class="dv-packdoc"<div class="icon-todo"></li>
<li class="dv-packdoc"<div class="icon-todo"></li>
</ul>
and the test would be something like :
viewer.selectNewDocument([2])
would this select the second child under the ul?
Or would I have to specify each child element?
If I understand correctly, you are trying to dynamically find the appropriate child element (li) from a dynamical length list (ul, where the list is populated based on user input, or other site actions). Correct?
I see two scenarios with two different approaches:
1. You have a set/fixed condition (way of identifying your target element): for example, in your list, the second li would be targeted by the below command.
viewer.selectNewDocument('ul.dv-packdocs li:nth-child(2)') (considering you are passing a complete selector to the selectNewDocument function)
, or
viewer.selectNewDocument(2), passing a number & form the selector inside the command (if you care for aesthetics):
selectNewDocument: function(index) {
this.api.perform((done) => {
// Click the second document in the list:
let selector = `ul.dv-packdocs li:nth-child(${index})`;
this.api.click(selector);
done();
});
return this;
},
Alternatively, if you would want the last document added, then you would have to issue a elements call on the ul to retrieve the length of the list, then use that in the same way to determine which li you have to click: viewer.selectNewDocument('ul.dv-packdocs li:nth-child('+length+')') (where length is the result of your elements call).
2. You don't have a fixed condition (I'll fill this up if the first part doesn't cover it, or later today, kinda slammed after the holidays)
Hope it's what you were looking for! Cheers!
I have few knockout attributes that are not associated with model. Rather i am assigning rank to every list element(incremental list +1). Now i want to use this rank1 element in child tags.
But i realize that i can't do it directly since its not coming from Model (example IsPremium below).
So I am finding closest element that has this rank1 attribute to find its value. I feel this is not optimized way since it will look up everytime.
Can i somehow bind this attribute to child elements as well?
Below is the code.
<script type="text/html" id="listingTemp">
<li data-bind='attr: {rank1 : $.getRank(IsPremium), isPremium : IsPremium}>
<div class="stock-detail">
..few other nested elements
<a class="slideShow" data-bind="attr: { isPremium : IsPremium} onclick="$.triggerSearchListingsTracking($(this).attr('isPremium '),$(this).closest('.listingContent').attr('rank1')
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.
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
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.