template.js 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  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 );
  35. }
  36. /**
  37. * Renders an element from definition.
  38. *
  39. * @protected
  40. * @param {TemplateDefinition} def Definition of an element.
  41. * @returns {HTMLElement} A rendered element.
  42. */
  43. _renderElement( def ) {
  44. if ( !def ) {
  45. return null;
  46. }
  47. const el = document.createElement( def.tag );
  48. // Set the text first.
  49. this._renderElementText( def, el );
  50. // Set attributes.
  51. this._renderElementAttributes( def, el );
  52. // Invoke children recursively.
  53. this._renderElementChildren( def, el );
  54. // Activate DOM binding for event listeners.
  55. this._activateElementListeners( def, el );
  56. return el;
  57. }
  58. /**
  59. * Renders element text content from definition.
  60. *
  61. * @protected
  62. * @param {TemplateDefinition} def Definition of an element.
  63. * @param {HTMLElement} el Element which is rendered.
  64. */
  65. _renderElementText( def, el ) {
  66. if ( def.text ) {
  67. if ( typeof def.text == 'function' ) {
  68. def.text( el, getTextUpdater() );
  69. } else {
  70. el.textContent = def.text;
  71. }
  72. }
  73. }
  74. /**
  75. * Renders element attributes from definition.
  76. *
  77. * @protected
  78. * @param {TemplateDefinition} def Definition of an element.
  79. * @param {HTMLElement} el Element which is rendered.
  80. */
  81. _renderElementAttributes( def, el ) {
  82. let attr, value;
  83. for ( attr in def.attrs ) {
  84. value = def.attrs[ attr ];
  85. // Attribute bound directly to the model.
  86. if ( typeof value == 'function' ) {
  87. value( el, getAttributeUpdater( attr ) );
  88. }
  89. // Explicit attribute definition (string).
  90. else {
  91. // Attribute can be an array, i.e. classes.
  92. if ( Array.isArray( value ) ) {
  93. value = value.join( ' ' );
  94. }
  95. el.setAttribute( attr, value );
  96. }
  97. }
  98. }
  99. /**
  100. * Recursively renders element children from definition by
  101. * calling {@link #_renderElement}.
  102. *
  103. * @protected
  104. * @param {TemplateDefinition} def Definition of an element.
  105. * @param {HTMLElement} el Element which is rendered.
  106. */
  107. _renderElementChildren( def, el ) {
  108. let child;
  109. if ( def.children ) {
  110. for ( child of def.children ) {
  111. el.appendChild( this._renderElement( child ) );
  112. }
  113. }
  114. }
  115. /**
  116. * Activates element `on` listeners passed in element definition.
  117. *
  118. * @protected
  119. * @param {TemplateDefinition} def Definition of an element.
  120. * @param {HTMLElement} el Element which is rendered.
  121. */
  122. _activateElementListeners( def, el ) {
  123. if ( def.on ) {
  124. let l, domEvtDef, name, selector;
  125. for ( l in def.on ) {
  126. domEvtDef = l.split( '@' );
  127. if ( domEvtDef.length == 2 ) {
  128. name = domEvtDef[ 0 ];
  129. selector = domEvtDef[ 1 ];
  130. } else {
  131. name = l;
  132. selector = null;
  133. }
  134. if ( Array.isArray( def.on[ l ] ) ) {
  135. def.on[ l ].map( i => i( el, name, selector ) );
  136. } else {
  137. def.on[ l ]( el, name, selector );
  138. }
  139. }
  140. }
  141. }
  142. }
  143. /**
  144. * Returns a function which, when called in the context of HTMLElement,
  145. * it replaces element children with a text node of given value.
  146. *
  147. * @protected
  148. * @param {Function}
  149. */
  150. function getTextUpdater() {
  151. return ( el, value ) => el.textContent = value;
  152. }
  153. /**
  154. * Returns a function which, when called in the context of HTMLElement,
  155. * it updates element's attribute with given value.
  156. *
  157. * @protected
  158. * @param {String} attr A name of the attribute to be updated.
  159. * @param {Function}
  160. */
  161. function getAttributeUpdater( attr ) {
  162. return ( el, value ) => el.setAttribute( attr, value );
  163. }
  164. return Template;
  165. } );
  166. /**
  167. * Definition of {@link Template}.
  168. *
  169. * {
  170. * tag: 'p',
  171. * children: [
  172. * {
  173. * tag: 'span',
  174. * attrs: { ... },
  175. * on: { ... }
  176. * },
  177. * {
  178. * ...
  179. * },
  180. * ...
  181. * ],
  182. * attrs: {
  183. * 'class': [ 'a', 'b' ],
  184. * id: 'c',
  185. * style: callback,
  186. * ...
  187. * },
  188. * on: {
  189. * event1: 'a'
  190. * event2: [ 'b', 'c', callback ],
  191. * 'event3@selector': 'd',
  192. * 'event4@selector': [ 'e', 'f', callback ],
  193. * ...
  194. * },
  195. * text: 'abc'
  196. * }
  197. *
  198. * @typedef TemplateDefinition
  199. * @type Object
  200. * @property {String} tag
  201. * @property {Array} [children]
  202. * @property {Object} [attrs]
  203. * @property {String} [text]
  204. * @property {Object} [on]
  205. */