Source: render/render.js

  1. import Script from '../component/script'
  2. /**
  3. * Representa o Render do framework.
  4. * @module Render
  5. * @param rootElement {HTMLElement} rootElement - Elemento HTML raíz da SPA.
  6. * @property {Object} targetComponent - Objeto literal contendo as propriedades de um componente a ser renderizado.
  7. * @property {HTMLElement} targetComponent.originalComponent - Elemento HTML do componente a ser renderizado.
  8. * @property {HTMLElement} targetComponent.formattedComponent - Elemento HTML formatado do componente renderizado.
  9. * @property {HTMLElement} targetComponent.rawTemplate - Elemento HTML TEMPLATE do componente.
  10. * @property {HTMLElement} targetComponent.rawScript - Elemento HTML SCRIPT do componente.
  11. * @property {HTMLElement} targetComponent.rawStyle - Elemento HTML STYLE do componente.
  12. * @property {Object} targetComponent.virtualTemplate - Objeto com a instância virtual do TEMPLATE.
  13. * @property {Object} targetComponent.virtualScript - Objeto com a instância virtual do SCRIPT.
  14. */
  15. export default class Render {
  16. constructor({ rootElement }) {
  17. this.rootElement = rootElement
  18. this.targetComponent = {
  19. originalComponent: undefined,
  20. formattedComponent: undefined,
  21. rawTemplate: undefined,
  22. rawScript: undefined,
  23. rawStyle: undefined,
  24. virtualScript: undefined,
  25. }
  26. }
  27. /**
  28. * Renderiza um Componente no rootElement caso receba o pârametro targetComponent ou atualiza os HTMLElements do rootElement baseados nos parâmetros virtuais.
  29. * @function render
  30. * @param targetComponent {Object | Undefined} targetComponent - Objeto literal contido nas rotas do Router.
  31. * @returns {undefined}
  32. */
  33. render(targetComponent) {
  34. this.clearElement()
  35. if (targetComponent) this.setTargetComponent(targetComponent)
  36. this.updateRawTemplateBasedOnVirtualScript()
  37. this.setTargetComponentFormattedDocument()
  38. this.appendFormattedComponentOnRootElement()
  39. }
  40. /**
  41. * Concatena a propriedade formattedComponent do Objeto literal targetComponent no rootElement.
  42. * @function appendFormattedComponentOnRootElement
  43. * @returns {undefined}
  44. */
  45. appendFormattedComponentOnRootElement() {
  46. const formatted = this.targetComponent.formattedComponent.cloneNode(true)
  47. this.rootElement.append(formatted)
  48. }
  49. /**
  50. * Atualiza os HTMLElements da propriedade rawTemplate do Objeto literal targetComponent baseados nos dados contidos na variável virtualScript no Objeto literal targetComponent.
  51. * @function updateRawTemplateBasedOnVirtualScript
  52. * @returns {undefined}
  53. */
  54. updateRawTemplateBasedOnVirtualScript() {
  55. const formatted = this.targetComponent.rawTemplate.content
  56. const reactiveElements = formatted.querySelectorAll('[leo-data]')
  57. reactiveElements.forEach((element) => {
  58. const propertyName = element.attributes['leo-data'].value
  59. const instance = this.targetComponent.virtualScript.instance
  60. const virtualValue = instance[propertyName]
  61. element.innerHTML = virtualValue
  62. })
  63. }
  64. /**
  65. * Exclui o conteúdo HTML de dentro do elemento desejado.
  66. * @function clearElement
  67. * @param targetElement {HTMLElement} targetElement - Elemento HTML a ser limpo.
  68. * @returns {undefined}
  69. */
  70. clearElement(targetElement = this.rootElement) {
  71. if (
  72. targetElement &&
  73. HTMLCollection.prototype.isPrototypeOf(targetElement.children)
  74. )
  75. targetElement.innerHTML = ''
  76. }
  77. /**
  78. * Chama as funções responsáveis por povoar as propriedades [originalComponent, rawTemplate, rawScript, rawStyle, virtualTemplate, virtualScript] do Objeto literal targetComponent.
  79. * @function setTargetComponent
  80. * @param targetComponent {HTMLElement} targetComponent - Elemento HTML do componente a ser instanciado.
  81. * @returns {undefined}
  82. */
  83. setTargetComponent(targetComponent) {
  84. this.setTargetComponentRawProperties(targetComponent)
  85. this.setTargetComponentVirtualProperties()
  86. }
  87. /**
  88. * Povoa as propriedades Object do Objeto literal targetComponent.
  89. * @function setTargetComponentVirtualProperties
  90. * @returns {undefined}
  91. */
  92. setTargetComponentVirtualProperties() {
  93. if (this.targetComponent.rawScript) {
  94. this.setVirtualScript()
  95. this.setVirtualScriptListener()
  96. }
  97. }
  98. /**
  99. * Povoa a propriedade virtualScript do Objeto literal targetComponent com uma instância da classe Script a partir da propriedade HTMLElement rawScript do Objeto literal targetComponent.
  100. * @function setVirtualScript
  101. * @returns {undefined}
  102. */
  103. setVirtualScript() {
  104. try {
  105. const rawJSCode = this.targetComponent.rawScript.innerHTML
  106. const ComponentScript = eval(rawJSCode)
  107. this.targetComponent.virtualScript = new Script(ComponentScript)
  108. } catch (err) {
  109. console.error(`@setVirtualScript: ${err}`)
  110. }
  111. }
  112. /**
  113. * Cria os listeners de eventos da propriedade virtualScript do Objeto literal targetComponent.
  114. * @function setVirtualScriptListener
  115. * @returns {undefined}
  116. */
  117. setVirtualScriptListener() {
  118. const self = this
  119. this.targetComponent.virtualScript.emitter.addListener('change', () =>
  120. self.onVirtualScriptChange()
  121. )
  122. }
  123. /**
  124. * Cria o handler do evento "change" da propriedade virtualScript do Objeto literal targetComponent.
  125. * @function onVirtualScriptChange
  126. * @returns {undefined}
  127. */
  128. onVirtualScriptChange() {
  129. this.render()
  130. }
  131. /**
  132. * Povoa as propriedades HTMLElement do Objeto literal targetComponent.
  133. * @function setTargetComponentRawProperties
  134. * @param targetComponent {HTMLElement} targetComponent - Elemento HTML do componente a ser instanciado.
  135. * @returns {undefined}
  136. */
  137. setTargetComponentRawProperties(targetComponent) {
  138. try {
  139. const documentFragment = this.getDocumentFragment(targetComponent)
  140. if (!documentFragment) throw `DocumentFragment not loaded!`
  141. if (!this.validateDocumentFragmentTagNames(documentFragment))
  142. throw `Invalid DocumentFragment TagNames!`
  143. let template = documentFragment.querySelector('template')
  144. let script = documentFragment.querySelector('script')
  145. let style = documentFragment.querySelector('style')
  146. this.targetComponent.originalComponent = documentFragment
  147. this.targetComponent.rawTemplate = template ? template : undefined
  148. this.targetComponent.rawScript = script ? script : undefined
  149. this.targetComponent.rawStyle = style ? style : undefined
  150. } catch (err) {
  151. console.error(`@setTargetComponentRawProperties: ${err}`)
  152. }
  153. }
  154. /**
  155. * Povoa a propriedade formattedComponent do Objeto literal targetComponent com a versão final dos HTMLElements a serem renderizados.
  156. * @function setTargetComponentFormattedDocument
  157. * @returns {undefined}
  158. */
  159. setTargetComponentFormattedDocument() {
  160. try {
  161. const rawTemplate = this.targetComponent.rawTemplate
  162. const rawStyle = this.targetComponent.rawStyle
  163. let newFormattedComponent = document.createDocumentFragment()
  164. if (rawTemplate)
  165. newFormattedComponent.append(rawTemplate.content.cloneNode(true))
  166. if (rawStyle) newFormattedComponent.append(rawStyle.cloneNode(true))
  167. this.targetComponent.formattedComponent = newFormattedComponent
  168. } catch (err) {
  169. console.error(`@setTargetComponentFormattedDocument: ${err}`)
  170. }
  171. }
  172. /**
  173. * Retorna o DocumentFragment do componente.
  174. * @function getDocumentFragment
  175. * @param targetComponent {Object} targetComponent - Objeto do componente.
  176. * @returns {DocumentFragment | undefined}
  177. */
  178. getDocumentFragment(targetComponent) {
  179. try {
  180. if (!targetComponent) throw `Component not found!`
  181. if (typeof targetComponent !== 'object') throw `Component is invalid!`
  182. const range = document.createRange()
  183. return range.createContextualFragment(targetComponent.component)
  184. } catch (err) {
  185. console.error(`@getDocumentFragment: ${err}`)
  186. }
  187. }
  188. /**
  189. * Valida as TagNames do DocumentFragment.
  190. * @function validateDocumentFragmentTagNames
  191. * @param contextualFragments {DocumentFragment} contextualFragments - Objeto do componente.
  192. * @returns {boolean}
  193. */
  194. validateDocumentFragmentTagNames(documentFragment) {
  195. try {
  196. if (!documentFragment) throw `DocumentFragment not found!`
  197. if (!documentFragment.children) throw `Object is not a DocumentFragment!`
  198. const children = documentFragment.children
  199. if (!children.length > 0) throw `DocumentFragment is empty!`
  200. Array.from(children).forEach((child) => {
  201. switch (child.tagName) {
  202. case 'TEMPLATE':
  203. case 'SCRIPT':
  204. case 'STYLE':
  205. break
  206. default:
  207. throw `Tag ${child.tagName} not supported!`
  208. }
  209. })
  210. return true
  211. } catch (err) {
  212. console.error(`@validateDocumentFragmentTagNames: ${err}`)
  213. return false
  214. }
  215. }
  216. /**
  217. * Envia a propriedade virtualScript do Objeto literal targetComponent o sinal para a sua finalização.
  218. * @function finishTargetComponent
  219. * @returns {undefined}
  220. */
  221. finishTargetComponent() {
  222. if (this.targetComponent.virtualScript)
  223. this.targetComponent.virtualScript.onFinish()
  224. }
  225. }