]> CyberLeo.Net >> Repos - Github/sugarcrm.git/blob - include/javascript/tiny_mce/classes/dom/DOMUtils.js
Release 6.2.3
[Github/sugarcrm.git] / include / javascript / tiny_mce / classes / dom / DOMUtils.js
1 /**
2  * DOMUtils.js
3  *
4  * Copyright 2009, Moxiecode Systems AB
5  * Released under LGPL License.
6  *
7  * License: http://tinymce.moxiecode.com/license
8  * Contributing: http://tinymce.moxiecode.com/contributing
9  */
10
11 (function(tinymce) {
12         // Shorten names
13         var each = tinymce.each,
14                 is = tinymce.is,
15                 isWebKit = tinymce.isWebKit,
16                 isIE = tinymce.isIE,
17                 Entities = tinymce.html.Entities,
18                 simpleSelectorRe = /^([a-z0-9],?)+$/i,
19                 blockElementsMap = tinymce.html.Schema.blockElementsMap,
20                 whiteSpaceRegExp = /^[ \t\r\n]*$/;
21
22         /**
23          * Utility class for various DOM manipulation and retrival functions.
24          *
25          * @class tinymce.dom.DOMUtils
26          * @example
27          * // Add a class to an element by id in the page
28          * tinymce.DOM.addClass('someid', 'someclass');
29          *
30          * // Add a class to an element by id inside the editor
31          * tinyMCE.activeEditor.dom.addClass('someid', 'someclass');
32          */
33         tinymce.create('tinymce.dom.DOMUtils', {
34                 doc : null,
35                 root : null,
36                 files : null,
37                 pixelStyles : /^(top|left|bottom|right|width|height|borderWidth)$/,
38                 props : {
39                         "for" : "htmlFor",
40                         "class" : "className",
41                         className : "className",
42                         checked : "checked",
43                         disabled : "disabled",
44                         maxlength : "maxLength",
45                         readonly : "readOnly",
46                         selected : "selected",
47                         value : "value",
48                         id : "id",
49                         name : "name",
50                         type : "type"
51                 },
52
53                 /**
54                  * Constructs a new DOMUtils instance. Consult the Wiki for more details on settings etc for this class.
55                  *
56                  * @constructor
57                  * @method DOMUtils
58                  * @param {Document} d Document reference to bind the utility class to.
59                  * @param {settings} s Optional settings collection.
60                  */
61                 DOMUtils : function(d, s) {
62                         var t = this, globalStyle, name;
63
64                         t.doc = d;
65                         t.win = window;
66                         t.files = {};
67                         t.cssFlicker = false;
68                         t.counter = 0;
69                         t.stdMode = !tinymce.isIE || d.documentMode >= 8;
70                         t.boxModel = !tinymce.isIE || d.compatMode == "CSS1Compat" || t.stdMode;
71                         t.hasOuterHTML = "outerHTML" in d.createElement("a");
72
73                         t.settings = s = tinymce.extend({
74                                 keep_values : false,
75                                 hex_colors : 1
76                         }, s);
77                         
78                         t.schema = s.schema;
79                         t.styles = new tinymce.html.Styles({
80                                 url_converter : s.url_converter,
81                                 url_converter_scope : s.url_converter_scope
82                         }, s.schema);
83
84                         // Fix IE6SP2 flicker and check it failed for pre SP2
85                         if (tinymce.isIE6) {
86                                 try {
87                                         d.execCommand('BackgroundImageCache', false, true);
88                                 } catch (e) {
89                                         t.cssFlicker = true;
90                                 }
91                         }
92
93                         if (isIE && s.schema) {
94                                 // Add missing HTML 4/5 elements to IE
95                                 ('abbr article aside audio canvas ' +
96                                 'details figcaption figure footer ' +
97                                 'header hgroup mark menu meter nav ' +
98                                 'output progress section summary ' +
99                                 'time video').replace(/\w+/g, function(name) {
100                                         d.createElement(name);
101                                 });
102
103                                 // Create all custom elements
104                                 for (name in s.schema.getCustomElements()) {
105                                         d.createElement(name);
106                                 }
107                         }
108
109                         tinymce.addUnload(t.destroy, t);
110                 },
111
112                 /**
113                  * Returns the root node of the document this is normally the body but might be a DIV. Parents like getParent will not
114                  * go above the point of this root node.
115                  *
116                  * @method getRoot
117                  * @return {Element} Root element for the utility class.
118                  */
119                 getRoot : function() {
120                         var t = this, s = t.settings;
121
122                         return (s && t.get(s.root_element)) || t.doc.body;
123                 },
124
125                 /**
126                  * Returns the viewport of the window.
127                  *
128                  * @method getViewPort
129                  * @param {Window} w Optional window to get viewport of.
130                  * @return {Object} Viewport object with fields x, y, w and h.
131                  */
132                 getViewPort : function(w) {
133                         var d, b;
134
135                         w = !w ? this.win : w;
136                         d = w.document;
137                         b = this.boxModel ? d.documentElement : d.body;
138
139                         // Returns viewport size excluding scrollbars
140                         return {
141                                 x : w.pageXOffset || b.scrollLeft,
142                                 y : w.pageYOffset || b.scrollTop,
143                                 w : w.innerWidth || b.clientWidth,
144                                 h : w.innerHeight || b.clientHeight
145                         };
146                 },
147
148                 /**
149                  * Returns the rectangle for a specific element.
150                  *
151                  * @method getRect
152                  * @param {Element/String} e Element object or element ID to get rectange from.
153                  * @return {object} Rectange for specified element object with x, y, w, h fields.
154                  */
155                 getRect : function(e) {
156                         var p, t = this, sr;
157
158                         e = t.get(e);
159                         p = t.getPos(e);
160                         sr = t.getSize(e);
161
162                         return {
163                                 x : p.x,
164                                 y : p.y,
165                                 w : sr.w,
166                                 h : sr.h
167                         };
168                 },
169
170                 /**
171                  * Returns the size dimensions of the specified element.
172                  *
173                  * @method getSize
174                  * @param {Element/String} e Element object or element ID to get rectange from.
175                  * @return {object} Rectange for specified element object with w, h fields.
176                  */
177                 getSize : function(e) {
178                         var t = this, w, h;
179
180                         e = t.get(e);
181                         w = t.getStyle(e, 'width');
182                         h = t.getStyle(e, 'height');
183
184                         // Non pixel value, then force offset/clientWidth
185                         if (w.indexOf('px') === -1)
186                                 w = 0;
187
188                         // Non pixel value, then force offset/clientWidth
189                         if (h.indexOf('px') === -1)
190                                 h = 0;
191
192                         return {
193                                 w : parseInt(w) || e.offsetWidth || e.clientWidth,
194                                 h : parseInt(h) || e.offsetHeight || e.clientHeight
195                         };
196                 },
197
198                 /**
199                  * Returns a node by the specified selector function. This function will
200                  * loop through all parent nodes and call the specified function for each node.
201                  * If the function then returns true indicating that it has found what it was looking for, the loop execution will then end
202                  * and the node it found will be returned.
203                  *
204                  * @method getParent
205                  * @param {Node/String} n DOM node to search parents on or ID string.
206                  * @param {function} f Selection function to execute on each node or CSS pattern.
207                  * @param {Node} r Optional root element, never go below this point.
208                  * @return {Node} DOM Node or null if it wasn't found.
209                  */
210                 getParent : function(n, f, r) {
211                         return this.getParents(n, f, r, false);
212                 },
213
214                 /**
215                  * Returns a node list of all parents matching the specified selector function or pattern.
216                  * If the function then returns true indicating that it has found what it was looking for and that node will be collected.
217                  *
218                  * @method getParents
219                  * @param {Node/String} n DOM node to search parents on or ID string.
220                  * @param {function} f Selection function to execute on each node or CSS pattern.
221                  * @param {Node} r Optional root element, never go below this point.
222                  * @return {Array} Array of nodes or null if it wasn't found.
223                  */
224                 getParents : function(n, f, r, c) {
225                         var t = this, na, se = t.settings, o = [];
226
227                         n = t.get(n);
228                         c = c === undefined;
229
230                         if (se.strict_root)
231                                 r = r || t.getRoot();
232
233                         // Wrap node name as func
234                         if (is(f, 'string')) {
235                                 na = f;
236
237                                 if (f === '*') {
238                                         f = function(n) {return n.nodeType == 1;};
239                                 } else {
240                                         f = function(n) {
241                                                 return t.is(n, na);
242                                         };
243                                 }
244                         }
245
246                         while (n) {
247                                 if (n == r || !n.nodeType || n.nodeType === 9)
248                                         break;
249
250                                 if (!f || f(n)) {
251                                         if (c)
252                                                 o.push(n);
253                                         else
254                                                 return n;
255                                 }
256
257                                 n = n.parentNode;
258                         }
259
260                         return c ? o : null;
261                 },
262
263                 /**
264                  * Returns the specified element by ID or the input element if it isn't a string.
265                  *
266                  * @method get
267                  * @param {String/Element} n Element id to look for or element to just pass though.
268                  * @return {Element} Element matching the specified id or null if it wasn't found.
269                  */
270                 get : function(e) {
271                         var n;
272
273                         if (e && this.doc && typeof(e) == 'string') {
274                                 n = e;
275                                 e = this.doc.getElementById(e);
276
277                                 // IE and Opera returns meta elements when they match the specified input ID, but getElementsByName seems to do the trick
278                                 if (e && e.id !== n)
279                                         return this.doc.getElementsByName(n)[1];
280                         }
281
282                         return e;
283                 },
284
285                 /**
286                  * Returns the next node that matches selector or function
287                  *
288                  * @method getNext
289                  * @param {Node} node Node to find siblings from.
290                  * @param {String/function} selector Selector CSS expression or function.
291                  * @return {Node} Next node item matching the selector or null if it wasn't found.
292                  */
293                 getNext : function(node, selector) {
294                         return this._findSib(node, selector, 'nextSibling');
295                 },
296
297                 /**
298                  * Returns the previous node that matches selector or function
299                  *
300                  * @method getPrev
301                  * @param {Node} node Node to find siblings from.
302                  * @param {String/function} selector Selector CSS expression or function.
303                  * @return {Node} Previous node item matching the selector or null if it wasn't found.
304                  */
305                 getPrev : function(node, selector) {
306                         return this._findSib(node, selector, 'previousSibling');
307                 },
308
309                 // #ifndef jquery
310
311                 /**
312                  * Selects specific elements by a CSS level 3 pattern. For example "div#a1 p.test".
313                  * This function is optimized for the most common patterns needed in TinyMCE but it also performes good enough
314                  * on more complex patterns.
315                  *
316                  * @method select
317                  * @param {String} p CSS level 1 pattern to select/find elements by.
318                  * @param {Object} s Optional root element/scope element to search in.
319                  * @return {Array} Array with all matched elements.
320                  * @example
321                  * // Adds a class to all paragraphs in the currently active editor
322                  * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'someclass');
323                  * 
324                  * // Adds a class to all spans that has the test class in the currently active editor
325                  * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('span.test'), 'someclass')
326                  */
327                 select : function(pa, s) {
328                         var t = this;
329
330                         return tinymce.dom.Sizzle(pa, t.get(s) || t.get(t.settings.root_element) || t.doc, []);
331                 },
332
333                 /**
334                  * Returns true/false if the specified element matches the specified css pattern.
335                  *
336                  * @method is
337                  * @param {Node/NodeList} n DOM node to match or an array of nodes to match.
338                  * @param {String} selector CSS pattern to match the element agains.
339                  */
340                 is : function(n, selector) {
341                         var i;
342
343                         // If it isn't an array then try to do some simple selectors instead of Sizzle for to boost performance
344                         if (n.length === undefined) {
345                                 // Simple all selector
346                                 if (selector === '*')
347                                         return n.nodeType == 1;
348
349                                 // Simple selector just elements
350                                 if (simpleSelectorRe.test(selector)) {
351                                         selector = selector.toLowerCase().split(/,/);
352                                         n = n.nodeName.toLowerCase();
353
354                                         for (i = selector.length - 1; i >= 0; i--) {
355                                                 if (selector[i] == n)
356                                                         return true;
357                                         }
358
359                                         return false;
360                                 }
361                         }
362
363                         return tinymce.dom.Sizzle.matches(selector, n.nodeType ? [n] : n).length > 0;
364                 },
365
366                 // #endif
367
368                 /**
369                  * Adds the specified element to another element or elements.
370                  *
371                  * @method add
372                  * @param {String/Element/Array} Element id string, DOM node element or array of id's or elements to add to.
373                  * @param {String/Element} n Name of new element to add or existing element to add.
374                  * @param {Object} a Optional object collection with arguments to add to the new element(s).
375                  * @param {String} h Optional inner HTML contents to add for each element.
376                  * @param {Boolean} c Optional internal state to indicate if it should create or add.
377                  * @return {Element/Array} Element that got created or array with elements if multiple elements where passed.
378                  * @example
379                  * // Adds a new paragraph to the end of the active editor
380                  * tinyMCE.activeEditor.dom.add(tinyMCE.activeEditor.getBody(), 'p', {title : 'my title'}, 'Some content');
381                  */
382                 add : function(p, n, a, h, c) {
383                         var t = this;
384
385                         return this.run(p, function(p) {
386                                 var e, k;
387
388                                 e = is(n, 'string') ? t.doc.createElement(n) : n;
389                                 t.setAttribs(e, a);
390
391                                 if (h) {
392                                         if (h.nodeType)
393                                                 e.appendChild(h);
394                                         else
395                                                 t.setHTML(e, h);
396                                 }
397
398                                 return !c ? p.appendChild(e) : e;
399                         });
400                 },
401
402                 /**
403                  * Creates a new element.
404                  *
405                  * @method create
406                  * @param {String} n Name of new element.
407                  * @param {Object} a Optional object name/value collection with element attributes.
408                  * @param {String} h Optional HTML string to set as inner HTML of the element.
409                  * @return {Element} HTML DOM node element that got created.
410                  * @example
411                  * // Adds an element where the caret/selection is in the active editor
412                  * var el = tinyMCE.activeEditor.dom.create('div', {id : 'test', 'class' : 'myclass'}, 'some content');
413                  * tinyMCE.activeEditor.selection.setNode(el);
414                  */
415                 create : function(n, a, h) {
416                         return this.add(this.doc.createElement(n), n, a, h, 1);
417                 },
418
419                 /**
420                  * Create HTML string for element. The element will be closed unless an empty inner HTML string is passed.
421                  *
422                  * @method createHTML
423                  * @param {String} n Name of new element.
424                  * @param {Object} a Optional object name/value collection with element attributes.
425                  * @param {String} h Optional HTML string to set as inner HTML of the element.
426                  * @return {String} String with new HTML element like for example: <a href="#">test</a>.
427                  * @example
428                  * // Creates a html chunk and inserts it at the current selection/caret location
429                  * tinyMCE.activeEditor.selection.setContent(tinyMCE.activeEditor.dom.createHTML('a', {href : 'test.html'}, 'some line'));
430                  */
431                 createHTML : function(n, a, h) {
432                         var o = '', t = this, k;
433
434                         o += '<' + n;
435
436                         for (k in a) {
437                                 if (a.hasOwnProperty(k))
438                                         o += ' ' + k + '="' + t.encode(a[k]) + '"';
439                         }
440
441                         // A call to tinymce.is doesn't work for some odd reason on IE9 possible bug inside their JS runtime
442                         if (typeof(h) != "undefined")
443                                 return o + '>' + h + '</' + n + '>';
444
445                         return o + ' />';
446                 },
447
448                 /**
449                  * Removes/deletes the specified element(s) from the DOM.
450                  *
451                  * @method remove
452                  * @param {String/Element/Array} node ID of element or DOM element object or array containing multiple elements/ids.
453                  * @param {Boolean} keep_children Optional state to keep children or not. If set to true all children will be placed at the location of the removed element.
454                  * @return {Element/Array} HTML DOM element that got removed or array of elements depending on input.
455                  * @example
456                  * // Removes all paragraphs in the active editor
457                  * tinyMCE.activeEditor.dom.remove(tinyMCE.activeEditor.dom.select('p'));
458                  * 
459                  * // Removes a element by id in the document
460                  * tinyMCE.DOM.remove('mydiv');
461                  */
462                 remove : function(node, keep_children) {
463                         return this.run(node, function(node) {
464                                 var child, parent = node.parentNode;
465
466                                 if (!parent)
467                                         return null;
468
469                                 if (keep_children) {
470                                         while (child = node.firstChild) {
471                                                 // IE 8 will crash if you don't remove completely empty text nodes
472                                                 if (!tinymce.isIE || child.nodeType !== 3 || child.nodeValue)
473                                                         parent.insertBefore(child, node);
474                                                 else
475                                                         node.removeChild(child);
476                                         }
477                                 }
478
479                                 return parent.removeChild(node);
480                         });
481                 },
482
483                 /**
484                  * Sets the CSS style value on a HTML element. The name can be a camelcase string
485                  * or the CSS style name like background-color.
486                  *
487                  * @method setStyle
488                  * @param {String/Element/Array} n HTML element/Element ID or Array of elements/ids to set CSS style value on.
489                  * @param {String} na Name of the style value to set.
490                  * @param {String} v Value to set on the style.
491                  * @example
492                  * // Sets a style value on all paragraphs in the currently active editor
493                  * tinyMCE.activeEditor.dom.setStyle(tinyMCE.activeEditor.dom.select('p'), 'background-color', 'red');
494                  * 
495                  * // Sets a style value to an element by id in the current document
496                  * tinyMCE.DOM.setStyle('mydiv', 'background-color', 'red');
497                  */
498                 setStyle : function(n, na, v) {
499                         var t = this;
500
501                         return t.run(n, function(e) {
502                                 var s, i;
503
504                                 s = e.style;
505
506                                 // Camelcase it, if needed
507                                 na = na.replace(/-(\D)/g, function(a, b){
508                                         return b.toUpperCase();
509                                 });
510
511                                 // Default px suffix on these
512                                 if (t.pixelStyles.test(na) && (tinymce.is(v, 'number') || /^[\-0-9\.]+$/.test(v)))
513                                         v += 'px';
514
515                                 switch (na) {
516                                         case 'opacity':
517                                                 // IE specific opacity
518                                                 if (isIE) {
519                                                         s.filter = v === '' ? '' : "alpha(opacity=" + (v * 100) + ")";
520
521                                                         if (!n.currentStyle || !n.currentStyle.hasLayout)
522                                                                 s.display = 'inline-block';
523                                                 }
524
525                                                 // Fix for older browsers
526                                                 s[na] = s['-moz-opacity'] = s['-khtml-opacity'] = v || '';
527                                                 break;
528
529                                         case 'float':
530                                                 isIE ? s.styleFloat = v : s.cssFloat = v;
531                                                 break;
532                                         
533                                         default:
534                                                 s[na] = v || '';
535                                 }
536
537                                 // Force update of the style data
538                                 if (t.settings.update_styles)
539                                         t.setAttrib(e, 'data-mce-style');
540                         });
541                 },
542
543                 /**
544                  * Returns the current style or runtime/computed value of a element.
545                  *
546                  * @method getStyle
547                  * @param {String/Element} n HTML element or element id string to get style from.
548                  * @param {String} na Style name to return.
549                  * @param {Boolean} c Computed style.
550                  * @return {String} Current style or computed style value of a element.
551                  */
552                 getStyle : function(n, na, c) {
553                         n = this.get(n);
554
555                         if (!n)
556                                 return;
557
558                         // Gecko
559                         if (this.doc.defaultView && c) {
560                                 // Remove camelcase
561                                 na = na.replace(/[A-Z]/g, function(a){
562                                         return '-' + a;
563                                 });
564
565                                 try {
566                                         return this.doc.defaultView.getComputedStyle(n, null).getPropertyValue(na);
567                                 } catch (ex) {
568                                         // Old safari might fail
569                                         return null;
570                                 }
571                         }
572
573                         // Camelcase it, if needed
574                         na = na.replace(/-(\D)/g, function(a, b){
575                                 return b.toUpperCase();
576                         });
577
578                         if (na == 'float')
579                                 na = isIE ? 'styleFloat' : 'cssFloat';
580
581                         // IE & Opera
582                         if (n.currentStyle && c)
583                                 return n.currentStyle[na];
584
585                         return n.style ? n.style[na] : undefined;
586                 },
587
588                 /**
589                  * Sets multiple styles on the specified element(s).
590                  *
591                  * @method setStyles
592                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set styles on.
593                  * @param {Object} o Name/Value collection of style items to add to the element(s).
594                  * @example
595                  * // Sets styles on all paragraphs in the currently active editor
596                  * tinyMCE.activeEditor.dom.setStyles(tinyMCE.activeEditor.dom.select('p'), {'background-color' : 'red', 'color' : 'green'});
597                  * 
598                  * // Sets styles to an element by id in the current document
599                  * tinyMCE.DOM.setStyles('mydiv', {'background-color' : 'red', 'color' : 'green'});
600                  */
601                 setStyles : function(e, o) {
602                         var t = this, s = t.settings, ol;
603
604                         ol = s.update_styles;
605                         s.update_styles = 0;
606
607                         each(o, function(v, n) {
608                                 t.setStyle(e, n, v);
609                         });
610
611                         // Update style info
612                         s.update_styles = ol;
613                         if (s.update_styles)
614                                 t.setAttrib(e, s.cssText);
615                 },
616
617                 /**
618                  * Removes all attributes from an element or elements.
619                  * 
620                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to remove attributes from.
621                  */
622                 removeAllAttribs: function(e) {
623                         return this.run(e, function(e) {
624                                 var i, attrs = e.attributes;
625                                 for (i = attrs.length - 1; i >= 0; i--) {
626                                         e.removeAttributeNode(attrs.item(i));
627                                 }
628                         });
629                 },
630
631                 /**
632                  * Sets the specified attributes value of a element or elements.
633                  *
634                  * @method setAttrib
635                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attribute on.
636                  * @param {String} n Name of attribute to set.
637                  * @param {String} v Value to set on the attribute of this value is falsy like null 0 or '' it will remove the attribute instead.
638                  * @example
639                  * // Sets an attribute to all paragraphs in the active editor
640                  * tinyMCE.activeEditor.dom.setAttrib(tinyMCE.activeEditor.dom.select('p'), 'class', 'myclass');
641                  * 
642                  * // Sets an attribute to a specific element in the current page
643                  * tinyMCE.dom.setAttrib('mydiv', 'class', 'myclass');
644                  */
645                 setAttrib : function(e, n, v) {
646                         var t = this;
647
648                         // Whats the point
649                         if (!e || !n)
650                                 return;
651
652                         // Strict XML mode
653                         if (t.settings.strict)
654                                 n = n.toLowerCase();
655
656                         return this.run(e, function(e) {
657                                 var s = t.settings;
658
659                                 switch (n) {
660                                         case "style":
661                                                 if (!is(v, 'string')) {
662                                                         each(v, function(v, n) {
663                                                                 t.setStyle(e, n, v);
664                                                         });
665
666                                                         return;
667                                                 }
668
669                                                 // No mce_style for elements with these since they might get resized by the user
670                                                 if (s.keep_values) {
671                                                         if (v && !t._isRes(v))
672                                                                 e.setAttribute('data-mce-style', v, 2);
673                                                         else
674                                                                 e.removeAttribute('data-mce-style', 2);
675                                                 }
676
677                                                 e.style.cssText = v;
678                                                 break;
679
680                                         case "class":
681                                                 e.className = v || ''; // Fix IE null bug
682                                                 break;
683
684                                         case "src":
685                                         case "href":
686                                                 if (s.keep_values) {
687                                                         if (s.url_converter)
688                                                                 v = s.url_converter.call(s.url_converter_scope || t, v, n, e);
689
690                                                         t.setAttrib(e, 'data-mce-' + n, v, 2);
691                                                 }
692
693                                                 break;
694
695                                         case "shape":
696                                                 e.setAttribute('data-mce-style', v);
697                                                 break;
698                                 }
699
700                                 if (is(v) && v !== null && v.length !== 0)
701                                         e.setAttribute(n, '' + v, 2);
702                                 else
703                                         e.removeAttribute(n, 2);
704                         });
705                 },
706
707                 /**
708                  * Sets the specified attributes of a element or elements.
709                  *
710                  * @method setAttribs
711                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set attributes on.
712                  * @param {Object} o Name/Value collection of attribute items to add to the element(s).
713                  * @example
714                  * // Sets some attributes to all paragraphs in the active editor
715                  * tinyMCE.activeEditor.dom.setAttribs(tinyMCE.activeEditor.dom.select('p'), {'class' : 'myclass', title : 'some title'});
716                  * 
717                  * // Sets some attributes to a specific element in the current page
718                  * tinyMCE.DOM.setAttribs('mydiv', {'class' : 'myclass', title : 'some title'});
719                  */
720                 setAttribs : function(e, o) {
721                         var t = this;
722
723                         return this.run(e, function(e) {
724                                 each(o, function(v, n) {
725                                         t.setAttrib(e, n, v);
726                                 });
727                         });
728                 },
729
730                 /**
731                  * Returns the specified attribute by name.
732                  *
733                  * @method getAttrib
734                  * @param {String/Element} e Element string id or DOM element to get attribute from.
735                  * @param {String} n Name of attribute to get.
736                  * @param {String} dv Optional default value to return if the attribute didn't exist.
737                  * @return {String} Attribute value string, default value or null if the attribute wasn't found.
738                  */
739                 getAttrib : function(e, n, dv) {
740                         var v, t = this, undef;
741
742                         e = t.get(e);
743
744                         if (!e || e.nodeType !== 1)
745                                 return dv === undef ? false : dv;
746
747                         if (!is(dv))
748                                 dv = '';
749
750                         // Try the mce variant for these
751                         if (/^(src|href|style|coords|shape)$/.test(n)) {
752                                 v = e.getAttribute("data-mce-" + n);
753
754                                 if (v)
755                                         return v;
756                         }
757
758                         if (isIE && t.props[n]) {
759                                 v = e[t.props[n]];
760                                 v = v && v.nodeValue ? v.nodeValue : v;
761                         }
762
763                         if (!v)
764                                 v = e.getAttribute(n, 2);
765
766                         // Check boolean attribs
767                         if (/^(checked|compact|declare|defer|disabled|ismap|multiple|nohref|noshade|nowrap|readonly|selected)$/.test(n)) {
768                                 if (e[t.props[n]] === true && v === '')
769                                         return n;
770
771                                 return v ? n : '';
772                         }
773
774                         // Inner input elements will override attributes on form elements
775                         if (e.nodeName === "FORM" && e.getAttributeNode(n))
776                                 return e.getAttributeNode(n).nodeValue;
777
778                         if (n === 'style') {
779                                 v = v || e.style.cssText;
780
781                                 if (v) {
782                                         v = t.serializeStyle(t.parseStyle(v), e.nodeName);
783
784                                         if (t.settings.keep_values && !t._isRes(v))
785                                                 e.setAttribute('data-mce-style', v);
786                                 }
787                         }
788
789                         // Remove Apple and WebKit stuff
790                         if (isWebKit && n === "class" && v)
791                                 v = v.replace(/(apple|webkit)\-[a-z\-]+/gi, '');
792
793                         // Handle IE issues
794                         if (isIE) {
795                                 switch (n) {
796                                         case 'rowspan':
797                                         case 'colspan':
798                                                 // IE returns 1 as default value
799                                                 if (v === 1)
800                                                         v = '';
801
802                                                 break;
803
804                                         case 'size':
805                                                 // IE returns +0 as default value for size
806                                                 if (v === '+0' || v === 20 || v === 0)
807                                                         v = '';
808
809                                                 break;
810
811                                         case 'width':
812                                         case 'height':
813                                         case 'vspace':
814                                         case 'checked':
815                                         case 'disabled':
816                                         case 'readonly':
817                                                 if (v === 0)
818                                                         v = '';
819
820                                                 break;
821
822                                         case 'hspace':
823                                                 // IE returns -1 as default value
824                                                 if (v === -1)
825                                                         v = '';
826
827                                                 break;
828
829                                         case 'maxlength':
830                                         case 'tabindex':
831                                                 // IE returns default value
832                                                 if (v === 32768 || v === 2147483647 || v === '32768')
833                                                         v = '';
834
835                                                 break;
836
837                                         case 'multiple':
838                                         case 'compact':
839                                         case 'noshade':
840                                         case 'nowrap':
841                                                 if (v === 65535)
842                                                         return n;
843
844                                                 return dv;
845
846                                         case 'shape':
847                                                 v = v.toLowerCase();
848                                                 break;
849
850                                         default:
851                                                 // IE has odd anonymous function for event attributes
852                                                 if (n.indexOf('on') === 0 && v)
853                                                         v = tinymce._replace(/^function\s+\w+\(\)\s+\{\s+(.*)\s+\}$/, '$1', '' + v);
854                                 }
855                         }
856
857                         return (v !== undef && v !== null && v !== '') ? '' + v : dv;
858                 },
859
860                 /**
861                  * Returns the absolute x, y position of a node. The position will be returned in a object with x, y fields.
862                  *
863                  * @method getPos
864                  * @param {Element/String} n HTML element or element id to get x, y position from.
865                  * @param {Element} ro Optional root element to stop calculations at.
866                  * @return {object} Absolute position of the specified element object with x, y fields.
867                  */
868                 getPos : function(n, ro) {
869                         var t = this, x = 0, y = 0, e, d = t.doc, r;
870
871                         n = t.get(n);
872                         ro = ro || d.body;
873
874                         if (n) {
875                                 // Use getBoundingClientRect if it exists since it's faster than looping offset nodes
876                                 if (n.getBoundingClientRect) {
877                                         n = n.getBoundingClientRect();
878                                         e = t.boxModel ? d.documentElement : d.body;
879
880                                         // Add scroll offsets from documentElement or body since IE with the wrong box model will use d.body and so do WebKit
881                                         // Also remove the body/documentelement clientTop/clientLeft on IE 6, 7 since they offset the position
882                                         x = n.left + (d.documentElement.scrollLeft || d.body.scrollLeft) - e.clientTop;
883                                         y = n.top + (d.documentElement.scrollTop || d.body.scrollTop) - e.clientLeft;
884
885                                         return {x : x, y : y};
886                                 }
887
888                                 r = n;
889                                 while (r && r != ro && r.nodeType) {
890                                         x += r.offsetLeft || 0;
891                                         y += r.offsetTop || 0;
892                                         r = r.offsetParent;
893                                 }
894
895                                 r = n.parentNode;
896                                 while (r && r != ro && r.nodeType) {
897                                         x -= r.scrollLeft || 0;
898                                         y -= r.scrollTop || 0;
899                                         r = r.parentNode;
900                                 }
901                         }
902
903                         return {x : x, y : y};
904                 },
905
906                 /**
907                  * Parses the specified style value into an object collection. This parser will also
908                  * merge and remove any redundant items that browsers might have added. It will also convert non hex
909                  * colors to hex values. Urls inside the styles will also be converted to absolute/relative based on settings.
910                  *
911                  * @method parseStyle
912                  * @param {String} st Style value to parse for example: border:1px solid red;.
913                  * @return {Object} Object representation of that style like {border : '1px solid red'}
914                  */
915                 parseStyle : function(st) {
916                         return this.styles.parse(st);
917                 },
918
919                 /**
920                  * Serializes the specified style object into a string.
921                  *
922                  * @method serializeStyle
923                  * @param {Object} o Object to serialize as string for example: {border : '1px solid red'}
924                  * @param {String} name Optional element name.
925                  * @return {String} String representation of the style object for example: border: 1px solid red.
926                  */
927                 serializeStyle : function(o, name) {
928                         return this.styles.serialize(o, name);
929                 },
930
931                 /**
932                  * Imports/loads the specified CSS file into the document bound to the class.
933                  *
934                  * @method loadCSS
935                  * @param {String} u URL to CSS file to load.
936                  * @example
937                  * // Loads a CSS file dynamically into the current document
938                  * tinymce.DOM.loadCSS('somepath/some.css');
939                  * 
940                  * // Loads a CSS file into the currently active editor instance
941                  * tinyMCE.activeEditor.dom.loadCSS('somepath/some.css');
942                  * 
943                  * // Loads a CSS file into an editor instance by id
944                  * tinyMCE.get('someid').dom.loadCSS('somepath/some.css');
945                  * 
946                  * // Loads multiple CSS files into the current document
947                  * tinymce.DOM.loadCSS('somepath/some.css,somepath/someother.css');
948                  */
949                 loadCSS : function(u) {
950                         var t = this, d = t.doc, head;
951
952                         if (!u)
953                                 u = '';
954
955                         head = t.select('head')[0];
956
957                         each(u.split(','), function(u) {
958                                 var link;
959
960                                 if (t.files[u])
961                                         return;
962
963                                 t.files[u] = true;
964                                 link = t.create('link', {rel : 'stylesheet', href : tinymce._addVer(u)});
965
966                                 // IE 8 has a bug where dynamically loading stylesheets would produce a 1 item remaining bug
967                                 // This fix seems to resolve that issue by realcing the document ones a stylesheet finishes loading
968                                 // It's ugly but it seems to work fine.
969                                 if (isIE && d.documentMode && d.recalc) {
970                                         link.onload = function() {
971                                                 if (d.recalc)
972                                                         d.recalc();
973
974                                                 link.onload = null;
975                                         };
976                                 }
977
978                                 head.appendChild(link);
979                         });
980                 },
981
982                 /**
983                  * Adds a class to the specified element or elements.
984                  *
985                  * @method addClass
986                  * @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs.
987                  * @param {String} c Class name to add to each element.
988                  * @return {String/Array} String with new class value or array with new class values for all elements.
989                  * @example
990                  * // Adds a class to all paragraphs in the active editor
991                  * tinyMCE.activeEditor.dom.addClass(tinyMCE.activeEditor.dom.select('p'), 'myclass');
992                  * 
993                  * // Adds a class to a specific element in the current page
994                  * tinyMCE.DOM.addClass('mydiv', 'myclass');
995                  */
996                 addClass : function(e, c) {
997                         return this.run(e, function(e) {
998                                 var o;
999
1000                                 if (!c)
1001                                         return 0;
1002
1003                                 if (this.hasClass(e, c))
1004                                         return e.className;
1005
1006                                 o = this.removeClass(e, c);
1007
1008                                 return e.className = (o != '' ? (o + ' ') : '') + c;
1009                         });
1010                 },
1011
1012                 /**
1013                  * Removes a class from the specified element or elements.
1014                  *
1015                  * @method removeClass
1016                  * @param {String/Element/Array} Element ID string or DOM element or array with elements or IDs.
1017                  * @param {String} c Class name to remove to each element.
1018                  * @return {String/Array} String with new class value or array with new class values for all elements.
1019                  * @example
1020                  * // Removes a class from all paragraphs in the active editor
1021                  * tinyMCE.activeEditor.dom.removeClass(tinyMCE.activeEditor.dom.select('p'), 'myclass');
1022                  * 
1023                  * // Removes a class from a specific element in the current page
1024                  * tinyMCE.DOM.removeClass('mydiv', 'myclass');
1025                  */
1026                 removeClass : function(e, c) {
1027                         var t = this, re;
1028
1029                         return t.run(e, function(e) {
1030                                 var v;
1031
1032                                 if (t.hasClass(e, c)) {
1033                                         if (!re)
1034                                                 re = new RegExp("(^|\\s+)" + c + "(\\s+|$)", "g");
1035
1036                                         v = e.className.replace(re, ' ');
1037                                         v = tinymce.trim(v != ' ' ? v : '');
1038
1039                                         e.className = v;
1040
1041                                         // Empty class attr
1042                                         if (!v) {
1043                                                 e.removeAttribute('class');
1044                                                 e.removeAttribute('className');
1045                                         }
1046
1047                                         return v;
1048                                 }
1049
1050                                 return e.className;
1051                         });
1052                 },
1053
1054                 /**
1055                  * Returns true if the specified element has the specified class.
1056                  *
1057                  * @method hasClass
1058                  * @param {String/Element} n HTML element or element id string to check CSS class on.
1059                  * @param {String} c CSS class to check for.
1060                  * @return {Boolean} true/false if the specified element has the specified class.
1061                  */
1062                 hasClass : function(n, c) {
1063                         n = this.get(n);
1064
1065                         if (!n || !c)
1066                                 return false;
1067
1068                         return (' ' + n.className + ' ').indexOf(' ' + c + ' ') !== -1;
1069                 },
1070
1071                 /**
1072                  * Shows the specified element(s) by ID by setting the "display" style.
1073                  *
1074                  * @method show
1075                  * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to show.
1076                  */
1077                 show : function(e) {
1078                         return this.setStyle(e, 'display', 'block');
1079                 },
1080
1081                 /**
1082                  * Hides the specified element(s) by ID by setting the "display" style.
1083                  *
1084                  * @method hide
1085                  * @param {String/Element/Array} e ID of DOM element or DOM element or array with elements or IDs to hide.
1086                  * @example
1087                  * // Hides a element by id in the document
1088                  * tinymce.DOM.hide('myid');
1089                  */
1090                 hide : function(e) {
1091                         return this.setStyle(e, 'display', 'none');
1092                 },
1093
1094                 /**
1095                  * Returns true/false if the element is hidden or not by checking the "display" style.
1096                  *
1097                  * @method isHidden
1098                  * @param {String/Element} e Id or element to check display state on.
1099                  * @return {Boolean} true/false if the element is hidden or not.
1100                  */
1101                 isHidden : function(e) {
1102                         e = this.get(e);
1103
1104                         return !e || e.style.display == 'none' || this.getStyle(e, 'display') == 'none';
1105                 },
1106
1107                 /**
1108                  * Returns a unique id. This can be useful when generating elements on the fly.
1109                  * This method will not check if the element allready exists.
1110                  *
1111                  * @method uniqueId
1112                  * @param {String} p Optional prefix to add infront of all ids defaults to "mce_".
1113                  * @return {String} Unique id.
1114                  */
1115                 uniqueId : function(p) {
1116                         return (!p ? 'mce_' : p) + (this.counter++);
1117                 },
1118
1119                 /**
1120                  * Sets the specified HTML content inside the element or elements. The HTML will first be processed this means
1121                  * URLs will get converted, hex color values fixed etc. Check processHTML for details.
1122                  *
1123                  * @method setHTML
1124                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set HTML inside.
1125                  * @param {String} h HTML content to set as inner HTML of the element.
1126                  * @example
1127                  * // Sets the inner HTML of all paragraphs in the active editor
1128                  * tinyMCE.activeEditor.dom.setHTML(tinyMCE.activeEditor.dom.select('p'), 'some inner html');
1129                  * 
1130                  * // Sets the inner HTML of a element by id in the document
1131                  * tinyMCE.DOM.setHTML('mydiv', 'some inner html');
1132                  */
1133                 setHTML : function(element, html) {
1134                         var self = this;
1135
1136                         return self.run(element, function(element) {
1137                                 if (isIE) {
1138                                         // Remove all child nodes, IE keeps empty text nodes in DOM
1139                                         while (element.firstChild)
1140                                                 element.removeChild(element.firstChild);
1141
1142                                         try {
1143                                                 // IE will remove comments from the beginning
1144                                                 // unless you padd the contents with something
1145                                                 element.innerHTML = '<br />' + html;
1146                                                 element.removeChild(element.firstChild);
1147                                         } catch (ex) {
1148                                                 // IE sometimes produces an unknown runtime error on innerHTML if it's an block element within a block element for example a div inside a p
1149                                                 // This seems to fix this problem
1150
1151                                                 // Create new div with HTML contents and a BR infront to keep comments
1152                                                 element = self.create('div');
1153                                                 element.innerHTML = '<br />' + html;
1154
1155                                                 // Add all children from div to target
1156                                                 each (element.childNodes, function(node, i) {
1157                                                         // Skip br element
1158                                                         if (i)
1159                                                                 element.appendChild(node);
1160                                                 });
1161                                         }
1162                                 } else
1163                                         element.innerHTML = html;
1164
1165                                 return html;
1166                         });
1167                 },
1168
1169                 /**
1170                  * Returns the outer HTML of an element.
1171                  *
1172                  * @method getOuterHTML
1173                  * @param {String/Element} elm Element ID or element object to get outer HTML from.
1174                  * @return {String} Outer HTML string.
1175                  * @example
1176                  * tinymce.DOM.getOuterHTML(editorElement);
1177                  * tinyMCE.activeEditor.getOuterHTML(tinyMCE.activeEditor.getBody());
1178                  */
1179                 getOuterHTML : function(elm) {
1180                         var doc, self = this;
1181
1182                         elm = self.get(elm);
1183
1184                         if (!elm)
1185                                 return null;
1186
1187                         if (elm.nodeType === 1 && self.hasOuterHTML)
1188                                 return elm.outerHTML;
1189
1190                         doc = (elm.ownerDocument || self.doc).createElement("body");
1191                         doc.appendChild(elm.cloneNode(true));
1192
1193                         return doc.innerHTML;
1194                 },
1195
1196                 /**
1197                  * Sets the specified outer HTML on a element or elements.
1198                  *
1199                  * @method setOuterHTML
1200                  * @param {Element/String/Array} e DOM element, element id string or array of elements/ids to set outer HTML on.
1201                  * @param {Object} h HTML code to set as outer value for the element.
1202                  * @param {Document} d Optional document scope to use in this process defaults to the document of the DOM class.
1203                  * @example
1204                  * // Sets the outer HTML of all paragraphs in the active editor
1205                  * tinyMCE.activeEditor.dom.setOuterHTML(tinyMCE.activeEditor.dom.select('p'), '<div>some html</div>');
1206                  * 
1207                  * // Sets the outer HTML of a element by id in the document
1208                  * tinyMCE.DOM.setOuterHTML('mydiv', '<div>some html</div>');
1209                  */
1210                 setOuterHTML : function(e, h, d) {
1211                         var t = this;
1212
1213                         function setHTML(e, h, d) {
1214                                 var n, tp;
1215
1216                                 tp = d.createElement("body");
1217                                 tp.innerHTML = h;
1218
1219                                 n = tp.lastChild;
1220                                 while (n) {
1221                                         t.insertAfter(n.cloneNode(true), e);
1222                                         n = n.previousSibling;
1223                                 }
1224
1225                                 t.remove(e);
1226                         };
1227
1228                         return this.run(e, function(e) {
1229                                 e = t.get(e);
1230
1231                                 // Only set HTML on elements
1232                                 if (e.nodeType == 1) {
1233                                         d = d || e.ownerDocument || t.doc;
1234
1235                                         if (isIE) {
1236                                                 try {
1237                                                         // Try outerHTML for IE it sometimes produces an unknown runtime error
1238                                                         if (isIE && e.nodeType == 1)
1239                                                                 e.outerHTML = h;
1240                                                         else
1241                                                                 setHTML(e, h, d);
1242                                                 } catch (ex) {
1243                                                         // Fix for unknown runtime error
1244                                                         setHTML(e, h, d);
1245                                                 }
1246                                         } else
1247                                                 setHTML(e, h, d);
1248                                 }
1249                         });
1250                 },
1251
1252                 /**
1253                  * Entity decode a string, resolves any HTML entities like &aring;.
1254                  *
1255                  * @method decode
1256                  * @param {String} s String to decode entities on.
1257                  * @return {String} Entity decoded string.
1258                  */
1259                 decode : Entities.decode,
1260
1261                 /**
1262                  * Entity encodes a string, encodes the most common entities <>"& into entities.
1263                  *
1264                  * @method encode
1265                  * @param {String} text String to encode with entities.
1266                  * @return {String} Entity encoded string.
1267                  */
1268                 encode : Entities.encodeAllRaw,
1269
1270                 /**
1271                  * Inserts a element after the reference element.
1272                  *
1273                  * @method insertAfter
1274                  * @param {Element} node Element to insert after the reference.
1275                  * @param {Element/String/Array} reference_node Reference element, element id or array of elements to insert after.
1276                  * @return {Element/Array} Element that got added or an array with elements. 
1277                  */
1278                 insertAfter : function(node, reference_node) {
1279                         reference_node = this.get(reference_node);
1280
1281                         return this.run(node, function(node) {
1282                                 var parent, nextSibling;
1283
1284                                 parent = reference_node.parentNode;
1285                                 nextSibling = reference_node.nextSibling;
1286
1287                                 if (nextSibling)
1288                                         parent.insertBefore(node, nextSibling);
1289                                 else
1290                                         parent.appendChild(node);
1291
1292                                 return node;
1293                         });
1294                 },
1295
1296                 /**
1297                  * Returns true/false if the specified element is a block element or not.
1298                  *
1299                  * @method isBlock
1300                  * @param {Node/String} node Element/Node to check.
1301                  * @return {Boolean} True/False state if the node is a block element or not.
1302                  */
1303                 isBlock : function(node) {
1304                         var type = node.nodeType;
1305
1306                         // If it's a node then check the type and use the nodeName
1307                         if (type)
1308                                 return !!(type === 1 && blockElementsMap[node.nodeName]);
1309
1310                         return !!blockElementsMap[node];
1311                 },
1312
1313                 /**
1314                  * Replaces the specified element or elements with the specified element, the new element will
1315                  * be cloned if multiple inputs elements are passed.
1316                  *
1317                  * @method replace
1318                  * @param {Element} n New element to replace old ones with.
1319                  * @param {Element/String/Array} o Element DOM node, element id or array of elements or ids to replace.
1320                  * @param {Boolean} k Optional keep children state, if set to true child nodes from the old object will be added to new ones.
1321                  */
1322                 replace : function(n, o, k) {
1323                         var t = this;
1324
1325                         if (is(o, 'array'))
1326                                 n = n.cloneNode(true);
1327
1328                         return t.run(o, function(o) {
1329                                 if (k) {
1330                                         each(tinymce.grep(o.childNodes), function(c) {
1331                                                 n.appendChild(c);
1332                                         });
1333                                 }
1334
1335                                 return o.parentNode.replaceChild(n, o);
1336                         });
1337                 },
1338
1339                 /**
1340                  * Renames the specified element to a new name and keep it's attributes and children.
1341                  *
1342                  * @method rename
1343                  * @param {Element} elm Element to rename.
1344                  * @param {String} name Name of the new element.
1345                  * @return New element or the old element if it needed renaming.
1346                  */
1347                 rename : function(elm, name) {
1348                         var t = this, newElm;
1349
1350                         if (elm.nodeName != name.toUpperCase()) {
1351                                 // Rename block element
1352                                 newElm = t.create(name);
1353
1354                                 // Copy attribs to new block
1355                                 each(t.getAttribs(elm), function(attr_node) {
1356                                         t.setAttrib(newElm, attr_node.nodeName, t.getAttrib(elm, attr_node.nodeName));
1357                                 });
1358
1359                                 // Replace block
1360                                 t.replace(newElm, elm, 1);
1361                         }
1362
1363                         return newElm || elm;
1364                 },
1365
1366                 /**
1367                  * Find the common ancestor of two elements. This is a shorter method than using the DOM Range logic.
1368                  *
1369                  * @method findCommonAncestor
1370                  * @param {Element} a Element to find common ancestor of.
1371                  * @param {Element} b Element to find common ancestor of.
1372                  * @return {Element} Common ancestor element of the two input elements.
1373                  */
1374                 findCommonAncestor : function(a, b) {
1375                         var ps = a, pe;
1376
1377                         while (ps) {
1378                                 pe = b;
1379
1380                                 while (pe && ps != pe)
1381                                         pe = pe.parentNode;
1382
1383                                 if (ps == pe)
1384                                         break;
1385
1386                                 ps = ps.parentNode;
1387                         }
1388
1389                         if (!ps && a.ownerDocument)
1390                                 return a.ownerDocument.documentElement;
1391
1392                         return ps;
1393                 },
1394
1395                 /**
1396                  * Parses the specified RGB color value and returns a hex version of that color.
1397                  *
1398                  * @method toHex
1399                  * @param {String} s RGB string value like rgb(1,2,3)
1400                  * @return {String} Hex version of that RGB value like #FF00FF.
1401                  */
1402                 toHex : function(s) {
1403                         var c = /^\s*rgb\s*?\(\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?,\s*?([0-9]+)\s*?\)\s*$/i.exec(s);
1404
1405                         function hex(s) {
1406                                 s = parseInt(s).toString(16);
1407
1408                                 return s.length > 1 ? s : '0' + s; // 0 -> 00
1409                         };
1410
1411                         if (c) {
1412                                 s = '#' + hex(c[1]) + hex(c[2]) + hex(c[3]);
1413
1414                                 return s;
1415                         }
1416
1417                         return s;
1418                 },
1419
1420                 /**
1421                  * Returns a array of all single CSS classes in the document. A single CSS class is a simple
1422                  * rule like ".class" complex ones like "div td.class" will not be added to output.
1423                  *
1424                  * @method getClasses
1425                  * @return {Array} Array with class objects each object has a class field might be other fields in the future.
1426                  */
1427                 getClasses : function() {
1428                         var t = this, cl = [], i, lo = {}, f = t.settings.class_filter, ov;
1429
1430                         if (t.classes)
1431                                 return t.classes;
1432
1433                         function addClasses(s) {
1434                                 // IE style imports
1435                                 each(s.imports, function(r) {
1436                                         addClasses(r);
1437                                 });
1438
1439                                 each(s.cssRules || s.rules, function(r) {
1440                                         // Real type or fake it on IE
1441                                         switch (r.type || 1) {
1442                                                 // Rule
1443                                                 case 1:
1444                                                         if (r.selectorText) {
1445                                                                 each(r.selectorText.split(','), function(v) {
1446                                                                         v = v.replace(/^\s*|\s*$|^\s\./g, "");
1447
1448                                                                         // Is internal or it doesn't contain a class
1449                                                                         if (/\.mce/.test(v) || !/\.[\w\-]+$/.test(v))
1450                                                                                 return;
1451
1452                                                                         // Remove everything but class name
1453                                                                         ov = v;
1454                                                                         v = tinymce._replace(/.*\.([a-z0-9_\-]+).*/i, '$1', v);
1455
1456                                                                         // Filter classes
1457                                                                         if (f && !(v = f(v, ov)))
1458                                                                                 return;
1459
1460                                                                         if (!lo[v]) {
1461                                                                                 cl.push({'class' : v});
1462                                                                                 lo[v] = 1;
1463                                                                         }
1464                                                                 });
1465                                                         }
1466                                                         break;
1467
1468                                                 // Import
1469                                                 case 3:
1470                                                         addClasses(r.styleSheet);
1471                                                         break;
1472                                         }
1473                                 });
1474                         };
1475
1476                         try {
1477                                 each(t.doc.styleSheets, addClasses);
1478                         } catch (ex) {
1479                                 // Ignore
1480                         }
1481
1482                         if (cl.length > 0)
1483                                 t.classes = cl;
1484
1485                         return cl;
1486                 },
1487
1488                 /**
1489                  * Executes the specified function on the element by id or dom element node or array of elements/id.
1490                  *
1491                  * @method run
1492                  * @param {String/Element/Array} Element ID or DOM element object or array with ids or elements.
1493                  * @param {function} f Function to execute for each item.
1494                  * @param {Object} s Optional scope to execute the function in.
1495                  * @return {Object/Array} Single object or array with objects depending on multiple input or not.
1496                  */
1497                 run : function(e, f, s) {
1498                         var t = this, o;
1499
1500                         if (t.doc && typeof(e) === 'string')
1501                                 e = t.get(e);
1502
1503                         if (!e)
1504                                 return false;
1505
1506                         s = s || this;
1507                         if (!e.nodeType && (e.length || e.length === 0)) {
1508                                 o = [];
1509
1510                                 each(e, function(e, i) {
1511                                         if (e) {
1512                                                 if (typeof(e) == 'string')
1513                                                         e = t.doc.getElementById(e);
1514
1515                                                 o.push(f.call(s, e, i));
1516                                         }
1517                                 });
1518
1519                                 return o;
1520                         }
1521
1522                         return f.call(s, e);
1523                 },
1524
1525                 /**
1526                  * Returns an NodeList with attributes for the element.
1527                  *
1528                  * @method getAttribs
1529                  * @param {HTMLElement/string} n Element node or string id to get attributes from.
1530                  * @return {NodeList} NodeList with attributes.
1531                  */
1532                 getAttribs : function(n) {
1533                         var o;
1534
1535                         n = this.get(n);
1536
1537                         if (!n)
1538                                 return [];
1539
1540                         if (isIE) {
1541                                 o = [];
1542
1543                                 // Object will throw exception in IE
1544                                 if (n.nodeName == 'OBJECT')
1545                                         return n.attributes;
1546
1547                                 // IE doesn't keep the selected attribute if you clone option elements
1548                                 if (n.nodeName === 'OPTION' && this.getAttrib(n, 'selected'))
1549                                         o.push({specified : 1, nodeName : 'selected'});
1550
1551                                 // It's crazy that this is faster in IE but it's because it returns all attributes all the time
1552                                 n.cloneNode(false).outerHTML.replace(/<\/?[\w:\-]+ ?|=[\"][^\"]+\"|=\'[^\']+\'|=[\w\-]+|>/gi, '').replace(/[\w:\-]+/gi, function(a) {
1553                                         o.push({specified : 1, nodeName : a});
1554                                 });
1555
1556                                 return o;
1557                         }
1558
1559                         return n.attributes;
1560                 },
1561
1562                 /**
1563                  * Returns true/false if the specified node is to be considered empty or not.
1564                  *
1565                  * @example
1566                  * tinymce.DOM.isEmpty(node, {img : true});
1567                  * @method isEmpty
1568                  * @param {Object} elements Optional name/value object with elements that are automatically treated as non empty elements.
1569                  * @return {Boolean} true/false if the node is empty or not.
1570                  */
1571                 isEmpty : function(node, elements) {
1572                         var self = this, i, attributes, type, walker, name;
1573
1574                         node = node.firstChild;
1575                         if (node) {
1576                                 walker = new tinymce.dom.TreeWalker(node);
1577                                 elements = elements || self.schema ? self.schema.getNonEmptyElements() : null;
1578
1579                                 do {
1580                                         type = node.nodeType;
1581
1582                                         if (type === 1) {
1583                                                 // Ignore bogus elements
1584                                                 if (node.getAttribute('data-mce-bogus'))
1585                                                         continue;
1586
1587                                                 // Keep empty elements like <img />
1588                                                 if (elements && elements[node.nodeName.toLowerCase()])
1589                                                         return false;
1590
1591                                                 // Keep elements with data-bookmark attributes or name attribute like <a name="1"></a>
1592                                                 attributes = self.getAttribs(node);
1593                                                 i = node.attributes.length;
1594                                                 while (i--) {
1595                                                         name = node.attributes[i].nodeName;
1596                                                         if (name === "name" || name === 'data-mce-bookmark')
1597                                                                 return false;
1598                                                 }
1599                                         }
1600
1601                                         // Keep non whitespace text nodes
1602                                         if ((type === 3 && !whiteSpaceRegExp.test(node.nodeValue)))
1603                                                 return false;
1604                                 } while (node = walker.next());
1605                         }
1606
1607                         return true;
1608                 },
1609
1610                 /**
1611                  * Destroys all internal references to the DOM to solve IE leak issues.
1612                  *
1613                  * @method destroy
1614                  */
1615                 destroy : function(s) {
1616                         var t = this;
1617
1618                         if (t.events)
1619                                 t.events.destroy();
1620
1621                         t.win = t.doc = t.root = t.events = null;
1622
1623                         // Manual destroy then remove unload handler
1624                         if (!s)
1625                                 tinymce.removeUnload(t.destroy);
1626                 },
1627
1628                 /**
1629                  * Created a new DOM Range object. This will use the native DOM Range API if it's
1630                  * available if it's not it will fallback to the custom TinyMCE implementation.
1631                  *
1632                  * @method createRng
1633                  * @return {DOMRange} DOM Range object.
1634                  * @example
1635                  * var rng = tinymce.DOM.createRng();
1636                  * alert(rng.startContainer + "," + rng.startOffset);
1637                  */
1638                 createRng : function() {
1639                         var d = this.doc;
1640
1641                         return d.createRange ? d.createRange() : new tinymce.dom.Range(this);
1642                 },
1643
1644                 /**
1645                  * Returns the index of the specified node within it's parent.
1646                  *
1647                  * @param {Node} node Node to look for.
1648                  * @param {boolean} normalized Optional true/false state if the index is what it would be after a normalization.
1649                  * @return {Number} Index of the specified node.
1650                  */
1651                 nodeIndex : function(node, normalized) {
1652                         var idx = 0, lastNodeType, lastNode, nodeType;
1653
1654                         if (node) {
1655                                 for (lastNodeType = node.nodeType, node = node.previousSibling, lastNode = node; node; node = node.previousSibling) {
1656                                         nodeType = node.nodeType;
1657
1658                                         // Normalize text nodes
1659                                         if (normalized && nodeType == 3) {
1660                                                 if (nodeType == lastNodeType || !node.nodeValue.length)
1661                                                         continue;
1662                                         }
1663                                         idx++;
1664                                         lastNodeType = nodeType;
1665                                 }
1666                         }
1667
1668                         return idx;
1669                 },
1670
1671                 /**
1672                  * Splits an element into two new elements and places the specified split
1673                  * element or element between the new ones. For example splitting the paragraph at the bold element in
1674                  * this example <p>abc<b>abc</b>123</p> would produce <p>abc</p><b>abc</b><p>123</p>. 
1675                  *
1676                  * @method split
1677                  * @param {Element} pe Parent element to split.
1678                  * @param {Element} e Element to split at.
1679                  * @param {Element} re Optional replacement element to replace the split element by.
1680                  * @return {Element} Returns the split element or the replacement element if that is specified.
1681                  */
1682                 split : function(pe, e, re) {
1683                         var t = this, r = t.createRng(), bef, aft, pa;
1684
1685                         // W3C valid browsers tend to leave empty nodes to the left/right side of the contents, this makes sense
1686                         // but we don't want that in our code since it serves no purpose for the end user
1687                         // For example if this is chopped:
1688                         //   <p>text 1<span><b>CHOP</b></span>text 2</p>
1689                         // would produce:
1690                         //   <p>text 1<span></span></p><b>CHOP</b><p><span></span>text 2</p>
1691                         // this function will then trim of empty edges and produce:
1692                         //   <p>text 1</p><b>CHOP</b><p>text 2</p>
1693                         function trim(node) {
1694                                 var i, children = node.childNodes, type = node.nodeType;
1695
1696                                 if (type == 1 && node.getAttribute('data-mce-type') == 'bookmark')
1697                                         return;
1698
1699                                 for (i = children.length - 1; i >= 0; i--)
1700                                         trim(children[i]);
1701
1702                                 if (type != 9) {
1703                                         // Keep non whitespace text nodes
1704                                         if (type == 3 && node.nodeValue.length > 0) {
1705                                                 // If parent element isn't a block or there isn't any useful contents for example "<p>   </p>"
1706                                                 if (!t.isBlock(node.parentNode) || tinymce.trim(node.nodeValue).length > 0)
1707                                                         return;
1708                                         } else if (type == 1) {
1709                                                 // If the only child is a bookmark then move it up
1710                                                 children = node.childNodes;
1711                                                 if (children.length == 1 && children[0] && children[0].nodeType == 1 && children[0].getAttribute('data-mce-type') == 'bookmark')
1712                                                         node.parentNode.insertBefore(children[0], node);
1713
1714                                                 // Keep non empty elements or img, hr etc
1715                                                 if (children.length || /^(br|hr|input|img)$/i.test(node.nodeName))
1716                                                         return;
1717                                         }
1718
1719                                         t.remove(node);
1720                                 }
1721
1722                                 return node;
1723                         };
1724
1725                         if (pe && e) {
1726                                 // Get before chunk
1727                                 r.setStart(pe.parentNode, t.nodeIndex(pe));
1728                                 r.setEnd(e.parentNode, t.nodeIndex(e));
1729                                 bef = r.extractContents();
1730
1731                                 // Get after chunk
1732                                 r = t.createRng();
1733                                 r.setStart(e.parentNode, t.nodeIndex(e) + 1);
1734                                 r.setEnd(pe.parentNode, t.nodeIndex(pe) + 1);
1735                                 aft = r.extractContents();
1736
1737                                 // Insert before chunk
1738                                 pa = pe.parentNode;
1739                                 pa.insertBefore(trim(bef), pe);
1740
1741                                 // Insert middle chunk
1742                                 if (re)
1743                                         pa.replaceChild(re, e);
1744                                 else
1745                                         pa.insertBefore(e, pe);
1746
1747                                 // Insert after chunk
1748                                 pa.insertBefore(trim(aft), pe);
1749                                 t.remove(pe);
1750
1751                                 return re || e;
1752                         }
1753                 },
1754
1755                 /**
1756                  * Adds an event handler to the specified object.
1757                  *
1758                  * @method bind
1759                  * @param {Element/Document/Window/Array/String} o Object or element id string to add event handler to or an array of elements/ids/documents.
1760                  * @param {String} n Name of event handler to add for example: click.
1761                  * @param {function} f Function to execute when the event occurs.
1762                  * @param {Object} s Optional scope to execute the function in.
1763                  * @return {function} Function callback handler the same as the one passed in.
1764                  */
1765                 bind : function(target, name, func, scope) {
1766                         var t = this;
1767
1768                         if (!t.events)
1769                                 t.events = new tinymce.dom.EventUtils();
1770
1771                         return t.events.add(target, name, func, scope || this);
1772                 },
1773
1774                 /**
1775                  * Removes the specified event handler by name and function from a element or collection of elements.
1776                  *
1777                  * @method unbind
1778                  * @param {String/Element/Array} o Element ID string or HTML element or an array of elements or ids to remove handler from.
1779                  * @param {String} n Event handler name like for example: "click"
1780                  * @param {function} f Function to remove.
1781                  * @return {bool/Array} Bool state if true if the handler was removed or an array with states if multiple elements where passed in.
1782                  */
1783                 unbind : function(target, name, func) {
1784                         var t = this;
1785
1786                         if (!t.events)
1787                                 t.events = new tinymce.dom.EventUtils();
1788
1789                         return t.events.remove(target, name, func);
1790                 },
1791
1792                 // #ifdef debug
1793
1794                 dumpRng : function(r) {
1795                         return 'startContainer: ' + r.startContainer.nodeName + ', startOffset: ' + r.startOffset + ', endContainer: ' + r.endContainer.nodeName + ', endOffset: ' + r.endOffset;
1796                 },
1797
1798                 // #endif
1799
1800                 _findSib : function(node, selector, name) {
1801                         var t = this, f = selector;
1802
1803                         if (node) {
1804                                 // If expression make a function of it using is
1805                                 if (is(f, 'string')) {
1806                                         f = function(node) {
1807                                                 return t.is(node, selector);
1808                                         };
1809                                 }
1810
1811                                 // Loop all siblings
1812                                 for (node = node[name]; node; node = node[name]) {
1813                                         if (f(node))
1814                                                 return node;
1815                                 }
1816                         }
1817
1818                         return null;
1819                 },
1820
1821                 _isRes : function(c) {
1822                         // Is live resizble element
1823                         return /^(top|left|bottom|right|width|height)/i.test(c) || /;\s*(top|left|bottom|right|width|height)/i.test(c);
1824                 }
1825
1826                 /*
1827                 walk : function(n, f, s) {
1828                         var d = this.doc, w;
1829
1830                         if (d.createTreeWalker) {
1831                                 w = d.createTreeWalker(n, NodeFilter.SHOW_TEXT, null, false);
1832
1833                                 while ((n = w.nextNode()) != null)
1834                                         f.call(s || this, n);
1835                         } else
1836                                 tinymce.walk(n, f, 'childNodes', s);
1837                 }
1838                 */
1839
1840                 /*
1841                 toRGB : function(s) {
1842                         var c = /^\s*?#([0-9A-F]{2})([0-9A-F]{1,2})([0-9A-F]{2})?\s*?$/.exec(s);
1843
1844                         if (c) {
1845                                 // #FFF -> #FFFFFF
1846                                 if (!is(c[3]))
1847                                         c[3] = c[2] = c[1];
1848
1849                                 return "rgb(" + parseInt(c[1], 16) + "," + parseInt(c[2], 16) + "," + parseInt(c[3], 16) + ")";
1850                         }
1851
1852                         return s;
1853                 }
1854                 */
1855         });
1856
1857         /**
1858          * Instance of DOMUtils for the current document.
1859          *
1860          * @property DOM
1861          * @member tinymce
1862          * @type tinymce.dom.DOMUtils
1863          * @example
1864          * // Example of how to add a class to some element by id
1865          * tinymce.DOM.addClass('someid', 'someclass');
1866          */
1867         tinymce.DOM = new tinymce.dom.DOMUtils(document, {process_html : 0});
1868 })(tinymce);