template.js 5.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242
  1. /**
  2. * @license Copyright (c) 2003-2015, CKSource - Frederico Knabben. All rights reserved.
  3. * For licensing, see LICENSE.md.
  4. */
  5. /* global document */
  6. 'use strict';
  7. /**
  8. * Basic Template class.
  9. *
  10. * @class Template
  11. */
  12. CKEDITOR.define( () => {
  13. class Template {
  14. /**
  15. * Creates an instance of the {@link Template} class.
  16. *
  17. * @param {TemplateDefinition} def The definition of the template.
  18. * @constructor
  19. */
  20. constructor( def ) {
  21. /**
  22. * Definition of this template.
  23. *
  24. * @property {TemplateDefinition}
  25. */
  26. this.def = def;
  27. }
  28. /**
  29. * Renders HTMLElement using {@link #def}.
  30. *
  31. * @returns {HTMLElement}
  32. */
  33. render() {
  34. return this._renderElement( this.def, true );
  35. }
  36. /**
  37. * Renders an element from definition.
  38. *
  39. * @protected
  40. * @param {TemplateDefinition} def Definition of an element.
  41. * @param {Boolean} intoFragment If set, children are rendered into DocumentFragment.
  42. * @returns {HTMLElement} A rendered element.
  43. */
  44. _renderElement( def, intoFragment ) {
  45. if ( !def ) {
  46. return null;
  47. }
  48. const el = document.createElement( def.tag );
  49. // Set the text first.
  50. this._renderElementText( def, el );
  51. // Set attributes.
  52. this._renderElementAttributes( def, el );
  53. // Invoke children recursively.
  54. if ( intoFragment ) {
  55. const docFragment = document.createDocumentFragment();
  56. this._renderElementChildren( def, docFragment );
  57. el.appendChild( docFragment );
  58. } else {
  59. this._renderElementChildren( def, el );
  60. }
  61. // Activate DOM binding for event listeners.
  62. this._activateElementListeners( def, el );
  63. return el;
  64. }
  65. /**
  66. * Renders element text content from definition.
  67. *
  68. * @protected
  69. * @param {TemplateDefinition} def Definition of an element.
  70. * @param {HTMLElement} el Element which is rendered.
  71. */
  72. _renderElementText( def, el ) {
  73. if ( def.text ) {
  74. if ( typeof def.text == 'function' ) {
  75. def.text( el, getTextUpdater() );
  76. } else {
  77. el.textContent = def.text;
  78. }
  79. }
  80. }
  81. /**
  82. * Renders element attributes from definition.
  83. *
  84. * @protected
  85. * @param {TemplateDefinition} def Definition of an element.
  86. * @param {HTMLElement} el Element which is rendered.
  87. */
  88. _renderElementAttributes( def, el ) {
  89. let attr, value;
  90. for ( attr in def.attrs ) {
  91. value = def.attrs[ attr ];
  92. // Attribute bound directly to the model.
  93. if ( typeof value == 'function' ) {
  94. value( el, getAttributeUpdater( attr ) );
  95. }
  96. // Explicit attribute definition (string).
  97. else {
  98. // Attribute can be an array, i.e. classes.
  99. if ( Array.isArray( value ) ) {
  100. value = value.join( ' ' );
  101. }
  102. el.setAttribute( attr, value );
  103. }
  104. }
  105. }
  106. /**
  107. * Recursively renders element children from definition by
  108. * calling {@link #_renderElement}.
  109. *
  110. * @protected
  111. * @param {TemplateDefinition} def Definition of an element.
  112. * @param {HTMLElement} el Element which is rendered.
  113. */
  114. _renderElementChildren( def, el ) {
  115. let child;
  116. if ( def.children ) {
  117. for ( child of def.children ) {
  118. el.appendChild( this._renderElement( child ) );
  119. }
  120. }
  121. }
  122. /**
  123. * Activates element `on` listeners passed in element definition.
  124. *
  125. * @protected
  126. * @param {TemplateDefinition} def Definition of an element.
  127. * @param {HTMLElement} el Element which is rendered.
  128. */
  129. _activateElementListeners( def, el ) {
  130. if ( def.on ) {
  131. let l, domEvtDef, name, selector;
  132. for ( l in def.on ) {
  133. domEvtDef = l.split( '@' );
  134. if ( domEvtDef.length == 2 ) {
  135. name = domEvtDef[ 0 ];
  136. selector = domEvtDef[ 1 ];
  137. } else {
  138. name = l;
  139. selector = null;
  140. }
  141. if ( Array.isArray( def.on[ l ] ) ) {
  142. def.on[ l ].map( i => i( el, name, selector ) );
  143. } else {
  144. def.on[ l ]( el, name, selector );
  145. }
  146. }
  147. }
  148. }
  149. }
  150. /**
  151. * Returns a function which, when called in the context of HTMLElement,
  152. * it replaces element children with a text node of given value.
  153. *
  154. * @protected
  155. * @param {Function}
  156. */
  157. function getTextUpdater() {
  158. return ( el, value ) => el.textContent = value;
  159. }
  160. /**
  161. * Returns a function which, when called in the context of HTMLElement,
  162. * it updates element's attribute with given value.
  163. *
  164. * @protected
  165. * @param {String} attr A name of the attribute to be updated.
  166. * @param {Function}
  167. */
  168. function getAttributeUpdater( attr ) {
  169. return ( el, value ) => el.setAttribute( attr, value );
  170. }
  171. return Template;
  172. } );
  173. /**
  174. * Definition of {@link Template}.
  175. *
  176. * {
  177. * tag: 'p',
  178. * children: [
  179. * {
  180. * tag: 'span',
  181. * attrs: { ... },
  182. * on: { ... }
  183. * },
  184. * {
  185. * ...
  186. * },
  187. * ...
  188. * ],
  189. * attrs: {
  190. * 'class': [ 'a', 'b' ],
  191. * id: 'c',
  192. * style: callback,
  193. * ...
  194. * },
  195. * on: {
  196. * event1: 'a'
  197. * event2: [ 'b', 'c', callback ],
  198. * 'event3@selector': 'd',
  199. * 'event4@selector': [ 'e', 'f', callback ],
  200. * ...
  201. * },
  202. * text: 'abc'
  203. * }
  204. *
  205. * @typedef TemplateDefinition
  206. * @type Object
  207. * @property {String} tag
  208. * @property {Array} [children]
  209. * @property {Object} [attrs]
  210. * @property {String} [text]
  211. * @property {Object} [on]
  212. */