1 | var exports = module.exports;
|
2 |
|
3 | var util = require("../util/util");
|
4 |
|
5 | var trackerInstance = function(context)
|
6 | {
|
7 | return context.get("__tracker");
|
8 | };
|
9 |
|
10 | var getParentContext = function(context)
|
11 | {
|
12 | var parentContext = null;
|
13 |
|
14 | // if this context has a parent...
|
15 | if (context.stack && context.stack.tail && context.stack.tail.length > 0)
|
16 | {
|
17 | // the current tip context
|
18 | // pop it off and "context" modifies
|
19 | var currentContext = context.pop();
|
20 |
|
21 | // the new tip is the parent
|
22 | parentContext = context.current();
|
23 |
|
24 | // restore the current context as tip
|
25 | context.push(currentContext);
|
26 | }
|
27 |
|
28 | return parentContext;
|
29 | };
|
30 |
|
31 | var id = exports.id = function(context)
|
32 | {
|
33 | var instance = trackerInstance(context);
|
34 |
|
35 | return instance.id;
|
36 | };
|
37 |
|
38 | /**
|
39 | * Starts a tracker within the current context.
|
40 | *
|
41 | * The tracker is bound to the Dust context's push and pop methods. A "__tracker" instance holds the dependency
|
42 | * tracker instance for that particular frame in the stack.
|
43 | *
|
44 | * When the context is popped, the tracker for the current frame copies some of it's dependency state from the child
|
45 | * to the parent. Dependency state is either of type "produces" or "requires".
|
46 | *
|
47 | * PRODUCES
|
48 | * --------
|
49 | *
|
50 | * The "produces" dependencies usually identify the content items or display assets that the current execution
|
51 | * rendered on the screen. It identifies which objects produced the rendered view.
|
52 | *
|
53 | * When the context pops, these dependencies always copy from the child context to the parent. Any wrapping fragments
|
54 | * inherit the produced dependencies of their children, all the way up to the page.
|
55 | *
|
56 | * That way, if a product dependency (i.e. a Cloud CMS content node) invalidates, all pages and nested fragments can be
|
57 | * detected and invalidated at once.
|
58 | *
|
59 | * In this sense, it is fair to think of produced dependencies as serving the purpose of how to optimize
|
60 | * invalidation.
|
61 | *
|
62 | * REQUIRES
|
63 | * --------
|
64 | *
|
65 | * The "requires" dependencies identify the required state of the parent such that the cached state of the
|
66 | * current execution context can be used.
|
67 | *
|
68 | * These dependencies are a list of "what must be true" about the outer variables such that we can use the cache
|
69 | * fragment.
|
70 | *
|
71 | * All requires variables are local. They do not propagate up to the parent in the same way as "produces" dependencies.
|
72 | * Rather, the nested nature of HTML ensures that outer HTML fragments will contain the HTML of inner HTML fragments.
|
73 | *
|
74 | * The "requires" dependencies serve as a kind of footprint that allows for a very fast pattern match against the
|
75 | * current set of known runtime variables at any point in the execution chain. For the top-level page, these include
|
76 | * things like the repository ID, the branchID and any other request state that was used to produce the page.
|
77 | *
|
78 | * All "requires" dependencies pass down to children but they do not pass back up to parents.
|
79 | *
|
80 | *
|
81 | * Requirements should look like:
|
82 | *
|
83 | * {
|
84 | * "param1": "abc",
|
85 | * "param2": "def"
|
86 | * }
|
87 | *
|
88 | * @param childContext
|
89 | * @param _id
|
90 | * @param _requirements
|
91 | */
|
92 | var start = exports.start = function(childContext, _id, _requirements)
|
93 | {
|
94 | var childTracker = {
|
95 | "requires": {},
|
96 | "produces": {}
|
97 | };
|
98 |
|
99 | var fc = function(parentTracker, childTracker, key)
|
100 | {
|
101 | if (parentTracker[key] && parentTracker[key].length > 0)
|
102 | {
|
103 | for (var k in parentTracker[key])
|
104 | {
|
105 | if (!childTracker[key][k]) {
|
106 | childTracker[key][k] = [];
|
107 | }
|
108 |
|
109 | for (var i = 0; i < parentTracker[key][k].length; i++)
|
110 | {
|
111 | childTracker[key][k].push(parentTracker[key][k][i]);
|
112 | }
|
113 | }
|
114 | }
|
115 | };
|
116 |
|
117 | // find the parent context
|
118 | var parentContext = getParentContext(childContext);
|
119 | if (parentContext)
|
120 | {
|
121 | // copy parent "requires" and "produces" into new child tracker object
|
122 | var parentTracker = trackerInstance(parentContext);
|
123 | if (parentTracker)
|
124 | {
|
125 | fc(parentTracker, childTracker, "requires");
|
126 | fc(parentTracker, childTracker, "produces");
|
127 | }
|
128 | }
|
129 |
|
130 | if (_id)
|
131 | {
|
132 | childTracker.id = _id;
|
133 | }
|
134 |
|
135 | if (_requirements)
|
136 | {
|
137 | requirements(childContext, _requirements);
|
138 | }
|
139 | };
|
140 |
|
141 | /**
|
142 | * Finishes a tracker.
|
143 | *
|
144 | * This hands back the tracker state. It also copies tracker dependencies up to the parent tracker.
|
145 | *
|
146 | * @param parentContext
|
147 | * @param childContext
|
148 | * @returns the tracker context
|
149 | */
|
150 | var finish = exports.finish = function(childContext)
|
151 | {
|
152 | // child tracker
|
153 | var childTracker = trackerInstance(childContext);
|
154 |
|
155 | // find the parent context
|
156 | var parentContext = getParentContext(childContext);
|
157 | if (parentContext)
|
158 | {
|
159 | // parent tracker
|
160 | var parentTracker = trackerInstance(parentContext);
|
161 |
|
162 | // now copy stuff back up
|
163 | if (parentTracker)
|
164 | {
|
165 | // any "produces" dependencies always copies up
|
166 | for (var name in childTracker.produces)
|
167 | {
|
168 | var array = childTracker.produces[name];
|
169 | if (array)
|
170 | {
|
171 | if (!parentTracker["produces"][name])
|
172 | {
|
173 | parentTracker["produces"][name] = [];
|
174 | }
|
175 |
|
176 | for (var i = 0; i < array.length; i++)
|
177 | {
|
178 | if (parentTracker["produces"][name].indexOf(array[i]) === -1)
|
179 | {
|
180 | parentTracker["produces"][name].push(array[i]);
|
181 | }
|
182 | }
|
183 | }
|
184 | }
|
185 | }
|
186 | }
|
187 | };
|
188 |
|
189 | /**
|
190 | * Marks that the current rendering requires the following requirements.
|
191 | *
|
192 | * @param context
|
193 | * @param requirements
|
194 | */
|
195 | var requirements = exports.requirements = function(context, requirements)
|
196 | {
|
197 | for (var k in requirements)
|
198 | {
|
199 | var v = requirements[k];
|
200 | if (v)
|
201 | {
|
202 | requires(context, k, v.value);
|
203 | }
|
204 | }
|
205 | };
|
206 |
|
207 | /**
|
208 | * Marks that the current rendering requires the following key=value to be in place.
|
209 | *
|
210 | * @param context
|
211 | * @param key
|
212 | * @param value
|
213 | */
|
214 | var requires = exports.requires = function(context, key, value)
|
215 | {
|
216 | if (typeof(value) !== "undefined" && value !== null)
|
217 | {
|
218 | var instance = trackerInstance(context);
|
219 |
|
220 | var requires = instance["requires"];
|
221 |
|
222 | var array = requires[key];
|
223 | if (!array)
|
224 | {
|
225 | requires[key] = array = [];
|
226 | }
|
227 |
|
228 | if (array.indexOf(value) === -1)
|
229 | {
|
230 | array.push(value);
|
231 | }
|
232 | }
|
233 | };
|
234 |
|
235 | /**
|
236 | * Marks that the current rendering produces a result that features the given key=value as part of its output.
|
237 | *
|
238 | * @param context
|
239 | * @param key
|
240 | * @param value
|
241 | */
|
242 | var produces = exports.produces = function(context, key, value)
|
243 | {
|
244 | if (typeof(value) !== "undefined" && value !== null)
|
245 | {
|
246 | var instance = trackerInstance(context);
|
247 |
|
248 | var produces = instance["produces"];
|
249 |
|
250 | var array = produces[key];
|
251 | if (!array)
|
252 | {
|
253 | array = [];
|
254 | produces[key] = array;
|
255 | }
|
256 |
|
257 | if (array.indexOf(value) === -1)
|
258 | {
|
259 | array.push(value);
|
260 | }
|
261 | }
|
262 | };
|