knockout javascript foreach binding - javascript

I'm trying to allow a user to create a casting and add an array of categories to this casting object. I was trying to use knockout's foreach binding to the array of categories and let users add new categories to the casting. I have created a jsfiddle to illustrate what I'm trying to explain here.
http://jsfiddle.net/msell/ueNg7/16/
The JSON object gets built up correctly as a user modifies a casting, but I cant quite get the list of castings to display.

You have several problems:
You are using Knockout 1.2.1
The foreach binding was not added until Knockout 2.0.
You are not using an observableArray
You need to modify your categories property to be a ko.observableArray(), instead of just an empty array. Otherwise Knockout will not be able to observe when you push to it, and the remove method will not exist.
Your this binding is wrong.
When called from event handlers, this will be set incorrectly. You can fix this in various ways, discussed in length in the Knockout documentation, but one easy fix is to change the references to viewModel instead of to this.
To fix all these, you should upgrade to Knockout 2.0, and change your view model declaration to be
var viewModel = {
name: ko.observable(''),
description: ko.observable(''),
categories: ko.observableArray(),
categoryToAdd: ko.observable(''),
removeCategory: function(category) {
viewModel.categories.remove(category);
},
addCategory: function() {
viewModel.categories.push(new Category(viewModel.categoryToAdd()));
viewModel.categoryToAdd('');
}
};
Here is a corrected JSFiddle: http://jsfiddle.net/ueNg7/19/

You need to use ko.observableArray for you array otherwise Knockout wont know when you change your array and wont update, also you should use a template instead, read here http://knockoutjs.com/documentation/template-binding.html#note_2_using_the_foreach_option_with_a_named_template
var viewModel = {
name: ko.observable(''),
description: ko.observable(''),
categories: ko.observableArray([]),
categoryToAdd: ko.observable(''),
removeCategory: function(category) {
this.categories.remove(category);
},
addCategory: function() {
this.categories.push(new Category(this.categoryToAdd()));
this.categoryToAdd('');
}
};

Related

Angular scope not updating after orderBy

I'm building an angular component that renders a table, and I'm running into some issues with the sorting function. The scope in this case looks like this:
$scope.listState = {
sortBy: '<string>',
sortReverse: <bool>,
headings: {...},
list: [
{
rowCols: {
user: 'timfranks#gmail.com',
name: 'Tim Franks',
since: '11/6/12'
}
rowState: {...}
},
{
{
user: 'albertjohns#sbcglobal.net',
name: 'Alber Johns',
since: '11/12/13'
},
rowState: {...}
},
{
{
user: 'johnsmith#sine.com',
name: 'John Smith',
since: '7/28/14'
},
rowState: {...}
}
]
};
I originally tried to sort the list via:
ng-repeat="row in $ctrl.list | orderBy:$ctrl.listState.sortBy:$ctrl.listState.sortReverse"
This didn't work, although in tracing with the debugger I found that orderBy was in fact getting the right arguments and returning a properly sorted list. Just to be sure, I changed the code to use orderBy in my controller, like this:
this.listState.list = _this.orderBy(listState.list, 'rowCols.' + listState.sortBy, listState.sortReverse);
This works for the first time (called within the constructor), but then stops working. I'm pretty sure this is some aspect of Angular's scope that I don't fully understand. Any help would be appreciated.
Using a filter in an ng-repeat expression does not update the original model, but rather creates a copy of the array.
You are on the right track with the this.listState.list = _this.orderBy(...) ... and it does make sense that it gets called only once.
If the listState.list model is getting new values after the controller loads and you want to resort with those new values, you would probably want to use something like:
$scope.$watchCollection('listState.list', function listChanged(newList, oldList){
$scope.listState.list = _this.orderBy(...);
});
I can't recall if $watchCollection is going to register a change if the order changes, but if you end up in an infinite loop with that code, you could put a blocker like:
var listSorting = false;
$scope.$watchCollection('listState.list', function listChanged(newList, oldList){
if(!listSorting){
listSorting = true;
$scope.listState.list = _this.orderBy(...);
$timeout(function resetSortBlock(){ // try it without the $timeout to see if it will work
listSorting = false;
});
}
});
Figured it out. The issue was that I used an attribute directive to render the list rows, and the attribute directive and ng-repeat were on the same element. This created a scope conflict between the two directives. I moved my directive to a div within the ng-repeat and it works fine.

Backbone - Get model's idAttribute value from the collection?

I am creating a generic table view that can be used with different collections. Here is an example of what I'm trying to do:
...
getModelIdAttribute: function () {
return this.collection.__proto__.model.prototype.idAttribute;
},
getModelFromRow: function (HTMLrow) {
return this.collection.get(this.dataTable.row(HTMLrow).data()[this.getModelIdAttribute()]);
},
...
The data() function returns an object with the attributes that were used to create the model, but I want to account for different idAttributes.
Currently, my way works - but __proto__ is deprecated and it seems like there should be an easier way that I am missing?
Thanks

Ember-data observe confusion

I have a model like this:
App.Category = DS.Model.extend({
title: DS.attr('string'),
items: DS.hasMany('item', {async: true}),
itemCount: function() {
return this.get('items').get('length');
}.property('items')
});
and it seems I cannot use "property" there if I want to have the UI update everytime a user adds or removes items.
From what I can tell I should be using "observes", but when I use that in place of "property" the handlebars {{itemCount}} tag just renders the function itself as a string.
Any help on getting this to render properly is much appreciated.
I think you can simply use :
{{items.length}}
in your handlebars template.
There's absolutely no need for an observer, computed properties do updates themselves.
And if you really want a computed property named itemCount, it would be :
itemCount: function() {
return this.get('items.length');
}.property('items.length')
Or even better :
itemCount: Ember.computed.alias('items.length')
Like #florent-blanvillain said, just use Ember.computed.alias. But in the future, when writing computed properties based on arrays, you need to use the #each syntax to get it to respond to changes in property values:
itemCount: function() {
return this.get('items').filterBy('isSelected');
}.property('items.#each.isSelected')
Something like that. See the docs on computed properties for more info.

KnockoutJS observable not updating

Hi I am new to knockoutjs and started learning from the Tutorial in their site.
Observable arrays are supposed to update all the bindings in a page. I am practicing in JsFiddle and my bindings are not being updated dynamically.
What am I doing wrong?
JSFiddle Example here
I am expecting that if we type in the textboxes it should update the table!
JS Code:
var viewmodel = {
posts: ko.observableArray(data)
};
The problem is that calling posts: ko.observableArray(data) will make only data observable, and not its elements. To make an entire object tree observable, you can use the mapping plugin, like in this updated fiddle: http://jsfiddle.net/gyW2k/7/
The only change to the code is:
var viewmodel = {
posts: ko.mapping.fromJS(data)
};
ko mapping docs: http://knockoutjs.com/documentation/plugins-mapping.html

Missing view.context or templateContext in an Ember event handler

I'm trying to push the object that populated a view into an array, but the reference is somehow getting lost. I've got an Ember view, with a defined eventManager:
FrontLine.NewProductButton = Em.View.extend({
tagName: 'button',
classNames: ['addtl_product',],
templateName: 'product-button',
eventManager: Ember.Object.create({
click: function(event, view) {
FrontLine.ProductsController.toggleProductToCustomer(event, view);
}
})
})
That view renders a bunch of buttons that are rendered with properties that come from objects in the ProductsController using the #each helper. That part works great. And when I click on any of those buttons, the click event is firing and doing whatever I ask, including successfully calling the handler function (toggleProductToCustomer) I've designated from my ProductsController:
FrontLine.ProductsController = Em.ArrayController.create({
content: [],
newProduct: function(productLiteral) {
this.pushObject(productLiteral);
},
toggleProductToCustomer: function(event, view){
FrontLine.CustomersController.currentCustomer.productSetAdditional.pushObject(view.context);
}
});
I'm trying to use that function to push the object whose properties populated that view into an array. Another place in my app (a simple search field), that works perfectly well, using pushObject(view.context). Here, however, all that gets pushed into the array is undefined. I tried using view.templateContext instead, but that doesn't work any better. When I try console.log-ing the button's view object from inside those functions, I get what I'd expect:
<(subclass of FrontLine.NewProductButton):ember623>
But either view.context or view.templateContext return undefined. How do I access the object I'm after, so I can add it to my array?
The simple answer is that it was one letter's difference:
view.content
or:
view.get('content')
provides the source object in that particular situation, rather than view.context.
(My only real challenge with Ember so far is that accessors for objects and properties vary so much from situation to situation, and there's no real documentation for that. Sometimes the object is at view.context, sometimes it's at view.content, sometimes _parentView.content, etc., etc. It would be awesome if there were a chart with the umpteen different syntaxes for accessing the same data, depending on which particular aperture you're reaching through to get it. I'm still discovering them...)

Categories

Resources