root/trunk/common/DOM/Proxy.js

Revision 159, 14.8 kB (checked in by ydnar, 3 years ago)

added line

  • Property svn:keywords set to Id
Line 
1/*
2DOM Proxy JavaScript Library
3$Id$
4
5Copyright (c) 2006, Six Apart, Ltd.
6All rights reserved.
7
8Redistribution and use in source and binary forms, with or without
9modification, are permitted provided that the following conditions are
10met:
11
12    * Redistributions of source code must retain the above copyright
13notice, this list of conditions and the following disclaimer.
14
15    * Redistributions in binary form must reproduce the above
16copyright notice, this list of conditions and the following disclaimer
17in the documentation and/or other materials provided with the
18distribution.
19
20    * Neither the name of "Six Apart" nor the names of its
21contributors may be used to endorse or promote products derived from
22this software without specific prior written permission.
23
24THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
25"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
26LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
27A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
28OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
30LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
31DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
32THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
33(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
34OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
35*/
36
37
38DOM.Proxy = new Class( Object, {
39
40    LEADING: -1,
41    INITIAL: 0,
42    TRAILING: 1,
43   
44   
45    /**
46     * class <code>DOM.Proxy</code>
47     * Initializes a document object model (dom) node, for proxying by this class.
48     * @param node <code>Node</code> A dom node.
49     */
50    init: function( startNode ) {
51        this.startNode = startNode;
52        this.update( startNode );
53    },
54   
55   
56    /**
57     * class <code>DOM.Proxy</code>
58     * Updates a node with either of the supplied parameters, and adds references onto the proxy
59     * that refer to dom node properties.
60     * @param node <code>Node</code> OPTIONAL
61     * @param edge <code>integer</code> OPTIONAL
62     * @return <code>Node</code> The dom node.
63     */
64    update: function( node, edge ) {
65        if( !node )
66            return;
67       
68        this.node = node || this.node;
69        this.edge = edge || this.INITIAL;
70       
71        this.nodeType = this.node.nodeType || 0;
72        this.nodeName = this.node.nodeName || "";
73        this.nodeValue = this.node.nodeValue || "";
74        this.tagName = this.node.tagName || "";
75        this.attributes = this.node.attributes || [];
76        this.parentNode = this.node.parentNode;
77        this.previousSibling = this.node.previousSibling;
78        this.nextSibling = this.node.nextSibling;
79        this.firstChild = this.node.firstChild;
80        this.lastChild = this.node.lastChild;
81        this.childNodes = this.node.childNodes || [];
82        this.empty = false;
83       
84        return node;
85    },
86   
87
88    /**
89     * class <code>DOM.Proxy</code>
90     * Gets the previous node in the current walk of the tree.
91     * @return <code>Node</code> A dom node OR <code>undefined</code>
92     */
93    getPreviousNode: function() {
94        if( !this.node )
95            return undefined;
96       
97        if( this.lastChild && this.edge != this.LEADING && !this.empty )
98            return this.update( this.lastChild, this.TRAILING ); // down
99       
100        if( this.previousSibling )
101            return this.update( this.previousSibling, this.TRAILING ); // left
102           
103        if( this.parentNode )
104            return this.update( this.parentNode, this.LEADING ); // up
105       
106        return undefined; // borked
107    },
108
109   
110    /**
111     * class <code>DOM.Proxy</code>
112     * Gets the previous node in the current walk of the tree.
113     * @return <code>Node</code> A dom node OR <code>undefined</code>
114     */
115    getNextNode: function() {
116        if( !this.node )
117            return undefined;
118       
119        if( this.firstChild && this.edge != this.TRAILING && !this.empty )
120            return this.update( this.firstChild, this.LEADING ); // down
121       
122        if( this.nextSibling )
123            return this.update( this.nextSibling, this.LEADING ); // right
124       
125        if( this.parentNode )
126            return this.update( this.parentNode, this.TRAILING ); // up
127       
128        return undefined; // borked
129    },
130   
131   
132    /**
133     * class <code>DOM.Proxy</code>
134     * Generate the markup needed to describe the node (including its subtree, if any).
135     * @return <code>string</code> The markup representation of this node.
136     */
137    toSource: function() {
138        return this.serialize.apply( this, arguments );
139    },
140   
141   
142    /**
143     * class <code>DOM.Proxy</code>
144     * Generate the markup needed to describe the node (including its subtree, if any).
145     * @return <code>string</code> The markup representation of this node.
146     */   
147    serialize: function() {
148        switch( this.nodeType ) {
149            case Node.TEXT_NODE:
150                return this.serializeTextNode();
151               
152            case Node.COMMENT_NODE:
153                return this.serializeComment();
154           
155            case Node.ELEMENT_NODE:
156                return this.serializeElement();
157           
158            default:
159                log( "Unknown nodeType: " + this.nodeType );
160        }
161        return "";
162    },
163
164   
165    /**
166     * class <code>DOM.Proxy</code>
167     * Serialize the child nodes of this node, if any.
168     * @return <code>string</code> The markup representation of the child nodes of this node.
169     */       
170    serializeChildNodes: function() {
171        var source = "";
172        for( var i = 0; i < this.childNodes.length; i++ ) {
173            var proxy = new this.constructor( this.childNodes[ i ] );
174            source += proxy.serialize();
175        }
176        return source;
177    },
178
179
180    /**
181     * class <code>DOM.Proxy</code>
182     * Serialize this text node.
183     * @return <code>string</code> The markup representation of this value of this text node.
184     */   
185    serializeTextNode: function() {
186        return this.nodeValue.encodeHTML();
187    },
188
189
190    /**
191     * class <code>DOM.Proxy</code>
192     * Serialize this comment node.
193     * @return <code>string</code> The markup representation of this value of this comment node
194     *         (i.e., the value enclosed between '<!' and '>', in addition to the standard xml
195     *         comment delimeter ('--') ).
196     */   
197    serializeComment: function() {
198        return "<!-- " + this.nodeValue.encodeHTML() + " -->";
199    },
200
201
202    /**
203     * class <code>DOM.Proxy</code>
204     * Serialize this element node.
205     * @return <code>string</code> The markup representation of this value of this element.
206     */   
207    serializeElement: function() {
208        if( !this.tagName )
209            return "";
210       
211        var source = "<" + this.tagName;
212        source += this.serializeAttributes();
213       
214        if( this.empty )
215            source += " />";
216        else {
217            source += ">";
218            source += this.serializeChildNodes();
219            source += "</" + this.tagName + ">";
220        }
221       
222        return source;
223    },
224   
225   
226    /**
227     * class <code>DOM.Proxy</code>
228     * Serialize the attributes of this node.
229     * @return <code>string</code> The markup representation of these attributes.
230     */   
231    serializeAttributes: function() {
232        var source = "", value;
233        for( var i = 0; i < this.attributes.length; i++ ) {
234            var name = this.attributes[ i ].nodeName;
235            value = this.attributes[ i ].nodeValue;
236            if ( exists( value ) && typeof value != "boolean" )
237                value = value.toString().encodeHTML();
238            else 
239                value = "";
240            source += " " + name + "=\"" + value + "\"";
241        }
242        return source;
243    }
244} );
245
246
247/* static methods */
248
249extend( DOM.Proxy, {
250    /**
251     * class <code>DOM.Proxy</code>
252     * This is a static method.<br/>
253     * It invokes the callback parameter on every previous node to the node parameter until a
254     * not-<code>undefined</code> return value is obtained from the callback.
255     * @param node <code>Node</code> A dom node.
256     * @param callback <code>function</code> A function / bound-method to call on every discovery of
257     *                              a successive, previous node.
258     * @return <code>any</code> The result of an invocation of the callback OR <code>undefined</code>
259     */   
260    forPrevious: function( node, callback ) {
261        var proxy = new this( node );
262        while( node = proxy.getPreviousNode() ) {
263            var out = callback( node, proxy.startNode );
264            if( defined( out ) )
265                return out;
266        }
267        return undefined;         
268    },
269   
270   
271    /**
272     * class <code>DOM.Proxy</code>
273     * This is a static method.<br/>
274     * It invokes the callback parameter on every next node to the node parameter until a
275     * not-<code>undefined</code> return value is obtained from the callback.
276     * @param node <code>Node</code> A dom node.
277     * @param callback <code>function</code> A function / bound-method to call on completion.
278     * @return <code>any</code> The result of an invocation of the callback OR <code>undefined</code>     
279     */   
280    forNext: function( node, callback ) {
281        var proxy = new this( node );
282        while( node = proxy.getNextNode() ) {
283            var out = callback( node, proxy.startNode );
284            if( defined( out ) )
285                return out;
286        }
287        return undefined;
288    },
289
290   
291    /**
292     * class <code>DOM.Proxy</code>
293     * This is a static method.<br/>
294     * It returns the previous text node to the parameter node, if any
295     * @param node <code>Node</code> A dom node.
296     * @return <code>Node</code> OR <code>undefined</code>.
297     */   
298    getPreviousTextNode: function( node ) {
299        return this.forPrevious( node, DOM.isTextNode );
300    },
301
302   
303    /**
304     * class <code>DOM.Proxy</code>
305     * This is a static method.<br/>
306     * It returns the previous inline text node to the parameter node, if any.
307     * @param node <code>Node</code> A dom node OR <code>undefined</code>.
308     * @return <code>Node</code> OR <code>undefined</code>.
309     */   
310    getPreviousInlineTextNode: function( node ) {
311        return this.forPrevious( node, DOM.isInlineTextNode );
312    },
313
314   
315    /**
316     * class <code>DOM.Proxy</code>
317     * This is a static method.<br/>
318     * It returns the text node next to the parameter node, if any.
319     * @param node <code>Node</code> A dom node OR <code>undefined</code>.
320     * @return <code>Node</code> OR <code>undefined</code>.
321     */   
322    getNextTextNode: function( node ) {
323        return this.forNext( node, DOM.isTextNode );
324    },
325
326   
327    /**
328     * class <code>DOM.Proxy</code>
329     * This is a static method.<br/>
330     * It returns the inline text node next to the parameter node, if any.
331     * @param node <code>Node</code> A dom node OR <code>undefined</code>.
332     * @return <code>Node</code> OR <code>undefined</code>.
333     */   
334    getNextInlineTextNode: function( node ) {
335        return this.forNext( node, DOM.isInlineTextNode );
336    },
337   
338   
339    /**
340     * class <code>DOM.Proxy</code>
341     * This is a static method.<br/> 
342     * It searches a node and its tree, if any, for matching text in a text node value.
343     * @param node <code>Node</code> A dom node.
344     * @param text <code>string</code> The text to search for.
345     * @return <code>Object</code> A hash with property <code>node</code> set to
346     *                             the node where the text was found or to <code>undefined</code>,
347     *                             and a property <code>offset</code>
348     *                             set to either an <code>int</code> (position of the found text in
349     *                             the text node value) or to <code>0</code>.
350     */   
351    findText: function( node, text ) {
352        if( node.nodeType == Node.TEXT_NODE ) {
353            var offset = node.nodeValue.indexOf( text );
354            if( offset >= 0 )
355                return { node: node, offset: offset };
356        } else if( node.nodeType == ELEMENT_NODE && node.childNodes ) {
357            for( var i = 0; i < node.childNodes.length; i++ ) {
358                var found = this.findText( node.childNodes[ i ], text );
359                if( found.node )
360                    return found;
361            }   
362        }
363        return { node: undefined, offset: 0 };
364    },
365   
366   
367    /**
368     * class <code>DOM.Proxy</code>
369     * This is a static method.<br/>
370     * It finds the first text node that exists in a series of text nodes that contains as much
371     * as or more text characters as specified by the parameter <code>offset</code>.
372     * @param node <code>Node</code> A dom node.
373     * @param offset <code>int</code> The number of characters to find.
374     * @return <code>Object</code> A hash with property <code>node</code> set to
375     *                             the node where the text position was found or to <code>undefined</code>,
376     *                             and a property <code>offset</code>
377     *                             set to either an <code>int</code> (the difference between the length of the
378     *                             <code>node</code> in the hash and the parameter <code>offset</code>)
379     *                             or to <code>0</code>.       
380     */   
381    findTextPosition: function( node, offset ) {
382        var position = { node: undefined, offset: 0 };
383        while( node ) {
384            if( node.nodeType == Node.TEXT_NODE ) { 
385                position.offset += node.length;
386                var delta = position.offset - offset;
387                if( delta >= 0 ) {
388                    position.node = node;
389                    position.offset = node.length - delta;
390                    break;
391                }
392            }
393            node = this.getNextTextNode( node );
394        }
395        return position;
396    },
397   
398   
399    /**
400     * class <code>DOM.Proxy</code>
401     * This is a static method.<br/>
402     * Generate the markup needed to describe the parameter <code>node</code> (including its subtree, if any).
403     * @param node <code>Node</code> A dom node.
404     * @return <code>string</code> The markup representation of this node.
405     */   
406    serialize: function( node ) {
407        var proxy = new this( node );
408        return proxy.serialize();
409    },
410   
411   
412    /**
413     * class <code>DOM.Proxy</code>
414     * This is a static method.<br/>
415     * Serialize the child nodes of the parameter <code>node</code>, if any.
416     * @param node <code>Node</code> A dom node.
417     * @return <code>string</code> The markup representation of the child nodes of this node.
418     */   
419    serializeChildNodes: function( node ) {
420        var proxy = new this( node );
421        return proxy.serializeChildNodes();
422    }
423} );
Note: See TracBrowser for help on using the browser.