Skip to content

Commit a2497c9

Browse files
author
Igor Melnikov
committed
MAGETWO-81980: Add after render callback
- add after render callback to know when knockout finished rendering
1 parent 839cacf commit a2497c9

File tree

2 files changed

+154
-11
lines changed

2 files changed

+154
-11
lines changed

app/code/Magento/Ui/view/base/web/js/lib/knockout/template/engine.js

Lines changed: 133 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,13 @@
33
* See COPYING.txt for license details.
44
*/
55
define([
6+
'jquery',
67
'ko',
78
'underscore',
89
'./observable_source',
910
'./renderer',
1011
'../../logger/console-logger'
11-
], function (ko, _, Source, renderer, consoleLogger) {
12+
], function ($, ko, _, Source, renderer, consoleLogger) {
1213
'use strict';
1314

1415
var RemoteTemplateEngine,
@@ -18,7 +19,74 @@ define([
1819
/**
1920
* Remote template engine class. Is used to be able to load remote templates via knockout template binding.
2021
*/
21-
RemoteTemplateEngine = function () {};
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+
updated;
65+
66+
if (typeof options === 'object') {
67+
if (options.templateEngine && options.templateEngine !== engine) {
68+
return origUpdate.apply(this, arguments);
69+
}
70+
71+
if (!options.name) {
72+
consoleLogger.error('Could not find template name', options);
73+
}
74+
templateName = options.name;
75+
} else if (typeof options === 'string') {
76+
templateName = options;
77+
} else {
78+
consoleLogger.error('Could not build a template binding', options);
79+
}
80+
engine._trackRender(templateName);
81+
updated = origUpdate.apply(this, arguments);
82+
83+
if (engine._hasTemplateLoaded(templateName)) {
84+
engine._releaseRender(templateName, 'sync');
85+
}
86+
87+
return updated;
88+
};
89+
};
2290

2391
/**
2492
* Creates unique template identifier based on template name and it's extenders (optional)
@@ -32,6 +100,64 @@ define([
32100
RemoteTemplateEngine.prototype = new NativeTemplateEngine;
33101
RemoteTemplateEngine.prototype.constructor = RemoteTemplateEngine;
34102

103+
/**
104+
* When an asynchronous render task begins, increment the internal counter for tracking when renders are complete.
105+
* @private
106+
*/
107+
RemoteTemplateEngine.prototype._trackRender = function (templateName) {
108+
var rendersForTemplate = this._templatesRendered[templateName] !== undefined ?
109+
this._templatesRendered[templateName] : 0;
110+
111+
this._rendersOutstanding++;
112+
this._templatesRendered[templateName] = rendersForTemplate + 1;
113+
this._resolveRenderWaits();
114+
};
115+
116+
/**
117+
* When an asynchronous render task ends, decrement the internal counter for tracking when renders are complete.
118+
* @private
119+
*/
120+
RemoteTemplateEngine.prototype._releaseRender = function (templateName) {
121+
var rendersForTemplate = this._templatesRendered[templateName];
122+
123+
this._rendersOutstanding--;
124+
this._templatesRendered[templateName] = rendersForTemplate - 1;
125+
this._resolveRenderWaits();
126+
};
127+
128+
/**
129+
* Check to see if renders are complete and trigger events for listeners.
130+
* @private
131+
*/
132+
RemoteTemplateEngine.prototype._resolveRenderWaits = function () {
133+
if (this._rendersOutstanding === 0) {
134+
this._events.triggerHandler('finishrender');
135+
}
136+
};
137+
138+
/**
139+
* Get a promise for the end of the current run of renders, both sync and async.
140+
* @return {jQueryPromise} - promise that resolves when render completes
141+
*/
142+
RemoteTemplateEngine.prototype.waitForFinishRender = function () {
143+
var defer = $.Deferred();
144+
145+
this._events.one('finishrender', defer.resolve);
146+
147+
return defer.promise();
148+
};
149+
150+
/**
151+
* Returns true if this template has already been asynchronously loaded and will be synchronously rendered.
152+
* @param {String} templateName
153+
* @returns {Boolean}
154+
* @private
155+
*/
156+
RemoteTemplateEngine.prototype._hasTemplateLoaded = function (templateName) {
157+
// Sources object will have cached template once makeTemplateSource has run
158+
return sources.hasOwnProperty(templateName);
159+
};
160+
35161
/**
36162
* Overrided method of native knockout template engine.
37163
* Caches template after it's unique name and renders in once.
@@ -43,7 +169,8 @@ define([
43169
* @returns {TemplateSource} Object with methods 'nodes' and 'data'.
44170
*/
45171
RemoteTemplateEngine.prototype.makeTemplateSource = function (template, templateDocument, options, bindingContext) {
46-
var source,
172+
var engine = this,
173+
source,
47174
templateId;
48175

49176
if (typeof template === 'string') {
@@ -60,12 +187,13 @@ define([
60187
component: bindingContext.$data.name
61188
});
62189

63-
renderer.render(template).done(function (rendered) {
190+
renderer.render(template).then(function (rendered) {
64191
consoleLogger.info('templateLoadedFromServer', {
65192
template: templateId,
66193
component: bindingContext.$data.name
67194
});
68195
source.nodes(rendered);
196+
engine._releaseRender(templateId, 'async');
69197
}).fail(function () {
70198
consoleLogger.error('templateLoadingFail', {
71199
template: templateId,
@@ -115,7 +243,7 @@ define([
115243
RemoteTemplateEngine.prototype.renderTemplate = function (template, bindingContext, options, templateDocument) {
116244
var templateSource = this.makeTemplateSource(template, templateDocument, options, bindingContext);
117245

118-
return this.renderTemplateSource(templateSource, bindingContext, options);
246+
return this.renderTemplateSource(templateSource);
119247
};
120248

121249
return new RemoteTemplateEngine;

app/code/Magento/Ui/view/base/web/js/lib/knockout/template/renderer.js

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@ define([
99
], function ($, _, loader) {
1010
'use strict';
1111

12-
var colonReg = /\\:/g,
13-
attributes = {},
14-
elements = {},
15-
globals = [],
12+
var colonReg = /\\:/g,
13+
renderedTemplatePromises = {},
14+
attributes = {},
15+
elements = {},
16+
globals = [],
1617
renderer,
1718
preset;
1819

@@ -24,11 +25,25 @@ define([
2425
*
2526
* @param {String} tmplPath - Path to the template.
2627
* @returns {jQueryPromise}
28+
* @alias getRendered
2729
*/
2830
render: function (tmplPath) {
29-
var loadPromise = loader.loadTemplate(tmplPath);
31+
var cachedPromise = renderedTemplatePromises[tmplPath];
3032

31-
return loadPromise.then(renderer.parseTemplate);
33+
if (!cachedPromise) {
34+
cachedPromise = renderedTemplatePromises[tmplPath] = loader
35+
.loadTemplate(tmplPath)
36+
.then(renderer.parseTemplate);
37+
}
38+
39+
return cachedPromise;
40+
},
41+
42+
/**
43+
* @ignore
44+
*/
45+
getRendered: function (tmplPath) {
46+
return renderer.render(tmplPath);
3247
},
3348

3449
/**

0 commit comments

Comments
 (0)