1/** 2 * Copyright © Magento, Inc. All rights reserved. 3 * See COPYING.txt for license details. 4 */ 5define([ 6 'jquery', 7 'ko', 8 'underscore', 9 './observable_source', 10 './renderer', 11 '../../logger/console-logger' 12], function ($, ko, _, Source, renderer, consoleLogger) { 13 'use strict'; 14 15 var RemoteTemplateEngine, 16 NativeTemplateEngine = ko.nativeTemplateEngine, 17 sources = {}; 18 19 /** 20 * Remote template engine class. Is used to be able to load remote templates via knockout template binding. 21 */ 22 RemoteTemplateEngine = function () { 23 // Instance reference for closure. 24 var engine = this, 25 // Decorate the builtin Knockout "template" binding to track synchronous template renders. 26 origUpdate = ko.bindingHandlers.template.update; 27 28 /** 29 * Counter to track the number of currently running render tasks (both synchronous and asynchronous). 30 * @type {Number} 31 * @private 32 */ 33 this._rendersOutstanding = 0; 34 35 /** 36 * Use a jQuery object as an event bus (but any event emitter with on/off/emit methods could work) 37 * @type {jQuery} 38 * @private 39 */ 40 this._events = $(this); 41 42 /** 43 * Rendered templates 44 * @type {Object} 45 * @private 46 */ 47 this._templatesRendered = {}; 48 49 /*eslint-disable no-unused-vars*/ 50 /** 51 * Decorate update method 52 * 53 * @param {HTMLElement} element 54 * @param {Function} valueAccessor 55 * @param {Object} allBindings 56 * @param {Object} viewModel 57 * @param {ko.bindingContext} bindingContext 58 * @returns {*} 59 */ 60 ko.bindingHandlers.template.update = function (element, valueAccessor, allBindings, viewModel, bindingContext) { 61 /*eslint-enable no-unused-vars*/ 62 var options = ko.utils.peekObservable(valueAccessor()), 63 templateName, 64 isSync, 65 updated; 66 67 if (typeof options === 'object') { 68 if (options.templateEngine && options.templateEngine !== engine) { 69 return origUpdate.apply(this, arguments); 70 } 71 72 if (!options.name) { 73 consoleLogger.error('Could not find template name', options); 74 } 75 templateName = options.name; 76 } else if (typeof options === 'string') { 77 templateName = options; 78 } else { 79 consoleLogger.error('Could not build a template binding', options); 80 } 81 engine._trackRender(templateName); 82 isSync = engine._hasTemplateLoaded(templateName); 83 updated = origUpdate.apply(this, arguments); 84 85 if (isSync) { 86 engine._releaseRender(templateName, 'sync'); 87 } 88 89 return updated; 90 }; 91 }; 92 93 /** 94 * Creates unique template identifier based on template name and it's extenders (optional) 95 * @param {String} templateName 96 * @return {String} - unique template identifier 97 */ 98 function createTemplateIdentifier(templateName) { 99 return templateName; 100 } 101 102 RemoteTemplateEngine.prototype = new NativeTemplateEngine; 103 RemoteTemplateEngine.prototype.constructor = RemoteTemplateEngine; 104 105 /** 106 * When an asynchronous render task begins, increment the internal counter for tracking when renders are complete. 107 * @private 108 */ 109 RemoteTemplateEngine.prototype._trackRender = function (templateName) { 110 var rendersForTemplate = this._templatesRendered[templateName] !== undefined ? 111 this._templatesRendered[templateName] : 0; 112 113 this._rendersOutstanding++; 114 this._templatesRendered[templateName] = rendersForTemplate + 1; 115 this._resolveRenderWaits(); 116 }; 117 118 /** 119 * When an asynchronous render task ends, decrement the internal counter for tracking when renders are complete. 120 * @private 121 */ 122 RemoteTemplateEngine.prototype._releaseRender = function (templateName) { 123 var rendersForTemplate = this._templatesRendered[templateName]; 124 125 this._rendersOutstanding--; 126 this._templatesRendered[templateName] = rendersForTemplate - 1; 127 this._resolveRenderWaits(); 128 }; 129 130 /** 131 * Check to see if renders are complete and trigger events for listeners. 132 * @private 133 */ 134 RemoteTemplateEngine.prototype._resolveRenderWaits = function () { 135 if (this._rendersOutstanding === 0) { 136 this._events.triggerHandler('finishrender'); 137 } 138 }; 139 140 /** 141 * Get a promise for the end of the current run of renders, both sync and async. 142 * @return {jQueryPromise} - promise that resolves when render completes 143 */ 144 RemoteTemplateEngine.prototype.waitForFinishRender = function () { 145 var defer = $.Deferred(); 146 147 this._events.one('finishrender', defer.resolve); 148 149 return defer.promise(); 150 }; 151 152 /** 153 * Returns true if this template has already been asynchronously loaded and will be synchronously rendered. 154 * @param {String} templateName 155 * @returns {Boolean} 156 * @private 157 */ 158 RemoteTemplateEngine.prototype._hasTemplateLoaded = function (templateName) { 159 // Sources object will have cached template once makeTemplateSource has run 160 return sources.hasOwnProperty(templateName); 161 }; 162 163 /** 164 * Overrided method of native knockout template engine. 165 * Caches template after it's unique name and renders in once. 166 * If template name is not typeof string, delegates work to knockout.templateSources.anonymousTemplate. 167 * @param {*} template 168 * @param {HTMLElement} templateDocument - document 169 * @param {Object} options - options, passed to template binding 170 * @param {ko.bindingContext} bindingContext 171 * @returns {TemplateSource} Object with methods 'nodes' and 'data'. 172 */ 173 RemoteTemplateEngine.prototype.makeTemplateSource = function (template, templateDocument, options, bindingContext) { 174 var engine = this, 175 source, 176 templateId; 177 178 if (typeof template === 'string') { 179 templateId = createTemplateIdentifier(template); 180 source = sources[templateId]; 181 182 if (!source) { 183 source = new Source(template); 184 source.requestedBy = bindingContext.$data.name; 185 sources[templateId] = source; 186 187 consoleLogger.info('templateStartLoading', { 188 template: templateId, 189 component: bindingContext.$data.name 190 }); 191 192 renderer.render(template).then(function (rendered) { 193 consoleLogger.info('templateLoadedFromServer', { 194 template: templateId, 195 component: bindingContext.$data.name 196 }); 197 source.nodes(rendered); 198 engine._releaseRender(templateId, 'async'); 199 }).fail(function () { 200 consoleLogger.error('templateLoadingFail', { 201 template: templateId, 202 component: bindingContext.$data.name 203 }); 204 }); 205 } 206 207 if (source.requestedBy !== bindingContext.$data.name) {
208 consoleLogger.info('templateLoadedFromCache', { 209 template: templateId, 210 component: bindingContext.$data.name 211 }); 212 } 213 214 return source; 215 } else if (template.nodeType === 1 || template.nodeType === 8) { 216 source = new ko.templateSources.anonymousTemplate(template); 217 218 return source; 219 } 220 221 throw new Error('Unknown template type: ' + template); 222 }; 223 224 /** 225 * Overrided method of native knockout template engine. 226 * Should return array of html elements. 227 * @param {TemplateSource} templateSource - object with methods 'nodes' and 'data'. 228 * @return {Array} - array of html elements 229 */ 230 RemoteTemplateEngine.prototype.renderTemplateSource = function (templateSource) { 231 var nodes = templateSource.nodes(); 232 233 return ko.utils.cloneNodes(nodes); 234 }; 235 236 /** 237 * Overrided method of native knockout template engine. 238 * Created in order to invoke makeTemplateSource method with custom set of params. 239 * @param {*} template - template identifier 240 * @param {ko.bindingContext} bindingContext 241 * @param {Object} options - options, passed to template binding 242 * @param {HTMLElement} templateDocument - document 243 * @return {Array} - array of html elements 244 */ 245 RemoteTemplateEngine.prototype.renderTemplate = function (template, bindingContext, options, templateDocument) { 246 var templateSource = this.makeTemplateSource(template, templateDocument, options, bindingContext); 247 248 return this.renderTemplateSource(templateSource); 249 }; 250 251 return new RemoteTemplateEngine; 252});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.