Currently i have a setup that looks like this:
ko.applyBindings(viewModel);
$(".removeButton").live("click", function() {
viewModel.ProductCategories.destroy(ko.dataFor(this));
});
$(".renameButton").live("click", function() {
ko.dataFor(this).Name("Renamed Successfully!");
});
This is working fine for me until i introduce the concept of child elements. At that point the remove event no longer works for those items.
Is there a more generic way of "destroying" an element?
KO obviously knows the element i am clicking (as i am able to rename).
[{"Id":1,"Name":"Bikes","Parent":null,"Children":[{"Id":5,"Name":"Mountain Bikes","Parent":1,"Children":null},{"Id":6,"Name":"Road Bikes","Parent":1,"Children":null},{"Id":7,"Name":"Touring Bikes","Parent":1,"Children":null}]},{"Id":2,"Name":"Components","Parent":null,"Children":[{"Id":8,"Name":"Handlebars","Parent":2,"Children":null},{"Id":9,"Name":"Bottom Brackets","Parent":2,"Children":null},{"Id":10,"Name":"Brakes","Parent":2,"Children":null},{"Id":11,"Name":"Chains","Parent":2,"Children":null]}]
The events above will all work on any element (child or otherwise) except for remove which only works on root elements.
Can i call remove on an element itself or will I have to add some way of working out where it lives inside the array and destroying it like that?
for example; this is preferable:
$(".removeButton").live("click", function() {
ko.dataFor(this).destroy();
});
to this:
$(".removeButton").live("click", function() {
viewModel.ProductCategories[someindex].Children.destroy(ko.dataFor(this));
});
Thanks,
Kohan
The main issue is determining who the parent array is when trying to destroy an item.
Several options:
Rather than ko.dataFor, you can use ko.contextFor which will return an object that includes properties like $data, $parent, $parents and $root.
If your arrays have the same name, then you could do something like: http://jsfiddle.net/rniemeyer/xJjK8/
If your arrays have different names, then you could add a hint on the button element to understand the name of the parent like: http://jsfiddle.net/rniemeyer/arpNx/
Otherwise, if you really wanted it to be generic, then you could use the with binding to force a scope block, which would allow you to access the parent array through $parent. However, this will be the unwrapped array and we really would want the observableArray. With some extra work, you could loop through the properties of the parent's parent and compare the underlying array with your unwrapped array to locate the actual observableArray that you would want to call destroy with your item. Like this: http://jsfiddle.net/rniemeyer/bBVrE/
Finally, if you take care in the way that your objects are created you can push the destroy functionality to the item itself rather than needing access directly to the parent. Here is a sample that shows adding a destroyMe method to an object that uses the parent that was passed to the constructor function: http://jsfiddle.net/rniemeyer/Eeryh/
Related
There may be a simple explanation to this, so please excuse my ignorance.
When working with an array from the child component and using .pop(), .splice(), .push() on the passed value it persists the parent array value just fine, but when I try to assign value(=) it does not persist the array in the parent component.
I created a simple example on Plunker.
The program has 3 buttons, clear, pop, and reload.
Clear list - assigns the #Input list to an empty array (this.list = []). Notice that the parent component counter doesn't change
Pop - performs a .pop() on the list. Notice that the parent component counter changes.
How come I can't just simply assign the array value?
I know can use an EventEmitter and have the parent listen for
the event and update it that way. Im trying to understand this
scenario and seeing if I can avoid the extra code.
The parent and child both have a reference to the same/one array object. So if you change elements of the array (e.g., modify an array element, or add or remove elements), both parent and child "see" the changes because the changes are happening to that same/one object.
If you assign a new array in the parent, Angular change detection will propagate the new array reference down to the child, since this is an input property on the child.
However, if you assign a new array in the child, the parent will still have a reference to the original array. Angular change detection won't help you here. You would have to inform the parent about this new array, e.g., using an EventEmitter.
This JSBin isolates a problem I ran into in my code. I have a hierarchy of embedded models and a computed property (data) that is supposed to fire whenever a value at the very bottom of the chain changes (symbol). The example displays the property directly as well as the result of the computed property. A button changes the value on click. You'll see that it updates the property but the computed property doesn't fire. Why doesn't selectedAllocation.positions.#each.instrument.symbol work to trigger the computation when any instrument.symbol changes?
If the example seems contrived, it's only because I tried to abstract something that is more complex in reality, e.g. there is more than just one object in these arrays and data is necessary because another library expects a simple JS object in a particular format.
Note that #each only works one level deep. You cannot use nested forms
like todos.#each.owner.name or todos.#each.owner.#each.name.
http://emberjs.com/guides/object-model/computed-properties-and-aggregate-data/
You'll need to create an alias to bring symbol up one level (Not a coffeescript guy, hopefully you can read through the hacking, the positions alias below is for kicks and giggles, makes no difference).
App.Position = Ember.Object.extend({
instrumentSymbol: Em.computed.alias('instrument.symbol')
})
App.IndexController = Ember.ArrayController.extend
selectedAllocation: null
positions: Em.computed.alias('selectedAllocation.positions'),
data: (->
#get("positions").map (position) ->
symbol: position.get "instrumentSymbol"
).property "positions.#each.instrumentSymbol"
...
http://jsbin.com/bivoyako/1/edit
I have two ExtJs TreePanel. One tree is fully loaded (call it FLTree) and second one is partially loaded (call it PL tree). When user clicks on a node in fully loaded tree & that node is present in partially loaded tree, I want to fire the checkchange event for that node in partially loaded tree.
Is this possible?
Yes, Ext.tree.Panel has the itemclick event which is fired when an item is clicked (you need to add it in the controller or in the treepanel's listeners property.
The attributes are: someFunctionName: function(treeview, record, item, index, e, eOpts) { ... }
From the record variable you can get the data needed from the first tree's selected node.
To find the other treepanel you can use the up() and down() methods on the treeview:
var parentContainer = treeview.up('container_xtype[someProperty1=someValue1]');
you can walk up in the component hierarchy (get that parent container which contains both treepanels).
var pLtree = parentContainer.down('treepanel[someProperty2=someValue2]');
If the two treepanel doesn't have common parent, then you can use the var pLtree = Ext.ComponentQuery.query('treepanel[someProperty2=someValue2]')[0]; global method which returns an array of matched components. BUT make sure that you use a good component selector query (you can check if the returned array's length == 1).
Finally you need to use the pLtree.fireEvent('checkchange', ...); which is described HERE.
I'm trying to change the view model which is bound to some part of a DOM template (instead of changing the values of the view model) but I just can't figure out how or if it's even possible
Here's the scenario:
Create a new View Model object
Bind it (e.g. applyBindings(myViewModel)
Create another View Model object
Bind the new object to the same part of the DOM so all elements are now bound to the new object.
I want to do the equivalent of changing the value of DataContext of a control in WPF (on which KO's MVVM pattern is based)
The reason for this is that I'm trying to use the same view model for both the representation of the object in a list and the representation of the object in its own view, so I already have a view model for all objects being shown in the list.
There are other workarounds but I think this would be the most elegant way to do it.
There are two way of working with multiple viewmodel. The first way is to do multiple binding like #nathan gonzalez said. You should do binding up your viewmodels. However this complicates things a bit. Therefore difficult to manage.
The second way is to use master viewmodel. I would recommend this.
http://jsfiddle.net/sinanakyazici/e29EZ/10/
<div data-bind="with: mainvm">
<span data-bind="text: prop, click : action"></span>
</div>
var vm = function(value)
{
this.prop = ko.observable(value);
var self = this;
this.action = function() {
console.log("clicked: " + self.prop());
}
}
var master = {
mainvm : ko.observable(null)
}
master.mainvm(new vm('viewmodel 1'));
master.mainvm(new vm('viewmodel 2'));
ko.applyBindings(master);
so ko.applyBindings() should cover this for you. you can pass in a 2nd parameter that tells which top level element to apply the bindings to, like so:
ko.applyBindings(myExistingViewModel, $('#someElementId')[0]);
you may want to clean the elements first though, like this:
ko.cleanNode($('#someElementId')[0]);
this completely removes the bindings and clears the in memory data for that element and its children bindings.
I have this controller with a value.
App.xcontroller = SC.ArrayController.create({
...some code...
array_values = [],
..more code...
})
Now i have somewhere in a view this valueBinding
valueBinding: 'App.xController.array_values',
When I change values in the array the view does not get updated. but when i do
the following in the controller:
var array_values = this.get('array_values');
... adding / removing values to the array....
if (x_values.contains(x)){
x_values.removeObject(x)
} else {
x_values.pushObject(x);
};
this.set('array_values', array_values.copy());
the binding works, the view gets updated. But ONLY with the copy().
I don't want to make a copy of the array, IMHO this is not efficient. I just want to
let the valueBinding know content has changed..
the x values are just a bunch of integers.
The reason i want this: I want to change the value key of a SegmentedItemView. I want to change the active buttons. But I do not know on forehand how many segmentedviews I have
so I thought i bind the value of every generated segemented view to some common array and change that common array to be able to change the active buttons on all of the segmented views. Since each button represents an item with an unique key it works fine. except that i have to copy the array each time.
set the content property of the xcontroller
Bind to the arrangedObjects property of the xcontroller
You need to use KVO compliant methods on the array to get the bindings to fire. The ArrayController itself has an addObject and removeObject methods. Arrays in SC have been augmented with a pushObject method (among others), which is also KVO compliant. So if you use the KVO methods the view should update.
The reason your view does not update is because you are bound to the array, but the array itself did not change. When you do a copy, the array itself changes, so the bindings fire.
You might also want to try
this.notifyPropertyChange('x_values');
in the controller after you make the changes, but that is less preferable to using the built in KVO functionality.