UNPKG

7.32 kBJavaScriptView Raw
1var exports = module.exports;
2
3var util = require("../util/util");
4
5var trackerInstance = function(context)
6{
7 return context.get("__tracker");
8};
9
10var 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
31var 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 */
92var 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 */
150var 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 */
195var 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 */
214var 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 */
242var 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};