tools.js 38 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md or http://ckeditor.com/license
  4. */
  5. /**
  6. * @fileOverview Defines the {@link CKEDITOR.tools} object that contains
  7. * utility functions.
  8. */
  9. ( function() {
  10. var functions = [],
  11. cssVendorPrefix =
  12. CKEDITOR.env.gecko ? '-moz-' :
  13. CKEDITOR.env.webkit ? '-webkit-' :
  14. CKEDITOR.env.ie ? '-ms-' :
  15. '',
  16. ampRegex = /&/g,
  17. gtRegex = />/g,
  18. ltRegex = /</g,
  19. quoteRegex = /"/g,
  20. allEscRegex = /&(lt|gt|amp|quot|nbsp|shy|#\d{1,5});/g,
  21. namedEntities = {
  22. lt: '<',
  23. gt: '>',
  24. amp: '&',
  25. quot: '"',
  26. nbsp: '\u00a0',
  27. shy: '\u00ad'
  28. },
  29. allEscDecode = function( match, code ) {
  30. if ( code[ 0 ] == '#' ) {
  31. return String.fromCharCode( parseInt( code.slice( 1 ), 10 ) );
  32. } else {
  33. return namedEntities[ code ];
  34. }
  35. };
  36. CKEDITOR.on( 'reset', function() {
  37. functions = [];
  38. } );
  39. /**
  40. * Utility functions.
  41. *
  42. * @class
  43. * @singleton
  44. */
  45. CKEDITOR.tools = {
  46. /**
  47. * Compares the elements of two arrays.
  48. *
  49. * var a = [ 1, 'a', 3 ];
  50. * var b = [ 1, 3, 'a' ];
  51. * var c = [ 1, 'a', 3 ];
  52. * var d = [ 1, 'a', 3, 4 ];
  53. *
  54. * alert( CKEDITOR.tools.arrayCompare( a, b ) ); // false
  55. * alert( CKEDITOR.tools.arrayCompare( a, c ) ); // true
  56. * alert( CKEDITOR.tools.arrayCompare( a, d ) ); // false
  57. *
  58. * @param {Array} arrayA An array to be compared.
  59. * @param {Array} arrayB The other array to be compared.
  60. * @returns {Boolean} `true` if the arrays have the same length and
  61. * their elements match.
  62. */
  63. arrayCompare: function( arrayA, arrayB ) {
  64. if ( !arrayA && !arrayB )
  65. return true;
  66. if ( !arrayA || !arrayB || arrayA.length != arrayB.length )
  67. return false;
  68. for ( var i = 0; i < arrayA.length; i++ ) {
  69. if ( arrayA[ i ] != arrayB[ i ] )
  70. return false;
  71. }
  72. return true;
  73. },
  74. /**
  75. * Finds the index of the first element in an array for which the `compareFunction` returns `true`.
  76. *
  77. * CKEDITOR.tools.getIndex( [ 1, 2, 4, 3, 5 ], function( el ) {
  78. * return el >= 3;
  79. * } ); // 2
  80. *
  81. * @since 4.5
  82. * @param {Array} array Array to search in.
  83. * @param {Function} compareFunction Compare function.
  84. * @returns {Number} The index of the first matching element or `-1` if none matches.
  85. */
  86. getIndex: function( arr, compareFunction ) {
  87. for ( var i = 0; i < arr.length; ++i ) {
  88. if ( compareFunction( arr[ i ] ) )
  89. return i;
  90. }
  91. return -1;
  92. },
  93. /**
  94. * Creates a deep copy of an object.
  95. *
  96. * **Note**: Recursive references are not supported.
  97. *
  98. * var obj = {
  99. * name: 'John',
  100. * cars: {
  101. * Mercedes: { color: 'blue' },
  102. * Porsche: { color: 'red' }
  103. * }
  104. * };
  105. * var clone = CKEDITOR.tools.clone( obj );
  106. * clone.name = 'Paul';
  107. * clone.cars.Porsche.color = 'silver';
  108. *
  109. * alert( obj.name ); // 'John'
  110. * alert( clone.name ); // 'Paul'
  111. * alert( obj.cars.Porsche.color ); // 'red'
  112. * alert( clone.cars.Porsche.color ); // 'silver'
  113. *
  114. * @param {Object} object The object to be cloned.
  115. * @returns {Object} The object clone.
  116. */
  117. clone: function( obj ) {
  118. var clone;
  119. // Array.
  120. if ( obj && ( obj instanceof Array ) ) {
  121. clone = [];
  122. for ( var i = 0; i < obj.length; i++ )
  123. clone[ i ] = CKEDITOR.tools.clone( obj[ i ] );
  124. return clone;
  125. }
  126. // "Static" types.
  127. if ( obj === null || ( typeof obj != 'object' ) || ( obj instanceof String ) || ( obj instanceof Number ) || ( obj instanceof Boolean ) || ( obj instanceof Date ) || ( obj instanceof RegExp ) )
  128. return obj;
  129. // DOM objects and window.
  130. if ( obj.nodeType || obj.window === obj )
  131. return obj;
  132. // Objects.
  133. clone = new obj.constructor();
  134. for ( var propertyName in obj ) {
  135. var property = obj[ propertyName ];
  136. clone[ propertyName ] = CKEDITOR.tools.clone( property );
  137. }
  138. return clone;
  139. },
  140. /**
  141. * Turns the first letter of a string to upper-case.
  142. *
  143. * @param {String} str
  144. * @param {Boolean} [keepCase] Keep the case of 2nd to last letter.
  145. * @returns {String}
  146. */
  147. capitalize: function( str, keepCase ) {
  148. return str.charAt( 0 ).toUpperCase() + ( keepCase ? str.slice( 1 ) : str.slice( 1 ).toLowerCase() );
  149. },
  150. /**
  151. * Copies the properties from one object to another. By default, properties
  152. * already present in the target object **are not** overwritten.
  153. *
  154. * // Create the sample object.
  155. * var myObject = {
  156. * prop1: true
  157. * };
  158. *
  159. * // Extend the above object with two properties.
  160. * CKEDITOR.tools.extend( myObject, {
  161. * prop2: true,
  162. * prop3: true
  163. * } );
  164. *
  165. * // Alert 'prop1', 'prop2' and 'prop3'.
  166. * for ( var p in myObject )
  167. * alert( p );
  168. *
  169. * @param {Object} target The object to be extended.
  170. * @param {Object...} source The object(s) from properties will be
  171. * copied. Any number of objects can be passed to this function.
  172. * @param {Boolean} [overwrite] If `true` is specified, it indicates that
  173. * properties already present in the target object could be
  174. * overwritten by subsequent objects.
  175. * @param {Object} [properties] Only properties within the specified names
  176. * list will be received from the source object.
  177. * @returns {Object} The extended object (target).
  178. */
  179. extend: function( target ) {
  180. var argsLength = arguments.length,
  181. overwrite, propertiesList;
  182. if ( typeof ( overwrite = arguments[ argsLength - 1 ] ) == 'boolean' )
  183. argsLength--;
  184. else if ( typeof ( overwrite = arguments[ argsLength - 2 ] ) == 'boolean' ) {
  185. propertiesList = arguments[ argsLength - 1 ];
  186. argsLength -= 2;
  187. }
  188. for ( var i = 1; i < argsLength; i++ ) {
  189. var source = arguments[ i ];
  190. for ( var propertyName in source ) {
  191. // Only copy existed fields if in overwrite mode.
  192. if ( overwrite === true || target[ propertyName ] == null ) {
  193. // Only copy specified fields if list is provided.
  194. if ( !propertiesList || ( propertyName in propertiesList ) )
  195. target[ propertyName ] = source[ propertyName ];
  196. }
  197. }
  198. }
  199. return target;
  200. },
  201. /**
  202. * Creates an object which is an instance of a class whose prototype is a
  203. * predefined object. All properties defined in the source object are
  204. * automatically inherited by the resulting object, including future
  205. * changes to it.
  206. *
  207. * @param {Object} source The source object to be used as the prototype for
  208. * the final object.
  209. * @returns {Object} The resulting copy.
  210. */
  211. prototypedCopy: function( source ) {
  212. var copy = function() {};
  213. copy.prototype = source;
  214. return new copy();
  215. },
  216. /**
  217. * Makes fast (shallow) copy of an object.
  218. * This method is faster than {@link #clone} which does
  219. * a deep copy of an object (including arrays).
  220. *
  221. * @since 4.1
  222. * @param {Object} source The object to be copied.
  223. * @returns {Object} Copy of `source`.
  224. */
  225. copy: function( source ) {
  226. var obj = {},
  227. name;
  228. for ( name in source )
  229. obj[ name ] = source[ name ];
  230. return obj;
  231. },
  232. /**
  233. * Checks if an object is an Array.
  234. *
  235. * alert( CKEDITOR.tools.isArray( [] ) ); // true
  236. * alert( CKEDITOR.tools.isArray( 'Test' ) ); // false
  237. *
  238. * @param {Object} object The object to be checked.
  239. * @returns {Boolean} `true` if the object is an Array, otherwise `false`.
  240. */
  241. isArray: function( object ) {
  242. return Object.prototype.toString.call( object ) == '[object Array]';
  243. },
  244. /**
  245. * Whether the object contains no properties of its own.
  246. *
  247. * @param object
  248. * @returns {Boolean}
  249. */
  250. isEmpty: function( object ) {
  251. for ( var i in object ) {
  252. if ( object.hasOwnProperty( i ) )
  253. return false;
  254. }
  255. return true;
  256. },
  257. /**
  258. * Generates an object or a string containing vendor-specific and vendor-free CSS properties.
  259. *
  260. * CKEDITOR.tools.cssVendorPrefix( 'border-radius', '0', true );
  261. * // On Firefox: '-moz-border-radius:0;border-radius:0'
  262. * // On Chrome: '-webkit-border-radius:0;border-radius:0'
  263. *
  264. * @param {String} property The CSS property name.
  265. * @param {String} value The CSS value.
  266. * @param {Boolean} [asString=false] If `true`, then the returned value will be a CSS string.
  267. * @returns {Object/String} The object containing CSS properties or its stringified version.
  268. */
  269. cssVendorPrefix: function( property, value, asString ) {
  270. if ( asString )
  271. return cssVendorPrefix + property + ':' + value + ';' + property + ':' + value;
  272. var ret = {};
  273. ret[ property ] = value;
  274. ret[ cssVendorPrefix + property ] = value;
  275. return ret;
  276. },
  277. /**
  278. * Transforms a CSS property name to its relative DOM style name.
  279. *
  280. * alert( CKEDITOR.tools.cssStyleToDomStyle( 'background-color' ) ); // 'backgroundColor'
  281. * alert( CKEDITOR.tools.cssStyleToDomStyle( 'float' ) ); // 'cssFloat'
  282. *
  283. * @method
  284. * @param {String} cssName The CSS property name.
  285. * @returns {String} The transformed name.
  286. */
  287. cssStyleToDomStyle: ( function() {
  288. var test = document.createElement( 'div' ).style;
  289. var cssFloat = ( typeof test.cssFloat != 'undefined' ) ? 'cssFloat' : ( typeof test.styleFloat != 'undefined' ) ? 'styleFloat' : 'float';
  290. return function( cssName ) {
  291. if ( cssName == 'float' )
  292. return cssFloat;
  293. else {
  294. return cssName.replace( /-./g, function( match ) {
  295. return match.substr( 1 ).toUpperCase();
  296. } );
  297. }
  298. };
  299. } )(),
  300. /**
  301. * Builds a HTML snippet from a set of `<style>/<link>`.
  302. *
  303. * @param {String/Array} css Each of which are URLs (absolute) of a CSS file or
  304. * a trunk of style text.
  305. * @returns {String}
  306. */
  307. buildStyleHtml: function( css ) {
  308. css = [].concat( css );
  309. var item,
  310. retval = [];
  311. for ( var i = 0; i < css.length; i++ ) {
  312. if ( ( item = css[ i ] ) ) {
  313. // Is CSS style text ?
  314. if ( /@import|[{}]/.test( item ) )
  315. retval.push( '<style>' + item + '</style>' );
  316. else
  317. retval.push( '<link type="text/css" rel=stylesheet href="' + item + '">' );
  318. }
  319. }
  320. return retval.join( '' );
  321. },
  322. /**
  323. * Replaces special HTML characters in a string with their relative HTML
  324. * entity values.
  325. *
  326. * alert( CKEDITOR.tools.htmlEncode( 'A > B & C < D' ) ); // 'A &gt; B &amp; C &lt; D'
  327. *
  328. * @param {String} text The string to be encoded.
  329. * @returns {String} The encoded string.
  330. */
  331. htmlEncode: function( text ) {
  332. // Backwards compatibility - accept also non-string values (casting is done below).
  333. // Since 4.4.8 we return empty string for null and undefined because these values make no sense.
  334. if ( text === undefined || text === null ) {
  335. return '';
  336. }
  337. return String( text ).replace( ampRegex, '&amp;' ).replace( gtRegex, '&gt;' ).replace( ltRegex, '&lt;' );
  338. },
  339. /**
  340. * Decodes HTML entities that browsers tend to encode when used in text nodes.
  341. *
  342. * alert( CKEDITOR.tools.htmlDecode( '&lt;a &amp; b &gt;' ) ); // '<a & b >'
  343. *
  344. * Read more about chosen entities in the [research](http://dev.ckeditor.com/ticket/13105#comment:8).
  345. *
  346. * @param {String} The string to be decoded.
  347. * @returns {String} The decoded string.
  348. */
  349. htmlDecode: function( text ) {
  350. // See:
  351. // * http://dev.ckeditor.com/ticket/13105#comment:8 and comment:9,
  352. // * http://jsperf.com/wth-is-going-on-with-jsperf JSPerf has some serious problems, but you can observe
  353. // that combined regexp tends to be quicker (except on V8). It will also not be prone to fail on '&amp;lt;'
  354. // (see http://dev.ckeditor.com/ticket/13105#DXWTF:CKEDITOR.tools.htmlEnDecodeAttr).
  355. return text.replace( allEscRegex, allEscDecode );
  356. },
  357. /**
  358. * Replaces special HTML characters in HTMLElement attribute with their relative HTML entity values.
  359. *
  360. * alert( CKEDITOR.tools.htmlEncodeAttr( '<a " b >' ) ); // '&lt;a &quot; b &gt;'
  361. *
  362. * @param {String} The attribute value to be encoded.
  363. * @returns {String} The encoded value.
  364. */
  365. htmlEncodeAttr: function( text ) {
  366. return CKEDITOR.tools.htmlEncode( text ).replace( quoteRegex, '&quot;' );
  367. },
  368. /**
  369. * Decodes HTML entities that browsers tend to encode when used in attributes.
  370. *
  371. * alert( CKEDITOR.tools.htmlDecodeAttr( '&lt;a &quot; b&gt;' ) ); // '<a " b>'
  372. *
  373. * Since CKEditor 4.5 this method simply executes {@link #htmlDecode} which covers
  374. * all necessary entities.
  375. *
  376. * @param {String} text The text to be decoded.
  377. * @returns {String} The decoded text.
  378. */
  379. htmlDecodeAttr: function( text ) {
  380. return CKEDITOR.tools.htmlDecode( text );
  381. },
  382. /**
  383. * Transforms text to valid HTML: creates paragraphs, replaces tabs with non-breaking spaces etc.
  384. *
  385. * @since 4.5
  386. * @param {String} text Text to transform.
  387. * @param {Number} enterMode Editor {@link CKEDITOR.config#enterMode Enter mode}.
  388. * @returns {String} HTML generated from the text.
  389. */
  390. transformPlainTextToHtml: function( text, enterMode ) {
  391. var isEnterBrMode = enterMode == CKEDITOR.ENTER_BR,
  392. // CRLF -> LF
  393. html = this.htmlEncode( text.replace( /\r\n/g, '\n' ) );
  394. // Tab -> &nbsp x 4;
  395. html = html.replace( /\t/g, '&nbsp;&nbsp; &nbsp;' );
  396. var paragraphTag = enterMode == CKEDITOR.ENTER_P ? 'p' : 'div';
  397. // Two line-breaks create one paragraphing block.
  398. if ( !isEnterBrMode ) {
  399. var duoLF = /\n{2}/g;
  400. if ( duoLF.test( html ) ) {
  401. var openTag = '<' + paragraphTag + '>', endTag = '</' + paragraphTag + '>';
  402. html = openTag + html.replace( duoLF, function() {
  403. return endTag + openTag;
  404. } ) + endTag;
  405. }
  406. }
  407. // One <br> per line-break.
  408. html = html.replace( /\n/g, '<br>' );
  409. // Compensate padding <br> at the end of block, avoid loosing them during insertion.
  410. if ( !isEnterBrMode ) {
  411. html = html.replace( new RegExp( '<br>(?=</' + paragraphTag + '>)' ), function( match ) {
  412. return CKEDITOR.tools.repeat( match, 2 );
  413. } );
  414. }
  415. // Preserve spaces at the ends, so they won't be lost after insertion (merged with adjacent ones).
  416. html = html.replace( /^ | $/g, '&nbsp;' );
  417. // Finally, preserve whitespaces that are to be lost.
  418. html = html.replace( /(>|\s) /g, function( match, before ) {
  419. return before + '&nbsp;';
  420. } ).replace( / (?=<)/g, '&nbsp;' );
  421. return html;
  422. },
  423. /**
  424. * Gets a unique number for this CKEDITOR execution session. It returns
  425. * consecutive numbers starting from 1.
  426. *
  427. * alert( CKEDITOR.tools.getNextNumber() ); // (e.g.) 1
  428. * alert( CKEDITOR.tools.getNextNumber() ); // 2
  429. *
  430. * @method
  431. * @returns {Number} A unique number.
  432. */
  433. getNextNumber: ( function() {
  434. var last = 0;
  435. return function() {
  436. return ++last;
  437. };
  438. } )(),
  439. /**
  440. * Gets a unique ID for CKEditor interface elements. It returns a
  441. * string with the "cke_" prefix and a consecutive number.
  442. *
  443. * alert( CKEDITOR.tools.getNextId() ); // (e.g.) 'cke_1'
  444. * alert( CKEDITOR.tools.getNextId() ); // 'cke_2'
  445. *
  446. * @returns {String} A unique ID.
  447. */
  448. getNextId: function() {
  449. return 'cke_' + this.getNextNumber();
  450. },
  451. /**
  452. * Gets a universally unique ID. It returns a random string
  453. * compliant with ISO/IEC 11578:1996, without dashes, with the "e" prefix to
  454. * make sure that the ID does not start with a number.
  455. *
  456. * @returns {String} A global unique ID.
  457. */
  458. getUniqueId: function() {
  459. var uuid = 'e'; // Make sure that id does not start with number.
  460. for ( var i = 0; i < 8; i++ ) {
  461. uuid += Math.floor( ( 1 + Math.random() ) * 0x10000 ).toString( 16 ).substring( 1 );
  462. }
  463. return uuid;
  464. },
  465. /**
  466. * Creates a function override.
  467. *
  468. * var obj = {
  469. * myFunction: function( name ) {
  470. * alert( 'Name: ' + name );
  471. * }
  472. * };
  473. *
  474. * obj.myFunction = CKEDITOR.tools.override( obj.myFunction, function( myFunctionOriginal ) {
  475. * return function( name ) {
  476. * alert( 'Overriden name: ' + name );
  477. * myFunctionOriginal.call( this, name );
  478. * };
  479. * } );
  480. *
  481. * @param {Function} originalFunction The function to be overridden.
  482. * @param {Function} functionBuilder A function that returns the new
  483. * function. The original function reference will be passed to this function.
  484. * @returns {Function} The new function.
  485. */
  486. override: function( originalFunction, functionBuilder ) {
  487. var newFn = functionBuilder( originalFunction );
  488. newFn.prototype = originalFunction.prototype;
  489. return newFn;
  490. },
  491. /**
  492. * Executes a function after a specified delay.
  493. *
  494. * CKEDITOR.tools.setTimeout( function() {
  495. * alert( 'Executed after 2 seconds' );
  496. * }, 2000 );
  497. *
  498. * @param {Function} func The function to be executed.
  499. * @param {Number} [milliseconds=0] The amount of time (in milliseconds) to wait
  500. * to fire the function execution.
  501. * @param {Object} [scope=window] The object to store the function execution scope
  502. * (the `this` object).
  503. * @param {Object/Array} [args] A single object, or an array of objects, to
  504. * pass as argument to the function.
  505. * @param {Object} [ownerWindow=window] The window that will be used to set the
  506. * timeout.
  507. * @returns {Object} A value that can be used to cancel the function execution.
  508. */
  509. setTimeout: function( func, milliseconds, scope, args, ownerWindow ) {
  510. if ( !ownerWindow )
  511. ownerWindow = window;
  512. if ( !scope )
  513. scope = ownerWindow;
  514. return ownerWindow.setTimeout( function() {
  515. if ( args )
  516. func.apply( scope, [].concat( args ) );
  517. else
  518. func.apply( scope );
  519. }, milliseconds || 0 );
  520. },
  521. /**
  522. * Removes spaces from the start and the end of a string. The following
  523. * characters are removed: space, tab, line break, line feed.
  524. *
  525. * alert( CKEDITOR.tools.trim( ' example ' ); // 'example'
  526. *
  527. * @method
  528. * @param {String} str The text from which the spaces will be removed.
  529. * @returns {String} The modified string without the boundary spaces.
  530. */
  531. trim: ( function() {
  532. // We are not using \s because we don't want "non-breaking spaces" to be caught.
  533. var trimRegex = /(?:^[ \t\n\r]+)|(?:[ \t\n\r]+$)/g;
  534. return function( str ) {
  535. return str.replace( trimRegex, '' );
  536. };
  537. } )(),
  538. /**
  539. * Removes spaces from the start (left) of a string. The following
  540. * characters are removed: space, tab, line break, line feed.
  541. *
  542. * alert( CKEDITOR.tools.ltrim( ' example ' ); // 'example '
  543. *
  544. * @method
  545. * @param {String} str The text from which the spaces will be removed.
  546. * @returns {String} The modified string excluding the removed spaces.
  547. */
  548. ltrim: ( function() {
  549. // We are not using \s because we don't want "non-breaking spaces" to be caught.
  550. var trimRegex = /^[ \t\n\r]+/g;
  551. return function( str ) {
  552. return str.replace( trimRegex, '' );
  553. };
  554. } )(),
  555. /**
  556. * Removes spaces from the end (right) of a string. The following
  557. * characters are removed: space, tab, line break, line feed.
  558. *
  559. * alert( CKEDITOR.tools.ltrim( ' example ' ); // ' example'
  560. *
  561. * @method
  562. * @param {String} str The text from which spaces will be removed.
  563. * @returns {String} The modified string excluding the removed spaces.
  564. */
  565. rtrim: ( function() {
  566. // We are not using \s because we don't want "non-breaking spaces" to be caught.
  567. var trimRegex = /[ \t\n\r]+$/g;
  568. return function( str ) {
  569. return str.replace( trimRegex, '' );
  570. };
  571. } )(),
  572. /**
  573. * Returns the index of an element in an array.
  574. *
  575. * var letters = [ 'a', 'b', 0, 'c', false ];
  576. * alert( CKEDITOR.tools.indexOf( letters, '0' ) ); // -1 because 0 !== '0'
  577. * alert( CKEDITOR.tools.indexOf( letters, false ) ); // 4 because 0 !== false
  578. *
  579. * @param {Array} array The array to be searched.
  580. * @param {Object/Function} value The element to be found. This can be an
  581. * evaluation function which receives a single parameter call for
  582. * each entry in the array, returning `true` if the entry matches.
  583. * @returns {Number} The (zero-based) index of the first entry that matches
  584. * the entry, or `-1` if not found.
  585. */
  586. indexOf: function( array, value ) {
  587. if ( typeof value == 'function' ) {
  588. for ( var i = 0, len = array.length; i < len; i++ ) {
  589. if ( value( array[ i ] ) )
  590. return i;
  591. }
  592. } else if ( array.indexOf )
  593. return array.indexOf( value );
  594. else {
  595. for ( i = 0, len = array.length; i < len; i++ ) {
  596. if ( array[ i ] === value )
  597. return i;
  598. }
  599. }
  600. return -1;
  601. },
  602. /**
  603. * Returns the index of an element in an array.
  604. *
  605. * var obj = { prop: true };
  606. * var letters = [ 'a', 'b', 0, obj, false ];
  607. *
  608. * alert( CKEDITOR.tools.indexOf( letters, '0' ) ); // null
  609. * alert( CKEDITOR.tools.indexOf( letters, function( value ) {
  610. * // Return true when passed value has property 'prop'.
  611. * return value && 'prop' in value;
  612. * } ) ); // obj
  613. *
  614. * @param {Array} array The array to be searched.
  615. * @param {Object/Function} value The element to be found. Can be an
  616. * evaluation function which receives a single parameter call for
  617. * each entry in the array, returning `true` if the entry matches.
  618. * @returns Object The value that was found in an array.
  619. */
  620. search: function( array, value ) {
  621. var index = CKEDITOR.tools.indexOf( array, value );
  622. return index >= 0 ? array[ index ] : null;
  623. },
  624. /**
  625. * Creates a function that will always execute in the context of a
  626. * specified object.
  627. *
  628. * var obj = { text: 'My Object' };
  629. *
  630. * function alertText() {
  631. * alert( this.text );
  632. * }
  633. *
  634. * var newFunc = CKEDITOR.tools.bind( alertText, obj );
  635. * newFunc(); // Alerts 'My Object'.
  636. *
  637. * @param {Function} func The function to be executed.
  638. * @param {Object} obj The object to which the execution context will be bound.
  639. * @returns {Function} The function that can be used to execute the
  640. * `func` function in the context of `obj`.
  641. */
  642. bind: function( func, obj ) {
  643. return function() {
  644. return func.apply( obj, arguments );
  645. };
  646. },
  647. /**
  648. * Class creation based on prototype inheritance which supports of the
  649. * following features:
  650. *
  651. * * Static fields
  652. * * Private fields
  653. * * Public (prototype) fields
  654. * * Chainable base class constructor
  655. *
  656. * @param {Object} definition The class definition object.
  657. * @returns {Function} A class-like JavaScript function.
  658. */
  659. createClass: function( definition ) {
  660. var $ = definition.$,
  661. baseClass = definition.base,
  662. privates = definition.privates || definition._,
  663. proto = definition.proto,
  664. statics = definition.statics;
  665. // Create the constructor, if not present in the definition.
  666. !$ && ( $ = function() {
  667. baseClass && this.base.apply( this, arguments );
  668. } );
  669. if ( privates ) {
  670. var originalConstructor = $;
  671. $ = function() {
  672. // Create (and get) the private namespace.
  673. var _ = this._ || ( this._ = {} );
  674. // Make some magic so "this" will refer to the main
  675. // instance when coding private functions.
  676. for ( var privateName in privates ) {
  677. var priv = privates[ privateName ];
  678. _[ privateName ] = ( typeof priv == 'function' ) ? CKEDITOR.tools.bind( priv, this ) : priv;
  679. }
  680. originalConstructor.apply( this, arguments );
  681. };
  682. }
  683. if ( baseClass ) {
  684. $.prototype = this.prototypedCopy( baseClass.prototype );
  685. $.prototype.constructor = $;
  686. // Super references.
  687. $.base = baseClass;
  688. $.baseProto = baseClass.prototype;
  689. // Super constructor.
  690. $.prototype.base = function() {
  691. this.base = baseClass.prototype.base;
  692. baseClass.apply( this, arguments );
  693. this.base = arguments.callee;
  694. };
  695. }
  696. if ( proto )
  697. this.extend( $.prototype, proto, true );
  698. if ( statics )
  699. this.extend( $, statics, true );
  700. return $;
  701. },
  702. /**
  703. * Creates a function reference that can be called later using
  704. * {@link #callFunction}. This approach is especially useful to
  705. * make DOM attribute function calls to JavaScript-defined functions.
  706. *
  707. * var ref = CKEDITOR.tools.addFunction( function() {
  708. * alert( 'Hello!');
  709. * } );
  710. * CKEDITOR.tools.callFunction( ref ); // 'Hello!'
  711. *
  712. * @param {Function} fn The function to be executed on call.
  713. * @param {Object} [scope] The object to have the context on `fn` execution.
  714. * @returns {Number} A unique reference to be used in conjuction with
  715. * {@link #callFunction}.
  716. */
  717. addFunction: function( fn, scope ) {
  718. return functions.push( function() {
  719. return fn.apply( scope || this, arguments );
  720. } ) - 1;
  721. },
  722. /**
  723. * Removes the function reference created with {@link #addFunction}.
  724. *
  725. * @param {Number} ref The function reference created with
  726. * {@link #addFunction}.
  727. */
  728. removeFunction: function( ref ) {
  729. functions[ ref ] = null;
  730. },
  731. /**
  732. * Executes a function based on the reference created with {@link #addFunction}.
  733. *
  734. * var ref = CKEDITOR.tools.addFunction( function() {
  735. * alert( 'Hello!');
  736. * } );
  737. * CKEDITOR.tools.callFunction( ref ); // 'Hello!'
  738. *
  739. * @param {Number} ref The function reference created with {@link #addFunction}.
  740. * @param {Mixed} params Any number of parameters to be passed to the executed function.
  741. * @returns {Mixed} The return value of the function.
  742. */
  743. callFunction: function( ref ) {
  744. var fn = functions[ ref ];
  745. return fn && fn.apply( window, Array.prototype.slice.call( arguments, 1 ) );
  746. },
  747. /**
  748. * Appends the `px` length unit to the size value if it is missing.
  749. *
  750. * var cssLength = CKEDITOR.tools.cssLength;
  751. * cssLength( 42 ); // '42px'
  752. * cssLength( '42' ); // '42px'
  753. * cssLength( '42px' ); // '42px'
  754. * cssLength( '42%' ); // '42%'
  755. * cssLength( 'bold' ); // 'bold'
  756. * cssLength( false ); // ''
  757. * cssLength( NaN ); // ''
  758. *
  759. * @method
  760. * @param {Number/String/Boolean} length
  761. */
  762. cssLength: ( function() {
  763. var pixelRegex = /^-?\d+\.?\d*px$/,
  764. lengthTrimmed;
  765. return function( length ) {
  766. lengthTrimmed = CKEDITOR.tools.trim( length + '' ) + 'px';
  767. if ( pixelRegex.test( lengthTrimmed ) )
  768. return lengthTrimmed;
  769. else
  770. return length || '';
  771. };
  772. } )(),
  773. /**
  774. * Converts the specified CSS length value to the calculated pixel length inside this page.
  775. *
  776. * **Note:** Percentage-based value is left intact.
  777. *
  778. * @method
  779. * @param {String} cssLength CSS length value.
  780. */
  781. convertToPx: ( function() {
  782. var calculator;
  783. return function( cssLength ) {
  784. if ( !calculator ) {
  785. calculator = CKEDITOR.dom.element.createFromHtml( '<div style="position:absolute;left:-9999px;' +
  786. 'top:-9999px;margin:0px;padding:0px;border:0px;"' +
  787. '></div>', CKEDITOR.document );
  788. CKEDITOR.document.getBody().append( calculator );
  789. }
  790. if ( !( /%$/ ).test( cssLength ) ) {
  791. calculator.setStyle( 'width', cssLength );
  792. return calculator.$.clientWidth;
  793. }
  794. return cssLength;
  795. };
  796. } )(),
  797. /**
  798. * String specified by `str` repeats `times` times.
  799. *
  800. * @param {String} str
  801. * @param {Number} times
  802. * @returns {String}
  803. */
  804. repeat: function( str, times ) {
  805. return new Array( times + 1 ).join( str );
  806. },
  807. /**
  808. * Returns the first successfully executed return value of a function that
  809. * does not throw any exception.
  810. *
  811. * @param {Function...} fn
  812. * @returns {Mixed}
  813. */
  814. tryThese: function() {
  815. var returnValue;
  816. for ( var i = 0, length = arguments.length; i < length; i++ ) {
  817. var lambda = arguments[ i ];
  818. try {
  819. returnValue = lambda();
  820. break;
  821. } catch ( e ) {}
  822. }
  823. return returnValue;
  824. },
  825. /**
  826. * Generates a combined key from a series of params.
  827. *
  828. * var key = CKEDITOR.tools.genKey( 'key1', 'key2', 'key3' );
  829. * alert( key ); // 'key1-key2-key3'.
  830. *
  831. * @param {String} subKey One or more strings used as subkeys.
  832. * @returns {String}
  833. */
  834. genKey: function() {
  835. return Array.prototype.slice.call( arguments ).join( '-' );
  836. },
  837. /**
  838. * Creates a "deferred" function which will not run immediately,
  839. * but rather runs as soon as the interpreter’s call stack is empty.
  840. * Behaves much like `window.setTimeout` with a delay.
  841. *
  842. * **Note:** The return value of the original function will be lost.
  843. *
  844. * @param {Function} fn The callee function.
  845. * @returns {Function} The new deferred function.
  846. */
  847. defer: function( fn ) {
  848. return function() {
  849. var args = arguments,
  850. self = this;
  851. window.setTimeout( function() {
  852. fn.apply( self, args );
  853. }, 0 );
  854. };
  855. },
  856. /**
  857. * Normalizes CSS data in order to avoid differences in the style attribute.
  858. *
  859. * @param {String} styleText The style data to be normalized.
  860. * @param {Boolean} [nativeNormalize=false] Parse the data using the browser.
  861. * @returns {String} The normalized value.
  862. */
  863. normalizeCssText: function( styleText, nativeNormalize ) {
  864. var props = [],
  865. name,
  866. parsedProps = CKEDITOR.tools.parseCssText( styleText, true, nativeNormalize );
  867. for ( name in parsedProps )
  868. props.push( name + ':' + parsedProps[ name ] );
  869. props.sort();
  870. return props.length ? ( props.join( ';' ) + ';' ) : '';
  871. },
  872. /**
  873. * Finds and converts `rgb(x,x,x)` color definition to hexadecimal notation.
  874. *
  875. * @param {String} styleText The style data (or just a string containing RGB colors) to be converted.
  876. * @returns {String} The style data with RGB colors converted to hexadecimal equivalents.
  877. */
  878. convertRgbToHex: function( styleText ) {
  879. return styleText.replace( /(?:rgb\(\s*(\d+)\s*,\s*(\d+)\s*,\s*(\d+)\s*\))/gi, function( match, red, green, blue ) {
  880. var color = [ red, green, blue ];
  881. // Add padding zeros if the hex value is less than 0x10.
  882. for ( var i = 0; i < 3; i++ )
  883. color[ i ] = ( '0' + parseInt( color[ i ], 10 ).toString( 16 ) ).slice( -2 );
  884. return '#' + color.join( '' );
  885. } );
  886. },
  887. /**
  888. * Turns inline style text properties into one hash.
  889. *
  890. * @param {String} styleText The style data to be parsed.
  891. * @param {Boolean} [normalize=false] Normalize properties and values
  892. * (e.g. trim spaces, convert to lower case).
  893. * @param {Boolean} [nativeNormalize=false] Parse the data using the browser.
  894. * @returns {Object} The object containing parsed properties.
  895. */
  896. parseCssText: function( styleText, normalize, nativeNormalize ) {
  897. var retval = {};
  898. if ( nativeNormalize ) {
  899. // Injects the style in a temporary span object, so the browser parses it,
  900. // retrieving its final format.
  901. var temp = new CKEDITOR.dom.element( 'span' );
  902. temp.setAttribute( 'style', styleText );
  903. styleText = CKEDITOR.tools.convertRgbToHex( temp.getAttribute( 'style' ) || '' );
  904. }
  905. // IE will leave a single semicolon when failed to parse the style text. (#3891)
  906. if ( !styleText || styleText == ';' )
  907. return retval;
  908. styleText.replace( /&quot;/g, '"' ).replace( /\s*([^:;\s]+)\s*:\s*([^;]+)\s*(?=;|$)/g, function( match, name, value ) {
  909. if ( normalize ) {
  910. name = name.toLowerCase();
  911. // Normalize font-family property, ignore quotes and being case insensitive. (#7322)
  912. // http://www.w3.org/TR/css3-fonts/#font-family-the-font-family-property
  913. if ( name == 'font-family' )
  914. value = value.toLowerCase().replace( /["']/g, '' ).replace( /\s*,\s*/g, ',' );
  915. value = CKEDITOR.tools.trim( value );
  916. }
  917. retval[ name ] = value;
  918. } );
  919. return retval;
  920. },
  921. /**
  922. * Serializes the `style name => value` hash to a style text.
  923. *
  924. * var styleObj = CKEDITOR.tools.parseCssText( 'color: red; border: none' );
  925. * console.log( styleObj.color ); // -> 'red'
  926. * CKEDITOR.tools.writeCssText( styleObj ); // -> 'color:red; border:none'
  927. * CKEDITOR.tools.writeCssText( styleObj, true ); // -> 'border:none; color:red'
  928. *
  929. * @since 4.1
  930. * @param {Object} styles The object contaning style properties.
  931. * @param {Boolean} [sort] Whether to sort CSS properties.
  932. * @returns {String} The serialized style text.
  933. */
  934. writeCssText: function( styles, sort ) {
  935. var name,
  936. stylesArr = [];
  937. for ( name in styles )
  938. stylesArr.push( name + ':' + styles[ name ] );
  939. if ( sort )
  940. stylesArr.sort();
  941. return stylesArr.join( '; ' );
  942. },
  943. /**
  944. * Compares two objects.
  945. *
  946. * **Note:** This method performs shallow, non-strict comparison.
  947. *
  948. * @since 4.1
  949. * @param {Object} left
  950. * @param {Object} right
  951. * @param {Boolean} [onlyLeft] Check only the properties that are present in the `left` object.
  952. * @returns {Boolean} Whether objects are identical.
  953. */
  954. objectCompare: function( left, right, onlyLeft ) {
  955. var name;
  956. if ( !left && !right )
  957. return true;
  958. if ( !left || !right )
  959. return false;
  960. for ( name in left ) {
  961. if ( left[ name ] != right[ name ] )
  962. return false;
  963. }
  964. if ( !onlyLeft ) {
  965. for ( name in right ) {
  966. if ( left[ name ] != right[ name ] )
  967. return false;
  968. }
  969. }
  970. return true;
  971. },
  972. /**
  973. * Returns an array of passed object's keys.
  974. *
  975. * console.log( CKEDITOR.tools.objectKeys( { foo: 1, bar: false } );
  976. * // -> [ 'foo', 'bar' ]
  977. *
  978. * @since 4.1
  979. * @param {Object} obj
  980. * @returns {Array} Object's keys.
  981. */
  982. objectKeys: function( obj ) {
  983. var keys = [];
  984. for ( var i in obj )
  985. keys.push( i );
  986. return keys;
  987. },
  988. /**
  989. * Converts an array to an object by rewriting array items
  990. * to object properties.
  991. *
  992. * var arr = [ 'foo', 'bar', 'foo' ];
  993. * console.log( CKEDITOR.tools.convertArrayToObject( arr ) );
  994. * // -> { foo: true, bar: true }
  995. * console.log( CKEDITOR.tools.convertArrayToObject( arr, 1 ) );
  996. * // -> { foo: 1, bar: 1 }
  997. *
  998. * @since 4.1
  999. * @param {Array} arr The array to be converted to an object.
  1000. * @param [fillWith=true] Set each property of an object to `fillWith` value.
  1001. */
  1002. convertArrayToObject: function( arr, fillWith ) {
  1003. var obj = {};
  1004. if ( arguments.length == 1 )
  1005. fillWith = true;
  1006. for ( var i = 0, l = arr.length; i < l; ++i )
  1007. obj[ arr[ i ] ] = fillWith;
  1008. return obj;
  1009. },
  1010. /**
  1011. * Tries to fix the `document.domain` of the current document to match the
  1012. * parent window domain, avoiding "Same Origin" policy issues.
  1013. * This is an Internet Explorer only requirement.
  1014. *
  1015. * @since 4.1.2
  1016. * @returns {Boolean} `true` if the current domain is already good or if
  1017. * it has been fixed successfully.
  1018. */
  1019. fixDomain: function() {
  1020. var domain;
  1021. while ( 1 ) {
  1022. try {
  1023. // Try to access the parent document. It throws
  1024. // "access denied" if restricted by the "Same Origin" policy.
  1025. domain = window.parent.document.domain;
  1026. break;
  1027. } catch ( e ) {
  1028. // Calculate the value to set to document.domain.
  1029. domain = domain ?
  1030. // If it is not the first pass, strip one part of the
  1031. // name. E.g. "test.example.com" => "example.com"
  1032. domain.replace( /.+?(?:\.|$)/, '' ) :
  1033. // In the first pass, we'll handle the
  1034. // "document.domain = document.domain" case.
  1035. document.domain;
  1036. // Stop here if there is no more domain parts available.
  1037. if ( !domain )
  1038. break;
  1039. document.domain = domain;
  1040. }
  1041. }
  1042. return !!domain;
  1043. },
  1044. /**
  1045. * Buffers `input` events (or any `input` calls)
  1046. * and triggers `output` not more often than once per `minInterval`.
  1047. *
  1048. * var buffer = CKEDITOR.tools.eventsBuffer( 200, function() {
  1049. * console.log( 'foo!' );
  1050. * } );
  1051. *
  1052. * buffer.input();
  1053. * // 'foo!' logged immediately.
  1054. * buffer.input();
  1055. * // Nothing logged.
  1056. * buffer.input();
  1057. * // Nothing logged.
  1058. * // ... after 200ms a single 'foo!' will be logged.
  1059. *
  1060. * Can be easily used with events:
  1061. *
  1062. * var buffer = CKEDITOR.tools.eventsBuffer( 200, function() {
  1063. * console.log( 'foo!' );
  1064. * } );
  1065. *
  1066. * editor.on( 'key', buffer.input );
  1067. * // Note: There is no need to bind buffer as a context.
  1068. *
  1069. * @since 4.2.1
  1070. * @param {Number} minInterval Minimum interval between `output` calls in milliseconds.
  1071. * @param {Function} output Function that will be executed as `output`.
  1072. * @param {Object} [scopeObj] The object used to scope the listener call (the `this` object).
  1073. * @returns {Object}
  1074. * @returns {Function} return.input Buffer's input method.
  1075. * @returns {Function} return.reset Resets buffered events &mdash; `output` will not be executed
  1076. * until next `input` is triggered.
  1077. */
  1078. eventsBuffer: function( minInterval, output, scopeObj ) {
  1079. var scheduled,
  1080. lastOutput = 0;
  1081. function triggerOutput() {
  1082. lastOutput = ( new Date() ).getTime();
  1083. scheduled = false;
  1084. if ( scopeObj ) {
  1085. output.call( scopeObj );
  1086. } else {
  1087. output();
  1088. }
  1089. }
  1090. return {
  1091. input: function() {
  1092. if ( scheduled )
  1093. return;
  1094. var diff = ( new Date() ).getTime() - lastOutput;
  1095. // If less than minInterval passed after last check,
  1096. // schedule next for minInterval after previous one.
  1097. if ( diff < minInterval )
  1098. scheduled = setTimeout( triggerOutput, minInterval - diff );
  1099. else
  1100. triggerOutput();
  1101. },
  1102. reset: function() {
  1103. if ( scheduled )
  1104. clearTimeout( scheduled );
  1105. scheduled = lastOutput = 0;
  1106. }
  1107. };
  1108. },
  1109. /**
  1110. * Enables HTML5 elements for older browsers (IE8) in the passed document.
  1111. *
  1112. * In IE8 this method can also be executed on a document fragment.
  1113. *
  1114. * **Note:** This method has to be used in the `<head>` section of the document.
  1115. *
  1116. * @since 4.3
  1117. * @param {Object} doc Native `Document` or `DocumentFragment` in which the elements will be enabled.
  1118. * @param {Boolean} [withAppend] Whether to append created elements to the `doc`.
  1119. */
  1120. enableHtml5Elements: function( doc, withAppend ) {
  1121. var els = 'abbr,article,aside,audio,bdi,canvas,data,datalist,details,figcaption,figure,footer,header,hgroup,main,mark,meter,nav,output,progress,section,summary,time,video'.split( ',' ),
  1122. i = els.length,
  1123. el;
  1124. while ( i-- ) {
  1125. el = doc.createElement( els[ i ] );
  1126. if ( withAppend )
  1127. doc.appendChild( el );
  1128. }
  1129. },
  1130. /**
  1131. * Checks if any of the `arr` items match the provided regular expression.
  1132. *
  1133. * @param {Array} arr The array whose items will be checked.
  1134. * @param {RegExp} regexp The regular expression.
  1135. * @returns {Boolean} Returns `true` for the first occurrence of the search pattern.
  1136. * @since 4.4
  1137. */
  1138. checkIfAnyArrayItemMatches: function( arr, regexp ) {
  1139. for ( var i = 0, l = arr.length; i < l; ++i ) {
  1140. if ( arr[ i ].match( regexp ) )
  1141. return true;
  1142. }
  1143. return false;
  1144. },
  1145. /**
  1146. * Checks if any of the `obj` properties match the provided regular expression.
  1147. *
  1148. * @param obj The object whose properties will be checked.
  1149. * @param {RegExp} regexp The regular expression.
  1150. * @returns {Boolean} Returns `true` for the first occurrence of the search pattern.
  1151. * @since 4.4
  1152. */
  1153. checkIfAnyObjectPropertyMatches: function( obj, regexp ) {
  1154. for ( var i in obj ) {
  1155. if ( i.match( regexp ) )
  1156. return true;
  1157. }
  1158. return false;
  1159. },
  1160. /**
  1161. * The data URI of a transparent image. May be used e.g. in HTML as an image source or in CSS in `url()`.
  1162. *
  1163. * @since 4.4
  1164. * @readonly
  1165. */
  1166. transparentImageData: 'data:image/gif;base64,R0lGODlhAQABAPABAP///wAAACH5BAEKAAAALAAAAAABAAEAAAICRAEAOw=='
  1167. };
  1168. } )();
  1169. // PACKAGER_RENAME( CKEDITOR.tools )