3
3
* See COPYING.txt for license details.
4
4
*/
5
5
define ( [
6
+ 'jquery' ,
6
7
'ko' ,
7
8
'underscore' ,
8
9
'./observable_source' ,
9
10
'./renderer' ,
10
11
'../../logger/console-logger'
11
- ] , function ( ko , _ , Source , renderer , consoleLogger ) {
12
+ ] , function ( $ , ko , _ , Source , renderer , consoleLogger ) {
12
13
'use strict' ;
13
14
14
15
var RemoteTemplateEngine ,
@@ -18,7 +19,74 @@ define([
18
19
/**
19
20
* Remote template engine class. Is used to be able to load remote templates via knockout template binding.
20
21
*/
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
+ } ;
22
90
23
91
/**
24
92
* Creates unique template identifier based on template name and it's extenders (optional)
@@ -32,6 +100,64 @@ define([
32
100
RemoteTemplateEngine . prototype = new NativeTemplateEngine ;
33
101
RemoteTemplateEngine . prototype . constructor = RemoteTemplateEngine ;
34
102
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
+
35
161
/**
36
162
* Overrided method of native knockout template engine.
37
163
* Caches template after it's unique name and renders in once.
@@ -43,7 +169,8 @@ define([
43
169
* @returns {TemplateSource } Object with methods 'nodes' and 'data'.
44
170
*/
45
171
RemoteTemplateEngine . prototype . makeTemplateSource = function ( template , templateDocument , options , bindingContext ) {
46
- var source ,
172
+ var engine = this ,
173
+ source ,
47
174
templateId ;
48
175
49
176
if ( typeof template === 'string' ) {
@@ -60,12 +187,13 @@ define([
60
187
component : bindingContext . $data . name
61
188
} ) ;
62
189
63
- renderer . render ( template ) . done ( function ( rendered ) {
190
+ renderer . render ( template ) . then ( function ( rendered ) {
64
191
consoleLogger . info ( 'templateLoadedFromServer' , {
65
192
template : templateId ,
66
193
component : bindingContext . $data . name
67
194
} ) ;
68
195
source . nodes ( rendered ) ;
196
+ engine . _releaseRender ( templateId , 'async' ) ;
69
197
} ) . fail ( function ( ) {
70
198
consoleLogger . error ( 'templateLoadingFail' , {
71
199
template : templateId ,
@@ -115,7 +243,7 @@ define([
115
243
RemoteTemplateEngine . prototype . renderTemplate = function ( template , bindingContext , options , templateDocument ) {
116
244
var templateSource = this . makeTemplateSource ( template , templateDocument , options , bindingContext ) ;
117
245
118
- return this . renderTemplateSource ( templateSource , bindingContext , options ) ;
246
+ return this . renderTemplateSource ( templateSource ) ;
119
247
} ;
120
248
121
249
return new RemoteTemplateEngine ;
0 commit comments