Prototype Selector Object Learning _prototype

Source: Internet
Author: User
Tags extend instance method tagname xpath
Copy Code code as follows:

function $$ () {
return selector.findchildelements (document, $A (arguments));
}

This class can be divided into three parts: the first part is based on different browsers to determine what DOM action to use. The operation of IE is the use of ordinary getelementby* series methods; FF is Document.evaluate;opera and Safari is Selectorsapi. The second part is the basic functions provided externally, such as Findelements,match and so on, element objects inside a lot of methods is directly called the object inside the method. The third part is XPath and some query Dom matching criteria, such as what string means to find First-child, what the string represents the query Nth-child.

Because of this object inside a lot of methods, do not give all the source code, in fact, I have only read some of the methods of coding. This is done using a simple example to walk through the process of DOM selection, depending on the browser's differences. In this process, you give the source code you need and explain it.

The specific examples are as follows:
Copy Code code as follows:

<div id= "Parent2" >
<div id= "NavBar" >
<a id= "N1" ></a>
<a></a>
</div>
<div id= "sidebar" >
<a id= "S1" ></a>
<a></a>
</div>
</div>

<script type= "Text/javascript" ><!--
$$ (' #navbar a ', ' #sidebar a ')
--></script>

The following is an example of FF, with the following process:
Copy Code code as follows:

/* First find the $$ method, which has been given, in this method will call the selector findchildelements method, and the first argument is document, leaving the parameter is the DOM query string array * *

Findchildelements:function (element, expressions) {
Here we first call split to handle the string array, determine if it is legal, and delete the space
expressions = Selector.split (Expressions.join (', '));
Handlers contains some methods for processing DOM nodes, such as Concat,unique
var results = [], h = selector.handlers;
Processing query expressions individually
for (var i = 0, L = expressions.length, selector i < L; i++) {
New Selector
selector = new Selector (Expressions[i].strip ());
Connect the queried node to the results
H.concat (results, selector.findelements (element));
}
If the number of nodes found is greater than one, the duplicate nodes are filtered out
Return (L > 1)? H.unique (results): results;
}

//===================================================

Selector.split Method:
Split:function (expression) {
var expressions = [];
Expression.scan (/([\w#:.~>+ () \s-]+|\*|\[.*?\]) + \s* (, |$)/, function (m) {
Alert (m[1]);
Expressions.push (M[1].strip ());
});
return expressions;
}

//===================================================

Selector.handlers objects
Handlers: {
Concat:function (A, b) {
for (var i = 0, node; node = b[i]; i++)
A.push (node);
return A;
},
//... Omit some methods
Unique:function (nodes) {
if (Nodes.length = = 0) return nodes;
var results = [], n;
for (var i = 0, L = nodes.length i < l; i++)
if (typeof (n = nodes[i]). _countedbyprototype = = ' undefined ') {
N._countedbyprototype = prototype.emptyfunction;
Results.push (Element.extend (n));
}
return Selector.handlers.unmark (results);
},

The next move to the new Selector object process!!

Copy Code code as follows:

First look at the initialization section of the selector.
You can see that the initialization section is about determining how you want to manipulate the DOM, and here's a few ways
var Selector = class.create ({
Initialize:function (expression) {
This.expression = Expression.strip ();

if (This.shoulduseselectorsapi ()) {
This.mode = ' Selectorsapi ';
else if (This.shouldusexpath ()) {
This.mode = ' XPath ';
This.compilexpathmatcher ();
} else {
This.mode = "normal";
This.compilematcher ();
}

}

//===================================================

XPATH,FF supports this approach
Shouldusexpath: (function () {

The following check whether the browser has a bug, the specific bug is how, I did not search the Internet. Probably means checking to see if the number of nodes can be found correctly.
var Is_descendant_selector_buggy = (function () {
var isbuggy = false;
if (document.evaluate && window. Xpathresult) {
var el = document.createelement (' div ');
el.innerhtml = ' <ul><li></li></ul><div><ul><li></li></ul> </div> ';
Here's the local-name () means to remove the namespace for the lookup
var XPath = ".//*[local-name () = ' ul ' or local-name () = ' ul ']" +
"//*[local-name () = ' Li ' or local-name () = ' li ']";
Document.evaluate is the core of the DOM query method, the specific use can be searched online
var result = Document.evaluate (XPath, el, NULL,
Xpathresult.ordered_node_snapshot_type, NULL);

Isbuggy = (result.snapshotlength!== 2);
el = null;
}
return isbuggy;
})();

return function () {
The returned method determines whether this DOM operation is supported.
if (! Prototype.BrowserFeatures.XPath) return false;

var e = this.expression;
Here you can see that Safari does not support-of-type expressions and empty expressions
if (Prototype.Browser.WebKit &&
(E.include ("-of-type") | | | e.include (": Empty"))
return false;

if ((/(\[[\w-]*?:|:checked)/). Test (E))
return false;

if (Is_descendant_selector_buggy) return false;

return true;
}

})(),

//===================================================

Sarafi and opera support this approach
Shoulduseselectorsapi:function () {
if (! Prototype.BrowserFeatures.SelectorsAPI) return false;
This determines whether case sensitive lookup is supported
if (selector.case_insensitive_class_names) return false;

if (! SELECTOR._DIV) Selector._div = new Element (' div ');
Check to see if the query in the empty Div throws an exception
try {
Selector._div.queryselector (this.expression);
catch (e) {
return false;
}

//===================================================

Selector.case_insensitive_class_names Property
/*document.compatmode is used to determine the rendering method used by the current browser.
When Document.compatmode equals Backcompat, the browser client area width is document.body.clientWidth;
When Document.compatmode equals Css1compat, the browser client area width is document.documentElement.clientWidth. */

if (Prototype.BrowserFeatures.SelectorsAPI &&
Document.compatmode = = ' Backcompat ') {
Selector.case_insensitive_class_names = (function () {
var div = document.createelement (' div '),
span = document.createelement (' span ');

Div.id = "prototype_test_id";
Span.classname = ' Test ';
Div.appendchild (span);
var isignored = (Div.queryselector (' #prototype_test_id. Test ')!== null);
div = span = null;
return isignored;
})();
}

return true;
},

//===================================================

If these two are not on the document.getelement (s) by* series method of processing, seemingly IE8 began to support Selectorapi, the rest of the version of IE can only be common method for DOM query

The following is the Shouldusexpath method supported by FF!!!

Copy Code code as follows:

When you decide that you want to query with XPath, you start calling the Compilexpathmatcher method.

Compilexpathmatcher:function () {
Below gives patterns, and XPath
var e = this.expression, PS = Selector.patterns,
x = Selector.xpath, Le, m, len = ps.length, name;

Determines whether the query string e is cached
if (Selector._cache[e]) {
This.xpath = Selector._cache[e]; Return
}
'.//* ' means querying all nodes under the current node don't know how to look at the XPath representation on the web
This.matcher = ['.//* '];
This le prevents an infinite loop lookup, and that regular expression matches all characters except a single spaces
while (e && le!= e && (/\s/). Test (e)) {
Le = e;
Find Pattern by individual
for (var i = 0; i<len; i++) {
The name here is the name attribute of the object in pattern.
name = Ps[i].name;
Here to see if the expression matches the regular expression of this pattern.
if (M = E.match (ps[i].re)) {
/*
Notice here, there are some methods in the following XPath, some strings, so here needs to be judged, string words, you need to call the template evaluate method, replace the #{inside ...} string; Is the method, then pass in the correct parameter invocation method
*/
This.matcher.push (Object.isfunction (X[name]) X[name] (m):
New Template (X[name]). Evaluate (m));
Remove the matching parts and continue with the following string match
E = E.replace (m[0], "");

Break
}
}

}
Joins all of the matching XPath expressions to form the final XPath query string
This.xpath = This.matcher.join (");
Put it in the cache
Selector._cache[this.expression] = This.xpath;
},
//==============================================

These patterns is to determine what the query string is looking for, based on the corresponding whole expression to judge, such as the string ' #navbar ' according to patterns match, then is the ID
Patterns: [
{name: ' latersibling ', Re:/^\s*~\s*/},
{Name: ' Child ', Re:/^\s*>\s*/},
{name: ' Adjacent ', Re:/^\s*\+\s*/},
{name: ' descendant ', Re:/^\s/},
{name: ' TagName ', Re:/^\s* (\*|[ \w\-]+) (\b|$)?/},
{name: ' id ', Re:/^# ([\w\-\*]+) (\b|$)/},
{name: ' ClassName ', Re:/^\. ([\w\-\*]+) (\b|$)/},
{name: ' Pseudo ', Re:
/^:((first|last|nth|nth-last|only) (-child|-of-type) |empty|checked| (en|d
IS) (Abled|not) (\ (. *?) \))? (\b|$| (? =\s| [: +~>]) / },
{name: ' Attrpresence ', Re:/^\[(?: [\w-]+:)? [ \w-]+) \]/},
{name: ' attr ', Re:
/\[(?: [\w-]*:)? [\w-]+) \s* (?:( [!^$*~|]? =) \s* ([' "]) ([^\4]*?) \4| ([^'"][^
\]]*?)))? \]/ }
],

//==============================================

/* When pattern is found, the XPath representation of the corresponding query string is found in the corresponding name. For example, the above ID, corresponding to the ID string, in the Compilexpathmatcher will determine whether the XPath is a string or a method, is the method will be passed in the corresponding parameters to call * *
XPath: {
Descendant: "//*",
Child: "/*",
Adjacent: "/following-sibling::* [1]",
Latersibling: '/following-sibling::* ',
Tagname:function (m) {
if (m[1] = = ' * ') return ';
Return "[local-name () = '" + m[1].tolowercase () +
"' or local-name () = '" + m[1].touppercase () + "']";
},
ClassName: [Contains (Concat (', @class, '), ' #{1} ')] ",
ID: "[@id = ' #{1} ']",
//... Omit some methods

//==============================================

Below enter the selector Findelements Method!!

Copy Code code as follows:

Findelements:function (Root) {
To determine if root is null or NULL, set to document
root = Root | | Document
var e = this.expression, results;
Determine which mode to manipulate the DOM, which is XPath under FF
Switch (this.mode) {
Case ' Selectorsapi ':

if (root!== document) {
var oldid = root.id, id = $ (Root). Identify ();
id = id.replace (/[\.:]/g, "\\$0");
e = "#" + ID + "" + E;

}
Results = $A (Root.queryselectorall (e)). Map (Element.extend);
Root.id = oldid;

return results;
Case ' XPath ':
Let's take a look at the _getelementsbyxpath method
Return Document._getelementsbyxpath (This.xpath, Root);
Default
return This.matcher (root);
}
},

//===========================================

This method is actually to put the found node into the results, and return, here to use the document.evaluate, the following gives a detailed explanation of this method URL
if (Prototype.BrowserFeatures.XPath) {
Document._getelementsbyxpath = function (expression, parentelement) {
var results = [];
var query = document.evaluate (expression, $ (parentelement) | | document,
NULL, xpathresult.ordered_node_snapshot_type, NULL);
for (var i = 0, length = query.snapshotlength i < length; i++)
Results.push (Element.extend (Query.snapshotitem (i)));
return results;
};
}

/*
The following is a document.evaluate approach to explain: https://developer.mozilla.org/cn/DOM/document.evaluate
*/

Use the examples given below to explain in a row:

First $$ inside calls the Findchildelements method, expressions is set to [' #navbar a ', ' #siderbar a ']

Call below: selector = new Selector (Expressions[i].strip ()); Create a new Selector object, call the Initialize method, which is to determine what Dom API to use, because it is FF, So it's This.shouldusexpath (), and then call Compilexpathmatcher ()

Then Compilexpathmatcher () inside the var e = this.expression, set E to ' #navbar A ', then go into the while loop, iterate over the patterns, check the matching pattern of the query string, find {name: ' id ', Re:/^# ([\w\-\*]+) (\b|$)/}, so name is ID, when m = After the E.match (ps[i].re) match, M is set to an array where m[0] is the entire matching string ' #navbar ', m[1] is the first grouped string ' NavBar '

Next Judge Object.isfunction (X[name]), because the ID corresponds to a string, so execute new Template (X[name]). Evaluate (M)), String: ID: "[@id = ' #{1} ']", #{1 in be replaced with m[1], i.e. ' navbar ', and finally put the result in This.matcher

And then by removing the first matching string, E becomes a ' a ', and here's a space! Next continue with the match

This time it matches: {name: ' descendant ', Re:/^\s/}, and then find the corresponding descendant item in the XPath: descendant: "//*", Then put the string in the This.matcher, remove the space E and leave the character ' a ', and continue to match

This word matches the following: {name: ' TagName ', Re:/^\s* (\*|[ \w\-]+) (\b|$)?/}, and then find the corresponding XPath entry for tagname,

Tagname:function (m) {
if (m[1] = = ' * ') return ';
Return "[local-name () = '" + m[1].tolowercase () +
"' or local-name () = '" + m[1].touppercase () + "']";
}

is a method, so it will call X[name] (m), and m[1]= ' a ', return the following string of characters, then in This.matcher, this time E is an empty string, while the first condition is not satisfied, exit the loop, Concatenate the This.matcher array into an XPath string:.//*[@id = ' NavBar ']//*[local-name () = ' A ' or local-name () = ' a ']

After initializing the selector, execute the Selector instance method findelements, which is called directly here: Document._getelementsbyxpath (This.xpath, Root);

Executes the true Dom Query method Document.evaluate in the _getelementsbyxpath method, and finally returns the result

The above is the entire query DOM under FF process!

Under IE and under the Opera,safari process is the same, but the implementation of the specific methods are slightly different, interested in their own research, those complex DOM selection operations do not cite examples. The processes constructed here are well worth learning, including the generation of XPath through pattern matching, and the Patterns,xpath.

As you can see, it's not easy to write a framework that is compatible with all browsers! Learn and learn!

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.