Prototype Selector object Learning

Source: Internet
Author: User

Copy codeThe Code is as follows: function $ (){
Return Selector. findChildElements (document, $ A (arguments ));
}

This class can be divided into three parts: The first part is to determine the DOM operation method used based on different browsers. Here, the Operation IE is to use the common getElementBy * series methods; FF is document. evaluate; Opera and Safari are selectorsAPI. The second part is the basic functions provided externally, such as findElements and match. Many methods in the Element object are called directly. The third part is some DOM matching criteria such as XPath. For example, what strings represent first-child queries and what strings represent nth-child queries.

Because there are many methods in this object, I will not provide all the source code. In fact, I only understand the code of some methods. Here we use a simple example based on different browsers to go through the DOM selection process. In this process, provide the required source code and describe it.

An example is as follows:Copy codeThe Code is 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 ')
// --> </Script>

The following describes the process using FF as an example:Copy codeThe Code is as follows:/* first find the $ method, as shown above. In this method, the findChildElements method of Selector is called, and the first parameter is document, the remaining parameter is the array of the DOM query string */

FindChildElements: function (element, expressions ){
// Here, we first call split to process the string array, determine whether it is legal, and delete spaces.
Expressions = Selector. split (expressions. join (','));
// Handlers contains some methods for DOM node processing, such as concat and unique.
Var results = [], h = Selector. handlers;
// Process Query expressions one by one
For (var I = 0, l = expressions. length, selector; I <l; I ++ ){
// Create a Selector
Selector = new Selector (expressions [I]. strip ());
// Connect the queried node to results
H. concat (results, selector. findElements (element ));
}
// If more than one node is found, filter out duplicate nodes.
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 object
Handlers :{
Concat: function (a, B ){
For (var I = 0, node; node = B [I]; I ++)
A. push (node );
Return;
},
//... Some methods are omitted
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 step is to create a Selector object !!

Copy codeThe Code is as follows: // first check the initialization part of Selector.
// You can see that the initialization part is to determine the method to operate the DOM. The following describes the methods.
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 and FF support this method
ShouldUseXPath: (function (){

// Check whether there is a BUG in the browser. I have not found this BUG on the Internet. This probably means checking whether a node number 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> </ul> <div> <ul> <li> </ul> </div> ';
// The local-name () indicates removing the namespace for search.
Var xpath = ". // * [local-name () = 'ul 'or local-name () = 'ul']" +
"// * [Local-name () = 'lil' or local-name () = 'lil']";
// Document. evaluate is the core DOM query method. You can search for it online.
Var result = document. evaluate (xpath, el, null,
XPathResult. ORDERED_NODE_SNAPSHOT_TYPE, null );

IsBuggy = (result. snapshotLength! = 2 );
El = null;
}
Return isBuggy;
})();

Return function (){
// Determine whether this DOM operation is supported in the returned method.
If (! Prototype. BrowserFeatures. XPath) return false;

Var e = this. expression;
// Safari does not support the-of-type 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;
}

})(),

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

// This method is supported by Sarafi and opera.
ShouldUseSelectorsAPI: function (){
If (! Prototype. BrowserFeatures. SelectorsAPI) return false;
// Determine whether case-sensitive search is supported
If (Selector. CASE_INSENSITIVE_CLASS_NAMES) return false;

If (! Selector. _ div) Selector. _ div = new Element ('div ');
// Check whether an exception is thrown when querying in an empty div.
Try {
Selector. _ div. querySelector (this. expression );
} Catch (e ){
Return false;
}

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

// Selector. CASE_INSENSITIVE_CLASS_NAMES attribute
/* Document. compatMode is used to determine the rendering mode used by the current browser.
When document. compatMode is equal to BackCompat, the client width of the browser is document. body. clientWidth;
When document.compatmodeis equal to css1compat, the client width of the browser is document.doc umentElement. clientWidth. */

If (Prototype. BrowserFeatures. SelectorsAPI &&
Document. compatMode = 'background '){
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;
},

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

// Use document if neither of them is used. getElement (s) By * series of methods for processing. It seems that IE8 has started to support SelectorAPI. In other versions, IE can only use common methods for DOM query.

// Switch to the shouldUseXPath method supported by FF below !!!

Copy codeThe Code is as follows: // when you determine whether to use XPath for query, the compileXPathMatcher method is called.

CompileXPathMatcher: function (){
// Provide the patterns and xpath below
Var e = this. expression, ps = Selector. patterns,
X = Selector. xpath, le, m, len = ps. length, name;

// Determine whether the query string e is cached
If (Selector. _ cache [e]) {
This. xpath = Selector. _ cache [e]; return;
}
// '. // *' Indicates to query all nodes under the current node. If you do not know how to query them, go online and check the XPath representation.
This. matcher = ['. // *'];
// Here the le prevents infinite loop searches. The regular expression matches all characters except a single space character.
While (e & le! = E & (/\ S/). test (e )){
Le = e;
// Search for pattern one by one
For (var I = 0; I <len; I ++ ){
// The name here is the name attribute of the object in pattern.
Name = ps [I]. name;
// Check whether the regular expression matches the pattern.
If (m = e. match (ps [I]. re )){
/*
Note that here, some methods and strings are in the following xpath, so you need to judge here. For strings, you need to call the evaluate Method of Template to replace #{...} string; if it is a method, pass in the correct parameter call Method
*/
This. matcher. push (Object. isFunction (x [name])? X [name] (m ):
New Template (x [name]). evaluate (m ));
// Remove the matching part and continue matching the following strings
E = e. replace (m [0], '');

Break;
}
}

}
// Connect all 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 are used to determine what the query string is looking for and determine based on the corresponding expression. For example, if the string '# navbar' matches patterns, it is the id.
Patterns :[
{Name: 'latersibling', re:/^ \ s *~ \ S */},
{Name: 'child ', re:/^ \ s *> \ s */},
{Name: 'adjacent ', re:/^ \ s * \ + \ s */},
{Name: 'scendant', re:/^ \ s /},
{Name: 'tagname', re:/^ \ s * (\ * | [\ w \-] +) (\ B | $ )? /},
{Name: 'id', re:/^ # ([\ w \-\ *] +) (\ B | $ )/},
{Name: 'classname', re:/^ \. ([\ w \-\ *] +) (\ B | $ )/},
{Name: 'sudo', re:
/^ :( (First | last | 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 | ([^ '"] [^
\] *?)? \]/}
],

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

/* After finding the pattern, use the corresponding name to find the xpath representation of the corresponding query string. For example, the above id corresponds to the id string. In compileXPathMatcher, it will determine whether xpath is a string or a method. If it is a method, it will pass in the corresponding parameters for calling */
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}']",
//... Some methods are omitted

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

// Enter the findElements method of Selector below !!

Copy codeThe Code is as follows: findElements: function (root ){
// Determine whether root is null. If it is null, it is set to document.
Root = root | document;
Var e = this. expression, results;
// Determine which mode is used to operate the DOM. In FF, It is xpath.
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 ':
// Take A Look At The _ getElementsByXPath method below
Return document. _ getElementsByXPath (this. xpath, root );
Default:
Return this. matcher (root );
}
},

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

// This method is actually to put the searched node in results and return it. Here document. evaluate is used, and the URL of detailed explanation of this method is given below
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 URL below is document. evaluate's method interpretation: https://developer.mozilla.org/cn/DOM/document.evaluate
*/

The following example is used to explain continuously:

First, call the findChildElements method in $. The expressions are set to ['# navbar A',' # siderbar a'].

The following call: selector = new Selector (expressions [I]. strip (); Create a Selector object and call the initialize method, that is, to determine the dom api used. Because it is FF, it is this. shouldUseXPath () and then call compileXPathMatcher ()

Then var e = this in compileXPathMatcher. expression, set e to '# navbar A', then enter the while LOOP, traverse patterns, and check the matching mode of the query string. Here, based on the regular expression of pattern, find {name: 'id', re:/^ # ([\ w \-\ *] +) (\ B | $)/}, so name is id, when m = e. match (ps [I]. re) after matching, m is set to an array, where m [0] is the entire matched string '# navbar ', m [1] is the matched first group string 'navbar'

Next, judge the Object. isFunction (x [name]). Because the id corresponds to a string, execute new Template (x [name]). evaluate (m), string: id: "[@ id = '# {1}']", in which # {1} is replaced with m [1], that is, 'navbar', and put the result to this. matcher

Then, by deleting the first matched string, e is changed to 'A', and there is a space here! Next, continue matching.

This time, the matching result is: {name: 'descendant', re:/^ \ s/}, and then find the corresponding descendant item in xpath: descendant :"//*", then place the string in this. in matcher, remove space e and only the character 'a' is left. Continue matching.

This word matches: {name: 'tagname', re:/^ \ s * (\ * | [\ w \-] +) (\ B | $ )? /}, And then find the xpath entry corresponding to the 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, and then put it in this. in matcher, this e is an empty string. If the first condition of while is not met, exit the loop and set this. the matcher array is connected to an xpath string :. // * [@ id = 'navbar'] // * [local-name () = 'A' or local-name () = 'a']

After the Selector is initialized, execute the Selector instance method findElements, and call document. _ getElementsByXPath (this. xpath, root) directly here );

Execute the real DOM query method document. evaluate in the _ getElementsByXPath method and return the result.

The above is the entire process of querying the DOM under FF!

The process in IE is the same as that in Opera and safari, but the specific method of execution is slightly different. If you are interested, you can study it on your own. The complex DOM selection operations will not be used as an example. The process constructed here is worth learning, including generating xpath through pattern matching and proposing those patterns and xpath.

It can be seen that it is not easy to write a framework compatible with all browsers! Learning!

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.