angular.js hierarchical model: grandchildren not working - javascript

I'm trying to get to know Angular a bit. In particular, I want to create a hierarchical data structure, that can be manipulated using a hierarchical view:
Root:
- addChild
- child 1: { remove, addChild, child1, child2, ...}
- child 2: { remove, addChild, child1, child2, ...}
....
(real code at http://jsfiddle.net/xtofl/n3jqM/12)
At the moment I try to stop at 2 levels, i.e. the Root has children and grandchildren.
The grandchildren's 'remove' button does trigger the child.remove(grandchild) function. However, the removal of the element does not result in rows being removed :(
I don't manage to understand why. On top of that, the example at the fiddle seems to add 4 grandchildren at once.
The relevant code:
function Controller($scope) {
$scope.nextChildIndex = 1;
$scope.addChild = function () {
$scope.children.push(makeChild($scope.nextChildIndex++));
};
$scope.removeChild = function (child) {
$scope.children.remove(child);
};
$scope.children = [];
}
var makeChild = function (i) {
var nextIndex = 1;
var ret = {
index: i,
children: []
};
ret.addChild = function () {
ret.children = makeChild(nextIndex++);
};
ret.removeChild = function (child) {
ret.children.remove(child);
};
return ret;
};
The relevant html:
<ul ng-repeat="grandchild in child.children">
<li class="grandchild">
<button ng-click="child.removeChild(grandchild)">-grandchild</button>
<span>child {{grandchild.index}}</span>
</li>
</ul>
Question: what is so wrong about this makeChild function that an ng-click="addChild()" call adds 4 li elements at once, and that the ng-click="child.removeChild(grandchild)" does not result in grandchildren to be removed?

you problem is not in AngularJS
it was mistake with Array.prototype.remove
it should be
Array.prototype.remove = function (element) {
var index = this.indexOf(element);
this.splice(index,1 );
};
updated fdl - http://jsfiddle.net/STEVER/n3jqM/13/
UPD:
and instead of
ret.children = makeChild(nextIndex++);
you should do
ret.children.push(makeChild(nextIndex++));
http://jsfiddle.net/STEVER/n3jqM/14/
enjoy ;)

Related

Pass function from parent to child

I'm a little weak in javascript.
I'm inspiring myself from this answer to pass a function from parent to child in REACT and I'm having a little difficulty.
Could someone help me correct my code?
Thanks!
var List = React.createClass({
deleting: function(test){
console.log(test);
},
render: function() {
var all = this.props.activities;
var test = List.deleting;
var list = all.map(function(a){
return (<ListItem act={a} del={test}>);
});
return (
<ul> {list}
</ul>
);
}
});
var ListItem = React.createClass({
deleting: function(e){
this.props.del(e.target.parentNode.firstChild.innerHTML);
},
render: function(){
return (
<li key={this.props.act}>{this.props.act}
<div onClick={this.deleting}>X</div>
</li>
);
}
});
The error I get:
You need pass reference to method .deleting that is part of List Object, now you are trying pass var test = List.deleting; that is undefined. In order to this in .map, refers to List, you should set this for .map by yourself - to do that just pass (in our case it should be this because this in render method refers to List) second argument to .map, and pass to del= attribute reference to method this.deleting.
Also set key attribute for ListItem, and in React all tags must be closed - so add /> ( now you are getting error because you have not closed tag ListItem) in the end of ListItem tag
var List = React.createClass({
deleting: function(test) {
console.log(test);
},
render: function() {
var all = this.props.activities;
var list = all.map(function(a) {
return (<ListItem key={a} act={a} del={this.deleting} />);
}, this);
return <ul> {list} </ul>
}
});
Example

How can I reuse an object literal

I'm trying to reuse an object I created to dynamically create more than one slider on a page.
My idea was to create an array and push my slider object there as often as needed, so I could access it by id. Unfortunatelly it doesn't work. Hope someone can point me in the right direction ...
So what I have is this;
var slider = {
"init":function(slide_it){
this.parent = $(slide_it);
/Count Elements and create a navigation depending on the count etc./
},
"otherstuff":{...}
}
In my (document).ready function I create an array and fill it up with different slider objects, add Ids to an accordion and call the init function:
var slide_array = [];
var accordion_sections = $('#accordion > div').length;
for(var i = 0; i < accordion_sections; i++){
slide_array.push(slider);
$('#accordion').children('div').eq(i).attr('id', 'slide_it_'+ i);
slide_array[i].init($('#slide_it_' + i).find('.slider'));
}
Then I have a button with class="next" and I call a function within the slider
$('.next').click(function(){
slide_array[0].otherstuff();
});
My plan is to get the parent of .next and its id so that I can use slide_array[parentID].otherstuff();
But ... it's not working propperly when I call the init function inside the for loop more then once.
More weird, some functions calls seem to work, other have no effect.
What am I doing wrong?
You can use Object.create.
var s1 = Object.create(slider),
s2 = Object.create(slider);
s1.init(...);
s2.init(...);
If you return this from init your will be able to chain like:
var s1 = Object.create(slider).init(...);
However at this point I would just ditch the object literal and use constructors, since this is what you need.
function Slider(slide_it) {
this.parent = $(slide_it);
}
Slider.prototype = {
constructor: Slider,
otherStuff: function () {}
};
var s1 = new Slider(...),
s2 = new Slider(...);
Write a function to return the object:
function slider() {
return {
"init":function(slide_it){
this.parent = $(slide_it);
/Count Elements and create a navigation depending on the count etc./
},
"otherstuff":{...}
};
}
Then:
slide_array.push( slider() );
That'll give you a separate object every time. In you're version, you're filling the array with references to the same single object.
Why not just turn that into a jQuery plugin ?
jQuery.fn.slider = function(options) {
return this.each(function() {
var sliderElem = $(this),
settings = $.extend({
speed : 3000,
something : 'other thing'
}, options);
otherStuff(sliderElem);
});
function otherStuff(elem) {
}
}
$('#accordion > div').slider();
No iteration or jumping through hoops, just call it on the collection and it creates a new slider for each element ?

Replacing specific items within observable arrays in knockout.js

I am quite new to knockout.js, and I am enjoying learning how to make interfaces with it. But I have a bit of a wall while trying to make my interface more efficient. What I am trying to achieve is remove only the elements selected by $('.document_checkbox').serializeArray(), which contains the revision_id. I will then re-add the entries to the view model with a modified call to self.getDocument(), passing only the modified records which will be re-added. Can anyone help me how to remove the entries from the arrays based on the 'revision_id' values of $('.document_checkbox').serializeArray()
?
function Document(data) {
this.line_id = data.line_id
this.revision_id = ko.observable(data.revision_id);
this.status_id = ko.observable(data.status_id);
}
function DocumentViewModel() {
var self = this;
self.documents = ko.observableArray([]);
self.getDocument = function(){
//Reset arrays
self.documents.removeAll();
//Dynamically build section arrays
$.getJSON("/Documentation/Get-Section", function(allData) {
$.map(allData, function(item) {
var section = { name: item.array_name, display_name: item.display_name, documents: ko.observableArray([])};
self.documents.push(section);
})
//Add document objects to the arrays
$.getJSON("/Documentation/Get-Document", function(allData){
$.map(allData, function(item) {
var section = ko.utils.arrayFirst(self.documents(), function(documentSection) {
return documentSection.name === item.array_name;
});
section.documents.push(new Document(item));
});
});
});
}
self.updateStatusBatch = function(data,event){
$.post('/Documentation/Update-Status-Batch',
{
revision_id : $('.document_checkbox').serializeArray(),
status_id : event.currentTarget.value
}).done(
function(){
//This is where I get confused.
});
}
}
You should modify the /Documentation/Update-Status-Batch in order that it returns the deleted item id. So you will be able to remove it on the client side.
Try this "done" function:
function(removedItemId) {
self.documents.remove(function(doc){
return doc.status_id == removedItemId;
})
}
Take a look at the remove function.
I hope it helps.

Having a directive listen to a function

I have the below code:
HTML
<div id="header">
<h1>The JSON Store</h1>
<div class="cart-info" ng-controller="CartController" quantity="basketContents">
My Cart (<span class="cart-items">{{basketCount()}}</span> items)
</div>
</div>
<div id="main" ng-view></div>
JavaScript
app.controller(
'CartController',
function ($scope, basketService) {
$scope.basketCount = basketService.getCount;
$scope.basketContents = basketService.items;
}
);
app.factory('basketService', function () {
return {
getCount: function () {
var basket = JSON.parse((localStorage.getItem('shoppingBasket') || '{ "items": [] }'));
var count = 0;
basket.items.forEach(function (element) {
count += element.quantity;
});
return count;
},
get items() {
var basket = JSON.parse((localStorage.getItem('shoppingBasket') || '{ "items": [] }'));
var quantities = basket.items.map(function (x) { return x.quantity; });
if (quantities.length > 0) {
var total = quantities.reduce(function (previousValue, currentValue) { return previousValue + currentValue; });
return total;
} else {
return 0;
}
},
addItem: function (item) {
var basket = JSON.parse((localStorage.getItem('shoppingBasket') || '{ "items": [] }'));
var itemStoredAlready = basket.items.filter(function (x) { return x.id === item.id; }).length === 1;
if (itemStoredAlready) {
var basketItem = basket.items.filter(function (x) { return x.id === item.id; })[0];
basketItem.quantity += parseInt(item.quantity, 10);
} else {
var basketItem = {};
basketItem.id = item.id;
basketItem.title = item.title;
basketItem.quantity = parseInt(item.quantity, 10);
basket.items.push(basketItem);
}
localStorage.setItem('shoppingBasket', JSON.stringify(basket));
}
};
});
app.directive('quantity', function () {
var linker = function (scope, element, attrs, basketService) {
scope.$watch(attrs.quantity, function (value, oldValue) {
if (value > oldValue) {
alert("added");
}
}, true);
};
return {
restrict: 'A',
link: linker
};
});
I then have other controllers that handle templates for the "main" div. In one of those controllers it calls basketService.addItem and the view is updated by basketCount() (I don't fully understand how that happens, something is triggering that I assume)
When the addItem is called I would like to do some jQuery animate or fadeIn and I know I have to use a Directive but I cannot understand how I get the directive to watch the addItem function and then to do some jQuery stuff. As you can see I have tried to expose a property in the basketService which is set in the CartController Scope and the custom HTML element but when something is added the HTML element is not getting updated and the directive function is not getting called.
Thanks for your help
Here's a Plunker - http://plnkr.co/edit/gis0114bTRRJLHCdYnMG?p=preview
The animation should be inside a directive (like you said).
And the controller should bind the directive to a property, that changes when the count is changed in the service. You accomplish that when using an isolated scope in the directive.
Here is an example, you can change the css() with your jQuery logic : http://plnkr.co/edit/Mi5QzViUgl5mS21EAKh6?p=preview
Hope it helps,
Shai
I cannot understand how I get the directive to watch the addItem function and then to do some jQuery stuff
Normally, you'll want to $watch a scope property, not a function. Here's one way to do it, that I think simplifies things:
Create a counter primitive on basketService. Increment this property when an item is added.
Create a basket property on the CartController that references the basketService
$watch('basket.counter') in the quantity directive
Because the quantity directive is not creating a new isolate scope, it does not need a value -- i.e., just <div class="..." ng-controller="..." quantity> is sufficient
Plnkr
There are other ways, like #ShaiRez suggested -- using an isolated scope. That's more complicated because you need to understand how isolate scopes work and you need to use attributes to pass information into the isolate scope, but it does make your directive more reusable.
Some other observations:
Instead of a getCount() function (which gets re-evaluated every digest cycle), how about an itemCount property instead. The property would be updated as part of "add" and "remove" operations.
Your plnkr is very large. For future questions it would be better to create a minimal example. (You will get more people to look at it, and you'll get a faster response).

Is there a way to only show parent nodes in a extjs tree

I want to show only parent nodes of a tree in extjs. In my datastore there are leaf nodes as well.
The output should be like -
Folder 1
Folder 1.1
Folder 2
Folder 3
Create a filter object that gets only parent nodes and add it to the store config:
E.g. filter for parent nodes only:
var nodeFilter = new Ext.util.Filter({
property: 'leaf',
value : false
});
Putting it on the treestore config:
var yourTreeStore = Ext.create('Ext.data.TreeStore', {
// other configs ...
filters: [nodeFilter]
});
EDIT:
incutonez is right, I submitted according to the API properties but did not notice the missing functions. They are easy enough to override though to apply filtering for a treestore though. This is working for me in 4.1b2:
Ext.override(Ext.data.TreeStore, {
hasFilter: false,
filter: function(filters, value) {
if (Ext.isString(filters)) {
filters = {
property: filters,
value: value
};
}
var me = this,
decoded = me.decodeFilters(filters),
i = 0,
length = decoded.length;
for (; i < length; i++) {
me.filters.replace(decoded[i]);
}
Ext.Array.each(me.filters.items, function(filter) {
Ext.Object.each(me.tree.nodeHash, function(key, node) {
if (filter.filterFn) {
if (!filter.filterFn(node)) node.remove();
} else {
if (node.data[filter.property] != filter.value) node.remove();
}
});
});
me.hasFilter = true;
},
clearFilter: function() {
var me = this;
me.filters.clear();
me.hasFilter = false;
me.load();
},
isFiltered: function() {
return this.hasFilter;
}
});
With this overrride in your code, you could create a "leaf only" filter as a function or a property/value pair as per the Ext.util.Filter API:
// leaf only filter as a property/value pair
var nodeFilter = new Ext.util.Filter({
property: 'leaf',
value : false
});
// leaf only filter as a function
var nodeFilter = Ext.create('Ext.util.Filter', {
filterFn: function(item) {
return !item.data.leaf;
}
});
You could then just call the filter function whenever to take out the leaf nodes:
myTreeStore.filter(nodeFilter);
TreeStores do not inherit filtering (because they're abstract stores), so Geronimo's answer did not work for me. I wish it did because it would've made my life a whole lot easier.
Anyway, I have a thread over on the Sencha forums that provides a working filtering solution. In my example, filtering is called by the filterBy function, so I'm sure you could tweak it to work your way.

Categories