PDA

View Full Version : getChildren



andycramb
24 Jun 2009, 10:34 AM
I was looking to see if there is a method that can pull back all the children of an element
I cannot see anything that does this at the moment
So from the html below I want to get all the divs under the wrapper div from something like this


Ext.get('wrapper').getChildren()Can I /should I poke this into the Ext.Element prototype?
Is there any existing methods I can use to achieve this
I am kinda lost in between Ext,Ext.Element, Ext.DomQuery
Any help is much appreciated



<div id="wrapper">
<div id="child1></div>
<div id="child2></div>
<div id="child3></div>
<div id="child4></div>
</div>

hendricd
24 Jun 2009, 10:41 AM
See Element.select.



example: Ext.get('wrapper').select('div').hide();
Ext.get('wrapper').query('div') //a simple Array of matching DOM nodes.

andycramb
24 Jun 2009, 11:22 AM
Thanks for your help
I maybe should have explained that I was thinking that I may not know the tag name, the id or class so would want something that could return a composite element based on structure
and then I could chain other methods for example


Ext.get('wrapper).getChildren().hide();

hendricd
24 Jun 2009, 11:32 AM
Ext.Element has a variety (http://extjs.com/products/extcore/docs/?class=Ext.Element) of methods available.

andycramb
24 Jun 2009, 12:17 PM
Thanks but I have been looking at the Element class for a while now without a light bulb moment

Joe
24 Jun 2009, 2:03 PM
A working example says 1000 works .. so here you go ...

HTML


<style>
.cool-looking {
color:red;
}
</style>
<div id="wrapper">
<div id="child1">1</div>
<div id="child2">2</div>
<div id="child3">3</div>
<div id="child4">4</div>
</div>



Using select ...
Creates a Ext.CompositeElement for child nodes based on the passed CSS selector (the selector should not contain an id).

You can then treat this composite element just like you would an element as shown below.

To hide:

var el = Ext.get('wrapper');
el.select('div').hide();


To add class:

var el = Ext.get('wrapper');
el.select('div').addClass('cool-looking');


Or you can use query and get back an array as detailed by hendricd earlier.

Hope that helps

andycramb
24 Jun 2009, 2:50 PM
Thanks Joe for the help as well

What I am trying to achieve is port over a MooTools sliding tab extension in to ExtJs core
I have an early prototype how it works using MooTools at the moment on my site (http://www.cramb.org.uk/slidingtabs/index_v3.html) to give you an idea of what I am trying to do. I am at the moment trying to do write an extension for Ext Core

I was looking for the child sections to be configurable so it maybe divs,uls or even tables. It would be up to the user implementing the structure to decide what there html would be
The extension I hope to write would not care what tags/html that was used, but would just get all the children under the wrapper div. It would be flexible enough to accommodate any tags as long as they were children of the wrapper div
So when I called getChildren it could return all child elements

I understand what yourself and hendricd have all already posted but I am looking for some more flexibility and I was also hoping to pick up how to add methods to Ext.Element and therefore understand some more about the Core while I was at it

Hope that makes sense
Thanks to both of you for your help

andycramb
25 Jun 2009, 7:09 AM
I am nudging closer to what I was looking for
I have got the basics up and running by doing this


Ext.Element.prototype.getChildren = function(){

mycomp = new Ext.CompositeElementLite();

Ext.each(this.dom.childNodes, function(v) {
if(v.nodeType == 1) {
mycomp.add(this);
}
});
return mycomp;
};

This allows me to do the code below which gets all child HTML elements


Ext.get('wrapper').getChildren();

Condor
25 Jun 2009, 7:21 AM
Wouldn't it be easier to use:

Ext.Element.prototype.getChildren = function(){
return this.select('> *');
};

Joe
25 Jun 2009, 7:39 AM
Since select normally returns the CompositeElementLite, I suggest a rename..


Ext.Element.prototype.selectChildren = function(){
return this.select('> *');
};

hello2008
25 Jun 2009, 7:56 AM
Wouldn't it be easier to use:

Ext.Element.prototype.getChildren = function(){
return this.select('> *');
};

wow, great solution!;)

andycramb
25 Jun 2009, 8:12 AM
I tried this earlier and abandoned it because it returned 11 items when I was expecting just the 5 divs. I couldnt quite understand at the time and thought it was maybe also selecting TextNodes or lineBreak as a node. That is why I thought I should check if the nodeType was equal to 1
Maybe you can understand what its doing as the first few items in the elements object are this based on

this.select('> *');[0] = "\n "
[1] = "div#pane"
[2] = "\n "
....

The html is roughly this:



<div id="wrapper">
<div id="ext" class="pane">content</div>
<div>content</div>
<div>content</div>
<div>content</div>
<div>content</div>
</div>

hello2008
25 Jun 2009, 8:40 AM
yes, all of Ext's DOM traversal methods should ignore text nodes, maybe?

like childElements (http://prototypejs.org/api/element/childElements) in Prototype and getChildren (http://mootools.net/docs/core/Element/Element#Element:getChildren) in MooTools...

hendricd
25 Jun 2009, 12:51 PM
yes, all of Ext's DOM traversal methods should ignore text nodes, maybe?

like childElements (http://prototypejs.org/api/element/childElements) in Prototype and getChildren (http://mootools.net/docs/core/Element/Element#Element:getChildren) in MooTools...

I would disagree with that. What if you wanted the textNodes?

Ext.select/query strengths are in their generic use, to get anything via a valid CSS selector.

If you don't want textNodes, filter them out:



var elmsOnly = someParent.query('> *').filter(function(node){
return node.nodeType != 3;
});

Joe
25 Jun 2009, 1:18 PM
You won't see the DomQuery updated to exclude the text entries, but here is an idea.

If you want to use select and get back a CompositeElementLite and you normally loop through the results using each, then the most efficient option may to add a new function to the CompositeElementLite called eachEl. Such a function which would skip the text node types. In this case the count would still show 11 but when you loop through, it only hits the five you want.

Here is an example





Ext.override( Ext.CompositeElementLite, {
/**
* Calls each .. but skips the Text Elements
* see each for details
*/
eachEl : function(fn, scope){
var me = this,
el = me.el;

Ext.each(me.elements, function(e,i) {
el.dom = e;
if( e.nodeType && e.nodeType != 3){
return fn.call(scope || el, el, me, i);
}
});
return me;
}
})

Ext.onReady(function(){
var el = Ext.get('wrapper');
var children = el.select('/ *');
//This returns all 11
alert( children.getCount() );
var ctr = 0;
// ... however you can use eachEl to loop through only the elements
children.eachEl( function(item){
ctr ++
});
alert( 'we looped ' + ctr );
})

Joe
25 Jun 2009, 1:23 PM
If you want to integrate with / use Element select-like funtionality directlyfrom the element - then here are some random thoughts / implementations.

An extension of the previous simpler idea / post.

A bit out there but ....

This allows you to use selectEl at the element level just like select .. making for easer updates / cross over.





Ext.override( Ext.CompositeElementLite, {
/**
* Calls the passed function passing (el, this, index) for each element EXCEPT TEXT NODES in this composite. <b>The element
* passed is the flyweight (shared) Ext.Element instance, so if you require a
* a reference to the dom node, use el.dom.</b>
* @param {Function} fn The function to call
* @param {Object} scope (optional) The <i>this</i> object (defaults to the element)
* @return {CompositeElement} this
*/
eachEl : function(fn, scope){
var me = this,
el = me.el;

Ext.each(me.elements, function(e,i) {
el.dom = e;
if( e.nodeType && e.nodeType != 3){
return fn.call(scope || el, el, me, i);
}
});
return me;
},
/**
* Removes all text nodes from elements (not great performance here I'd imagine, use eachEl to just skip uneeded ones)
* @return {CompositeElement} this
*/
elOnly : function(){
var me = this;
var newelems = [];
me.eachEl(function(el){
newelems.push(el.dom);
});
me.elements = newelems;
return me;
}

})

Ext.override( Ext.Element, {
selectEl : function(selector,unique){
var ret = this.select(selector);
return ret.elOnly();
},
selectChildren : function(){
return this.selectEl('> *');
}
})

Ext.onReady(function(){
var el = Ext.get('wrapper');
var children = el.selectChildren();

// only the five
alert( children.getCount() );
var ctr = 0;
// ... can use each .. no need for eachEl
children.each( function(item){
ctr ++
});
alert( 'we looped ' + ctr );
})

hendricd
25 Jun 2009, 1:33 PM
@Joe -- :)
And what if I wanted all the empty textNodes (for replacement) whose previous sibling had an attribute bra="large" ?

Got an override for that?

That sort of filtering belongs in your application logic, especially considering all the possible CSS selector variants one might use in developing stuff for Core (or any framework).

IMHO: Javascript has plenty of Array.prototypes to handle that sort of thing. Let's not bloat Core for narrow use-cases.

But, hey, go nuts! ;)

andycramb
25 Jun 2009, 2:18 PM
@ Joe Thanks for your help
I will apply those techniques and go through them in Firebug
Certainly there are many ways to do this and one of them may prove more useful as I add the rest of the functionality
I will certainly take the learning as best I can. I may have some questions
In fact where does the el(bold) come from in here?


eachEl : function(fn, scope){
var me = this,
el = me.el;
and why is it passed in twice here(bolded again)?


if( e.nodeType && e.nodeType != 3){
return fn.call(scope || el, el, me, i);

Joe
25 Jun 2009, 3:02 PM
@andy ... one is for scope and the other is the first param of the function call.

@hendricd - you are obviously one of the smartest people arount and I have nothing but respect for you .. you are correct in what you say.

I knew that post would get answers like this ..


That sort of filtering belongs in your application logic, especially considering all the possible CSS selector variants one might use in developing stuff for Core (or any framework).

In fact, I agree about most of this should be at the app level and hence my "tossing out ideas" .. not "here is how you should do it".

At the same time ... pulling DOM nodes using a selector and then filtering out the text nodes so you get back the same set of nodes with all browsers / that you really need / is another story. I can see a valid use for having such a function at a base level / built in so I provided such a solution for anyone that desires such a solution.

hello2008
26 Jun 2009, 1:06 AM
I would disagree with that. What if you wanted the textNodes?

Ext.select/query strengths are in their generic use, to get anything via a valid CSS selector.

If you don't want textNodes, filter them out:



var elmsOnly = someParent.query('> *').filter(function(node){
return node.nodeType != 3;
});


well, the way of filter(fn) is really good;)
if I want text nodes, I will write codes in the way of native DOM manipulation

Condor
26 Jun 2009, 1:36 AM
How about an extra pseudo operator?

Ext.DomQuery.pseudos.nodeType = function(c, a){
var r = [], ri = -1, n;
for(var i = 0; n = c[i]; i++){
if(n.nodeType == a){
r[++ri] = n;
}
}
return r;
};
Now you can simply use:

Ext.Element.prototype.getChildren = function(){
return this.select('> *:nodeType(1)');
};

andycramb
26 Jun 2009, 2:47 AM
Just did a quick test and it works a treat
Another great approach
It also has the benefit of being flexible enough that you can pass in the nodeType you could be looking for.
I tested for textNodes and it pulled them out as well
It also seems a natural fit with the other pseudo selectors as there is a pseudo selector that checks the nodeValue.

Thanks Condor

Joe
26 Jun 2009, 2:51 AM
Was looking for how to do this .. I tried tons of stuff



Ext.Element.prototype.getChildren = function(){
return this.select('> *:nodeType(1)');
};

.. thanks!

andycramb
26 Jun 2009, 4:04 AM
Just to show off its flexible benefits of Condor's approach


Ext.Element.prototype.getSiblings = function(){

//Condor
return this.select('~ *:nodeType(1)');
};

Ext.get('pane1').getSiblings()

evant
26 Jun 2009, 4:33 PM
I think you're the only person to use the ~ operator. A bug was fixed in it a while back and I don't think anyone noticed ;)

hello2008
1 Jul 2009, 1:42 AM
I think you're the only person to use the ~ operator. A bug was fixed in it a while back and I don't think anyone noticed ;)

=D> by the way, where can I get some more reference or examples to learn about the ~ operator?

Condor
1 Jul 2009, 1:45 AM
Read the DomQuery API docs (http://extjs.com/deploy/ext-3.0-rc2/docs/?class=Ext.DomQuery).

hello2008
1 Jul 2009, 2:07 AM
Read the DomQuery API docs (http://extjs.com/deploy/ext-3.0-rc2/docs/?class=Ext.DomQuery).

thanks for your quick help, I will read it seriously;)

andycramb
1 Jul 2009, 2:11 AM
http://www.w3.org/TR/css3-selectors/#adjacent-sibling-combinators
http://reference.sitepoint.com/css/generalsiblingselector

hello2008
1 Jul 2009, 2:12 AM
http://www.w3.org/TR/css3-selectors/#adjacent-sibling-combinators
http://reference.sitepoint.com/css/generalsiblingselector

nice, really thanks ;)