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,76 @@ 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
+ 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
+ } ;
22
92
23
93
/**
24
94
* Creates unique template identifier based on template name and it's extenders (optional)
@@ -32,6 +102,64 @@ define([
32
102
RemoteTemplateEngine . prototype = new NativeTemplateEngine ;
33
103
RemoteTemplateEngine . prototype . constructor = RemoteTemplateEngine ;
34
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
+
35
163
/**
36
164
* Overrided method of native knockout template engine.
37
165
* Caches template after it's unique name and renders in once.
@@ -43,7 +171,8 @@ define([
43
171
* @returns {TemplateSource } Object with methods 'nodes' and 'data'.
44
172
*/
45
173
RemoteTemplateEngine . prototype . makeTemplateSource = function ( template , templateDocument , options , bindingContext ) {
46
- var source ,
174
+ var engine = this ,
175
+ source ,
47
176
templateId ;
48
177
49
178
if ( typeof template === 'string' ) {
@@ -60,12 +189,13 @@ define([
60
189
component : bindingContext . $data . name
61
190
} ) ;
62
191
63
- renderer . render ( template ) . done ( function ( rendered ) {
192
+ renderer . render ( template ) . then ( function ( rendered ) {
64
193
consoleLogger . info ( 'templateLoadedFromServer' , {
65
194
template : templateId ,
66
195
component : bindingContext . $data . name
67
196
} ) ;
68
197
source . nodes ( rendered ) ;
198
+ engine . _releaseRender ( templateId , 'async' ) ;
69
199
} ) . fail ( function ( ) {
70
200
consoleLogger . error ( 'templateLoadingFail' , {
71
201
template : templateId ,
@@ -115,7 +245,7 @@ define([
115
245
RemoteTemplateEngine . prototype . renderTemplate = function ( template , bindingContext , options , templateDocument ) {
116
246
var templateSource = this . makeTemplateSource ( template , templateDocument , options , bindingContext ) ;
117
247
118
- return this . renderTemplateSource ( templateSource , bindingContext , options ) ;
248
+ return this . renderTemplateSource ( templateSource ) ;
119
249
} ;
120
250
121
251
return new RemoteTemplateEngine ;
0 commit comments