Build your own framework
Creating your own framework
Also I am currently in the process of building an AJAX based add-on that is based on accessing remote content and reformatting it for use on websites in various ways so the name Getme.js seems to be in keeping with the aims of this add-on which will no doubt implement this code or a variation of it when complete.
G = getEl = function(id){ return document.getElementById(id); }
My function will do without the onDOMLoad function that JQuery supports with its main constructor when an anonymous function is passed in but it will offer the ability to return nodes in a variety of ways including by passing the constructor an #ID, .className, <tag>, a P:first-child > SPAN CSS Selector or an HTML string which can be converted into a node list.
// instead of having to do this
Getme.setHTML(Getme('DIV > P.myClass'),"<p>New HTML</p>");
//we could do this
Getme('DIV > P.myClass').setHTML("<p>New HTML</p>");// set the class to "blue" and set some style properties for all elements that match the selector e.g all P tags that are descendants of DIV's
Getme('DIV > P').setAtts({class:"blue",style:"color:red;font-weight:bold"});That's all I want this example code to do which if you think about it is pretty powerful stuff anyway. The code format is loosely based on JQuery and if you have ever looked at JQueries source code and wondered what the hell is going on then this example might help you get your head round it.
(function(){
// set some shortcuts to optimise references to global vars
var window = this, // pointing window to this (which refers to the global object) speeds up references to window
undefined, // this creates an undefined variable we can use to test other undefined variables against
query = !!document.querySelectorAll;
// rest of code e.g object definition, functions and methods
})()Now this self calling anonymous function ensures that any objects defined within it are created and returned when the function runs. The open and close () brackets at the end of the anonymous function ensure this. Plus any variables declared within the function are only accessible inside the anonymous function which means we are not polluting the global namespace. To learn more about this modular method please read the following article:
As you can see there are a few variables declared within this function that to the beginner may see pretty odd e.g
var window = this,
undefined,var myvar;
if(myvar == "undefined") alert("undefined");
if(myvar === undefined) alert("undefined");// the G function is a short cut to instantiating with Getme object
G = function(sel, context){
return new Getme(sel, context);
}
// the Getme constuctor pass in a selector/context
// can handle multiple forms of selector
// ID - #ID to return element by id e.g G('#myID')
// CLASS - .myClass to return elements by className
// TAG - <P> or <SPAN> to return all elements of a certain node type
// SELECTOR - DIV P SPAN to return all elements that are SPANS decendants of P who are decendants of DIV
// NodeList - <DIV><SPAN>hello there</SPAN></DIV> will return a nodelist containing the elements specified by the HTML passed in
Getme = function(sel, context){
this.Getme = "Getme version 1.0.4",
this.nodes = this.nodes || [],
this.context = this.context || document; // default context to document
// main regEx to determine if selector is HTML string, ID or classname
var re_getme = /^<([^> ]+)[^>]*>(?:.|\n)+?<\/\1>$|^(\#([-\w]+)|\.(\w[-\w]+))$/i,
// regEx to match <SPAN> <P> <H1> tags
re_tag = /^<([a-z1-9]+?)>$/i,
match;
// if no selector passed in default to document
if(!sel) sel = document;
// if we have been passed a string then this could either be
// an id of an element, a class name, a tag or an HTML string
if(typeof(sel)==="string"){
// look for a single HTML tag e.g <P> or <SPAN>
match = re_tag.exec(sel);
if(match && match[1]){
// return all nodes matching the tag
this.nodes = this.context.getElementsByTagName(match[1]);
}else{
// run regex to look for ID, class or HTML string
// match[1] = HTML string - matching start and end tag
// match[3] = ID
// match[4] = class name
var match = re_getme.exec(sel)||[];
// if we have an ID with or without a # e.g #myid or myid treated as an ID
if(match[3]){
// get element by id
this.nodes[0] = document.getElementById(match[3]);
// if we have a class name e.g .myClass
}else if(match[4]){
// get elements by class name
this.nodes = Getme.funcs.getElementsByClassName( match[4],this.context);
// if we have a valid HTML string e.g <P><STRONG>hello</STRONG></P>
}else if(match[1]){
// create nodes from html
var div = document.createElement("DIV");
div.innerHTML = sel;
this.nodes = div.childNodes;
}else{
// if querySelectorAll is available for modern browsers we can use that e.g
// FF 3.2+, Safari 3.2+, Opera 10, Chrome 3, IE 8 (standards mode)
if(query && this.context === document){
this.nodes = Getme.funcs.selector(sel);
}else{
// otherwise revert to Sizzle which makes a good job of handling older browsers
this.nodes = Getme.find(sel,this.context);
}
}
}
}else if(sel.nodeType){
// already got a node add
this.nodes[0] = sel;
}else if(Getme.funcs.isArray(sel)){
this.nodes = sel;
}else{
this.nodes = Getme.funcs.makeArray(sel);
}
this.length = this.nodes.length;
return this;
}- #myID which will return a reference to an element with the ID myID
- .myClass which will return a node list of all elements with the className myClass
- <span> or <p> which will return a node list of all elements with the tag specified e.g SPAN or P
- <div><span>hello</span></div> which will be used to create a node list from the HTML specified.
- An array of elements can be passed in gained from some other method or an object hash of elements which will be converted into an array.
- DIV#myID > P:first-child or any other CSS 3 Selector can be passed in and a nodelist will be returned if the browser in question supports document.querySelectorAll. Otherwise if Sizzle has been added to the codebase then it will handle older browsers without the use of XPath.
// returns elements by selector e.g DIV P SPAN
// supported in later browsers IE 8 (standards mode), FF 3.1+, Safari 3.1+
selector : function(query){
try {
return document.querySelectorAll( query )
} catch(e){}
return [];
}// returns the specified element from the current nodeList
get : function(idx){
// return the specified item by index otherwise the first item
return (typeof(idx)=="number") ? this.nodes[idx] : this.nodes[0];
},
// sets the innerHTML of one or more nodes.
setHtml : function(content){
if(content){
// if a node has been passed in we take its innerHTML to use as the content for the node we
// wish to set the html for
if(content.nodeType){
html = content.innerHTML;
}else if(typeof(content)==="string"){
html = content;
}else{
html = "";
}
// use our each function to loop through each child node removing it from the DOM
this.each(function(){
if ( this.hasChildNodes() ){
while ( this.childNodes.length >= 1 ){
this.removeChild( this.firstChild );
}
}
// now add our innerHTML into the empty node
this.innerHTML = html;
})
}
return this;
},
// allows Getme object to reference the static foreach function automatically passing the current node list e.g G('SPAN.myclass').each(function(){})
each : function(callback){
this.foreach(this.nodes,callback);
return this;
},
// allows the setting of style values to the current nodelist
setStyle : function(style, val){
if(style){
var self = this,
atts;
// may have passed att=val pair or a object hash for atts
if(val !== undefined){
// create object hash
atts = eval('({'+style+':"'+val+'"})');
}else{
atts = style;
}
this.each(function(){
self.setProperties.call(this.style,atts);
})
}
return this;
}As you can see these methods are all chainable in that they can be referenced in the following manner:
G('P > SPAN.info').setHTML('Use the help icon for more info.').setStyle({color:red,fontSize:8px})I also have added another object called Getme.funcs which holds a number of utility functions that can be called statically without a Getme object reference having to be instantiated. For example in the Getme constructor I make a call to the getElementsByClass method in the following manner:
// get elements by class name
this.nodes = Getme.funcs.getElementsByClassName( match[4],this.context);
// loop through an array of items in arrEls calling somefunc for each
Getme.funcs.foreach(arrEls, function(){ somefunc(arrEls[this] );
I also extend my Getme.prototype object with Getme.funcs once both objects have been defined. This is to allow any Getme objects to have the benefit of having these methods added to their prototype.
// allows Getme object to reference the foreach function automatically passing the current node list e.g G('<pan>').each(function(){})
each : function(callback){
this.foreach(this.nodes,callback);
return this;
}// if querySelectorAll is available for modern browsers we can use that e.g
// FF 3.2+, Safari 3.2+, Opera 10, Chrome 3, IE 8 (standards mode)
if(query && this.context === document){
this.nodes = Getme.funcs.selector(sel);
}else{
// otherwise revert to Sizzle which makes a good job of handling older browsers
this.nodes = Getme.find(sel,this.context);
}Which passes off any selectors for browsers with no querySelectorAll support to Getme.find which is a pointer to the Sizzle function. If you scroll to the bottom of the Sizzle code you will see I have hooked in my Getme object in a similar way as JQuery hooks itself into Sizzle e.g
// EXPOSE Sizzle to Getme.js
window.Sizzle = Sizzle;
Getme.find = Sizzle;
Getme.filter = Sizzle.filter;
Getme.expr = Sizzle.selectors;
Getme.expr[":"] = Getme.expr.filters;This example Getme.js is not a complete framework and has not been built to become one rather its an example of how easy it is to utilise other useful libraries such as Sizzle and add them to your own code without having to worry about all the bloat that many frameworks come with. You can start off with a basic piece of code to manipulate and search for DOM nodes and then extend it as and when you require new functionality.
Labels: codebase, css selectors, framework, jQuery, prototype



10 Comments:
This is great article just what I wanted to know. I have often tried to look at the source code in Jquery and just spent a lot of time scratching my head wondering what is going on.
I never realised that to return nodes by CSS 3 selectors was so easy in modern browsers.
I think I might even use this Getme.js as the basis of my own framework.
Thanks!
I'm glad you found the article helpful.
The code in Getme.js is only a small cut down version of what is possible and I am planning on using it as the base for my own framework that I am working on which is designed more for working with remote content and parsing, cleaning it for use on other sites e.g my www.hattrickheaven.com site. If you click on one of the news articles you will see a little loader icon whilst the content is taken from the remote URL, parsed and formatted in real time before being inserted into the DOM
Very helpful article, many thanks, I'm currently trying to put together a very small libray to do some iPhone web app work, and this is just the sort of think I needed to get me started.
BTW I tried using it in Safari and I got an error TypeError: Result of expression 'list.call' [undefined] is not a function. in line 341. Any ideas?
Can you give me an example of the code you are trying to pass to the Getme object or a link to the test page?
Line 341 is
// if typeof list is a function then run that to generate node list
if(typeof(list) == "function"){
list = list.call();
which is in the foreach function and is designed to create a nodelist from a function passed as a parameter. The output of this function is then used as the nodelist to run your callback functions against. Whereas if you just used .each it would automatically pass the current this.nodes property.
I would need to see the function you are passing into the .foreach method.
Also you could try replacing the line
list = list.call();
with
list = list();
to see if that works.
I don't know what version of Safari you are using or what function object support it has on the IPHONE.
If list = list(); works then you could try
if(Function.call){
list = list.call():
}else{
list = list();
}
As this would handle old and new browser / JS implementations.
Even if you get it working can you please sent me over the code either as a comment or to my email so I can test it myself on Safari and provide details of the webkit version etc.
Thanks
}
Hi,
I did a little checking myself on the Safari issue and I think I've fixed it. I wasn't doing anything difficult just trying to set the the content of a div tag to hello world using the setHtml function. Works fine in Firefox but produces a type of error in Safari.
This piece of code here produces the error:
if(typeof(list) == "function"){
list = list.call();
}
So I checked the result of typeof(list) in Firefox and it said 'Object' but Safari thought it (erroneously) was a 'Function'
A quick check on Google then produced this article:
http://juhukinners.com/2009/01/11/typeof-considered-useless-or-how-to-write-robust-type-checks/
which explains why typeof isn't a good way to check for Functions, since Safari has a problem with that. So I switched the code out for:
if((list) instanceof Function){
list = list.call();
}
like the article suggests and also in the isFunction part as well:
isFunction : function(o){
return ((o) instanceof Function);
},
and now it works. Cool :)
Chris.
Hi Chris
Can you show me how you were trying to call the setHtml function as by the sounds of it you were passing the results of a function in to foreach for the list parameter instead of an actual function.
For example the following code has been tested in FF 3, Safari 4, Chrome 3 and IE 8 and works fine
with my isFunction test just doing a test like so
return (typeof(o)=="function")
The foreach method is called by the each method (used by setStyle, setAtts etc) and the the list parameter can be passed as either a nodelist/array OR a function that returns a nodelist/array.
I read the article you mentioned and I know that Jquery does use a different method for checking for functions but this doesn't seem to work in IE 7 or 8.
For example if you run the following code
function test(){
isFunction1 = function( obj ) {
return toString.call(obj) === "[object Function]";
}
isFunction2 = function( obj ) {
return (typeof(obj)=="function")
}
r = function (){return Getme.funcs.getElementsByClassName("rob",null,"P")}
ShowDebug("typeof(r) = " + (typeof(r)))
ShowDebug("is r a function = " + isFunction2(r))
ShowDebug("is r a function = " + isFunction1(r))
}
// where ShowDebug is my function on the test page outputting messages to the main DIV or console.
I ran this code on the following browsers and got these results:
Firefox 3.5
1: typeof(r) = function
2: is r a function = true
3: is r a function = false
IE 7
1: typeof(r) = function
2: is r a function = true
object doesn't support this property or method toString.call(obj)
IE 8
1: typeof(r) = function
2: is r a function = true
object doesn't support this property or method toString.call(obj)
Chrome 3.0
1: typeof(r) = function
2: is r a function = true
3: is r a function = true
Safari 4.0.3
1: typeof(r) = function
2: is r a function = true
3: is r a function = true
Opera 10
1: typeof(r) = function
2: is r a function = true
3: is r a function = true
So it seems different browsers act in their own way and maybe both methods need to be modified to work cross browser?
You still didn't tell me which version of Safari you were using and was it on a Mac or PC??
Also show me some example code and I will have a look at it.
The code I used was:
G('div').setHtml('<p>Hello World</p>');
C.
And which version of Safari?
Was it on a PC or MAC?
I have been running it on Safari 3.2 > 4.0 and its worked fine for me.
Hi Chris
You still haven't got back to me about the computer you were running this on e.g Mac or PC.
If you were running it on an IPhone then I actually haven't tested Getme on that yet as I don't own one. However I know plenty of people that do.
Let me know so that I can resolve the issue.
Thanks
Rob
I managed to see the problem you were experiencing on a friends iphone with a new test page I created.
I have updated the Getme source code to use this new test for isFunction as well as replacing the code within the foreach method to use isFunction instead of doing its own check.
Post a Comment
Subscribe to Post Comments [Atom]
Links to this post:
Create a Link
<< Home