UNPKG

1.13 MBJavaScriptView Raw
1/*!
2 infusion - v3.0.0-dev.20190906T123329Z.e5ad6fbc6.FLUID-6359-src
3 Friday, September 6th, 2019, 8:38:51 AM
4 branch: FLUID-6359-src
5 revision: e5ad6fbc6
6*/
7/*!
8 * Fluid Infusion v3.0.0
9 *
10 * Infusion is distributed under the Educational Community License 2.0 and new BSD licenses:
11 * http://wiki.fluidproject.org/display/fluid/Fluid+Licensing
12 *
13 * Copyright The Infusion copyright holders
14 * See the AUTHORS.md file at the top-level directory of this distribution and at
15 * https://github.com/fluid-project/infusion/raw/master/AUTHORS.md
16 */
17/*
18Copyright The Infusion copyright holders
19See the AUTHORS.md file at the top-level directory of this distribution and at
20https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
21
22Licensed under the Educational Community License (ECL), Version 2.0 or the New
23BSD license. You may not use this file except in compliance with one these
24Licenses.
25
26You may obtain a copy of the ECL 2.0 License and BSD License at
27https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
28
29Includes code from Underscore.js 1.4.3
30http://underscorejs.org
31(c) 2009-2012 Jeremy Ashkenas, DocumentCloud Inc.
32Underscore may be freely distributed under the MIT license.
33*/
34
35/* global console */
36
37var fluid_3_0_0 = fluid_3_0_0 || {};
38var fluid = fluid || fluid_3_0_0;
39
40(function ($, fluid) {
41 "use strict";
42
43 fluid.version = "Infusion 3.0.0";
44
45 // Export this for use in environments like node.js, where it is useful for
46 // configuring stack trace behaviour
47 fluid.Error = Error;
48
49 fluid.environment = {
50 fluid: fluid
51 };
52
53 fluid.global = fluid.global || typeof window !== "undefined" ?
54 window : typeof self !== "undefined" ? self : {};
55
56 // A standard utility to schedule the invocation of a function after the current
57 // stack returns. On browsers this defaults to setTimeout(func, 1) but in
58 // other environments can be customised - e.g. to process.nextTick in node.js
59 // In future, this could be optimised in the browser to not dispatch into the event queue
60 fluid.invokeLater = function (func) {
61 return setTimeout(func, 1);
62 };
63
64 // The following flag defeats all logging/tracing activities in the most performance-critical parts of the framework.
65 // This should really be performed by a build-time step which eliminates calls to pushActivity/popActivity and fluid.log.
66 fluid.defeatLogging = true;
67
68 // This flag enables the accumulating of all "activity" records generated by pushActivity into a running trace, rather
69 // than removing them from the stack record permanently when receiving popActivity. This trace will be consumed by
70 // visual debugging tools.
71 fluid.activityTracing = false;
72 fluid.activityTrace = [];
73
74 var activityParser = /(%\w+)/g;
75
76 // Renders a single activity element in a form suitable to be sent to a modern browser's console
77 // unsupported, non-API function
78 fluid.renderOneActivity = function (activity, nowhile) {
79 var togo = nowhile === true ? [] : [" while "];
80 var message = activity.message;
81 var index = activityParser.lastIndex = 0;
82 while (true) {
83 var match = activityParser.exec(message);
84 if (match) {
85 var key = match[1].substring(1);
86 togo.push(message.substring(index, match.index));
87 togo.push(activity.args[key]);
88 index = activityParser.lastIndex;
89 }
90 else {
91 break;
92 }
93 }
94 if (index < message.length) {
95 togo.push(message.substring(index));
96 }
97 return togo;
98 };
99
100 // Renders an activity stack in a form suitable to be sent to a modern browser's console
101 // unsupported, non-API function
102 fluid.renderActivity = function (activityStack, renderer) {
103 renderer = renderer || fluid.renderOneActivity;
104 return fluid.transform(activityStack, renderer);
105 };
106
107 // Definitions for ThreadLocals - lifted here from
108 // FluidIoC.js so that we can issue calls to fluid.describeActivity for debugging purposes
109 // in the core framework
110
111 // unsupported, non-API function
112 fluid.singleThreadLocal = function (initFunc) {
113 var value = initFunc();
114 return function (newValue) {
115 return newValue === undefined ? value : value = newValue;
116 };
117 };
118
119 // Currently we only support single-threaded environments - ensure that this function
120 // is not used on startup so it can be successfully monkey-patched
121 // only remaining uses of threadLocals are for activity reporting and in the renderer utilities
122 // unsupported, non-API function
123 fluid.threadLocal = fluid.singleThreadLocal;
124
125 // unsupported, non-API function
126 fluid.globalThreadLocal = fluid.threadLocal(function () {
127 return {};
128 });
129
130 // Return an array of objects describing the current activity
131 // unsupported, non-API function
132 fluid.getActivityStack = function () {
133 var root = fluid.globalThreadLocal();
134 if (!root.activityStack) {
135 root.activityStack = [];
136 }
137 return root.activityStack;
138 };
139
140 // Return an array of objects describing the current activity
141 // unsupported, non-API function
142 fluid.describeActivity = fluid.getActivityStack;
143
144 // Renders either the current activity or the supplied activity to the console
145 fluid.logActivity = function (activity) {
146 activity = activity || fluid.describeActivity();
147 var rendered = fluid.renderActivity(activity).reverse();
148 if (rendered.length > 0) {
149 fluid.log("Current activity: ");
150 fluid.each(rendered, function (args) {
151 fluid.log.apply(null, args);
152 });
153 }
154 };
155
156 // Execute the supplied function with the specified activity description pushed onto the stack
157 // unsupported, non-API function
158 fluid.pushActivity = function (type, message, args) {
159 var record = {type: type, message: message, args: args, time: new Date().getTime()};
160 if (fluid.activityTracing) {
161 fluid.activityTrace.push(record);
162 }
163 if (fluid.passLogLevel(fluid.logLevel.TRACE)) {
164 fluid.log.apply(null, fluid.renderOneActivity(record, true));
165 }
166 var activityStack = fluid.getActivityStack();
167 activityStack.push(record);
168 };
169
170 // Undo the effect of the most recent pushActivity, or multiple frames if an argument is supplied
171 fluid.popActivity = function (popframes) {
172 popframes = popframes || 1;
173 if (fluid.activityTracing) {
174 fluid.activityTrace.push({pop: popframes});
175 }
176 var activityStack = fluid.getActivityStack();
177 var popped = activityStack.length - popframes;
178 activityStack.length = popped < 0 ? 0 : popped;
179 };
180 // "this-ist" style Error so that we can distinguish framework errors whilst still retaining access to platform Error features
181 // Solution taken from http://stackoverflow.com/questions/8802845/inheriting-from-the-error-object-where-is-the-message-property#answer-17936621
182 fluid.FluidError = function (/*message*/) {
183 var togo = Error.apply(this, arguments);
184 this.message = togo.message;
185 try { // This technique is necessary on IE11 since otherwise the stack entry is not filled in
186 throw togo;
187 } catch (togo) {
188 this.stack = togo.stack;
189 }
190 return this;
191 };
192 fluid.FluidError.prototype = Object.create(Error.prototype);
193
194 // The framework's built-in "log" failure handler - this logs the supplied message as well as any framework activity in progress via fluid.log
195 fluid.logFailure = function (args, activity) {
196 fluid.log.apply(null, [fluid.logLevel.FAIL, "ASSERTION FAILED: "].concat(args));
197 fluid.logActivity(activity);
198 };
199
200 fluid.renderLoggingArg = function (arg) {
201 return arg === undefined ? "undefined" : fluid.isPrimitive(arg) || !fluid.isPlainObject(arg) ? arg : JSON.stringify(arg);
202 };
203
204 // The framework's built-in "fail" failure handler - this throws an exception of type <code>fluid.FluidError</code>
205 fluid.builtinFail = function (args /*, activity*/) {
206 var message = fluid.transform(args, fluid.renderLoggingArg).join("");
207 throw new fluid.FluidError("Assertion failure - check console for more details: " + message);
208 };
209
210 /**
211 * Signals an error to the framework. The default behaviour is to log a structured error message and throw an exception. This strategy may be configured using the legacy
212 * API <code>fluid.pushSoftFailure</code> or else by adding and removing suitably namespaced listeners to the special event <code>fluid.failureEvent</code>
213 *
214 * @param {String} message - The error message to log.
215 *
216 * All arguments after the first are passed on to (and should be suitable to pass on to) the native console.log
217 * function.
218 */
219 fluid.fail = function (/* message, ... */) {
220 var args = fluid.makeArray(arguments);
221 var activity = fluid.makeArray(fluid.describeActivity()); // Take copy since we will destructively modify
222 fluid.popActivity(activity.length); // clear any current activity - TODO: the framework currently has no exception handlers, although it will in time
223 if (fluid.failureEvent) { // notify any framework failure prior to successfully setting up the failure event below
224 fluid.failureEvent.fire(args, activity);
225 } else {
226 fluid.logFailure(args, activity);
227 fluid.builtinFail(args, activity);
228 }
229 };
230
231 // TODO: rescued from kettleCouchDB.js - clean up in time
232 fluid.expect = function (name, target, members) {
233 fluid.transform(fluid.makeArray(members), function (key) {
234 if (typeof target[key] === "undefined") {
235 fluid.fail(name + " missing required parameter " + key);
236 }
237 });
238 };
239
240 // Logging
241
242 /** Returns whether logging is enabled - legacy method
243 * @return {Boolean} `true` if the current logging level exceeds `fluid.logLevel.IMPORTANT`
244 */
245 fluid.isLogging = function () {
246 return logLevelStack[0].priority > fluid.logLevel.IMPORTANT.priority;
247 };
248
249 /** Determines whether the supplied argument is a valid logLevel marker
250 * @param {Any} arg - The value to be tested
251 * @return {Boolean} `true` if the supplied argument is a logLevel marker
252 */
253 fluid.isLogLevel = function (arg) {
254 return fluid.isMarker(arg) && arg.priority !== undefined;
255 };
256
257 /** Check whether the current framework logging level would cause a message logged with the specified level to be
258 * logged. Clients who issue particularly expensive log payload arguments are recommended to guard their logging
259 * statements with this function
260 * @param {LogLevel} testLogLevel - The logLevel value which the current logging level will be tested against.
261 * Accepts one of the members of the <code>fluid.logLevel</code> structure.
262 * @return {Boolean} Returns <code>true</code> if a message supplied at that log priority would be accepted at the current logging level.
263 */
264
265 fluid.passLogLevel = function (testLogLevel) {
266 return testLogLevel.priority <= logLevelStack[0].priority;
267 };
268
269 /** Method to allow user to control the current framework logging level. The supplied level will be pushed onto a stack
270 * of logging levels which may be popped via `fluid.popLogging`.
271 * @param {Boolean|LogLevel} enabled - Either a boolean, for which <code>true</code>
272 * represents <code>fluid.logLevel.INFO</code> and <code>false</code> represents <code>fluid.logLevel.IMPORTANT</code> (the default),
273 * or else any other member of the structure <code>fluid.logLevel</code>
274 * Messages whose priority is strictly less than the current logging level will not be shown by `fluid.log`
275 */
276 fluid.setLogging = function (enabled) {
277 var logLevel;
278 if (typeof enabled === "boolean") {
279 logLevel = fluid.logLevel[enabled ? "INFO" : "IMPORTANT"];
280 } else if (fluid.isLogLevel(enabled)) {
281 logLevel = enabled;
282 } else {
283 fluid.fail("Unrecognised fluid logging level ", enabled);
284 }
285 logLevelStack.unshift(logLevel);
286 fluid.defeatLogging = !fluid.isLogging();
287 };
288
289 fluid.setLogLevel = fluid.setLogging;
290
291 /** Undo the effect of the most recent "setLogging", returning the logging system to its previous state
292 * @return {LogLevel} The logLevel that was just popped
293 */
294 fluid.popLogging = function () {
295 var togo = logLevelStack.length === 1 ? logLevelStack[0] : logLevelStack.shift();
296 fluid.defeatLogging = !fluid.isLogging();
297 return togo;
298 };
299
300 /** Actually do the work of logging <code>args</code> to the environment's console. If the standard "console"
301 * stream is available, the message will be sent there.
302 * @param {Array} args - The complete array of arguments to be logged
303 */
304 fluid.doBrowserLog = function (args) {
305 if (typeof (console) !== "undefined") {
306 if (console.debug) {
307 console.debug.apply(console, args);
308 } else if (typeof (console.log) === "function") {
309 console.log.apply(console, args);
310 }
311 }
312 };
313
314 /* Log a message to a suitable environmental console. If the first argument to fluid.log is
315 * one of the members of the <code>fluid.logLevel</code> structure, this will be taken as the priority
316 * of the logged message - else if will default to <code>fluid.logLevel.INFO</code>. If the logged message
317 * priority does not exceed that set by the most recent call to the <code>fluid.setLogging</code> function,
318 * the message will not appear.
319 */
320 fluid.log = function (/* message /*, ... */) {
321 var directArgs = fluid.makeArray(arguments);
322 var userLogLevel = fluid.logLevel.INFO;
323 if (fluid.isLogLevel(directArgs[0])) {
324 userLogLevel = directArgs.shift();
325 }
326 if (fluid.passLogLevel(userLogLevel)) {
327 fluid.loggingEvent.fire(directArgs);
328 }
329 };
330
331 // Functional programming utilities.
332
333 // Type checking functions
334
335 /** Check whether the argument is a value other than null or undefined
336 * @param {Any} value - The value to be tested
337 * @return {Boolean} `true` if the supplied value is other than null or undefined
338 */
339 fluid.isValue = function (value) {
340 return value !== undefined && value !== null;
341 };
342
343 /** Check whether the argument is a primitive type
344 * @param {Any} value - The value to be tested
345 * @return {Boolean} `true` if the supplied value is a JavaScript (ES5) primitive
346 */
347 fluid.isPrimitive = function (value) {
348 var valueType = typeof (value);
349 return !value || valueType === "string" || valueType === "boolean" || valueType === "number" || valueType === "function";
350 };
351
352 /** Determines whether the supplied object is an jQuery object. The strategy uses optimised inspection of the
353 * constructor prototype since jQuery may not actually be loaded
354 * @param {Any} totest - The value to be tested
355 * @return {Boolean} `true` if the supplied value is a jQuery object
356 */
357 fluid.isJQuery = function (totest) {
358 return Boolean(totest && totest.jquery && totest.constructor && totest.constructor.prototype
359 && totest.constructor.prototype.jquery);
360 };
361
362 /** Determines whether the supplied object is an array. The strategy used is an optimised
363 * approach taken from an earlier version of jQuery - detecting whether the toString() version
364 * of the object agrees with the textual form [object Array], or else whether the object is a
365 * jQuery object (the most common source of "fake arrays").
366 * @param {Any} totest - The value to be tested
367 * @return {Boolean} `true` if the supplied value is an array
368 */
369 // Note: The primary place jQuery->Array conversion is used in the framework is in dynamic components with a jQuery source.
370 fluid.isArrayable = function (totest) {
371 return Boolean(totest) && (Object.prototype.toString.call(totest) === "[object Array]" || fluid.isJQuery(totest));
372 };
373
374 /** Determines whether the supplied object is a plain JSON-forming container - that is, it is either a plain Object
375 * or a plain Array. Note that this differs from jQuery's isPlainObject which does not pass Arrays.
376 * @param {Any} totest - The object to be tested
377 * @param {Boolean} [strict] - (optional) If `true`, plain Arrays will fail the test rather than passing.
378 * @return {Boolean} - `true` if `totest` is a plain object, `false` otherwise.
379 */
380 fluid.isPlainObject = function (totest, strict) {
381 var string = Object.prototype.toString.call(totest);
382 if (string === "[object Array]") {
383 return !strict;
384 } else if (string !== "[object Object]") {
385 return false;
386 } // FLUID-5226: This inventive strategy taken from jQuery detects whether the object's prototype is directly Object.prototype by virtue of having an "isPrototypeOf" direct member
387 return !totest.constructor || !totest.constructor.prototype || Object.prototype.hasOwnProperty.call(totest.constructor.prototype, "isPrototypeOf");
388 };
389
390 /** Returns a string typeCode representing the type of the supplied value at a coarse level.
391 * Returns <code>primitive</code>, <code>array</code> or <code>object</code> depending on whether the supplied object has
392 * one of those types, by use of the <code>fluid.isPrimitive</code>, <code>fluid.isPlainObject</code> and <code>fluid.isArrayable</code> utilities
393 * @param {Any} totest - The value to be tested
394 * @return {String} Either `primitive`, `array` or `object` depending on the type of the supplied value
395 */
396 fluid.typeCode = function (totest) {
397 return fluid.isPrimitive(totest) || !fluid.isPlainObject(totest) ? "primitive" :
398 fluid.isArrayable(totest) ? "array" : "object";
399 };
400
401 fluid.isIoCReference = function (ref) {
402 return typeof(ref) === "string" && ref.charAt(0) === "{" && ref.indexOf("}") > 0;
403 };
404
405 fluid.isDOMNode = function (obj) {
406 // This could be more sound, but messy:
407 // http://stackoverflow.com/questions/384286/javascript-isdom-how-do-you-check-if-a-javascript-object-is-a-dom-object
408 // The real problem is browsers like IE6, 7 and 8 which still do not feature a "constructor" property on DOM nodes
409 return obj && typeof (obj.nodeType) === "number";
410 };
411
412 fluid.isComponent = function (obj) {
413 return obj && obj.constructor === fluid.componentConstructor;
414 };
415
416 fluid.isUncopyable = function (totest) {
417 return fluid.isPrimitive(totest) || !fluid.isPlainObject(totest);
418 };
419
420 fluid.isApplicable = function (totest) {
421 return totest.apply && typeof(totest.apply) === "function";
422 };
423
424 /* A basic utility that returns its argument unchanged */
425 fluid.identity = function (arg) {
426 return arg;
427 };
428
429 /** A function which raises a failure if executed */
430 fluid.notImplemented = function () {
431 fluid.fail("This operation is not implemented");
432 };
433
434 /** Returns the first of its arguments if it is not `undefined`, otherwise returns the second.
435 * @param {Any} a - The first argument to be tested for being `undefined`
436 * @param {Any} b - The fallback argument, to be returned if `a` is `undefined`
437 * @return {Any} `a` if it is not `undefined`, else `b`.
438 */
439 fluid.firstDefined = function (a, b) {
440 return a === undefined ? b : a;
441 };
442
443 /* Return an empty container as the same type as the argument (either an array or hash). */
444 fluid.freshContainer = function (tocopy) {
445 return fluid.isArrayable(tocopy) ? [] : {};
446 };
447
448 fluid.copyRecurse = function (tocopy, segs) {
449 if (segs.length > fluid.strategyRecursionBailout) {
450 fluid.fail("Runaway recursion encountered in fluid.copy - reached path depth of " + fluid.strategyRecursionBailout + " via path of " + segs.join(".") +
451 "this object is probably circularly connected. Either adjust your object structure to remove the circularity or increase fluid.strategyRecursionBailout");
452 }
453 if (fluid.isUncopyable(tocopy)) {
454 return tocopy;
455 } else {
456 return fluid.transform(tocopy, function (value, key) {
457 segs.push(key);
458 var togo = fluid.copyRecurse(value, segs);
459 segs.pop();
460 return togo;
461 });
462 }
463 };
464
465 /* Performs a deep copy (clone) of its argument. This will guard against cloning a circular object by terminating if it reaches a path depth
466 * greater than <code>fluid.strategyRecursionBailout</code>
467 */
468
469 fluid.copy = function (tocopy) {
470 return fluid.copyRecurse(tocopy, []);
471 };
472
473 // TODO: Coming soon - reimplementation of $.extend using strategyRecursionBailout
474 fluid.extend = $.extend;
475
476 /* Corrected version of jQuery makeArray that returns an empty array on undefined rather than crashing.
477 * We don't deal with as many pathological cases as jQuery */
478 fluid.makeArray = function (arg) {
479 var togo = [];
480 if (arg !== null && arg !== undefined) {
481 if (fluid.isPrimitive(arg) || fluid.isPlainObject(arg, true) || typeof(arg.length) !== "number") {
482 togo.push(arg);
483 }
484 else {
485 for (var i = 0; i < arg.length; ++i) {
486 togo[i] = arg[i];
487 }
488 }
489 }
490 return togo;
491 };
492
493 /** Pushes an element or elements onto an array, initialising the array as a member of a holding object if it is
494 * not already allocated.
495 * @param {Array|Object} holder - The holding object whose member is to receive the pushed element(s).
496 * @param {String} member - The member of the <code>holder</code> onto which the element(s) are to be pushed
497 * @param {Array|Object} topush - If an array, these elements will be added to the end of the array using Array.push.apply. If an object, it will be pushed to the end of the array using Array.push.
498 */
499 fluid.pushArray = function (holder, member, topush) {
500 var array = holder[member] ? holder[member] : (holder[member] = []);
501 if (fluid.isArrayable(topush)) {
502 array.push.apply(array, topush);
503 } else {
504 array.push(topush);
505 }
506 };
507
508 function transformInternal(source, togo, key, args) {
509 var transit = source[key];
510 for (var j = 0; j < args.length - 1; ++j) {
511 transit = args[j + 1](transit, key);
512 }
513 togo[key] = transit;
514 }
515
516 /** Return an array or hash of objects, transformed by one or more functions. Similar to
517 * jQuery.map, only will accept an arbitrary list of transformation functions and also
518 * works on non-arrays.
519 * @param {Array|Object} source - The initial container of objects to be transformed. If the source is
520 * neither an array nor an object, it will be returned untransformed
521 * @param {...Function} fn1, fn2, etc. - An arbitrary number of optional further arguments,
522 * all of type Function, accepting the signature (object, index), where object is the
523 * structure member to be transformed, and index is its key or index. Each function will be
524 * applied in turn to each structure member, which will be replaced by the return value
525 * from the function.
526 * @return {Array|Object} - The finally transformed list, where each member has been replaced by the
527 * original member acted on by the function or functions.
528 */
529 fluid.transform = function (source) {
530 if (fluid.isPrimitive(source)) {
531 return source;
532 }
533 var togo = fluid.freshContainer(source);
534 if (fluid.isArrayable(source)) {
535 for (var i = 0; i < source.length; ++i) {
536 transformInternal(source, togo, i, arguments);
537 }
538 } else {
539 for (var key in source) {
540 transformInternal(source, togo, key, arguments);
541 }
542 }
543 return togo;
544 };
545
546 /** Better jQuery.each which works on hashes as well as having the arguments the right way round.
547 * @param {Arrayable|Object} source - The container to be iterated over
548 * @param {Function} func - A function accepting (value, key) for each iterated
549 * object.
550 */
551 fluid.each = function (source, func) {
552 if (fluid.isArrayable(source)) {
553 for (var i = 0; i < source.length; ++i) {
554 func(source[i], i);
555 }
556 } else {
557 for (var key in source) {
558 func(source[key], key);
559 }
560 }
561 };
562
563 fluid.make_find = function (find_if) {
564 var target = find_if ? false : undefined;
565 return function (source, func, deffolt) {
566 var disp;
567 if (fluid.isArrayable(source)) {
568 for (var i = 0; i < source.length; ++i) {
569 disp = func(source[i], i);
570 if (disp !== target) {
571 return find_if ? source[i] : disp;
572 }
573 }
574 } else {
575 for (var key in source) {
576 disp = func(source[key], key);
577 if (disp !== target) {
578 return find_if ? source[key] : disp;
579 }
580 }
581 }
582 return deffolt;
583 };
584 };
585
586 /** Scan through an array or hash of objects, terminating on the first member which
587 * matches a predicate function.
588 * @param {Arrayable|Object} source - The array or hash of objects to be searched.
589 * @param {Function} func - A predicate function, acting on a member. A predicate which
590 * returns any value which is not <code>undefined</code> will terminate
591 * the search. The function accepts (object, index).
592 * @param {Object} deflt - A value to be returned in the case no predicate function matches
593 * a structure member. The default will be the natural value of <code>undefined</code>
594 * @return The first return value from the predicate function which is not <code>undefined</code>
595 */
596 fluid.find = fluid.make_find(false);
597 /* The same signature as fluid.find, only the return value is the actual element for which the
598 * predicate returns a value different from <code>false</code>
599 */
600 fluid.find_if = fluid.make_find(true);
601
602 /** Scan through an array of objects, "accumulating" a value over them
603 * (may be a straightforward "sum" or some other chained computation). "accumulate" is the name derived
604 * from the C++ STL, other names for this algorithm are "reduce" or "fold".
605 * @param {Array} list - The list of objects to be accumulated over.
606 * @param {Function} fn - An "accumulation function" accepting the signature (object, total, index) where
607 * object is the list member, total is the "running total" object (which is the return value from the previous function),
608 * and index is the index number.
609 * @param {Object} arg - The initial value for the "running total" object.
610 * @return {Object} the final running total object as returned from the final invocation of the function on the last list member.
611 */
612 fluid.accumulate = function (list, fn, arg) {
613 for (var i = 0; i < list.length; ++i) {
614 arg = fn(list[i], arg, i);
615 }
616 return arg;
617 };
618
619 /** Returns the sum of its two arguments. A useful utility to combine with fluid.accumulate to compute totals
620 * @param {Number|Boolean} a - The first operand to be added
621 * @param {Number|Boolean} b - The second operand to be added
622 * @return {Number} The sum of the two operands
623 **/
624 fluid.add = function (a, b) {
625 return a + b;
626 };
627
628 /** Scan through an array or hash of objects, removing those which match a predicate. Similar to
629 * jQuery.grep, only acts on the list in-place by removal, rather than by creating
630 * a new list by inclusion.
631 * @param {Array|Object} source - The array or hash of objects to be scanned over. Note that in the case this is an array,
632 * the iteration will proceed from the end of the array towards the front.
633 * @param {Function} fn - A predicate function determining whether an element should be
634 * removed. This accepts the standard signature (object, index) and returns a "truthy"
635 * result in order to determine that the supplied object should be removed from the structure.
636 * @param {Array|Object} [target] - (optional) A target object of the same type as <code>source</code>, which will
637 * receive any objects removed from it.
638 * @return {Array|Object} - <code>target</code>, containing the removed elements, if it was supplied, or else <code>source</code>
639 * modified by the operation of removing the matched elements.
640 */
641 fluid.remove_if = function (source, fn, target) {
642 if (fluid.isArrayable(source)) {
643 for (var i = source.length - 1; i >= 0; --i) {
644 if (fn(source[i], i)) {
645 if (target) {
646 target.unshift(source[i]);
647 }
648 source.splice(i, 1);
649 }
650 }
651 } else {
652 for (var key in source) {
653 if (fn(source[key], key)) {
654 if (target) {
655 target[key] = source[key];
656 }
657 delete source[key];
658 }
659 }
660 }
661 return target || source;
662 };
663
664 /** Fills an array of given size with copies of a value or result of a function invocation
665 * @param {Number} n - The size of the array to be filled
666 * @param {Object|Function} generator - Either a value to be replicated or function to be called
667 * @param {Boolean} applyFunc - If true, treat the generator value as a function to be invoked with
668 * argument equal to the index position
669 */
670
671 fluid.generate = function (n, generator, applyFunc) {
672 var togo = [];
673 for (var i = 0; i < n; ++i) {
674 togo[i] = applyFunc ? generator(i) : generator;
675 }
676 return togo;
677 };
678
679 /** Returns an array of size count, filled with increasing integers, starting at 0 or at the index specified by first.
680 * @param {Number} count - Size of the filled array to be returned
681 * @param {Number} [first] - (optional, defaults to 0) First element to appear in the array
682 */
683
684 fluid.iota = function (count, first) {
685 first = first || 0;
686 var togo = [];
687 for (var i = 0; i < count; ++i) {
688 togo[togo.length] = first++;
689 }
690 return togo;
691 };
692
693 /** Extracts a particular member from each top-level member of a container, returning a new container of the same type
694 * @param {Array|Object} holder - The container to be filtered
695 * @param {String|String[]} name - An EL path to be fetched from each top-level member
696 * @return {Object} - The desired member component.
697 */
698 fluid.getMembers = function (holder, name) {
699 return fluid.transform(holder, function (member) {
700 return fluid.get(member, name);
701 });
702 };
703
704 /** Accepts an object to be filtered, and an array of keys. Either all keys not present in
705 * the array are removed, or only keys present in the array are returned.
706 * @param {Array|Object} toFilter - The object to be filtered - this will be NOT modified by the operation (current implementation
707 * passes through $.extend shallow algorithm)
708 * @param {String[]} keys - The array of keys to operate with
709 * @param {Boolean} exclude - If <code>true</code>, the keys listed are removed rather than included
710 * @return {Object} the filtered object (the same object that was supplied as <code>toFilter</code>
711 */
712 fluid.filterKeys = function (toFilter, keys, exclude) {
713 return fluid.remove_if($.extend({}, toFilter), function (value, key) {
714 return exclude ^ (keys.indexOf(key) === -1);
715 });
716 };
717
718 /* A convenience wrapper for <code>fluid.filterKeys</code> with the parameter <code>exclude</code> set to <code>true</code>
719 * Returns the supplied object with listed keys removed */
720 fluid.censorKeys = function (toCensor, keys) {
721 return fluid.filterKeys(toCensor, keys, true);
722 };
723
724 /* Return the keys in the supplied object as an array. Note that this will return keys found in the prototype chain as well as "own properties", unlike Object.keys() */
725 fluid.keys = function (obj) {
726 var togo = [];
727 for (var key in obj) {
728 togo.push(key);
729 }
730 return togo;
731 };
732
733 /* Return the values in the supplied object as an array */
734 fluid.values = function (obj) {
735 var togo = [];
736 for (var key in obj) {
737 togo.push(obj[key]);
738 }
739 return togo;
740 };
741
742 /*
743 * Searches through the supplied object, and returns <code>true</code> if the supplied value
744 * can be found
745 */
746 fluid.contains = function (obj, value) {
747 return obj ? (fluid.isArrayable(obj) ? obj.indexOf(value) !== -1 : fluid.find(obj, function (thisValue) {
748 if (value === thisValue) {
749 return true;
750 }
751 })) : undefined;
752 };
753
754 /**
755 * Searches through the supplied object for the first value which matches the one supplied.
756 * @param {Object} obj - the Object to be searched through
757 * @param {Object} value - the value to be found. This will be compared against the object's
758 * member using === equality.
759 * @return {String} The first key whose value matches the one supplied
760 */
761 fluid.keyForValue = function (obj, value) {
762 return fluid.find(obj, function (thisValue, key) {
763 if (value === thisValue) {
764 return key;
765 }
766 });
767 };
768
769 /** Converts an array into an object whose keys are the elements of the array, each with the value "true"
770 * @param {String[]} array - The array to be converted to a hash
771 * @return hash {Object} An object with value <code>true</code> for each key taken from a member of <code>array</code>
772 */
773
774 fluid.arrayToHash = function (array) {
775 var togo = {};
776 fluid.each(array, function (el) {
777 togo[el] = true;
778 });
779 return togo;
780 };
781
782 /** Applies a stable sorting algorithm to the supplied array and comparator (note that Array.sort in JavaScript is not specified
783 * to be stable). The algorithm used will be an insertion sort, which whilst quadratic in time, will perform well
784 * on small array sizes.
785 * @param {Array} array - The array to be sorted. This input array will be modified in place.
786 * @param {Function} func - A comparator returning >0, 0, or <0 on pairs of elements representing their sort order (same contract as Array.sort comparator)
787 */
788
789 fluid.stableSort = function (array, func) {
790 for (var i = 0; i < array.length; i++) {
791 var j, k = array[i];
792 for (j = i; j > 0 && func(k, array[j - 1]) < 0; j--) {
793 array[j] = array[j - 1];
794 }
795 array[j] = k;
796 }
797 };
798
799 /* Converts a hash into an object by hoisting out the object's keys into an array element via the supplied String "key", and then transforming via an optional further function, which receives the signature
800 * (newElement, oldElement, key) where newElement is the freshly cloned element, oldElement is the original hash's element, and key is the key of the element.
801 * If the function is not supplied, the old element is simply deep-cloned onto the new element (same effect as transform fluid.transforms.deindexIntoArrayByKey).
802 * The supplied hash will not be modified, unless the supplied function explicitly does so by modifying its 2nd argument.
803 */
804 fluid.hashToArray = function (hash, keyName, func) {
805 var togo = [];
806 fluid.each(hash, function (el, key) {
807 var newEl = {};
808 newEl[keyName] = key;
809 if (func) {
810 newEl = func(newEl, el, key) || newEl;
811 } else {
812 $.extend(true, newEl, el);
813 }
814 togo.push(newEl);
815 });
816 return togo;
817 };
818
819 /* Converts an array consisting of a mixture of arrays and non-arrays into the concatenation of any inner arrays
820 * with the non-array elements
821 */
822 fluid.flatten = function (array) {
823 var togo = [];
824 fluid.each(array, function (element) {
825 if (fluid.isArrayable(element)) {
826 togo = togo.concat(element);
827 } else {
828 togo.push(element);
829 }
830 });
831 return togo;
832 };
833
834 /**
835 * Clears an object or array of its contents. For objects, each property is deleted.
836 *
837 * @param {Object|Array} target - the target to be cleared
838 */
839 fluid.clear = function (target) {
840 if (fluid.isArrayable(target)) {
841 target.length = 0;
842 } else {
843 for (var i in target) {
844 delete target[i];
845 }
846 }
847 };
848
849 /**
850 * @param {Boolean} ascending <code>true</code> if a comparator is to be returned which
851 * sorts strings in descending order of length.
852 * @return {Function} - A comparison function.
853 */
854 fluid.compareStringLength = function (ascending) {
855 return ascending ? function (a, b) {
856 return a.length - b.length;
857 } : function (a, b) {
858 return b.length - a.length;
859 };
860 };
861
862 /**
863 * Returns the converted integer if the input string can be converted to an integer. Otherwise, return NaN.
864 * @param {String} string - A string to be returned in integer form.
865 * @return {Number|NaN} - The numeric value if the string can be converted, otherwise, returns NaN.
866 */
867 fluid.parseInteger = function (string) {
868 return isFinite(string) && ((string % 1) === 0) ? Number(string) : NaN;
869 };
870
871 /**
872 * Derived from Sindre Sorhus's round-to node module ( https://github.com/sindresorhus/round-to ).
873 * License: MIT
874 *
875 * Rounds the supplied number to at most the number of decimal places indicated by the scale, omitting any trailing 0s.
876 * There are three possible rounding methods described below: "round", "ceil", "floor"
877 * Round: Numbers are rounded away from 0 (i.e 0.5 -> 1, -0.5 -> -1).
878 * Ceil: Numbers are rounded up
879 * Floor: Numbers are rounded down
880 * If the scale is invalid (i.e falsey, not a number, negative value), it is treated as 0.
881 * If the scale is a floating point number, it is rounded to an integer.
882 *
883 * @param {Number} num - the number to be rounded
884 * @param {Number} scale - the maximum number of decimal places to round to.
885 * @param {String} [method] - (optional) Request a rounding method to use ("round", "ceil", "floor").
886 * If nothing or an invalid method is provided, it will default to "round".
887 * @return {Number} The num value rounded to the specified number of decimal places.
888 */
889 fluid.roundToDecimal = function (num, scale, method) {
890 // treat invalid scales as 0
891 scale = scale && scale >= 0 ? Math.round(scale) : 0;
892
893 if (method === "ceil" || method === "floor") {
894 // The following is derived from https://github.com/sindresorhus/round-to/blob/v2.0.0/index.js#L20
895 return Number(Math[method](num + "e" + scale) + "e-" + scale);
896 } else {
897 // The following is derived from https://github.com/sindresorhus/round-to/blob/v2.0.0/index.js#L17
898 var sign = num >= 0 ? 1 : -1; // manually calculating the sign because Math.sign is not supported in IE
899 return Number(sign * (Math.round(Math.abs(num) + "e" + scale) + "e-" + scale));
900 }
901 };
902
903 /**
904 * Copied from Underscore.js 1.4.3 - see licence at head of this file
905 *
906 * Will execute the passed in function after the specified amount of time since it was last executed.
907 * @param {Function} func - the function to execute
908 * @param {Number} wait - the number of milliseconds to wait before executing the function
909 * @param {Boolean} immediate - Whether to trigger the function at the start (true) or end (false) of
910 * the wait interval.
911 * @return {Function} - A function that can be called as though it were the original function.
912 */
913 fluid.debounce = function (func, wait, immediate) {
914 var timeout, result;
915 return function () {
916 var context = this, args = arguments;
917 var later = function () {
918 timeout = null;
919 if (!immediate) {
920 result = func.apply(context, args);
921 }
922 };
923 var callNow = immediate && !timeout;
924 clearTimeout(timeout);
925 timeout = setTimeout(later, wait);
926 if (callNow) {
927 result = func.apply(context, args);
928 }
929 return result;
930 };
931 };
932
933 /** Calls Object.freeze at each level of containment of the supplied object
934 * @param {Any} tofreeze - The material to freeze.
935 * @return {Any} - The supplied argument, recursively frozen.
936 */
937 fluid.freezeRecursive = function (tofreeze) {
938 if (fluid.isPlainObject(tofreeze)) {
939 fluid.each(tofreeze, function (value) {
940 fluid.freezeRecursive(value);
941 });
942 return Object.freeze(tofreeze);
943 } else {
944 return tofreeze;
945 }
946 };
947
948 /* A set of special "marker values" used in signalling in function arguments and return values,
949 * to partially compensate for JavaScript's lack of distinguished types. These should never appear
950 * in JSON structures or other kinds of static configuration. An API specifically documents if it
951 * accepts or returns any of these values, and if so, what its semantic is - most are of private
952 * use internal to the framework */
953
954 fluid.marker = function () {};
955
956 fluid.makeMarker = function (value, extra) {
957 var togo = Object.create(fluid.marker.prototype);
958 togo.value = value;
959 $.extend(togo, extra);
960 return Object.freeze(togo);
961 };
962
963 /* A special "marker object" representing that a distinguished
964 * (probably context-dependent) value should be substituted.
965 */
966 fluid.VALUE = fluid.makeMarker("VALUE");
967
968 /* A special "marker object" representing that no value is present (where
969 * signalling using the value "undefined" is not possible - e.g. the return value from a "strategy") */
970 fluid.NO_VALUE = fluid.makeMarker("NO_VALUE");
971
972 /* A marker indicating that a value requires to be expanded after component construction begins */
973 fluid.EXPAND = fluid.makeMarker("EXPAND");
974
975 /* Determine whether an object is any marker, or a particular marker - omit the
976 * 2nd argument to detect any marker
977 */
978 fluid.isMarker = function (totest, type) {
979 if (!(totest instanceof fluid.marker)) {
980 return false;
981 }
982 if (!type) {
983 return true;
984 }
985 return totest.value === type.value;
986 };
987
988 fluid.logLevelsSpec = {
989 "FATAL": 0,
990 "FAIL": 5,
991 "WARN": 10,
992 "IMPORTANT": 12, // The default logging "off" level - corresponds to the old "false"
993 "INFO": 15, // The default logging "on" level - corresponds to the old "true"
994 "TRACE": 20
995 };
996
997 /* A structure holding all supported log levels as supplied as a possible first argument to fluid.log
998 * Members with a higher value of the "priority" field represent lower priority logging levels */
999 // Moved down here since it uses fluid.transform and fluid.makeMarker on startup
1000 fluid.logLevel = fluid.transform(fluid.logLevelsSpec, function (value, key) {
1001 return fluid.makeMarker(key, {priority: value});
1002 });
1003 var logLevelStack = [fluid.logLevel.IMPORTANT]; // The stack of active logging levels, with the current level at index 0
1004
1005
1006 // Model functions
1007 fluid.model = {}; // cannot call registerNamespace yet since it depends on fluid.model
1008
1009 /* Copy a source "model" onto a target */
1010 fluid.model.copyModel = function (target, source) {
1011 fluid.clear(target);
1012 $.extend(true, target, source);
1013 };
1014
1015 /** Parse an EL expression separated by periods (.) into its component segments.
1016 * @param {String} EL - The EL expression to be split
1017 * @return {String[]} the component path expressions.
1018 * TODO: This needs to be upgraded to handle (the same) escaping rules (as RSF), so that
1019 * path segments containing periods and backslashes etc. can be processed, and be harmonised
1020 * with the more complex implementations in fluid.pathUtil(data binding).
1021 */
1022 fluid.model.parseEL = function (EL) {
1023 return EL === "" ? [] : String(EL).split(".");
1024 };
1025
1026 /* Compose an EL expression from two separate EL expressions. The returned
1027 * expression will be the one that will navigate the first expression, and then
1028 * the second, from the value reached by the first. Either prefix or suffix may be
1029 * the empty string */
1030 fluid.model.composePath = function (prefix, suffix) {
1031 return prefix === "" ? suffix : (suffix === "" ? prefix : prefix + "." + suffix);
1032 };
1033
1034 /* Compose any number of path segments, none of which may be empty */
1035 fluid.model.composeSegments = function () {
1036 return fluid.makeArray(arguments).join(".");
1037 };
1038
1039 /* Returns the index of the last occurrence of the period character . in the supplied string */
1040 fluid.lastDotIndex = function (path) {
1041 return path.lastIndexOf(".");
1042 };
1043
1044 /* Returns all of an EL path minus its final segment - if the path consists of just one segment, returns "" -
1045 * WARNING - this method does not follow escaping rules */
1046 fluid.model.getToTailPath = function (path) {
1047 var lastdot = fluid.lastDotIndex(path);
1048 return lastdot === -1 ? "" : path.substring(0, lastdot);
1049 };
1050
1051 /* Returns the very last path component of an EL path
1052 * WARNING - this method does not follow escaping rules */
1053 fluid.model.getTailPath = function (path) {
1054 var lastdot = fluid.lastDotIndex(path);
1055 return path.substring(lastdot + 1);
1056 };
1057
1058 /* Helpful alias for old-style API */
1059 fluid.path = fluid.model.composeSegments;
1060 fluid.composePath = fluid.model.composePath;
1061
1062
1063 // unsupported, NON-API function
1064 fluid.requireDataBinding = function () {
1065 fluid.fail("Please include DataBinding.js in order to operate complex model accessor configuration");
1066 };
1067
1068 fluid.model.setWithStrategy = fluid.model.getWithStrategy = fluid.requireDataBinding;
1069
1070 // unsupported, NON-API function
1071 fluid.model.resolvePathSegment = function (root, segment, create, origEnv) {
1072 // TODO: This branch incurs a huge cost that we incur across the whole framework, just to support the DOM binder
1073 // usage. We need to either do something "schematic" or move to proxies
1074 if (!origEnv && root.resolvePathSegment) {
1075 var togo = root.resolvePathSegment(segment);
1076 if (togo !== undefined) { // To resolve FLUID-6132
1077 return togo;
1078 }
1079 }
1080 if (create && root[segment] === undefined) {
1081 // This optimisation in this heavily used function has a fair effect
1082 return root[segment] = {};
1083 }
1084 return root[segment];
1085 };
1086
1087 // unsupported, NON-API function
1088 fluid.model.parseToSegments = function (EL, parseEL, copy) {
1089 return typeof(EL) === "number" || typeof(EL) === "string" ? parseEL(EL) : (copy ? fluid.makeArray(EL) : EL);
1090 };
1091
1092 // unsupported, NON-API function
1093 fluid.model.pathToSegments = function (EL, config) {
1094 var parser = config && config.parser ? config.parser.parse : fluid.model.parseEL;
1095 return fluid.model.parseToSegments(EL, parser);
1096 };
1097
1098 // Overall strategy skeleton for all implementations of fluid.get/set
1099 fluid.model.accessImpl = function (root, EL, newValue, config, initSegs, returnSegs, traverser) {
1100 var segs = fluid.model.pathToSegments(EL, config);
1101 var initPos = 0;
1102 if (initSegs) {
1103 initPos = initSegs.length;
1104 segs = initSegs.concat(segs);
1105 }
1106 var uncess = newValue === fluid.NO_VALUE ? 0 : 1;
1107 root = traverser(root, segs, initPos, config, uncess);
1108 if (newValue === fluid.NO_VALUE || newValue === fluid.VALUE) { // get or custom
1109 return returnSegs ? {root: root, segs: segs} : root;
1110 }
1111 else { // set
1112 root[segs[segs.length - 1]] = newValue;
1113 }
1114 };
1115
1116 // unsupported, NON-API function
1117 fluid.model.accessSimple = function (root, EL, newValue, environment, initSegs, returnSegs) {
1118 return fluid.model.accessImpl(root, EL, newValue, environment, initSegs, returnSegs, fluid.model.traverseSimple);
1119 };
1120
1121 // unsupported, NON-API function
1122 fluid.model.traverseSimple = function (root, segs, initPos, environment, uncess) {
1123 var origEnv = environment;
1124 var limit = segs.length - uncess;
1125 for (var i = 0; i < limit; ++i) {
1126 if (!root) {
1127 return undefined;
1128 }
1129 var segment = segs[i];
1130 if (environment && environment[segment]) {
1131 root = environment[segment];
1132 } else {
1133 root = fluid.model.resolvePathSegment(root, segment, uncess === 1, origEnv);
1134 }
1135 environment = null;
1136 }
1137 return root;
1138 };
1139
1140 fluid.model.setSimple = function (root, EL, newValue, environment, initSegs) {
1141 fluid.model.accessSimple(root, EL, newValue, environment, initSegs, false);
1142 };
1143
1144 /* Optimised version of fluid.get for uncustomised configurations */
1145
1146 fluid.model.getSimple = function (root, EL, environment, initSegs) {
1147 if (EL === null || EL === undefined || EL.length === 0) {
1148 return root;
1149 }
1150 return fluid.model.accessSimple(root, EL, fluid.NO_VALUE, environment, initSegs, false);
1151 };
1152
1153 /* Even more optimised version which assumes segs are parsed and no configuration */
1154 fluid.getImmediate = function (root, segs, i) {
1155 var limit = (i === undefined ? segs.length : i + 1);
1156 for (var j = 0; j < limit; ++j) {
1157 root = root ? root[segs[j]] : undefined;
1158 }
1159 return root;
1160 };
1161
1162 // unsupported, NON-API function
1163 // Returns undefined to signal complex configuration which needs to be farmed out to DataBinding.js
1164 // any other return represents an environment value AND a simple configuration we can handle here
1165 fluid.decodeAccessorArg = function (arg3) {
1166 return (!arg3 || arg3 === fluid.model.defaultGetConfig || arg3 === fluid.model.defaultSetConfig) ?
1167 null : (arg3.type === "environment" ? arg3.value : undefined);
1168 };
1169
1170 fluid.set = function (root, EL, newValue, config, initSegs) {
1171 var env = fluid.decodeAccessorArg(config);
1172 if (env === undefined) {
1173 fluid.model.setWithStrategy(root, EL, newValue, config, initSegs);
1174 } else {
1175 fluid.model.setSimple(root, EL, newValue, env, initSegs);
1176 }
1177 };
1178
1179 /** Evaluates an EL expression by fetching a dot-separated list of members
1180 * recursively from a provided root.
1181 * @param {Object} root - The root data structure in which the EL expression is to be evaluated
1182 * @param {String|Array} EL - The EL expression to be evaluated, or an array of path segments
1183 * @param {Object} [config] - An optional configuration or environment structure which can customise the fetch operation
1184 * @return {Any} The fetched data value.
1185 */
1186
1187 fluid.get = function (root, EL, config, initSegs) {
1188 var env = fluid.decodeAccessorArg(config);
1189 return env === undefined ?
1190 fluid.model.getWithStrategy(root, EL, config, initSegs)
1191 : fluid.model.accessImpl(root, EL, fluid.NO_VALUE, env, null, false, fluid.model.traverseSimple);
1192 };
1193
1194 fluid.getGlobalValue = function (path, env) {
1195 if (path) {
1196 env = env || fluid.environment;
1197 return fluid.get(fluid.global, path, {type: "environment", value: env});
1198 }
1199 };
1200
1201 /**
1202 * Allows for the binding to a "this-ist" function
1203 * @param {Object} obj - "this-ist" object to bind to
1204 * @param {Object} fnName - The name of the function to call.
1205 * @param {Object} args - Arguments to call the function with.
1206 * @return {Any} - The return value (if any) of the underlying function.
1207 */
1208 fluid.bind = function (obj, fnName, args) {
1209 return obj[fnName].apply(obj, fluid.makeArray(args));
1210 };
1211
1212 /**
1213 * Allows for the calling of a function from an EL expression "functionPath", with the arguments "args", scoped to an framework version "environment".
1214 * @param {Object} functionPath - An EL expression
1215 * @param {Object} args - An array of arguments to be applied to the function, specified in functionPath
1216 * @param {Object} [environment] - (optional) The object to scope the functionPath to (typically the framework root for version control)
1217 * @return {Any} - The return value from the invoked function.
1218 */
1219 fluid.invokeGlobalFunction = function (functionPath, args, environment) {
1220 var func = fluid.getGlobalValue(functionPath, environment);
1221 if (!func) {
1222 fluid.fail("Error invoking global function: " + functionPath + " could not be located");
1223 } else {
1224 return func.apply(null, fluid.isArrayable(args) ? args : fluid.makeArray(args));
1225 }
1226 };
1227
1228 /* Registers a new global function at a given path */
1229
1230 fluid.registerGlobalFunction = function (functionPath, func, env) {
1231 env = env || fluid.environment;
1232 fluid.set(fluid.global, functionPath, func, {type: "environment", value: env});
1233 };
1234
1235 fluid.setGlobalValue = fluid.registerGlobalFunction;
1236
1237 /* Ensures that an entry in the global namespace exists. If it does not, a new entry is created as {} and returned. If an existing
1238 * value is found, it is returned instead */
1239 fluid.registerNamespace = function (naimspace, env) {
1240 env = env || fluid.environment;
1241 var existing = fluid.getGlobalValue(naimspace, env);
1242 if (!existing) {
1243 existing = {};
1244 fluid.setGlobalValue(naimspace, existing, env);
1245 }
1246 return existing;
1247 };
1248
1249 // stubs for two functions in FluidDebugging.js
1250 fluid.dumpEl = fluid.identity;
1251 fluid.renderTimestamp = fluid.identity;
1252
1253 /*** The Fluid instance id ***/
1254
1255 // unsupported, NON-API function
1256 fluid.generateUniquePrefix = function () {
1257 return (Math.floor(Math.random() * 1e12)).toString(36) + "-";
1258 };
1259
1260 var fluid_prefix = fluid.generateUniquePrefix();
1261
1262 fluid.fluidInstance = fluid_prefix;
1263
1264 var fluid_guid = 1;
1265
1266 /* Allocate a string value that will be unique within this Infusion instance (frame or process), and
1267 * globally unique with high probability (50% chance of collision after a million trials) */
1268
1269 fluid.allocateGuid = function () {
1270 return fluid_prefix + (fluid_guid++);
1271 };
1272
1273 /*** The Fluid Event system. ***/
1274
1275 fluid.registerNamespace("fluid.event");
1276
1277 // Fluid priority system for encoding relative positions of, e.g. listeners, transforms, options, in lists
1278
1279 fluid.extremePriority = 4e9; // around 2^32 - allows headroom of 21 fractional bits for sub-priorities
1280 fluid.priorityTypes = {
1281 first: -1,
1282 last: 1,
1283 before: 0,
1284 after: 0
1285 };
1286 // TODO: This should be properly done with defaults blocks and a much more performant fluid.indexDefaults
1287 fluid.extremalPriorities = {
1288 // a built-in definition to allow test infrastructure "last" listeners to sort after all impl listeners, and authoring/debugging listeners to sort after those
1289 // these are "priority intensities", and will be flipped for "first" listeners
1290 none: 0,
1291 testing: 10,
1292 authoring: 20
1293 };
1294
1295 // unsupported, NON-API function
1296 // TODO: Note - no "fixedOnly = true" sites remain in the framework
1297 fluid.parsePriorityConstraint = function (constraint, fixedOnly, site) {
1298 var segs = constraint.split(":");
1299 var type = segs[0];
1300 var lookup = fluid.priorityTypes[type];
1301 if (lookup === undefined) {
1302 fluid.fail("Invalid constraint type in priority field " + constraint + ": the only supported values are " + fluid.keys(fluid.priorityTypes).join(", ") + " or numeric");
1303 }
1304 if (fixedOnly && lookup === 0) {
1305 fluid.fail("Constraint type in priority field " + constraint + " is not supported in a " + site + " record - you must use either a numeric value or first, last");
1306 }
1307 return {
1308 type: segs[0],
1309 target: segs[1]
1310 };
1311 };
1312
1313 // unsupported, NON-API function
1314 fluid.parsePriority = function (priority, count, fixedOnly, site) {
1315 priority = priority || 0;
1316 var togo = {
1317 count: count || 0,
1318 fixed: null,
1319 constraint: null,
1320 site: site
1321 };
1322 if (typeof(priority) === "number") {
1323 togo.fixed = -priority;
1324 } else {
1325 togo.constraint = fluid.parsePriorityConstraint(priority, fixedOnly, site);
1326 }
1327 var multiplier = togo.constraint ? fluid.priorityTypes[togo.constraint.type] : 0;
1328 if (multiplier !== 0) {
1329 var target = togo.constraint.target || "none";
1330 var extremal = fluid.extremalPriorities[target];
1331 if (extremal === undefined) {
1332 fluid.fail("Unrecognised extremal priority target " + target + ": the currently supported values are " + fluid.keys(fluid.extremalPriorities).join(", ") + ": register your value in fluid.extremalPriorities");
1333 }
1334 togo.fixed = multiplier * (fluid.extremePriority + extremal);
1335 }
1336 if (togo.fixed !== null) {
1337 togo.fixed += togo.count / 1024; // use some fractional bits to encode count bias
1338 }
1339
1340 return togo;
1341 };
1342
1343 fluid.renderPriority = function (parsed) {
1344 return parsed.constraint ? (parsed.constraint.target ? parsed.constraint.type + ":" + parsed.constraint.target : parsed.constraint.type ) : Math.floor(parsed.fixed);
1345 };
1346
1347 // unsupported, NON-API function
1348 fluid.compareByPriority = function (recA, recB) {
1349 if (recA.priority.fixed !== null && recB.priority.fixed !== null) {
1350 return recA.priority.fixed - recB.priority.fixed;
1351 } else { // sort constraint records to the end
1352 // relies on JavaScript boolean coercion rules (ECMA 9.3 toNumber)
1353 return (recA.priority.fixed === null) - (recB.priority.fixed === null);
1354 }
1355 };
1356
1357 fluid.honourConstraint = function (array, firstConstraint, c) {
1358 var constraint = array[c].priority.constraint;
1359 var matchIndex = fluid.find(array, function (element, index) {
1360 return element.namespace === constraint.target ? index : undefined;
1361 }, -1);
1362 if (matchIndex === -1) { // TODO: We should report an error during firing if this condition persists until then
1363 return true;
1364 } else if (matchIndex >= firstConstraint) {
1365 return false;
1366 } else {
1367 var offset = constraint.type === "after" ? 1 : 0;
1368 var target = matchIndex + offset;
1369 var temp = array[c];
1370 for (var shift = c; shift >= target; --shift) {
1371 array[shift] = array[shift - 1];
1372 }
1373 array[target] = temp;
1374 return true;
1375 }
1376 };
1377
1378 // unsupported, NON-API function
1379 // Priorities accepted from users have higher numbers representing high priority (sort first) -
1380 fluid.sortByPriority = function (array) {
1381 fluid.stableSort(array, fluid.compareByPriority);
1382
1383 var firstConstraint = fluid.find(array, function (element, index) {
1384 return element.priority.constraint && fluid.priorityTypes[element.priority.constraint.type] === 0 ? index : undefined;
1385 }, array.length);
1386
1387 while (true) {
1388 if (firstConstraint === array.length) {
1389 return array;
1390 }
1391 var oldFirstConstraint = firstConstraint;
1392 for (var c = firstConstraint; c < array.length; ++c) {
1393 var applied = fluid.honourConstraint(array, firstConstraint, c);
1394 if (applied) {
1395 ++firstConstraint;
1396 }
1397 }
1398 if (firstConstraint === oldFirstConstraint) {
1399 var holders = array.slice(firstConstraint);
1400 fluid.fail("Could not find targets for any constraints in " + holders[0].priority.site + " ", holders, ": none of the targets (" + fluid.getMembers(holders, "priority.constraint.target").join(", ") +
1401 ") matched any namespaces of the elements in (", array.slice(0, firstConstraint), ") - this is caused by either an invalid or circular reference");
1402 }
1403 }
1404 };
1405
1406 /** Parse a hash containing prioritised records (for example, as found in a ContextAwareness record) and return a sorted array of these records in priority order.
1407 * @param {Object} records - A hash of key names to prioritised records. Each record may contain an member `namespace` - if it does not, the namespace will be taken from the
1408 * record's key. It may also contain a `String` member `priority` encoding a priority with respect to these namespaces as document at http://docs.fluidproject.org/infusion/development/Priorities.html .
1409 * @param {String} name - A human-readable name describing the supplied records, which will be incorporated into the message of any error encountered when resolving the priorities
1410 * @return {Array} An array of the same elements supplied to `records`, sorted into priority order. The supplied argument `records` will not be modified.
1411 */
1412 fluid.parsePriorityRecords = function (records, name) {
1413 var array = fluid.hashToArray(records, "namespace", function (newElement, oldElement) {
1414 $.extend(newElement, oldElement);
1415 newElement.priority = fluid.parsePriority(oldElement.priority, 0, false, name);
1416 });
1417 fluid.sortByPriority(array);
1418 return array;
1419 };
1420
1421 fluid.event.identifyListener = function (listener, soft) {
1422 if (typeof(listener) !== "string" && !listener.$$fluid_guid && !soft) {
1423 listener.$$fluid_guid = fluid.allocateGuid();
1424 }
1425 return listener.$$fluid_guid;
1426 };
1427
1428 // unsupported, NON-API function
1429 fluid.event.impersonateListener = function (origListener, newListener) {
1430 fluid.event.identifyListener(origListener);
1431 newListener.$$fluid_guid = origListener.$$fluid_guid;
1432 };
1433
1434
1435 // unsupported, NON-API function
1436 fluid.event.sortListeners = function (listeners) {
1437 var togo = [];
1438 fluid.each(listeners, function (oneNamespace) {
1439 var headHard; // notify only the first listener with hard namespace - or else all if all are soft
1440 for (var i = 0; i < oneNamespace.length; ++i) {
1441 var thisListener = oneNamespace[i];
1442 if (!thisListener.softNamespace && !headHard) {
1443 headHard = thisListener;
1444 }
1445 }
1446 if (headHard) {
1447 togo.push(headHard);
1448 } else {
1449 togo = togo.concat(oneNamespace);
1450 }
1451 });
1452 return fluid.sortByPriority(togo);
1453 };
1454
1455 // unsupported, NON-API function
1456 fluid.event.resolveListener = function (listener) {
1457 var listenerName = listener.globalName || (typeof(listener) === "string" ? listener : null);
1458 if (listenerName) {
1459 var listenerFunc = fluid.getGlobalValue(listenerName);
1460 if (!listenerFunc) {
1461 fluid.fail("Unable to look up name " + listenerName + " as a global function");
1462 } else {
1463 listener = listenerFunc;
1464 }
1465 }
1466 return listener;
1467 };
1468
1469 /* Generate a name for a component for debugging purposes */
1470 fluid.nameComponent = function (that) {
1471 return that ? "component with typename " + that.typeName + " and id " + that.id : "[unknown component]";
1472 };
1473
1474 fluid.event.nameEvent = function (that, eventName) {
1475 return eventName + " of " + fluid.nameComponent(that);
1476 };
1477
1478 /** Construct an "event firer" object which can be used to register and deregister
1479 * listeners, to which "events" can be fired. These events consist of an arbitrary
1480 * function signature. General documentation on the Fluid events system is at
1481 * http://docs.fluidproject.org/infusion/development/InfusionEventSystem.html .
1482 * @param {Object} options - A structure to configure this event firer. Supported fields:
1483 * {String} name - a readable name for this firer to be used in diagnostics and debugging
1484 * {Boolean} preventable - If <code>true</code> the return value of each handler will
1485 * be checked for <code>false</code> in which case further listeners will be shortcircuited, and this
1486 * will be the return value of fire()
1487 * @return {Object} - The newly-created event firer.
1488 */
1489 fluid.makeEventFirer = function (options) {
1490 options = options || {};
1491 var name = options.name || "<anonymous>";
1492 var that;
1493
1494 var lazyInit = function () { // Lazy init function to economise on object references for events which are never listened to
1495 // The authoritative list of all listeners, a hash indexed by namespace, looking up to a stack (array) of
1496 // listener records in "burial order"
1497 that.listeners = {};
1498 // An index of all listeners by "id" - we should consider removing this since it is only used during removal
1499 // and because that.listeners is a hash of stacks we can't really accelerate removal by much
1500 that.byId = {};
1501 // The "live" list of listeners which will be notified in order on any firing. Recomputed on any use of
1502 // addListener/removeListener
1503 that.sortedListeners = [];
1504 // arguments after 3rd are not part of public API
1505 // listener as Object is used only by ChangeApplier to tunnel path, segs, etc as part of its "spec"
1506 /** Adds a listener to this event.
1507 * @param {Function|String} listener - The listener function to be added, or a global name resolving to a function. The signature of the function is arbitrary and matches that sent to event.fire()
1508 * @param {String} namespace - (Optional) A namespace for this listener. At most one listener with a particular namespace can be active on an event at one time. Removing successively added listeners with a particular
1509 * namespace will expose previously added ones in a stack idiom
1510 * @param {String|Number} priority - A priority for the listener relative to others, perhaps expressed with a constraint relative to the namespace of another - see
1511 * http://docs.fluidproject.org/infusion/development/Priorities.html
1512 * @param {String} softNamespace - An unsupported internal option that is not part of the public API.
1513 * @param {String} listenerId - An unsupported internal option that is not part of the public API.
1514 */
1515 that.addListener = function (listener, namespace, priority, softNamespace, listenerId) {
1516 var record;
1517 if (that.destroyed) {
1518 fluid.fail("Cannot add listener to destroyed event firer " + that.name);
1519 }
1520 if (!listener) {
1521 return;
1522 }
1523 if (fluid.isPlainObject(listener, true) && !fluid.isApplicable(listener)) {
1524 record = listener;
1525 listener = record.listener;
1526 namespace = record.namespace;
1527 priority = record.priority;
1528 softNamespace = record.softNamespace;
1529 listenerId = record.listenerId;
1530 }
1531 if (typeof(listener) === "string") {
1532 listener = {globalName: listener};
1533 }
1534 var id = listenerId || fluid.event.identifyListener(listener);
1535 namespace = namespace || id;
1536 record = $.extend(record || {}, {
1537 namespace: namespace,
1538 listener: listener,
1539 softNamespace: softNamespace,
1540 listenerId: listenerId,
1541 priority: fluid.parsePriority(priority, that.sortedListeners.length, false, "listeners")
1542 });
1543 that.byId[id] = record;
1544
1545 var thisListeners = (that.listeners[namespace] = fluid.makeArray(that.listeners[namespace]));
1546 thisListeners[softNamespace ? "push" : "unshift"] (record);
1547
1548 that.sortedListeners = fluid.event.sortListeners(that.listeners);
1549 };
1550 that.addListener.apply(null, arguments);
1551 };
1552 that = {
1553 eventId: fluid.allocateGuid(),
1554 name: name,
1555 ownerId: options.ownerId,
1556 typeName: "fluid.event.firer",
1557 destroy: function () {
1558 that.destroyed = true;
1559 },
1560 addListener: function () {
1561 lazyInit.apply(null, arguments);
1562 },
1563 /** Removes a listener previously registered with this event.
1564 * @param {Function|String} toremove - Either the listener function, the namespace of a listener (in which case a previous listener with that namespace may be uncovered) or an id sent to the undocumented
1565 * `listenerId` argument of `addListener
1566 */
1567 // Can be supplied either listener, namespace, or id (which may match either listener function's guid or original listenerId argument)
1568 removeListener: function (listener) {
1569 if (!that.listeners) { return; }
1570 var namespace, id, record;
1571 if (typeof (listener) === "string") {
1572 namespace = listener;
1573 record = that.listeners[namespace];
1574 if (!record) { // it was an id and not a namespace - take the namespace from its record later
1575 id = namespace;
1576 namespace = null;
1577 }
1578 }
1579 else if (typeof(listener) === "function") {
1580 id = fluid.event.identifyListener(listener, true);
1581 if (!id) {
1582 fluid.fail("Cannot remove unregistered listener function ", listener, " from event " + that.name);
1583 }
1584 }
1585 var rec = that.byId[id];
1586 var softNamespace = rec && rec.softNamespace;
1587 namespace = namespace || (rec && rec.namespace) || id;
1588 delete that.byId[id];
1589 record = that.listeners[namespace];
1590 if (record) {
1591 if (softNamespace) {
1592 fluid.remove_if(record, function (thisLis) {
1593 return thisLis.listener.$$fluid_guid === id || thisLis.listenerId === id;
1594 });
1595 } else {
1596 record.shift();
1597 }
1598 if (record.length === 0) {
1599 delete that.listeners[namespace];
1600 }
1601 }
1602 that.sortedListeners = fluid.event.sortListeners(that.listeners);
1603 },
1604 /* Fires this event to all listeners which are active. They will be notified in order of priority. The signature of this method is free. */
1605 fire: function () {
1606 var listeners = that.sortedListeners;
1607 if (!listeners || that.destroyed) { return; }
1608 for (var i = 0; i < listeners.length; ++i) {
1609 var lisrec = listeners[i];
1610 if (typeof(lisrec.listener) !== "function") {
1611 lisrec.listener = fluid.event.resolveListener(lisrec.listener);
1612 }
1613 var listener = lisrec.listener;
1614 var ret = listener.apply(null, arguments);
1615 var value;
1616 if (options.preventable && ret === false || that.destroyed) {
1617 value = false;
1618 }
1619 if (value !== undefined) {
1620 return value;
1621 }
1622 }
1623 }
1624 };
1625 return that;
1626 };
1627
1628 // unsupported, NON-API function
1629 // Fires to an event which may not be instantiated (in which case no-op) - primary modern usage is to resolve FLUID-5904
1630 fluid.fireEvent = function (component, eventName, args) {
1631 var firer = component.events[eventName];
1632 if (firer) {
1633 firer.fire.apply(null, fluid.makeArray(args));
1634 }
1635 };
1636
1637 // unsupported, NON-API function
1638 fluid.event.addListenerToFirer = function (firer, value, namespace, wrapper) {
1639 wrapper = wrapper || fluid.identity;
1640 if (fluid.isArrayable(value)) {
1641 for (var i = 0; i < value.length; ++i) {
1642 fluid.event.addListenerToFirer(firer, value[i], namespace, wrapper);
1643 }
1644 } else if (typeof (value) === "function" || typeof (value) === "string") {
1645 wrapper(firer).addListener(value, namespace);
1646 } else if (value && typeof (value) === "object") {
1647 wrapper(firer).addListener(value.listener, namespace || value.namespace, value.priority, value.softNamespace, value.listenerId);
1648 }
1649 };
1650
1651 // unsupported, NON-API function - non-IOC passthrough
1652 fluid.event.resolveListenerRecord = function (records) {
1653 return { records: records };
1654 };
1655
1656 fluid.expandImmediate = function (material) {
1657 fluid.fail("fluid.expandImmediate could not be loaded - please include FluidIoC.js in order to operate IoC-driven event with descriptor " + material);
1658 };
1659
1660 // unsupported, NON-API function
1661 fluid.mergeListeners = function (that, events, listeners) {
1662 fluid.each(listeners, function (value, key) {
1663 var firer, namespace;
1664 if (fluid.isIoCReference(key)) {
1665 firer = fluid.expandImmediate(key, that);
1666 if (!firer) {
1667 fluid.fail("Error in listener record: key " + key + " could not be looked up to an event firer - did you miss out \"events.\" when referring to an event firer?");
1668 }
1669 } else {
1670 var keydot = key.indexOf(".");
1671
1672 if (keydot !== -1) {
1673 namespace = key.substring(keydot + 1);
1674 key = key.substring(0, keydot);
1675 }
1676 if (!events[key]) {
1677 fluid.fail("Listener registered for event " + key + " which is not defined for this component");
1678 }
1679 firer = events[key];
1680 }
1681 var record = fluid.event.resolveListenerRecord(value, that, key, namespace, true);
1682 fluid.event.addListenerToFirer(firer, record.records, namespace, record.adderWrapper);
1683 });
1684 };
1685
1686 // unsupported, NON-API function
1687 fluid.eventFromRecord = function (eventSpec, eventKey, that) {
1688 var isIoCEvent = eventSpec && (typeof (eventSpec) !== "string" || fluid.isIoCReference(eventSpec));
1689 var event;
1690 if (isIoCEvent) {
1691 if (!fluid.event.resolveEvent) {
1692 fluid.fail("fluid.event.resolveEvent could not be loaded - please include FluidIoC.js in order to operate IoC-driven event with descriptor ",
1693 eventSpec);
1694 } else {
1695 event = fluid.event.resolveEvent(that, eventKey, eventSpec);
1696 }
1697 } else {
1698 event = fluid.makeEventFirer({
1699 name: fluid.event.nameEvent(that, eventKey),
1700 preventable: eventSpec === "preventable",
1701 ownerId: that.id
1702 });
1703 }
1704 return event;
1705 };
1706
1707 // unsupported, NON-API function - this is patched from FluidIoC.js
1708 fluid.instantiateFirers = function (that, options) {
1709 fluid.each(options.events, function (eventSpec, eventKey) {
1710 that.events[eventKey] = fluid.eventFromRecord(eventSpec, eventKey, that);
1711 });
1712 };
1713
1714 // unsupported, NON-API function
1715 fluid.mergeListenerPolicy = function (target, source, key) {
1716 if (typeof (key) !== "string") {
1717 fluid.fail("Error in listeners declaration - the keys in this structure must resolve to event names - got " + key + " from ", source);
1718 }
1719 // cf. triage in mergeListeners
1720 var hasNamespace = !fluid.isIoCReference(key) && key.indexOf(".") !== -1;
1721 return hasNamespace ? (source || target) : fluid.arrayConcatPolicy(target, source);
1722 };
1723
1724 // unsupported, NON-API function
1725 fluid.makeMergeListenersPolicy = function (merger, modelRelay) {
1726 return function (target, source) {
1727 target = target || {};
1728 if (modelRelay && (fluid.isArrayable(source) || typeof(source.target) === "string")) { // This form allowed for modelRelay
1729 target[""] = merger(target[""], source, "");
1730 } else {
1731 fluid.each(source, function (listeners, key) {
1732 target[key] = merger(target[key], listeners, key);
1733 });
1734 }
1735 return target;
1736 };
1737 };
1738
1739 fluid.validateListenersImplemented = function (that) {
1740 var errors = [];
1741 fluid.each(that.events, function (event, name) {
1742 fluid.each(event.sortedListeners, function (lisrec) {
1743 if (lisrec.listener === fluid.notImplemented || lisrec.listener.globalName === "fluid.notImplemented") {
1744 errors.push({name: name, namespace: lisrec.namespace, componentSource: fluid.model.getSimple(that.options.listeners, [name + "." + lisrec.namespace, 0, "componentSource"])});
1745 }
1746 });
1747 });
1748 return errors;
1749 };
1750
1751 /* Removes duplicated and empty elements from an already sorted array. */
1752 fluid.unique = function (array) {
1753 return fluid.remove_if(array, function (element, i) {
1754 return !element || i > 0 && element === array[i - 1];
1755 });
1756 };
1757
1758 fluid.arrayConcatPolicy = function (target, source) {
1759 return fluid.makeArray(target).concat(fluid.makeArray(source));
1760 };
1761
1762 /*** FLUID LOGGING SYSTEM ***/
1763
1764 // This event represents the process of resolving the action of a request to fluid.log. Each listener shares
1765 // access to an array, shallow-copied from the original arguments list to fluid.log, which is assumed writeable
1766 // and which they may splice, transform, etc. before it is dispatched to the listener with namespace "log" which
1767 // actually performs the logging action
1768 fluid.loggingEvent = fluid.makeEventFirer({name: "logging event"});
1769
1770 fluid.addTimestampArg = function (args) {
1771 var arg0 = fluid.renderTimestamp(new Date()) + ": ";
1772 args.unshift(arg0);
1773 };
1774
1775 fluid.loggingEvent.addListener(fluid.doBrowserLog, "log");
1776 // Not intended to be overridden - just a positional placeholder so that the priority of
1777 // actions filtering the log arguments before dispatching may be referred to it
1778 fluid.loggingEvent.addListener(fluid.identity, "filterArgs", "before:log");
1779 fluid.loggingEvent.addListener(fluid.addTimestampArg, "addTimestampArg", "after:filterArgs");
1780
1781 /*** FLUID ERROR SYSTEM ***/
1782
1783 fluid.failureEvent = fluid.makeEventFirer({name: "failure event"});
1784
1785 fluid.failureEvent.addListener(fluid.builtinFail, "fail");
1786 fluid.failureEvent.addListener(fluid.logFailure, "log", "before:fail");
1787
1788 /**
1789 * Configure the behaviour of fluid.fail by pushing or popping a disposition record onto a stack.
1790 * @param {Number|Function} condition - Supply either a function, which will be called with two arguments, args (the complete arguments to
1791 * fluid.fail) and activity, an array of strings describing the current framework invocation state.
1792 * Or, the argument may be the number <code>-1</code> indicating that the previously supplied disposition should
1793 * be popped off the stack
1794 */
1795 fluid.pushSoftFailure = function (condition) {
1796 if (typeof (condition) === "function") {
1797 fluid.failureEvent.addListener(condition, "fail");
1798 } else if (condition === -1) {
1799 fluid.failureEvent.removeListener("fail");
1800 } else if (typeof(condition) === "boolean") {
1801 fluid.fail("pushSoftFailure with boolean value is no longer supported");
1802 }
1803 };
1804
1805 /*** DEFAULTS AND OPTIONS MERGING SYSTEM ***/
1806
1807 // A function to tag the types of all Fluid components
1808 fluid.componentConstructor = function () {};
1809
1810 /** Create a "type tag" component with no state but simply a type name and id. The most
1811 * minimal form of Fluid component */
1812 // No longer a publically supported function - we don't abolish this because it is too annoying to prevent
1813 // circularity during the bootup of the IoC system if we try to construct full components before it is complete
1814 // unsupported, non-API function
1815 fluid.typeTag = function (name) {
1816 var that = Object.create(fluid.componentConstructor.prototype);
1817 that.typeName = name;
1818 that.id = fluid.allocateGuid();
1819 return that;
1820 };
1821
1822 var gradeTick = 1; // tick counter for managing grade cache invalidation
1823 var gradeTickStore = {};
1824
1825 fluid.defaultsStore = {};
1826
1827 // unsupported, NON-API function
1828 // Recursively builds up "gradeStructure" in first argument. 2nd arg receives gradeNames to be resolved, with stronger grades at right (defaults order)
1829 // builds up gradeStructure.gradeChain pushed from strongest to weakest (reverse defaults order)
1830 fluid.resolveGradesImpl = function (gs, gradeNames) {
1831 gradeNames = fluid.makeArray(gradeNames);
1832 for (var i = gradeNames.length - 1; i >= 0; --i) { // from stronger to weaker
1833 var gradeName = gradeNames[i];
1834 if (gradeName && !gs.gradeHash[gradeName]) {
1835 var isDynamic = fluid.isIoCReference(gradeName);
1836 var options = (isDynamic ? null : fluid.rawDefaults(gradeName)) || {};
1837 var thisTick = gradeTickStore[gradeName] || (gradeTick - 1); // a nonexistent grade is recorded as just previous to current
1838 gs.lastTick = Math.max(gs.lastTick, thisTick);
1839 gs.gradeHash[gradeName] = true;
1840 gs.gradeChain.push(gradeName);
1841 var oGradeNames = fluid.makeArray(options.gradeNames);
1842 for (var j = oGradeNames.length - 1; j >= 0; --j) { // from stronger to weaker grades
1843 // TODO: in future, perhaps restore mergedDefaultsCache function of storing resolved gradeNames for bare grades
1844 fluid.resolveGradesImpl(gs, oGradeNames[j]);
1845 }
1846 }
1847 }
1848 return gs;
1849 };
1850
1851 // unsupported, NON-API function
1852 fluid.resolveGradeStructure = function (defaultName, gradeNames) {
1853 var gradeStruct = {
1854 lastTick: 0,
1855 gradeChain: [],
1856 gradeHash: {}
1857 };
1858 // stronger grades appear to the right in defaults - dynamic grades are stronger still - FLUID-5085
1859 // we supply these to resolveGradesImpl with strong grades at the right
1860 fluid.resolveGradesImpl(gradeStruct, [defaultName].concat(fluid.makeArray(gradeNames)));
1861 gradeStruct.gradeChain.reverse(); // reverse into defaults order
1862 return gradeStruct;
1863 };
1864
1865 fluid.hasGrade = function (options, gradeName) {
1866 return !options || !options.gradeNames ? false : fluid.contains(options.gradeNames, gradeName);
1867 };
1868
1869 // unsupported, NON-API function
1870 fluid.resolveGrade = function (defaults, defaultName, gradeNames) {
1871 var gradeStruct = fluid.resolveGradeStructure(defaultName, gradeNames);
1872 // TODO: Fault in the merging algorithm does not actually treat arguments as immutable - failure in FLUID-5082 tests
1873 // due to listeners mergePolicy
1874 var mergeArgs = fluid.transform(gradeStruct.gradeChain, fluid.rawDefaults, fluid.copy);
1875 fluid.remove_if(mergeArgs, function (options) {
1876 return !options;
1877 });
1878 var mergePolicy = {};
1879 for (var i = 0; i < mergeArgs.length; ++i) {
1880 if (mergeArgs[i] && mergeArgs[i].mergePolicy) {
1881 mergePolicy = $.extend(true, mergePolicy, mergeArgs[i].mergePolicy);
1882 }
1883 }
1884 mergeArgs = [mergePolicy, {}].concat(mergeArgs);
1885 var mergedDefaults = fluid.merge.apply(null, mergeArgs);
1886 mergedDefaults.gradeNames = gradeStruct.gradeChain; // replace these since mergePolicy version is inadequate
1887 fluid.freezeRecursive(mergedDefaults);
1888 return {defaults: mergedDefaults, lastTick: gradeStruct.lastTick};
1889 };
1890
1891 fluid.mergedDefaultsCache = {};
1892
1893 // unsupported, NON-API function
1894 fluid.gradeNamesToKey = function (defaultName, gradeNames) {
1895 return defaultName + "|" + gradeNames.join("|");
1896 };
1897
1898 // unsupported, NON-API function
1899 // The main entry point to acquire the fully merged defaults for a combination of defaults plus mixin grades - from FluidIoC.js as well as recursively within itself
1900 fluid.getMergedDefaults = function (defaultName, gradeNames) {
1901 gradeNames = fluid.makeArray(gradeNames);
1902 var key = fluid.gradeNamesToKey(defaultName, gradeNames);
1903 var mergedDefaults = fluid.mergedDefaultsCache[key];
1904 if (mergedDefaults) {
1905 var lastTick = 0; // check if cache should be invalidated through real latest tick being later than the one stored
1906 var searchGrades = mergedDefaults.defaults.gradeNames || [];
1907 for (var i = 0; i < searchGrades.length; ++i) {
1908 lastTick = Math.max(lastTick, gradeTickStore[searchGrades[i]] || 0);
1909 }
1910 if (lastTick > mergedDefaults.lastTick) {
1911 if (fluid.passLogLevel(fluid.logLevel.TRACE)) {
1912 fluid.log(fluid.logLevel.TRACE, "Clearing cache for component " + defaultName + " with gradeNames ", searchGrades);
1913 }
1914 mergedDefaults = null;
1915 }
1916 }
1917 if (!mergedDefaults) {
1918 var defaults = fluid.rawDefaults(defaultName);
1919 if (!defaults) {
1920 return defaults;
1921 }
1922 mergedDefaults = fluid.mergedDefaultsCache[key] = fluid.resolveGrade(defaults, defaultName, gradeNames);
1923 }
1924 return mergedDefaults.defaults;
1925 };
1926
1927 // unsupported, NON-API function
1928 /** Upgrades an element of an IoC record which designates a function to prepare for a {func, args} representation.
1929 * @param {Any} rec - If the record is of a primitive type,
1930 * @param {String} key - The key in the returned record to hold the function, this will default to `funcName` if `rec` is a `string` *not*
1931 * holding an IoC reference, or `func` otherwise
1932 * @return {Object} The original `rec` if it was not of primitive type, else a record holding { key : rec } if it was of primitive type.
1933 */
1934 fluid.upgradePrimitiveFunc = function (rec, key) {
1935 if (rec && fluid.isPrimitive(rec)) {
1936 var togo = {};
1937 togo[key || (typeof(rec) === "string" && rec.charAt(0) !== "{" ? "funcName" : "func")] = rec;
1938 togo.args = fluid.NO_VALUE;
1939 return togo;
1940 } else {
1941 return rec;
1942 }
1943 };
1944
1945 // unsupported, NON-API function
1946 // Modify supplied options record to include "componentSource" annotation required by FLUID-5082
1947 // TODO: This function really needs to act recursively in order to catch listeners registered for subcomponents - fix with FLUID-5614
1948 fluid.annotateListeners = function (componentName, options) {
1949 options.listeners = fluid.transform(options.listeners, function (record) {
1950 var togo = fluid.makeArray(record);
1951 return fluid.transform(togo, function (onerec) {
1952 onerec = fluid.upgradePrimitiveFunc(onerec, "listener");
1953 onerec.componentSource = componentName;
1954 return onerec;
1955 });
1956 });
1957 options.invokers = fluid.transform(options.invokers, function (record) {
1958 record = fluid.upgradePrimitiveFunc(record);
1959 if (record) {
1960 record.componentSource = componentName;
1961 }
1962 return record;
1963 });
1964 };
1965
1966 // unsupported, NON-API function
1967 fluid.rawDefaults = function (componentName) {
1968 var entry = fluid.defaultsStore[componentName];
1969 return entry && entry.options;
1970 };
1971
1972 // unsupported, NON-API function
1973 fluid.registerRawDefaults = function (componentName, options) {
1974 fluid.pushActivity("registerRawDefaults", "registering defaults for grade %componentName with options %options",
1975 {componentName: componentName, options: options});
1976 var optionsCopy = fluid.expandCompact ? fluid.expandCompact(options) : fluid.copy(options);
1977 fluid.annotateListeners(componentName, optionsCopy);
1978 var callerInfo = fluid.getCallerInfo && fluid.getCallerInfo(6);
1979 fluid.defaultsStore[componentName] = {
1980 options: optionsCopy,
1981 callerInfo: callerInfo
1982 };
1983 gradeTickStore[componentName] = gradeTick++;
1984 fluid.popActivity();
1985 };
1986
1987 // unsupported, NON-API function
1988 fluid.doIndexDefaults = function (defaultName, defaults, index, indexSpec) {
1989 var requiredGrades = fluid.makeArray(indexSpec.gradeNames);
1990 for (var i = 0; i < requiredGrades.length; ++i) {
1991 if (!fluid.hasGrade(defaults, requiredGrades[i])) { return; }
1992 }
1993 var indexFunc = typeof(indexSpec.indexFunc) === "function" ? indexSpec.indexFunc : fluid.getGlobalValue(indexSpec.indexFunc);
1994 var keys = indexFunc(defaults) || [];
1995 for (var j = 0; j < keys.length; ++j) {
1996 fluid.pushArray(index, keys[j], defaultName);
1997 }
1998 };
1999
2000 /** Evaluates an index specification over all the defaults records registered into the system.
2001 * @param {String} indexName - The name of this index record (currently ignored)
2002 * @param {Object} indexSpec - Specification of the index to be performed - fields:
2003 * gradeNames: {String|String[]} List of grades that must be matched by this indexer
2004 * indexFunc: {String|Function} An index function which accepts a defaults record and returns an array of keys
2005 * @return A structure indexing keys to arrays of matched gradenames
2006 */
2007 // The expectation is that this function is extremely rarely used with respect to registration of defaults
2008 // in the system, so currently we do not make any attempts to cache the results. The field "indexName" is
2009 // supplied in case a future implementation chooses to implement caching
2010 fluid.indexDefaults = function (indexName, indexSpec) {
2011 var index = {};
2012 for (var defaultName in fluid.defaultsStore) {
2013 var defaults = fluid.getMergedDefaults(defaultName);
2014 fluid.doIndexDefaults(defaultName, defaults, index, indexSpec);
2015 }
2016 return index;
2017 };
2018
2019 /**
2020 * Retrieves and stores a grade's configuration centrally.
2021 * @param {String} componentName - The name of the grade whose options are to be read or written
2022 * @param {Object} [options] - An (optional) object containing the options to be set
2023 * @return {Object|undefined} - If `options` is omitted, returns the defaults for `componentName`. Otherwise,
2024 * creates an instance of the named component with the supplied options.
2025 */
2026 fluid.defaults = function (componentName, options) {
2027 if (options === undefined) {
2028 return fluid.getMergedDefaults(componentName);
2029 }
2030 else {
2031 if (options && options.options) {
2032 fluid.fail("Probable error in options structure for " + componentName +
2033 " with option named \"options\" - perhaps you meant to write these options at top level in fluid.defaults? - ", options);
2034 }
2035 fluid.registerRawDefaults(componentName, options);
2036 var gradedDefaults = fluid.getMergedDefaults(componentName);
2037 if (!fluid.hasGrade(gradedDefaults, "fluid.function")) {
2038 fluid.makeComponentCreator(componentName);
2039 }
2040 }
2041 };
2042
2043 fluid.makeComponentCreator = function (componentName) {
2044 var creator = function () {
2045 var defaults = fluid.getMergedDefaults(componentName);
2046 if (!defaults.gradeNames || defaults.gradeNames.length === 0) {
2047 fluid.fail("Cannot make component creator for type " + componentName + " which does not have any gradeNames defined");
2048 } else if (!defaults.initFunction) {
2049 var blankGrades = [];
2050 for (var i = 0; i < defaults.gradeNames.length; ++i) {
2051 var gradeName = defaults.gradeNames[i];
2052 var rawDefaults = fluid.rawDefaults(gradeName);
2053 if (!rawDefaults) {
2054 blankGrades.push(gradeName);
2055 }
2056 }
2057 if (blankGrades.length === 0) {
2058 fluid.fail("Cannot make component creator for type " + componentName + " which does not have an initFunction defined");
2059 } else {
2060 fluid.fail("The grade hierarchy of component with type " + componentName + " is incomplete - it inherits from the following grade(s): " +
2061 blankGrades.join(", ") + " for which the grade definitions are corrupt or missing. Please check the files which might include these " +
2062 "grades and ensure they are readable and have been loaded by this instance of Infusion");
2063 }
2064 } else {
2065 return fluid.initComponent(componentName, arguments);
2066 }
2067 };
2068 var existing = fluid.getGlobalValue(componentName);
2069 if (existing) {
2070 $.extend(creator, existing);
2071 }
2072 fluid.setGlobalValue(componentName, creator);
2073 };
2074
2075 fluid.emptyPolicy = fluid.freezeRecursive({});
2076 // unsupported, NON-API function
2077 fluid.derefMergePolicy = function (policy) {
2078 return (policy ? policy["*"] : fluid.emptyPolicy) || fluid.emptyPolicy;
2079 };
2080
2081 // unsupported, NON-API function
2082 fluid.compileMergePolicy = function (mergePolicy) {
2083 var builtins = {}, defaultValues = {};
2084 var togo = {builtins: builtins, defaultValues: defaultValues};
2085
2086 if (!mergePolicy) {
2087 return togo;
2088 }
2089 fluid.each(mergePolicy, function (value, key) {
2090 var parsed = {}, builtin = true;
2091 if (typeof(value) === "function") {
2092 parsed.func = value;
2093 }
2094 else if (typeof(value) === "object") {
2095 parsed = value;
2096 }
2097 else if (!fluid.isDefaultValueMergePolicy(value)) {
2098 var split = value.split(/\s*,\s*/);
2099 for (var i = 0; i < split.length; ++i) {
2100 parsed[split[i]] = true;
2101 }
2102 }
2103 else {
2104 // Convert to ginger self-reference - NB, this can only be parsed by IoC
2105 fluid.set(defaultValues, key, "{that}.options." + value);
2106 togo.hasDefaults = true;
2107 builtin = false;
2108 }
2109 if (builtin) {
2110 fluid.set(builtins, fluid.composePath(key, "*"), parsed);
2111 }
2112 });
2113 return togo;
2114 };
2115
2116 // TODO: deprecate this method of detecting default value merge policies before 1.6 in favour of
2117 // explicit typed records a la ModelTransformations
2118 // unsupported, NON-API function
2119 fluid.isDefaultValueMergePolicy = function (policy) {
2120 return typeof(policy) === "string" &&
2121 (policy.indexOf(",") === -1 && !/replace|nomerge|noexpand/.test(policy));
2122 };
2123
2124 // unsupported, NON-API function
2125 fluid.mergeOneImpl = function (thisTarget, thisSource, j, sources, newPolicy, i, segs) {
2126 var togo = thisTarget;
2127
2128 var primitiveTarget = fluid.isPrimitive(thisTarget);
2129
2130 if (thisSource !== undefined) {
2131 if (!newPolicy.func && thisSource !== null && fluid.isPlainObject(thisSource) && !newPolicy.nomerge) {
2132 if (primitiveTarget) {
2133 togo = thisTarget = fluid.freshContainer(thisSource);
2134 }
2135 // recursion is now external? We can't do it from here since sources are not all known
2136 // options.recurse(thisTarget, i + 1, segs, sources, newPolicyHolder, options);
2137 } else {
2138 sources[j] = undefined;
2139 if (newPolicy.func) {
2140 togo = newPolicy.func.call(null, thisTarget, thisSource, segs[i - 1], segs, i); // NB - change in this mostly unused argument
2141 } else {
2142 togo = thisSource;
2143 }
2144 }
2145 }
2146 return togo;
2147 };
2148 // NB - same quadratic worry about these as in FluidIoC in the case the RHS trundler is live -
2149 // since at each regeneration step driving the RHS we are discarding the "cursor arguments" these
2150 // would have to be regenerated at each step - although in practice this can only happen once for
2151 // each object for all time, since after first resolution it will be concrete.
2152 function regenerateCursor(source, segs, limit, sourceStrategy) {
2153 for (var i = 0; i < limit; ++i) {
2154 source = sourceStrategy(source, segs[i], i, fluid.makeArray(segs)); // copy for FLUID-5243
2155 }
2156 return source;
2157 }
2158
2159 function regenerateSources(sources, segs, limit, sourceStrategies) {
2160 var togo = [];
2161 for (var i = 0; i < sources.length; ++i) {
2162 var thisSource = regenerateCursor(sources[i], segs, limit, sourceStrategies[i]);
2163 if (thisSource !== undefined) {
2164 togo.push(thisSource);
2165 }
2166 }
2167 return togo;
2168 }
2169
2170 // unsupported, NON-API function
2171 fluid.fetchMergeChildren = function (target, i, segs, sources, mergePolicy, options) {
2172 var thisPolicy = fluid.derefMergePolicy(mergePolicy);
2173 for (var j = sources.length - 1; j >= 0; --j) { // this direction now irrelevant - control is in the strategy
2174 var source = sources[j];
2175 // NB - this detection relies on strategy return being complete objects - which they are
2176 // although we need to set up the roots separately. We need to START the process of evaluating each
2177 // object root (sources) COMPLETELY, before we even begin! Even if the effect of this is to cause a
2178 // dispatch into ourselves almost immediately. We can do this because we can take control over our
2179 // TARGET objects and construct them early. Even if there is a self-dispatch, it will be fine since it is
2180 // DIRECTED and so will not trouble our "slow" detection of properties. After all self-dispatches end, control
2181 // will THEN return to "evaluation of arguments" (expander blocks) and only then FINALLY to this "slow"
2182 // traversal of concrete properties to do the final merge.
2183 if (source !== undefined) {
2184 fluid.each(source, function (newSource, name) {
2185 var childPolicy = fluid.concreteTrundler(mergePolicy, name);
2186 // 2nd arm of condition is an Outrageous bodge to fix FLUID-4930 further. See fluid.tests.retrunking in FluidIoCTests.js
2187 // We make extra use of the old "evaluateFully" flag and ensure to flood any trunk objects again during final "initter" phase of merging.
2188 // The problem is that a custom mergePolicy may have replaced the system generated trunk with a differently structured object which we must not
2189 // corrupt. This work should properly be done with a set of dedicated provenance/progress records in a separate structure
2190 if (!(name in target) || (options.evaluateFully && childPolicy === undefined && !fluid.isPrimitive(target[name]))) { // only request each new target key once -- all sources will be queried per strategy
2191 segs[i] = name;
2192 options.strategy(target, name, i + 1, segs, sources, mergePolicy);
2193 }
2194 });
2195 if (thisPolicy.replace) { // this branch primarily deals with a policy of replace at the root
2196 break;
2197 }
2198 }
2199 }
2200 return target;
2201 };
2202
2203 // A special marker object which will be placed at a current evaluation point in the tree in order
2204 // to protect against circular evaluation
2205 fluid.inEvaluationMarker = Object.freeze({"__CURRENTLY_IN_EVALUATION__": true});
2206
2207 // A path depth above which the core "process strategies" will bail out, assuming that the
2208 // structure has become circularly linked. Helpful in environments such as Firebug which will
2209 // kill the browser process if they happen to be open when a stack overflow occurs. Also provides
2210 // a more helpful diagnostic.
2211 fluid.strategyRecursionBailout = 50;
2212
2213 // unsupported, NON-API function
2214 fluid.makeMergeStrategy = function (options) {
2215 var strategy = function (target, name, i, segs, sources, policy) {
2216 if (i > fluid.strategyRecursionBailout) {
2217 fluid.fail("Overflow/circularity in options merging, current path is ", segs, " at depth " , i, " - please protect components from merging using the \"nomerge\" merge policy");
2218 }
2219 if (fluid.isPrimitive(target)) { // For "use strict"
2220 return undefined; // Review this after FLUID-4925 since the only trigger is in slow component lookahead
2221 }
2222 if (fluid.isTracing) {
2223 fluid.tracing.pathCount.push(fluid.path(segs.slice(0, i)));
2224 }
2225
2226 var oldTarget;
2227 if (name in target) { // bail out if our work has already been done
2228 oldTarget = target[name];
2229 if (!options.evaluateFully) { // see notes on this hack in "initter" - early attempt to deal with FLUID-4930
2230 return oldTarget;
2231 }
2232 }
2233 else {
2234 if (target !== fluid.inEvaluationMarker) { // TODO: blatant "coding to the test" - this enables the simplest "re-trunking" in
2235 // FluidIoCTests to function. In practice, we need to throw away this implementation entirely in favour of the
2236 // "iterative deepening" model coming with FLUID-4925
2237 target[name] = fluid.inEvaluationMarker;
2238 }
2239 }
2240 if (sources === undefined) { // recover our state in case this is an external entry point
2241 segs = fluid.makeArray(segs); // avoid trashing caller's segs
2242 sources = regenerateSources(options.sources, segs, i - 1, options.sourceStrategies);
2243 policy = regenerateCursor(options.mergePolicy, segs, i - 1, fluid.concreteTrundler);
2244 }
2245 var newPolicyHolder = fluid.concreteTrundler(policy, name);
2246 var newPolicy = fluid.derefMergePolicy(newPolicyHolder);
2247
2248 var start, limit, mul;
2249 if (newPolicy.replace) {
2250 start = 1 - sources.length; limit = 0; mul = -1;
2251 }
2252 else {
2253 start = 0; limit = sources.length - 1; mul = +1;
2254 }
2255 var newSources = [];
2256 var thisTarget;
2257
2258 for (var j = start; j <= limit; ++j) { // TODO: try to economise on this array and on gaps
2259 var k = mul * j;
2260 var thisSource = options.sourceStrategies[k](sources[k], name, i, segs); // Run the RH algorithm in "driving" mode
2261 if (thisSource !== undefined) {
2262 if (!fluid.isPrimitive(thisSource)) {
2263 newSources[k] = thisSource;
2264 }
2265 if (oldTarget === undefined) {
2266 if (mul === -1) { // if we are going backwards, it is "replace"
2267 thisTarget = target[name] = thisSource;
2268 break;
2269 }
2270 else {
2271 // write this in early, since early expansions may generate a trunk object which is written in to by later ones
2272 thisTarget = fluid.mergeOneImpl(thisTarget, thisSource, j, newSources, newPolicy, i, segs, options);
2273 if (target !== fluid.inEvaluationMarker) {
2274 target[name] = thisTarget;
2275 }
2276 }
2277 }
2278 }
2279 }
2280 if (oldTarget !== undefined) {
2281 thisTarget = oldTarget;
2282 }
2283 if (newSources.length > 0) {
2284 if (fluid.isPlainObject(thisTarget)) {
2285 fluid.fetchMergeChildren(thisTarget, i, segs, newSources, newPolicyHolder, options);
2286 }
2287 }
2288 if (oldTarget === undefined && newSources.length === 0) {
2289 delete target[name]; // remove the evaluation marker - nothing to evaluate
2290 }
2291 return thisTarget;
2292 };
2293 options.strategy = strategy;
2294 return strategy;
2295 };
2296
2297 // A simple stand-in for "fluid.get" where the material is covered by a single strategy
2298 fluid.driveStrategy = function (root, pathSegs, strategy) {
2299 pathSegs = fluid.makeArray(pathSegs);
2300 for (var i = 0; i < pathSegs.length; ++i) {
2301 if (!root) {
2302 return undefined;
2303 }
2304 root = strategy(root, pathSegs[i], i + 1, pathSegs);
2305 }
2306 return root;
2307 };
2308
2309 // A very simple "new inner trundler" that just performs concrete property access
2310 // Note that every "strategy" is also a "trundler" of this type, considering just the first two arguments
2311 fluid.concreteTrundler = function (source, seg) {
2312 return !source ? undefined : source[seg];
2313 };
2314
2315 /** Merge a collection of options structures onto a target, following an optional policy.
2316 * This method is now used only for the purpose of merging "dead" option documents in order to
2317 * cache graded component defaults. Component option merging is now performed by the
2318 * fluid.makeMergeOptions pathway which sets up a deferred merging process. This function
2319 * will not be removed in the Fluid 2.0 release but it is recommended that users not call it
2320 * directly.
2321 * The behaviour of this function is explained more fully on
2322 * the page http://wiki.fluidproject.org/display/fluid/Options+Merging+for+Fluid+Components .
2323 * @param {Object|String} policy - A "policy object" specifiying the type of merge to be performed.
2324 * If policy is of type {String} it should take on the value "replace" representing
2325 * a static policy. If it is an
2326 * Object, it should contain a mapping of EL paths onto these String values, representing a
2327 * fine-grained policy. If it is an Object, the values may also themselves be EL paths
2328 * representing that a default value is to be taken from that path.
2329 * @param {...Object} options1, options2, .... - an arbitrary list of options structure which are to
2330 * be merged together. These will not be modified.
2331 */
2332
2333 fluid.merge = function (policy /*, ... sources */) {
2334 var sources = Array.prototype.slice.call(arguments, 1);
2335 var compiled = fluid.compileMergePolicy(policy).builtins;
2336 var options = fluid.makeMergeOptions(compiled, sources, {});
2337 options.initter();
2338 return options.target;
2339 };
2340
2341 // unsupported, NON-API function
2342 fluid.simpleGingerBlock = function (source, recordType) {
2343 var block = {
2344 target: source,
2345 simple: true,
2346 strategy: fluid.concreteTrundler,
2347 initter: fluid.identity,
2348 recordType: recordType,
2349 priority: fluid.mergeRecordTypes[recordType]
2350 };
2351 return block;
2352 };
2353
2354 // unsupported, NON-API function
2355 fluid.makeMergeOptions = function (policy, sources, userOptions) {
2356 // note - we close over the supplied policy as a shared object reference - it will be updated during discovery
2357 var options = {
2358 mergePolicy: policy,
2359 sources: sources
2360 };
2361 options = $.extend(options, userOptions);
2362 options.target = options.target || fluid.freshContainer(options.sources[0]);
2363 options.sourceStrategies = options.sourceStrategies || fluid.generate(options.sources.length, fluid.concreteTrundler);
2364 options.initter = function () {
2365 // This hack is necessary to ensure that the FINAL evaluation doesn't balk when discovering a trunk path which was already
2366 // visited during self-driving via the expander. This bi-modality is sort of rubbish, but we currently don't have "room"
2367 // in the strategy API to express when full evaluation is required - and the "flooding API" is not standardised. See FLUID-4930
2368 options.evaluateFully = true;
2369 fluid.fetchMergeChildren(options.target, 0, [], options.sources, options.mergePolicy, options);
2370 };
2371 fluid.makeMergeStrategy(options);
2372 return options;
2373 };
2374
2375 // unsupported, NON-API function
2376 fluid.transformOptions = function (options, transRec) {
2377 fluid.expect("Options transformation record", transRec, ["transformer", "config"]);
2378 var transFunc = fluid.getGlobalValue(transRec.transformer);
2379 return transFunc.call(null, options, transRec.config);
2380 };
2381
2382 // unsupported, NON-API function
2383 fluid.findMergeBlocks = function (mergeBlocks, recordType) {
2384 return fluid.remove_if(fluid.makeArray(mergeBlocks), function (block) { return block.recordType !== recordType; });
2385 };
2386
2387 // unsupported, NON-API function
2388 fluid.transformOptionsBlocks = function (mergeBlocks, transformOptions, recordTypes) {
2389 fluid.each(recordTypes, function (recordType) {
2390 var blocks = fluid.findMergeBlocks(mergeBlocks, recordType);
2391 fluid.each(blocks, function (block) {
2392 var source = block.source ? "source" : "target"; // TODO: Problem here with irregular presentation of options which consist of a reference in their entirety
2393 block[block.simple || source === "target" ? "target" : "source"] = fluid.transformOptions(block[source], transformOptions);
2394 });
2395 });
2396 };
2397
2398 // unsupported, NON-API function
2399 fluid.dedupeDistributionNamespaces = function (mergeBlocks) { // to implement FLUID-5824
2400 var byNamespace = {};
2401 fluid.remove_if(mergeBlocks, function (mergeBlock) {
2402 var ns = mergeBlock.namespace;
2403 if (ns) {
2404 if (byNamespace[ns] && byNamespace[ns] !== mergeBlock.contextThat.id) { // source check for FLUID-5835
2405 return true;
2406 } else {
2407 byNamespace[ns] = mergeBlock.contextThat.id;
2408 }
2409 }
2410 });
2411 };
2412
2413 // unsupported, NON-API function
2414 fluid.deliverOptionsStrategy = fluid.identity;
2415 fluid.computeComponentAccessor = fluid.identity;
2416 fluid.computeDynamicComponents = fluid.identity;
2417
2418 // The types of merge record the system supports, with the weakest records first
2419 fluid.mergeRecordTypes = {
2420 defaults: 1000,
2421 defaultValueMerge: 900,
2422 subcomponentRecord: 800,
2423 user: 700,
2424 distribution: 100 // and above
2425 };
2426
2427 // Utility used in the framework (primarily with distribution assembly), unconnected with new ChangeApplier
2428 // unsupported, NON-API function
2429 fluid.model.applyChangeRequest = function (model, request) {
2430 var segs = request.segs;
2431 if (segs.length === 0) {
2432 if (request.type === "ADD") {
2433 $.extend(true, model, request.value);
2434 } else {
2435 fluid.clear(model);
2436 }
2437 } else if (request.type === "ADD") {
2438 fluid.model.setSimple(model, request.segs, request.value);
2439 } else {
2440 for (var i = 0; i < segs.length - 1; ++i) {
2441 model = model[segs[i]];
2442 if (!model) {
2443 return;
2444 }
2445 }
2446 var last = segs[segs.length - 1];
2447 delete model[last];
2448 }
2449 };
2450
2451 /** Delete the value in the supplied object held at the specified path
2452 * @param {Object} target - The object holding the value to be deleted (possibly empty)
2453 * @param {String[]} segs - the path of the value to be deleted
2454 */
2455 // unsupported, NON-API function
2456 fluid.destroyValue = function (target, segs) {
2457 if (target) {
2458 fluid.model.applyChangeRequest(target, {type: "DELETE", segs: segs});
2459 }
2460 };
2461
2462 /**
2463 * Merges the component's declared defaults, as obtained from fluid.defaults(),
2464 * with the user's specified overrides.
2465 *
2466 * @param {Object} that - the instance to attach the options to
2467 * @param {String} componentName - the unique "name" of the component, which will be used
2468 * to fetch the default options from store. By recommendation, this should be the global
2469 * name of the component's creator function.
2470 * @param {Object} userOptions - the user-specified configuration options for this component
2471 */
2472 // unsupported, NON-API function
2473 fluid.mergeComponentOptions = function (that, componentName, userOptions, localOptions) {
2474 var rawDefaults = fluid.rawDefaults(componentName);
2475 var defaults = fluid.getMergedDefaults(componentName, rawDefaults && rawDefaults.gradeNames ? null : localOptions.gradeNames);
2476 var sharedMergePolicy = {};
2477
2478 var mergeBlocks = [];
2479
2480 if (fluid.expandComponentOptions) {
2481 mergeBlocks = mergeBlocks.concat(fluid.expandComponentOptions(sharedMergePolicy, defaults, userOptions, that));
2482 }
2483 else {
2484 mergeBlocks = mergeBlocks.concat([fluid.simpleGingerBlock(defaults, "defaults"),
2485 fluid.simpleGingerBlock(userOptions, "user")]);
2486 }
2487 var options = {}; // ultimate target
2488 var sourceStrategies = [], sources = [];
2489 var baseMergeOptions = {
2490 target: options,
2491 sourceStrategies: sourceStrategies
2492 };
2493 // Called both from here and from IoC whenever there is a change of block content or arguments which
2494 // requires them to be resorted and rebound
2495 var updateBlocks = function () {
2496 fluid.each(mergeBlocks, function (block) {
2497 if (fluid.isPrimitive(block.priority)) {
2498 block.priority = fluid.parsePriority(block.priority, 0, false, "options distribution");
2499 }
2500 });
2501 fluid.sortByPriority(mergeBlocks);
2502 fluid.dedupeDistributionNamespaces(mergeBlocks);
2503 sourceStrategies.length = 0;
2504 sources.length = 0;
2505 fluid.each(mergeBlocks, function (block) {
2506 sourceStrategies.push(block.strategy);
2507 sources.push(block.target);
2508 });
2509 };
2510 updateBlocks();
2511 var mergeOptions = fluid.makeMergeOptions(sharedMergePolicy, sources, baseMergeOptions);
2512 mergeOptions.mergeBlocks = mergeBlocks;
2513 mergeOptions.updateBlocks = updateBlocks;
2514 mergeOptions.destroyValue = function (segs) { // This method is a temporary hack to assist FLUID-5091
2515 for (var i = 0; i < mergeBlocks.length; ++i) {
2516 if (!mergeBlocks[i].immutableTarget) {
2517 fluid.destroyValue(mergeBlocks[i].target, segs);
2518 }
2519 }
2520 fluid.destroyValue(baseMergeOptions.target, segs);
2521 };
2522
2523 var compiledPolicy;
2524 var mergePolicy;
2525 function computeMergePolicy() {
2526 // Decode the now available mergePolicy
2527 mergePolicy = fluid.driveStrategy(options, "mergePolicy", mergeOptions.strategy);
2528 mergePolicy = $.extend({}, fluid.rootMergePolicy, mergePolicy);
2529 compiledPolicy = fluid.compileMergePolicy(mergePolicy);
2530 // TODO: expandComponentOptions has already put some builtins here - performance implications of the now huge
2531 // default mergePolicy material need to be investigated as well as this deep merge
2532 $.extend(true, sharedMergePolicy, compiledPolicy.builtins); // ensure it gets broadcast to all sharers
2533 }
2534 computeMergePolicy();
2535 mergeOptions.computeMergePolicy = computeMergePolicy;
2536
2537 if (compiledPolicy.hasDefaults) {
2538 if (fluid.generateExpandBlock) {
2539 mergeBlocks.push(fluid.generateExpandBlock({
2540 options: compiledPolicy.defaultValues,
2541 recordType: "defaultValueMerge",
2542 priority: fluid.mergeRecordTypes.defaultValueMerge
2543 }, that, {}));
2544 updateBlocks();
2545 }
2546 else {
2547 fluid.fail("Cannot operate mergePolicy ", mergePolicy, " for component ", that, " without including FluidIoC.js");
2548 }
2549 }
2550 that.options = options;
2551 fluid.driveStrategy(options, "gradeNames", mergeOptions.strategy);
2552
2553 fluid.deliverOptionsStrategy(that, options, mergeOptions); // do this early to broadcast and receive "distributeOptions"
2554
2555 fluid.computeComponentAccessor(that, userOptions && userOptions.localRecord);
2556
2557 var transformOptions = fluid.driveStrategy(options, "transformOptions", mergeOptions.strategy);
2558 if (transformOptions) {
2559 fluid.transformOptionsBlocks(mergeBlocks, transformOptions, ["user", "subcomponentRecord"]);
2560 updateBlocks(); // because the possibly simple blocks may have changed target
2561 }
2562
2563 if (!baseMergeOptions.target.mergePolicy) {
2564 computeMergePolicy();
2565 }
2566
2567 return mergeOptions;
2568 };
2569
2570 // The Fluid Component System proper
2571
2572 // The base system grade definitions
2573
2574 fluid.defaults("fluid.function", {});
2575
2576 /** Invoke a global function by name and named arguments. A courtesy to allow declaratively encoded function calls
2577 * to use named arguments rather than bare arrays.
2578 * @param {String} name - A global name which can be resolved to a Function. The defaults for this name must
2579 * resolve onto a grade including "fluid.function". The defaults record should also contain an entry
2580 * <code>argumentMap</code>, a hash of argument names onto indexes.
2581 * @param {Object} spec - A named hash holding the argument values to be sent to the function. These will be looked
2582 * up in the <code>argumentMap</code> and resolved into a flat list of arguments.
2583 * @return {Any} The return value from the function
2584 */
2585 fluid.invokeGradedFunction = function (name, spec) {
2586 var defaults = fluid.defaults(name);
2587 if (!defaults || !defaults.argumentMap || !fluid.hasGrade(defaults, "fluid.function")) {
2588 fluid.fail("Cannot look up name " + name +
2589 " to a function with registered argumentMap - got defaults ", defaults);
2590 }
2591 var args = [];
2592 fluid.each(defaults.argumentMap, function (value, key) {
2593 args[value] = spec[key];
2594 });
2595 return fluid.invokeGlobalFunction(name, args);
2596 };
2597
2598 fluid.noNamespaceDistributionPrefix = "no-namespace-distribution-";
2599
2600 fluid.mergeOneDistribution = function (target, source, key) {
2601 var namespace = source.namespace || key || fluid.noNamespaceDistributionPrefix + fluid.allocateGuid();
2602 source.namespace = namespace;
2603 target[namespace] = $.extend(true, {}, target[namespace], source);
2604 };
2605
2606 fluid.distributeOptionsPolicy = function (target, source) {
2607 target = target || {};
2608 if (fluid.isArrayable(source)) {
2609 for (var i = 0; i < source.length; ++i) {
2610 fluid.mergeOneDistribution(target, source[i]);
2611 }
2612 } else if (typeof(source.target) === "string") {
2613 fluid.mergeOneDistribution(target, source);
2614 } else {
2615 fluid.each(source, function (oneSource, key) {
2616 fluid.mergeOneDistribution(target, oneSource, key);
2617 });
2618 }
2619 return target;
2620 };
2621
2622 fluid.mergingArray = function () {};
2623 fluid.mergingArray.prototype = [];
2624
2625 // Defer all evaluation of all nested members to resolve FLUID-5668
2626 fluid.membersMergePolicy = function (target, source) {
2627 target = target || {};
2628 fluid.each(source, function (oneSource, key) {
2629 if (!target[key]) {
2630 target[key] = new fluid.mergingArray();
2631 }
2632 if (oneSource instanceof fluid.mergingArray) {
2633 target[key].push.apply(target[key], oneSource);
2634 } else if (oneSource !== undefined) {
2635 target[key].push(oneSource);
2636 }
2637 });
2638 return target;
2639 };
2640
2641 fluid.invokerStrategies = fluid.arrayToHash(["func", "funcName", "listener", "this", "method", "changePath", "value"]);
2642
2643 // Resolve FLUID-5741, FLUID-5184 by ensuring that we avoid mixing incompatible invoker strategies
2644 fluid.invokersMergePolicy = function (target, source) {
2645 target = target || {};
2646 fluid.each(source, function (oneInvoker, name) {
2647 if (!oneInvoker) {
2648 target[name] = oneInvoker;
2649 return;
2650 } else {
2651 oneInvoker = fluid.upgradePrimitiveFunc(oneInvoker);
2652 }
2653 var oneT = target[name];
2654 if (!oneT) {
2655 oneT = target[name] = {};
2656 }
2657 for (var key in fluid.invokerStrategies) {
2658 if (key in oneInvoker) {
2659 for (var key2 in fluid.invokerStrategies) {
2660 oneT[key2] = undefined; // can't delete since stupid driveStrategy bug from recordStrategy reinstates them
2661 }
2662 }
2663 }
2664 $.extend(oneT, oneInvoker);
2665 });
2666 return target;
2667 };
2668
2669 fluid.rootMergePolicy = {
2670 gradeNames: fluid.arrayConcatPolicy,
2671 distributeOptions: fluid.distributeOptionsPolicy,
2672 members: {
2673 noexpand: true,
2674 func: fluid.membersMergePolicy
2675 },
2676 invokers: {
2677 noexpand: true,
2678 func: fluid.invokersMergePolicy
2679 },
2680 transformOptions: "replace",
2681 listeners: fluid.makeMergeListenersPolicy(fluid.mergeListenerPolicy)
2682 };
2683
2684 fluid.defaults("fluid.component", {
2685 initFunction: "fluid.initLittleComponent",
2686 mergePolicy: fluid.rootMergePolicy,
2687 argumentMap: {
2688 options: 0
2689 },
2690 events: { // Three standard lifecycle points common to all components
2691 onCreate: null,
2692 onDestroy: null,
2693 afterDestroy: null
2694 }
2695 });
2696
2697 fluid.defaults("fluid.emptySubcomponent", {
2698 gradeNames: ["fluid.component"]
2699 });
2700
2701 /* Compute a "nickname" given a fully qualified typename, by returning the last path
2702 * segment.
2703 */
2704 fluid.computeNickName = function (typeName) {
2705 var segs = fluid.model.parseEL(typeName);
2706 return segs[segs.length - 1];
2707 };
2708
2709 /** A specially recognised grade tag which directs the IoC framework to instantiate this component first amongst
2710 * its set of siblings, since it is likely to bear a context-forming type name. This will be removed from the framework
2711 * once we have implemented FLUID-4925 "wave of explosions" */
2712 fluid.defaults("fluid.typeFount", {
2713 gradeNames: ["fluid.component"]
2714 });
2715
2716 /**
2717 * Creates a new "little component": a that-ist object with options merged into it by the framework.
2718 * This method is a convenience for creating small objects that have options but don't require full
2719 * View-like features such as the DOM Binder or events
2720 *
2721 * @param {Object} name - The name of the little component to create
2722 * @param {Object} options - User-supplied options to merge with the defaults
2723 */
2724 // NOTE: the 3rd argument localOptions is NOT to be advertised as part of the stable API, it is present
2725 // just to allow backward compatibility whilst grade specifications are not mandatory - similarly for 4th arg "receiver"
2726 // NOTE historical name to avoid confusion with fluid.initComponent below - this will all be refactored with FLUID-4925
2727 fluid.initLittleComponent = function (name, userOptions, localOptions, receiver) {
2728 var that = fluid.typeTag(name);
2729 that.lifecycleStatus = "constructing";
2730 localOptions = localOptions || {gradeNames: "fluid.component"};
2731
2732 that.destroy = fluid.makeRootDestroy(that); // overwritten by FluidIoC for constructed subcomponents
2733 var mergeOptions = fluid.mergeComponentOptions(that, name, userOptions, localOptions);
2734 mergeOptions.exceptions = {members: {model: true, modelRelay: true}}; // don't evaluate these in "early flooding" - they must be fetched explicitly
2735 var options = that.options;
2736 that.events = {};
2737 // deliver to a non-IoC side early receiver of the component (currently only initView)
2738 (receiver || fluid.identity)(that, options, mergeOptions.strategy);
2739 fluid.computeDynamicComponents(that, mergeOptions);
2740
2741 // TODO: ****THIS**** is the point we must deliver and suspend!! Construct the "component skeleton" first, and then continue
2742 // for as long as we can continue to find components.
2743 for (var i = 0; i < mergeOptions.mergeBlocks.length; ++i) {
2744 mergeOptions.mergeBlocks[i].initter();
2745 }
2746 mergeOptions.initter();
2747 delete options.mergePolicy;
2748
2749 fluid.instantiateFirers(that, options);
2750 fluid.mergeListeners(that, that.events, options.listeners);
2751
2752 return that;
2753 };
2754
2755 fluid.diagnoseFailedView = fluid.identity;
2756
2757 // unsupported, NON-API function
2758 fluid.makeRootDestroy = function (that) {
2759 return function () {
2760 fluid.doDestroy(that);
2761 fluid.fireEvent(that, "afterDestroy", [that, "", null]);
2762 };
2763 };
2764
2765 /* Returns <code>true</code> if the supplied reference holds a component which has been destroyed */
2766 fluid.isDestroyed = function (that) {
2767 return that.lifecycleStatus === "destroyed";
2768 };
2769
2770 // unsupported, NON-API function
2771 fluid.doDestroy = function (that, name, parent) {
2772 fluid.fireEvent(that, "onDestroy", [that, name || "", parent]);
2773 that.lifecycleStatus = "destroyed";
2774 for (var key in that.events) {
2775 if (key !== "afterDestroy" && typeof(that.events[key].destroy) === "function") {
2776 that.events[key].destroy();
2777 }
2778 }
2779 if (that.applier) { // TODO: Break this out into the grade's destroyer
2780 that.applier.destroy();
2781 }
2782 };
2783
2784 // unsupported, NON-API function
2785 fluid.initComponent = function (componentName, initArgs) {
2786 var options = fluid.defaults(componentName);
2787 if (!options.gradeNames) {
2788 fluid.fail("Cannot initialise component " + componentName + " which has no gradeName registered");
2789 }
2790 var args = [componentName].concat(fluid.makeArray(initArgs));
2791 var that;
2792 fluid.pushActivity("initComponent", "constructing component of type %componentName with arguments %initArgs",
2793 {componentName: componentName, initArgs: initArgs});
2794 that = fluid.invokeGlobalFunction(options.initFunction, args);
2795 fluid.diagnoseFailedView(componentName, that, options, args);
2796 if (fluid.initDependents) {
2797 fluid.initDependents(that);
2798 }
2799 var errors = fluid.validateListenersImplemented(that);
2800 if (errors.length > 0) {
2801 fluid.fail(fluid.transform(errors, function (error) {
2802 return ["Error constructing component ", that, " - the listener for event " + error.name + " with namespace " + error.namespace + (
2803 (error.componentSource ? " which was defined in grade " + error.componentSource : "") + " needs to be overridden with a concrete implementation")];
2804 })).join("\n");
2805 }
2806 if (that.lifecycleStatus === "constructing") {
2807 that.lifecycleStatus = "constructed";
2808 }
2809 that.events.onCreate.fire(that);
2810 fluid.popActivity();
2811 return that;
2812 };
2813
2814 // unsupported, NON-API function
2815 fluid.initSubcomponentImpl = function (that, entry, args) {
2816 var togo;
2817 if (typeof (entry) !== "function") {
2818 var entryType = typeof (entry) === "string" ? entry : entry.type;
2819 togo = entryType === "fluid.emptySubcomponent" ?
2820 null : fluid.invokeGlobalFunction(entryType, args);
2821 } else {
2822 togo = entry.apply(null, args);
2823 }
2824 return togo;
2825 };
2826
2827 // ******* SELECTOR ENGINE *********
2828
2829 // selector regexps copied from jQuery - recent versions correct the range to start C0
2830 // The initial portion of the main character selector: "just add water" to add on extra
2831 // accepted characters, as well as the "\\\\." -> "\." portion necessary for matching
2832 // period characters escaped in selectors
2833 var charStart = "(?:[\\w\\u00c0-\\uFFFF*_-";
2834
2835 fluid.simpleCSSMatcher = {
2836 regexp: new RegExp("([#.]?)(" + charStart + "]|\\\\.)+)", "g"),
2837 charToTag: {
2838 "": "tag",
2839 "#": "id",
2840 ".": "clazz"
2841 }
2842 };
2843
2844 fluid.IoCSSMatcher = {
2845 regexp: new RegExp("([&#]?)(" + charStart + "]|\\.|\\/)+)", "g"),
2846 charToTag: {
2847 "": "context",
2848 "&": "context",
2849 "#": "id"
2850 }
2851 };
2852
2853 var childSeg = new RegExp("\\s*(>)?\\s*", "g");
2854// var whiteSpace = new RegExp("^\\w*$");
2855
2856 // Parses a selector expression into a data structure holding a list of predicates
2857 // 2nd argument is a "strategy" structure, e.g. fluid.simpleCSSMatcher or fluid.IoCSSMatcher
2858 // unsupported, non-API function
2859 fluid.parseSelector = function (selstring, strategy) {
2860 var togo = [];
2861 selstring = selstring.trim();
2862 //ws-(ss*)[ws/>]
2863 var regexp = strategy.regexp;
2864 regexp.lastIndex = 0;
2865 var lastIndex = 0;
2866 while (true) {
2867 var atNode = []; // a list of predicates at a particular node
2868 var first = true;
2869 while (true) {
2870 var segMatch = regexp.exec(selstring);
2871 if (!segMatch) {
2872 break;
2873 }
2874 if (segMatch.index !== lastIndex) {
2875 if (first) {
2876 fluid.fail("Error in selector string - cannot match child selector expression starting at " + selstring.substring(lastIndex));
2877 }
2878 else {
2879 break;
2880 }
2881 }
2882 var thisNode = {};
2883 var text = segMatch[2];
2884 var targetTag = strategy.charToTag[segMatch[1]];
2885 if (targetTag) {
2886 thisNode[targetTag] = text;
2887 }
2888 atNode[atNode.length] = thisNode;
2889 lastIndex = regexp.lastIndex;
2890 first = false;
2891 }
2892 childSeg.lastIndex = lastIndex;
2893 var fullAtNode = {predList: atNode};
2894 var childMatch = childSeg.exec(selstring);
2895 if (!childMatch || childMatch.index !== lastIndex) {
2896 fluid.fail("Error in selector string - can not match child selector expression at " + selstring.substring(lastIndex));
2897 }
2898 if (childMatch[1] === ">") {
2899 fullAtNode.child = true;
2900 }
2901 togo[togo.length] = fullAtNode;
2902 // >= test here to compensate for IE bug http://blog.stevenlevithan.com/archives/exec-bugs
2903 if (childSeg.lastIndex >= selstring.length) {
2904 break;
2905 }
2906 lastIndex = childSeg.lastIndex;
2907 regexp.lastIndex = childSeg.lastIndex;
2908 }
2909 return togo;
2910 };
2911
2912 // Message resolution and templating
2913
2914 /**
2915 *
2916 * Take an original object and represent it using top-level sub-elements whose keys are EL Paths. For example,
2917 * `originalObject` might look like:
2918 *
2919 * ```
2920 * {
2921 * deep: {
2922 * path: {
2923 * value: "foo",
2924 * emptyObject: {},
2925 * array: [ "peas", "porridge", "hot"]
2926 * }
2927 * }
2928 * }
2929 * ```
2930 *
2931 * Calling `fluid.flattenObjectKeys` on this would result in a new object that looks like:
2932 *
2933 * ```
2934 * {
2935 * "deep": "[object Object]",
2936 * "deep.path": "[object Object]",
2937 * "deep.path.value": "foo",
2938 * "deep.path.array": "peas,porridge,hot",
2939 * "deep.path.array.0": "peas",
2940 * "deep.path.array.1": "porridge",
2941 * "deep.path.array.2": "hot"
2942 * }
2943 * ```
2944 *
2945 * This function preserves the previous functionality of displaying an entire object using its `toString` function,
2946 * which is why many of the paths above resolve to "[object Object]".
2947 *
2948 * This function is an unsupported non-API function that is used in by `fluid.stringTemplate` (see below).
2949 *
2950 * @param {Object} originalObject - An object.
2951 * @return {Object} A representation of the original object that only contains top-level sub-elements whose keys are EL Paths.
2952 *
2953 */
2954 // unsupported, non-API function
2955 fluid.flattenObjectPaths = function (originalObject) {
2956 var flattenedObject = {};
2957 fluid.each(originalObject, function (value, key) {
2958 if (value !== null && typeof value === "object") {
2959 var flattenedSubObject = fluid.flattenObjectPaths(value);
2960 fluid.each(flattenedSubObject, function (subValue, subKey) {
2961 flattenedObject[key + "." + subKey] = subValue;
2962 });
2963 if (typeof fluid.get(value, "toString") === "function") {
2964 flattenedObject[key] = value.toString();
2965 }
2966 }
2967 else {
2968 flattenedObject[key] = value;
2969 }
2970 });
2971 return flattenedObject;
2972 };
2973
2974 /**
2975 *
2976 * Simple string template system. Takes a template string containing tokens in the form of "%value" or
2977 * "%deep.path.to.value". Returns a new string with the tokens replaced by the specified values. Keys and values
2978 * can be of any data type that can be coerced into a string.
2979 *
2980 * @param {String} template - A string (can be HTML) that contains tokens embedded into it.
2981 * @param {Object} values - A collection of token keys and values.
2982 * @return {String} A string whose tokens have been replaced with values.
2983 *
2984 */
2985 fluid.stringTemplate = function (template, values) {
2986 var flattenedValues = fluid.flattenObjectPaths(values);
2987 var keys = fluid.keys(flattenedValues);
2988 keys = keys.sort(fluid.compareStringLength());
2989 for (var i = 0; i < keys.length; ++i) {
2990 var key = keys[i];
2991 var templatePlaceholder = "%" + key;
2992 var replacementValue = flattenedValues[key];
2993
2994 var indexOfPlaceHolder = -1;
2995 while ((indexOfPlaceHolder = template.indexOf(templatePlaceholder)) !== -1) {
2996 template = template.slice(0, indexOfPlaceHolder) + replacementValue + template.slice(indexOfPlaceHolder + templatePlaceholder.length);
2997 }
2998 }
2999 return template;
3000 };
3001
3002})(jQuery, fluid_3_0_0);
3003;
3004/*!
3005 Copyright 2011 unscriptable.com / John Hann
3006 Copyright The Infusion copyright holders
3007 See the AUTHORS.md file at the top-level directory of this distribution and at
3008 https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
3009
3010 License MIT
3011*/
3012
3013var fluid_3_0_0 = fluid_3_0_0 || {};
3014
3015(function ($, fluid) {
3016 "use strict";
3017
3018// Light fluidification of minimal promises library. See original gist at
3019// https://gist.github.com/unscriptable/814052 for limitations and commentary
3020
3021// This implementation provides what could be described as "flat promises" with
3022// no support for structured programming idioms involving promise composition.
3023// It provides what a proponent of mainstream promises would describe as
3024// a "glorified callback aggregator"
3025
3026 fluid.promise = function () {
3027 var that = {
3028 onResolve: [],
3029 onReject: []
3030 // disposition
3031 // value
3032 };
3033 that.then = function (onResolve, onReject) {
3034 if (onResolve) {
3035 if (that.disposition === "resolve") {
3036 onResolve(that.value);
3037 } else {
3038 that.onResolve.push(onResolve);
3039 }
3040 }
3041 if (onReject) {
3042 if (that.disposition === "reject") {
3043 onReject(that.value);
3044 } else {
3045 that.onReject.push(onReject);
3046 }
3047 }
3048 return that;
3049 };
3050 that.resolve = function (value) {
3051 if (that.disposition) {
3052 fluid.fail("Error: resolving promise ", that,
3053 " which has already received \"" + that.disposition + "\"");
3054 } else {
3055 that.complete("resolve", that.onResolve, value);
3056 }
3057 return that;
3058 };
3059 that.reject = function (reason) {
3060 if (that.disposition) {
3061 fluid.fail("Error: rejecting promise ", that,
3062 "which has already received \"" + that.disposition + "\"");
3063 } else {
3064 that.complete("reject", that.onReject, reason);
3065 }
3066 return that;
3067 };
3068 // PRIVATE, NON-API METHOD
3069 that.complete = function (which, queue, arg) {
3070 that.disposition = which;
3071 that.value = arg;
3072 for (var i = 0; i < queue.length; ++i) {
3073 queue[i](arg);
3074 }
3075 };
3076 return that;
3077 };
3078
3079 /* Any object with a member <code>then</code> of type <code>function</code> passes this test.
3080 * This includes essentially every known variety, including jQuery promises.
3081 */
3082 fluid.isPromise = function (totest) {
3083 return totest && typeof(totest.then) === "function";
3084 };
3085
3086 /** Coerces any value to a promise
3087 * @param {Any} promiseOrValue - The value to be coerced
3088 * @return {Promise} - If the supplied value is already a promise, it is returned unchanged. Otherwise a fresh promise is created with the value as resolution and returned
3089 */
3090 fluid.toPromise = function (promiseOrValue) {
3091 if (fluid.isPromise(promiseOrValue)) {
3092 return promiseOrValue;
3093 } else {
3094 var togo = fluid.promise();
3095 togo.resolve(promiseOrValue);
3096 return togo;
3097 }
3098 };
3099
3100 /* Chains the resolution methods of one promise (target) so that they follow those of another (source).
3101 * That is, whenever source resolves, target will resolve, or when source rejects, target will reject, with the
3102 * same payloads in each case.
3103 */
3104 fluid.promise.follow = function (source, target) {
3105 source.then(target.resolve, target.reject);
3106 };
3107
3108 /** Returns a promise whose resolved value is mapped from the source promise or value by the supplied function.
3109 * @param {Object|Promise} source - An object or promise whose value is to be mapped
3110 * @param {Function} func - A function which will map the resolved promise value
3111 * @return {Promise} - A promise for the resolved mapped value.
3112 */
3113 fluid.promise.map = function (source, func) {
3114 var promise = fluid.toPromise(source);
3115 var togo = fluid.promise();
3116 promise.then(function (value) {
3117 var mapped = func(value);
3118 if (fluid.isPromise(mapped)) {
3119 fluid.promise.follow(mapped, togo);
3120 } else {
3121 togo.resolve(mapped);
3122 }
3123 }, function (error) {
3124 togo.reject(error);
3125 });
3126 return togo;
3127 };
3128
3129 /* General skeleton for all sequential promise algorithms, e.g. transform, reduce, sequence, etc.
3130 * These accept a variable "strategy" pair to customise the interchange of values and final return
3131 */
3132
3133 fluid.promise.makeSequencer = function (sources, options, strategy) {
3134 if (!fluid.isArrayable(sources)) {
3135 fluid.fail("fluid.promise sequence algorithms must be supplied an array as source");
3136 }
3137 return {
3138 sources: sources,
3139 resolvedSources: [], // the values of "sources" only with functions invoked (an array of promises or values)
3140 index: 0,
3141 strategy: strategy,
3142 options: options, // available to be supplied to each listener
3143 returns: [],
3144 promise: fluid.promise() // the final return value
3145 };
3146 };
3147
3148 fluid.promise.progressSequence = function (that, retValue) {
3149 that.returns.push(retValue);
3150 that.index++;
3151 // No we dun't have no tail recursion elimination
3152 fluid.promise.resumeSequence(that);
3153 };
3154
3155 fluid.promise.processSequenceReject = function (that, error) { // Allow earlier promises in the sequence to wrap the rejection supplied by later ones (FLUID-5584)
3156 for (var i = that.index - 1; i >= 0; --i) {
3157 var resolved = that.resolvedSources[i];
3158 var accumulator = fluid.isPromise(resolved) && typeof(resolved.accumulateRejectionReason) === "function" ? resolved.accumulateRejectionReason : fluid.identity;
3159 error = accumulator(error);
3160 }
3161 that.promise.reject(error);
3162 };
3163
3164 fluid.promise.resumeSequence = function (that) {
3165 if (that.index === that.sources.length) {
3166 that.promise.resolve(that.strategy.resolveResult(that));
3167 } else {
3168 var value = that.strategy.invokeNext(that);
3169 that.resolvedSources[that.index] = value;
3170 if (fluid.isPromise(value)) {
3171 value.then(function (retValue) {
3172 fluid.promise.progressSequence(that, retValue);
3173 }, function (error) {
3174 fluid.promise.processSequenceReject(that, error);
3175 });
3176 } else {
3177 fluid.promise.progressSequence(that, value);
3178 }
3179 }
3180 };
3181
3182 // SEQUENCE ALGORITHM APPLYING PROMISES
3183
3184 fluid.promise.makeSequenceStrategy = function () {
3185 return {
3186 invokeNext: function (that) {
3187 var source = that.sources[that.index];
3188 return typeof(source) === "function" ? source(that.options) : source;
3189 },
3190 resolveResult: function (that) {
3191 return that.returns;
3192 }
3193 };
3194 };
3195
3196 // accepts an array of values, promises or functions returning promises - in the case of functions returning promises,
3197 // will assure that at most one of these is "in flight" at a time - that is, the succeeding function will not be invoked
3198 // until the promise at the preceding position has resolved
3199 fluid.promise.sequence = function (sources, options) {
3200 var sequencer = fluid.promise.makeSequencer(sources, options, fluid.promise.makeSequenceStrategy());
3201 fluid.promise.resumeSequence(sequencer);
3202 return sequencer.promise;
3203 };
3204
3205 // TRANSFORM ALGORITHM APPLYING PROMISES
3206
3207 fluid.promise.makeTransformerStrategy = function () {
3208 return {
3209 invokeNext: function (that) {
3210 var lisrec = that.sources[that.index];
3211 lisrec.listener = fluid.event.resolveListener(lisrec.listener);
3212 var value = lisrec.listener.apply(null, [that.returns[that.index], that.options]);
3213 return value;
3214 },
3215 resolveResult: function (that) {
3216 return that.returns[that.index];
3217 }
3218 };
3219 };
3220
3221 // Construct a "mini-object" managing the process of a sequence of transforms,
3222 // each of which may be synchronous or return a promise
3223 fluid.promise.makeTransformer = function (listeners, payload, options) {
3224 listeners.unshift({listener:
3225 function () {
3226 return payload;
3227 }
3228 });
3229 var sequencer = fluid.promise.makeSequencer(listeners, options, fluid.promise.makeTransformerStrategy());
3230 sequencer.returns.push(null); // first dummy return from initial entry
3231 fluid.promise.resumeSequence(sequencer);
3232 return sequencer;
3233 };
3234
3235 fluid.promise.filterNamespaces = function (listeners, namespaces) {
3236 if (!namespaces) {
3237 return listeners;
3238 }
3239 return fluid.remove_if(fluid.makeArray(listeners), function (element) {
3240 return element.namespace && !element.softNamespace && !fluid.contains(namespaces, element.namespace);
3241 });
3242 };
3243
3244 /** Top-level API to operate a Fluid event which manages a sequence of
3245 * chained transforms. Rather than being a standard listener accepting the
3246 * same payload, each listener to the event accepts the payload returned by the
3247 * previous listener, and returns either a transformed payload or else a promise
3248 * yielding such a payload.
3249 * @param {fluid.eventFirer} event - A Fluid event to which the listeners are to be interpreted as
3250 * elements cooperating in a chained transform. Each listener will receive arguments <code>(payload, options)</code> where <code>payload</code>
3251 * is the (successful, resolved) return value of the previous listener, and <code>options</code> is the final argument to this function
3252 * @param {Object|Promise} payload - The initial payload input to the transform chain
3253 * @param {Object} options - A free object containing options governing the transform. Fields interpreted at this top level are:
3254 * reverse {Boolean}: <code>true</code> if the listeners are to be called in reverse order of priority (typically the case for an inverse transform)
3255 * filterTransforms {Array}: An array of listener namespaces. If this field is set, only the transform elements whose listener namespaces listed in this array will be applied.
3256 * @return {fluid.promise} A promise which will yield either the final transformed value, or the response of the first transform which fails.
3257 */
3258
3259 fluid.promise.fireTransformEvent = function (event, payload, options) {
3260 options = options || {};
3261 var listeners = options.reverse ? fluid.makeArray(event.sortedListeners).reverse() :
3262 fluid.makeArray(event.sortedListeners);
3263 listeners = fluid.promise.filterNamespaces(listeners, options.filterNamespaces);
3264 var transformer = fluid.promise.makeTransformer(listeners, payload, options);
3265 return transformer.promise;
3266 };
3267
3268
3269})(jQuery, fluid_3_0_0);
3270;
3271/*
3272Copyright The Infusion copyright holders
3273See the AUTHORS.md file at the top-level directory of this distribution and at
3274https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
3275
3276Licensed under the Educational Community License (ECL), Version 2.0 or the New
3277BSD license. You may not use this file except in compliance with one these
3278Licenses.
3279
3280You may obtain a copy of the ECL 2.0 License and BSD License at
3281https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
3282*/
3283
3284var fluid_3_0_0 = fluid_3_0_0 || {};
3285
3286(function ($, fluid) {
3287 "use strict";
3288
3289 /** NOTE: Much of this work originated from https://github.com/fluid-project/kettle/blob/master/lib/dataSource-core.js **/
3290
3291 /** Some common content encodings - suitable to appear as the "encoding" subcomponent of a dataSource **/
3292
3293 fluid.defaults("fluid.dataSource.encoding.JSON", {
3294 gradeNames: "fluid.component",
3295 invokers: {
3296 parse: "fluid.dataSource.parseJSON",
3297 render: "fluid.dataSource.stringifyJSON"
3298 },
3299 contentType: "application/json"
3300 });
3301
3302 fluid.defaults("fluid.dataSource.encoding.none", {
3303 gradeNames: "fluid.component",
3304 invokers: {
3305 parse: "fluid.identity",
3306 render: "fluid.identity"
3307 },
3308 contentType: "text/plain"
3309 });
3310
3311 fluid.dataSource.parseJSON = function (string) {
3312 var togo = fluid.promise();
3313 if (!string) {
3314 togo.resolve(undefined);
3315 } else {
3316 try {
3317 togo.resolve(JSON.parse(string));
3318 } catch (err) {
3319 togo.reject({
3320 message: err
3321 });
3322 }
3323 }
3324 return togo;
3325 };
3326
3327 fluid.dataSource.stringifyJSON = function (obj) {
3328 return obj === undefined ? "" : JSON.stringify(obj, null, 4);
3329 };
3330
3331 /**
3332 * The head of the hierarchy of dataSource components. These abstract
3333 * over the process of read and write access to data, following a simple CRUD-type semantic, indexed by
3334 * a coordinate model (directModel) and which may be asynchronous.
3335 * Top-level methods are:
3336 * get(directModel[, callback|options] - to get the data from data resource
3337 * set(directModel, model[, callback|options] - to set the data
3338 *
3339 *
3340 * directModel: An object expressing an "index" into some set of
3341 * state which can be read or written.
3342 *
3343 * model: The payload sent to the storage.
3344 *
3345 * options: An object expressing implementation specific details
3346 * regarding the handling of a request. Note: this does not
3347 * include details for identifying the resource. Those should be
3348 * placed in the directModel.
3349 */
3350 fluid.defaults("fluid.dataSource", {
3351 gradeNames: ["fluid.component"],
3352 events: {
3353 // The "onRead" event is operated in a custom workflow by fluid.fireTransformEvent to
3354 // process dataSource payloads during the get process. Each listener
3355 // receives the data returned by the last.
3356 onRead: null,
3357 onError: null
3358 },
3359 components: {
3360 encoding: {
3361 type: "fluid.dataSource.encoding.JSON"
3362 }
3363 },
3364 listeners: {
3365 // handler for "onRead.impl" must be implemented by a concrete subgrade
3366 // Note: The intial payload (first argument) will be undefined
3367 "onRead.impl": {
3368 func: "fluid.notImplemented",
3369 priority: "first"
3370 },
3371 "onRead.encoding": {
3372 func: "{encoding}.parse",
3373 priority: "after:impl"
3374 }
3375 },
3376 invokers: {
3377 get: {
3378 funcName: "fluid.dataSource.get",
3379 args: ["{that}", "{arguments}.0", "{arguments}.1"] // directModel, options/callback
3380 }
3381 }
3382 });
3383
3384
3385 /**
3386 * Base grade for adding write configuration to a dataSource.
3387 *
3388 * Grade linkage should be used to apply the concrete writable grade to the datasource configuration.
3389 * For example fluid.makeGradeLinkage("kettle.dataSource.CouchDB.linkage", ["fluid.dataSource.writable", "kettle.dataSource.CouchDB"], "kettle.dataSource.CouchDB.writable");
3390 */
3391 fluid.defaults("fluid.dataSource.writable", {
3392 gradeNames: ["fluid.component"],
3393 events: {
3394 // events "onWrite" and "onWriteResponse" are operated in a custom workflow by fluid.fireTransformEvent to
3395 // process dataSource payloads during the set process. Each listener
3396 // receives the data returned by the last.
3397 onWrite: null,
3398 onWriteResponse: null
3399 },
3400 listeners: {
3401 "onWrite.encoding": {
3402 func: "{encoding}.render"
3403 },
3404 // handler for "onWrite.impl" must be implemented by a concrete subgrade
3405 "onWrite.impl": {
3406 func: "fluid.notImplemented",
3407 priority: "after:encoding"
3408 },
3409 "onWriteResponse.encoding": {
3410 func: "{encoding}.parse"
3411 }
3412 },
3413 invokers: {
3414 set: {
3415 funcName: "fluid.dataSource.set",
3416 args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"] // directModel, model, options/callback
3417 }
3418 }
3419 });
3420
3421 // Registers the default promise handlers for a dataSource operation -
3422 // i) If the user has supplied a function in place of method `options`, register this function as a success handler
3423 // ii) if the user has supplied an onError handler in method `options`, this is registered - otherwise
3424 // we register the firer of the dataSource's own onError method.
3425
3426 fluid.dataSource.registerStandardPromiseHandlers = function (that, promise, options) {
3427 promise.then(typeof(options) === "function" ? options : null,
3428 options.onError ? options.onError : that.events.onError.fire);
3429 };
3430
3431 fluid.dataSource.defaultiseOptions = function (componentOptions, options, directModel, isSet) {
3432 options = options || {};
3433 options.directModel = directModel;
3434 options.operation = isSet ? "set" : "get";
3435 options.notFoundIsEmpty = options.notFoundIsEmpty || componentOptions.notFoundIsEmpty;
3436 return options;
3437 };
3438
3439 /** Operate the core "transforming promise workflow" of a dataSource's `get` method. The initial listener provides the initial payload;
3440 * which then proceeds through the transform chain to arrive at the final payload.
3441 * @param that {Component} The dataSource itself
3442 * @param directModel {Object} The direct model expressing the "coordinates" of the model to be fetched
3443 * @param options {Object} A structure of options configuring the action of this get request - many of these will be specific to the particular concrete DataSource
3444 * @return {Promise} A promise for the final resolved payload
3445 */
3446
3447 fluid.dataSource.get = function (that, directModel, options) {
3448 options = fluid.dataSource.defaultiseOptions(that.options, options, directModel);
3449 var promise = fluid.promise.fireTransformEvent(that.events.onRead, undefined, options);
3450 fluid.dataSource.registerStandardPromiseHandlers(that, promise, options);
3451 return promise;
3452 };
3453
3454 /** Operate the core "transforming promise workflow" of a dataSource's `set` method.
3455 * Any return from this is then pushed forwards through a range of the transforms (typically, e.g. just decoding it as JSON)
3456 * on its way back to the user via the onWriteResponse event.
3457 * @param that {Component} The dataSource itself
3458 * @param directModel {Object} The direct model expressing the "coordinates" of the model to be written
3459 * @param model {Object} The payload to be written to the dataSource
3460 * @param options {Object} A structure of options configuring the action of this set request - many of these will be specific to the particular concrete DataSource
3461 * @return {Promise} A promise for the final resolved payload (not all DataSources will provide any for a `set` method)
3462 */
3463
3464 fluid.dataSource.set = function (that, directModel, model, options) {
3465 options = fluid.dataSource.defaultiseOptions(that.options, options, directModel, true); // shared and writeable between all participants
3466 var transformPromise = fluid.promise.fireTransformEvent(that.events.onWrite, model, options);
3467 var togo = fluid.promise();
3468 transformPromise.then(function (setResponse) {
3469 var options2 = fluid.dataSource.defaultiseOptions(that.options, fluid.copy(options), directModel);
3470 var retransformed = fluid.promise.fireTransformEvent(that.events.onWriteResponse, setResponse, options2);
3471 fluid.promise.follow(retransformed, togo);
3472 }, function (error) {
3473 togo.reject(error);
3474 });
3475 fluid.dataSource.registerStandardPromiseHandlers(that, togo, options);
3476 return togo;
3477 };
3478
3479})(jQuery, fluid_3_0_0);
3480;
3481/*
3482Copyright 2005-2013 jQuery Foundation, Inc. and other contributors
3483Copyright The Infusion copyright holders
3484See the AUTHORS.md file at the top-level directory of this distribution and at
3485https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
3486
3487Licensed under the Educational Community License (ECL), Version 2.0 or the New
3488BSD license. You may not use this file except in compliance with one these
3489Licenses.
3490
3491You may obtain a copy of the ECL 2.0 License and BSD License at
3492https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
3493*/
3494
3495/** This file contains functions which depend on the presence of a DOM document
3496 * but which do not depend on the contents of Fluid.js **/
3497
3498var fluid_3_0_0 = fluid_3_0_0 || {};
3499
3500(function ($, fluid) {
3501 "use strict";
3502
3503 // polyfill for $.browser which was removed in jQuery 1.9 and later
3504 // Taken from jquery-migrate-1.2.1.js,
3505 // jQuery Migrate - v1.2.1 - 2013-05-08
3506 // https://github.com/jquery/jquery-migrate
3507 // Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors; Licensed MIT
3508
3509 fluid.uaMatch = function (ua) {
3510 ua = ua.toLowerCase();
3511
3512 var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
3513 /(webkit)[ \/]([\w.]+)/.exec( ua ) ||
3514 /(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
3515 /(msie) ([\w.]+)/.exec( ua ) ||
3516 ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) || [];
3517
3518 return {
3519 browser: match[ 1 ] || "",
3520 version: match[ 2 ] || "0"
3521 };
3522 };
3523
3524 var matched, browser;
3525
3526 // Don't clobber any existing jQuery.browser in case it's different
3527 if (!$.browser) {
3528 if (!!navigator.userAgent.match(/Trident\/7\./)) {
3529 browser = { // From http://stackoverflow.com/questions/18684099/jquery-fail-to-detect-ie-11
3530 msie: true,
3531 version: 11
3532 };
3533 } else {
3534 matched = fluid.uaMatch(navigator.userAgent);
3535 browser = {};
3536
3537 if (matched.browser) {
3538 browser[matched.browser] = true;
3539 browser.version = matched.version;
3540 }
3541 // Chrome is Webkit, but Webkit is also Safari.
3542 if (browser.chrome) {
3543 browser.webkit = true;
3544 } else if (browser.webkit) {
3545 browser.safari = true;
3546 }
3547 }
3548 $.browser = browser;
3549 }
3550
3551 // Private constants.
3552 var NAMESPACE_KEY = "fluid-scoped-data";
3553
3554 /*
3555 * Gets stored state from the jQuery instance's data map.
3556 * This function is unsupported: It is not really intended for use by implementors.
3557 */
3558 fluid.getScopedData = function (target, key) {
3559 var data = $(target).data(NAMESPACE_KEY);
3560 return data ? data[key] : undefined;
3561 };
3562
3563 /*
3564 * Stores state in the jQuery instance's data map. Unlike jQuery's version,
3565 * accepts multiple-element jQueries.
3566 * This function is unsupported: It is not really intended for use by implementors.
3567 */
3568 fluid.setScopedData = function (target, key, value) {
3569 $(target).each(function () {
3570 var data = $.data(this, NAMESPACE_KEY) || {};
3571 data[key] = value;
3572
3573 $.data(this, NAMESPACE_KEY, data);
3574 });
3575 };
3576
3577 /** Global focus manager - makes use of "focusin" event supported in jquery 1.4.2 or later.
3578 */
3579
3580 var lastFocusedElement = null;
3581
3582 $(document).on("focusin", function (event) {
3583 lastFocusedElement = event.target;
3584 });
3585
3586 fluid.getLastFocusedElement = function () {
3587 return lastFocusedElement;
3588 };
3589
3590
3591 var ENABLEMENT_KEY = "enablement";
3592
3593 /** Queries or sets the enabled status of a control. An activatable node
3594 * may be "disabled" in which case its keyboard bindings will be inoperable
3595 * (but still stored) until it is reenabled again.
3596 * This function is unsupported: It is not really intended for use by implementors.
3597 */
3598
3599 fluid.enabled = function (target, state) {
3600 target = $(target);
3601 if (state === undefined) {
3602 return fluid.getScopedData(target, ENABLEMENT_KEY) !== false;
3603 }
3604 else {
3605 $("*", target).add(target).each(function () {
3606 if (fluid.getScopedData(this, ENABLEMENT_KEY) !== undefined) {
3607 fluid.setScopedData(this, ENABLEMENT_KEY, state);
3608 }
3609 else if (/select|textarea|input/i.test(this.nodeName)) {
3610 $(this).prop("disabled", !state);
3611 }
3612 });
3613 fluid.setScopedData(target, ENABLEMENT_KEY, state);
3614 }
3615 };
3616
3617 fluid.initEnablement = function (target) {
3618 fluid.setScopedData(target, ENABLEMENT_KEY, true);
3619 };
3620
3621 // This utility is required through the use of newer versions of jQuery which will obscure the original
3622 // event responsible for interaction with a target. This is currently use in Tooltip.js and FluidView.js
3623 // "dead man's blur" but would be of general utility
3624
3625 fluid.resolveEventTarget = function (event) {
3626 while (event.originalEvent && event.originalEvent.target) {
3627 event = event.originalEvent;
3628 }
3629 return event.target;
3630 };
3631
3632 // These function (fluid.focus() and fluid.blur()) serve several functions. They should be used by
3633 // all implementation both in test cases and component implementation which require to trigger a focus
3634 // event. Firstly, they restore the old behaviour in jQuery versions prior to 1.10 in which a focus
3635 // trigger synchronously relays to a focus handler. In newer jQueries this defers to the real browser
3636 // relay with numerous platform and timing-dependent effects.
3637 // Secondly, they are necessary since simulation of focus events by jQuery under IE
3638 // is not sufficiently good to intercept the "focusin" binding. Any code which triggers
3639 // focus or blur synthetically throughout the framework and client code must use this function,
3640 // especially if correct cross-platform interaction is required with the "deadMansBlur" function.
3641
3642 function applyOp(node, func) {
3643 node = $(node);
3644 node.trigger("fluid-" + func);
3645 node.triggerHandler(func);
3646 node[func]();
3647 return node;
3648 }
3649
3650 $.each(["focus", "blur"], function (i, name) {
3651 fluid[name] = function (elem) {
3652 return applyOp(elem, name);
3653 };
3654 });
3655
3656 /* Sets the value to the DOM element and triggers the change event on the element.
3657 * Note: when using jQuery val() function to change the node value, the change event would
3658 * not be fired automatically, it requires to be initiated by the user.
3659 *
3660 * @param {A jQueryable DOM element} node - A selector, a DOM node, or a jQuery instance
3661 * @param {String|Number|Array} value - A string of text, a number, or an array of strings
3662 * corresponding to the value of each matched element to set in the node
3663 */
3664 fluid.changeElementValue = function (node, value) {
3665 node = $(node);
3666 node.val(value).change();
3667 };
3668
3669})(jQuery, fluid_3_0_0);
3670;
3671/*
3672Copyright The Infusion copyright holders
3673See the AUTHORS.md file at the top-level directory of this distribution and at
3674https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
3675
3676Licensed under the Educational Community License (ECL), Version 2.0 or the New
3677BSD license. You may not use this file except in compliance with one these
3678Licenses.
3679
3680You may obtain a copy of the ECL 2.0 License and BSD License at
3681https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
3682*/
3683
3684var fluid_3_0_0 = fluid_3_0_0 || {};
3685
3686(function ($, fluid) {
3687 "use strict";
3688
3689 fluid.dom = fluid.dom || {};
3690
3691 // Node walker function for iterateDom.
3692 var getNextNode = function (iterator) {
3693 if (iterator.node.firstChild) {
3694 iterator.node = iterator.node.firstChild;
3695 iterator.depth += 1;
3696 return iterator;
3697 }
3698 while (iterator.node) {
3699 if (iterator.node.nextSibling) {
3700 iterator.node = iterator.node.nextSibling;
3701 return iterator;
3702 }
3703 iterator.node = iterator.node.parentNode;
3704 iterator.depth -= 1;
3705 }
3706 return iterator;
3707 };
3708
3709 /**
3710 * Walks the DOM, applying the specified acceptor function to each element.
3711 * There is a special case for the acceptor, allowing for quick deletion of elements and their children.
3712 * Return "delete" from your acceptor function if you want to delete the element in question.
3713 * Return "stop" to terminate iteration.
3714
3715 * Implementation note - this utility exists mainly for performance reasons. It was last tested
3716 * carefully some time ago (around jQuery 1.2) but at that time was around 3-4x faster at raw DOM
3717 * filtration tasks than the jQuery equivalents, which was an important source of performance loss in the
3718 * Reorderer component. General clients of the framework should use this method with caution if at all, and
3719 * the performance issues should be reassessed when we have time.
3720 *
3721 * @param {Element} node - The node to start walking from.
3722 * @param {Function} acceptor - The function to invoke with each DOM element.
3723 * @param {Boolean} allNodes - Use <code>true</code> to call acceptor on all nodes, rather than just element nodes
3724 * (type 1).
3725 * @return {Object|undefined} - Returns `undefined` if the run completed successfully. If a node stopped the run,
3726 * that node is returned.
3727 */
3728 fluid.dom.iterateDom = function (node, acceptor, allNodes) {
3729 var currentNode = {node: node, depth: 0};
3730 var prevNode = node;
3731 var condition;
3732 while (currentNode.node !== null && currentNode.depth >= 0 && currentNode.depth < fluid.dom.iterateDom.DOM_BAIL_DEPTH) {
3733 condition = null;
3734 if (currentNode.node.nodeType === 1 || allNodes) {
3735 condition = acceptor(currentNode.node, currentNode.depth);
3736 }
3737 if (condition) {
3738 if (condition === "delete") {
3739 currentNode.node.parentNode.removeChild(currentNode.node);
3740 currentNode.node = prevNode;
3741 }
3742 else if (condition === "stop") {
3743 return currentNode.node;
3744 }
3745 }
3746 prevNode = currentNode.node;
3747 currentNode = getNextNode(currentNode);
3748 }
3749 };
3750
3751 // Work around IE circular DOM issue. This is the default max DOM depth on IE.
3752 // http://msdn2.microsoft.com/en-us/library/ms761392(VS.85).aspx
3753 fluid.dom.iterateDom.DOM_BAIL_DEPTH = 256;
3754
3755 /**
3756 * Checks if the specified container is actually the parent of containee.
3757 *
3758 * @param {Element} container - the potential parent
3759 * @param {Element} containee - the child in question
3760 * @return {Boolean} - `true` if `container` contains `containee`, `false` otherwise.
3761 */
3762 fluid.dom.isContainer = function (container, containee) {
3763 for (; containee; containee = containee.parentNode) {
3764 if (container === containee) {
3765 return true;
3766 }
3767 }
3768 return false;
3769 };
3770
3771 /* Return the element text from the supplied DOM node as a single String.
3772 * Implementation note - this is a special-purpose utility used in the framework in just one
3773 * position in the Reorderer. It only performs a "shallow" traversal of the text and was intended
3774 * as a quick and dirty means of extracting element labels where the user had not explicitly provided one.
3775 * It should not be used by general users of the framework and its presence here needs to be
3776 * reassessed.
3777 */
3778 fluid.dom.getElementText = function (element) {
3779 var nodes = element.childNodes;
3780 var text = "";
3781 for (var i = 0; i < nodes.length; ++i) {
3782 var child = nodes[i];
3783 if (child.nodeType === 3) {
3784 text = text + child.nodeValue;
3785 }
3786 }
3787 return text;
3788 };
3789
3790})(jQuery, fluid_3_0_0);
3791;
3792/*
3793Copyright The Infusion copyright holders
3794See the AUTHORS.md file at the top-level directory of this distribution and at
3795https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
3796
3797Licensed under the Educational Community License (ECL), Version 2.0 or the New
3798BSD license. You may not use this file except in compliance with one these
3799Licenses.
3800
3801You may obtain a copy of the ECL 2.0 License and BSD License at
3802https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
3803*/
3804
3805fluid_3_0_0 = fluid_3_0_0 || {};
3806
3807(function ($, fluid) {
3808 "use strict";
3809
3810 var unUnicode = /(\\u[\dabcdef]{4}|\\x[\dabcdef]{2})/g;
3811
3812 fluid.unescapeProperties = function (string) {
3813 string = string.replace(unUnicode, function (match) {
3814 var code = match.substring(2);
3815 var parsed = parseInt(code, 16);
3816 return String.fromCharCode(parsed);
3817 });
3818 var pos = 0;
3819 while (true) {
3820 var backpos = string.indexOf("\\", pos);
3821 if (backpos === -1) {
3822 break;
3823 }
3824 if (backpos === string.length - 1) {
3825 return [string.substring(0, string.length - 1), true];
3826 }
3827 var replace = string.charAt(backpos + 1);
3828 if (replace === "n") { replace = "\n"; }
3829 if (replace === "r") { replace = "\r"; }
3830 if (replace === "t") { replace = "\t"; }
3831 string = string.substring(0, backpos) + replace + string.substring(backpos + 2);
3832 pos = backpos + 1;
3833 }
3834 return [string, false];
3835 };
3836
3837 var breakPos = /[^\\][\s:=]/;
3838
3839 fluid.parseJavaProperties = function (text) {
3840 // File format described at http://java.sun.com/javase/6/docs/api/java/util/Properties.html#load(java.io.Reader)
3841 var togo = {};
3842 text = text.replace(/\r\n/g, "\n");
3843 text = text.replace(/\r/g, "\n");
3844 var lines = text.split("\n");
3845 var contin, key, valueComp, valueRaw, valueEsc;
3846 for (var i = 0; i < lines.length; ++i) {
3847 var line = $.trim(lines[i]);
3848 if (!line || line.charAt(0) === "#" || line.charAt(0) === "!") {
3849 continue;
3850 }
3851 if (!contin) {
3852 valueComp = "";
3853 var breakpos = line.search(breakPos);
3854 if (breakpos === -1) {
3855 key = line;
3856 valueRaw = "";
3857 }
3858 else {
3859 key = $.trim(line.substring(0, breakpos + 1)); // +1 since first char is escape exclusion
3860 valueRaw = $.trim(line.substring(breakpos + 2));
3861 if (valueRaw.charAt(0) === ":" || valueRaw.charAt(0) === "=") {
3862 valueRaw = $.trim(valueRaw.substring(1));
3863 }
3864 }
3865
3866 key = fluid.unescapeProperties(key)[0];
3867 valueEsc = fluid.unescapeProperties(valueRaw);
3868 }
3869 else {
3870 valueEsc = fluid.unescapeProperties(line);
3871 }
3872
3873 contin = valueEsc[1];
3874 if (!valueEsc[1]) { // this line was not a continuation line - store the value
3875 togo[key] = valueComp + valueEsc[0];
3876 }
3877 else {
3878 valueComp += valueEsc[0];
3879 }
3880 }
3881 return togo;
3882 };
3883
3884 /**
3885 *
3886 * Expand a message string with respect to a set of arguments, following a basic subset of the Java MessageFormat
3887 * rules.
3888 * http://java.sun.com/j2se/1.4.2/docs/api/java/text/MessageFormat.html
3889 *
3890 * The message string is expected to contain replacement specifications such as {0}, {1}, {2}, etc.
3891 *
3892 * @param {String} messageString - The message key to be expanded
3893 * @param {String|String[]} args - A single string or array of strings to be substituted into the message.
3894 * @return {String} - The expanded message string.
3895 */
3896 fluid.formatMessage = function (messageString, args) {
3897 if (!args) {
3898 return messageString;
3899 }
3900 if (typeof(args) === "string") {
3901 args = [args];
3902 }
3903 for (var i = 0; i < args.length; ++i) {
3904 messageString = messageString.replace("{" + i + "}", args[i]);
3905 }
3906 return messageString;
3907 };
3908
3909})(jQuery, fluid_3_0_0);
3910;
3911/*
3912Copyright The Infusion copyright holders
3913See the AUTHORS.md file at the top-level directory of this distribution and at
3914https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
3915
3916Licensed under the Educational Community License (ECL), Version 2.0 or the New
3917BSD license. You may not use this file except in compliance with one these
3918Licenses.
3919
3920You may obtain a copy of the ECL 2.0 License and BSD License at
3921https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
3922*/
3923
3924var fluid_3_0_0 = fluid_3_0_0 || {};
3925
3926(function ($, fluid) {
3927 "use strict";
3928
3929 /** Render a timestamp from a Date object into a helpful fixed format for debug logs to millisecond accuracy
3930 * @param {Date} date - The date to be rendered
3931 * @return {String} - A string format consisting of hours:minutes:seconds.millis for the datestamp padded to fixed with
3932 */
3933
3934 fluid.renderTimestamp = function (date) {
3935 var zeropad = function (num, width) {
3936 if (!width) { width = 2; }
3937 var numstr = (num === undefined ? "" : num.toString());
3938 return "00000".substring(5 - width + numstr.length) + numstr;
3939 };
3940 return zeropad(date.getHours()) + ":" + zeropad(date.getMinutes()) + ":" + zeropad(date.getSeconds()) + "." + zeropad(date.getMilliseconds(), 3);
3941 };
3942
3943 fluid.isTracing = false;
3944
3945 fluid.registerNamespace("fluid.tracing");
3946
3947 fluid.tracing.pathCount = [];
3948
3949 fluid.tracing.summarisePathCount = function (pathCount) {
3950 pathCount = pathCount || fluid.tracing.pathCount;
3951 var togo = {};
3952 for (var i = 0; i < pathCount.length; ++i) {
3953 var path = pathCount[i];
3954 if (!togo[path]) {
3955 togo[path] = 1;
3956 }
3957 else {
3958 ++togo[path];
3959 }
3960 }
3961 var toReallyGo = [];
3962 fluid.each(togo, function (el, path) {
3963 toReallyGo.push({path: path, count: el});
3964 });
3965 toReallyGo.sort(function (a, b) {return b.count - a.count;});
3966 return toReallyGo;
3967 };
3968
3969 fluid.tracing.condensePathCount = function (prefixes, pathCount) {
3970 prefixes = fluid.makeArray(prefixes);
3971 var prefixCount = {};
3972 fluid.each(prefixes, function (prefix) {
3973 prefixCount[prefix] = 0;
3974 });
3975 var togo = [];
3976 fluid.each(pathCount, function (el) {
3977 var path = el.path;
3978 if (!fluid.find(prefixes, function (prefix) {
3979 if (path.indexOf(prefix) === 0) {
3980 prefixCount[prefix] += el.count;
3981 return true;
3982 }
3983 })) {
3984 togo.push(el);
3985 }
3986 });
3987 fluid.each(prefixCount, function (count, path) {
3988 togo.unshift({path: path, count: count});
3989 });
3990 return togo;
3991 };
3992
3993 // Exception stripping code taken from https://github.com/emwendelin/javascript-stacktrace/blob/master/stacktrace.js
3994 // BSD licence, see header
3995
3996 fluid.detectStackStyle = function (e) {
3997 var style = "other";
3998 var stackStyle = {
3999 offset: 0
4000 };
4001 if (e.arguments) {
4002 style = "chrome";
4003 } else if (typeof window !== "undefined" && window.opera && e.stacktrace) {
4004 style = "opera10";
4005 } else if (e.stack) {
4006 style = "firefox";
4007 // Detect FireFox 4-style stacks which are 1 level less deep
4008 stackStyle.offset = e.stack.indexOf("Trace exception") === -1 ? 1 : 0;
4009 } else if (typeof window !== "undefined" && window.opera && !("stacktrace" in e)) { //Opera 9-
4010 style = "opera";
4011 }
4012 stackStyle.style = style;
4013 return stackStyle;
4014 };
4015
4016 fluid.obtainException = function () {
4017 try {
4018 throw new Error("Trace exception");
4019 }
4020 catch (e) {
4021 return e;
4022 }
4023 };
4024
4025 var stackStyle = fluid.detectStackStyle(fluid.obtainException());
4026
4027 fluid.registerNamespace("fluid.exceptionDecoders");
4028
4029 fluid.decodeStack = function () {
4030 if (stackStyle.style !== "firefox") {
4031 return null;
4032 }
4033 var e = fluid.obtainException();
4034 return fluid.exceptionDecoders[stackStyle.style](e);
4035 };
4036
4037 fluid.exceptionDecoders.firefox = function (e) {
4038 var delimiter = "at ";
4039 var lines = e.stack.replace(/(?:\n@:0)?\s+$/m, "").replace(/^\(/gm, "{anonymous}(").split("\n");
4040 return fluid.transform(lines, function (line) {
4041 line = line.replace(/\)/g, "");
4042 var atind = line.indexOf(delimiter);
4043 return atind === -1 ? [line] : [line.substring(atind + delimiter.length), line.substring(0, atind)];
4044 });
4045 };
4046
4047 // Main entry point for callers.
4048 fluid.getCallerInfo = function (atDepth) {
4049 atDepth = (atDepth || 3) - stackStyle.offset;
4050 var stack = fluid.decodeStack();
4051 var element = stack && stack[atDepth][0];
4052 if (element) {
4053 var lastslash = element.lastIndexOf("/");
4054 if (lastslash === -1) {
4055 lastslash = 0;
4056 }
4057 var nextColon = element.indexOf(":", lastslash);
4058 return {
4059 path: element.substring(0, lastslash),
4060 filename: element.substring(lastslash + 1, nextColon),
4061 index: element.substring(nextColon + 1)
4062 };
4063 } else {
4064 return null;
4065 }
4066 };
4067
4068 /** Generates a string for padding purposes by replicating a character a given number of times
4069 * @param {Character} c - A character to be used for padding
4070 * @param {Integer} count - The number of times to repeat the character
4071 * @return A string of length <code>count</code> consisting of repetitions of the supplied character
4072 */
4073 // UNOPTIMISED
4074 fluid.generatePadding = function (c, count) {
4075 var togo = "";
4076 for (var i = 0; i < count; ++i) {
4077 togo += c;
4078 }
4079 return togo;
4080 };
4081
4082 // Marker so that we can render a custom string for properties which are not direct and concrete
4083 fluid.SYNTHETIC_PROPERTY = Object.freeze({});
4084
4085 // utility to avoid triggering custom getter code which could throw an exception - e.g. express 3.x's request object
4086 fluid.getSafeProperty = function (obj, key) {
4087 var desc = Object.getOwnPropertyDescriptor(obj, key); // supported on all of our environments - is broken on IE8
4088 return desc && !desc.get ? obj[key] : fluid.SYNTHETIC_PROPERTY;
4089 };
4090
4091 function printImpl(obj, small, options) {
4092 function out(str) {
4093 options.output += str;
4094 }
4095 var big = small + options.indentChars, isFunction = typeof(obj) === "function";
4096 if (options.maxRenderChars !== undefined && options.output.length > options.maxRenderChars) {
4097 return true;
4098 }
4099 if (obj === null) {
4100 out("null");
4101 } else if (obj === undefined) {
4102 out("undefined"); // NB - object invalid for JSON interchange
4103 } else if (obj === fluid.SYNTHETIC_PROPERTY) {
4104 out("[Synthetic property]");
4105 } else if (fluid.isPrimitive(obj) && !isFunction) {
4106 out(JSON.stringify(obj));
4107 }
4108 else {
4109 if (options.stack.indexOf(obj) !== -1) {
4110 out("(CIRCULAR)"); // NB - object invalid for JSON interchange
4111 return;
4112 }
4113 options.stack.push(obj);
4114 var i;
4115 if (fluid.isArrayable(obj)) {
4116 if (obj.length === 0) {
4117 out("[]");
4118 } else {
4119 out("[\n" + big);
4120 for (i = 0; i < obj.length; ++i) {
4121 if (printImpl(obj[i], big, options)) {
4122 return true;
4123 }
4124 if (i !== obj.length - 1) {
4125 out(",\n" + big);
4126 }
4127 }
4128 out("\n" + small + "]");
4129 }
4130 }
4131 else {
4132 out("{" + (isFunction ? " Function" : "") + "\n" + big); // NB - Function object invalid for JSON interchange
4133 var keys = fluid.keys(obj);
4134 for (i = 0; i < keys.length; ++i) {
4135 var key = keys[i];
4136 var value = fluid.getSafeProperty(obj, key);
4137 out(JSON.stringify(key) + ": ");
4138 if (printImpl(value, big, options)) {
4139 return true;
4140 }
4141 if (i !== keys.length - 1) {
4142 out(",\n" + big);
4143 }
4144 }
4145 out("\n" + small + "}");
4146 }
4147 options.stack.pop();
4148 }
4149 return;
4150 }
4151
4152 /** Render a complex JSON object into a nicely indented format suitable for human readability.
4153 * @param {Object} obj - The object to be rendered
4154 * @param {Object} options - An options structure governing the rendering process. This supports the following options:
4155 * <code>indent</code> {Integer} the number of space characters to be used to indent each level of containment (default value: 4)
4156 * <code>maxRenderChars</code> {Integer} rendering the object will cease once this number of characters has been generated
4157 * @return {String} - The generated output.
4158 */
4159 fluid.prettyPrintJSON = function (obj, options) {
4160 options = $.extend({indent: 4, stack: [], output: ""}, options);
4161 options.indentChars = fluid.generatePadding(" ", options.indent);
4162 printImpl(obj, "", options);
4163 return options.output;
4164 };
4165
4166 /**
4167 * Dumps a DOM element into a readily recognisable form for debugging - produces a
4168 * "semi-selector" summarising its tag name, class and id, whichever are set.
4169 *
4170 * @param {jQueryable} element - The element to be dumped
4171 * @return {String} - A string representing the element.
4172 */
4173 fluid.dumpEl = function (element) {
4174 var togo;
4175
4176 if (!element) {
4177 return "null";
4178 }
4179 if (element.nodeType === 3 || element.nodeType === 8) {
4180 return "[data: " + element.data + "]";
4181 }
4182 if (element.nodeType === 9) {
4183 return "[document: location " + element.location + "]";
4184 }
4185 if (!element.nodeType && fluid.isArrayable(element)) {
4186 togo = "[";
4187 for (var i = 0; i < element.length; ++i) {
4188 togo += fluid.dumpEl(element[i]);
4189 if (i < element.length - 1) {
4190 togo += ", ";
4191 }
4192 }
4193 return togo + "]";
4194 }
4195 element = $(element);
4196 togo = element.get(0).tagName;
4197 if (element.id) {
4198 togo += "#" + element.id;
4199 }
4200 if (element.attr("class")) {
4201 togo += "." + element.attr("class");
4202 }
4203 return togo;
4204 };
4205
4206})(jQuery, fluid_3_0_0);
4207;
4208/*
4209Copyright The Infusion copyright holders
4210See the AUTHORS.md file at the top-level directory of this distribution and at
4211https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
4212
4213Licensed under the Educational Community License (ECL), Version 2.0 or the New
4214BSD license. You may not use this file except in compliance with one these
4215Licenses.
4216
4217You may obtain a copy of the ECL 2.0 License and BSD License at
4218https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
4219*/
4220
4221var fluid_3_0_0 = fluid_3_0_0 || {};
4222
4223(function ($, fluid) {
4224 "use strict";
4225
4226 /** NOTE: The contents of this file are by default NOT PART OF THE PUBLIC FLUID API unless explicitly annotated before the function **/
4227
4228 /* The Fluid "IoC System proper" - resolution of references and
4229 * completely automated instantiation of declaratively defined
4230 * component trees */
4231
4232 // Currently still uses manual traversal - once we ban manually instantiated components,
4233 // it will use the instantiator's records instead.
4234 fluid.visitComponentChildren = function (that, visitor, options, segs) {
4235 segs = segs || [];
4236 for (var name in that) {
4237 var component = that[name];
4238 // This entire algorithm is primitive and expensive and will be removed once we can abolish manual init components
4239 if (!fluid.isComponent(component) || (options.visited && options.visited[component.id])) {
4240 continue;
4241 }
4242 segs.push(name);
4243 if (options.visited) { // recall that this is here because we may run into a component that has been cross-injected which might otherwise cause cyclicity
4244 options.visited[component.id] = true;
4245 }
4246 if (visitor(component, name, segs, segs.length - 1)) {
4247 return true;
4248 }
4249 if (!options.flat) {
4250 fluid.visitComponentChildren(component, visitor, options, segs);
4251 }
4252 segs.pop();
4253 }
4254 };
4255
4256 fluid.getContextHash = function (instantiator, that) {
4257 var shadow = instantiator.idToShadow[that.id];
4258 return shadow && shadow.contextHash;
4259 };
4260
4261 fluid.componentHasGrade = function (that, gradeName) {
4262 var contextHash = fluid.getContextHash(fluid.globalInstantiator, that);
4263 return !!(contextHash && contextHash[gradeName]);
4264 };
4265
4266 // A variant of fluid.visitComponentChildren that supplies the signature expected for fluid.matchIoCSelector
4267 // this is: thatStack, contextHashes, memberNames, i - note, the supplied arrays are NOT writeable and shared through the iteration
4268 fluid.visitComponentsForMatching = function (that, options, visitor) {
4269 var instantiator = fluid.getInstantiator(that);
4270 options = $.extend({
4271 visited: {},
4272 instantiator: instantiator
4273 }, options);
4274 var thatStack = [that];
4275 var contextHashes = [fluid.getContextHash(instantiator, that)];
4276 var visitorWrapper = function (component, name, segs) {
4277 thatStack.length = 1;
4278 contextHashes.length = 1;
4279 for (var i = 0; i < segs.length; ++i) {
4280 var child = thatStack[i][segs[i]];
4281 thatStack[i + 1] = child;
4282 contextHashes[i + 1] = fluid.getContextHash(instantiator, child) || {};
4283 }
4284 return visitor(component, thatStack, contextHashes, segs, segs.length);
4285 };
4286 fluid.visitComponentChildren(that, visitorWrapper, options, []);
4287 };
4288
4289 fluid.getMemberNames = function (instantiator, thatStack) {
4290 if (thatStack.length === 0) { // Odd edge case for FLUID-6126 from fluid.computeDistributionPriority
4291 return [];
4292 } else {
4293 var path = instantiator.idToPath(thatStack[thatStack.length - 1].id);
4294 var segs = instantiator.parseEL(path);
4295 // TODO: we should now have no longer shortness in the stack
4296 segs.unshift.apply(segs, fluid.generate(thatStack.length - segs.length, ""));
4297 return segs;
4298 }
4299 };
4300
4301 // thatStack contains an increasing list of MORE SPECIFIC thats.
4302 // this visits all components starting from the current location (end of stack)
4303 // in visibility order UP the tree.
4304 fluid.visitComponentsForVisibility = function (instantiator, thatStack, visitor, options) {
4305 options = options || {
4306 visited: {},
4307 flat: true,
4308 instantiator: instantiator
4309 };
4310 var memberNames = fluid.getMemberNames(instantiator, thatStack);
4311 for (var i = thatStack.length - 1; i >= 0; --i) {
4312 var that = thatStack[i];
4313
4314 // explicitly visit the direct parent first
4315 options.visited[that.id] = true;
4316 if (visitor(that, memberNames[i], memberNames, i)) {
4317 return;
4318 }
4319
4320 if (fluid.visitComponentChildren(that, visitor, options, memberNames)) {
4321 return;
4322 }
4323 memberNames.pop();
4324 }
4325 };
4326
4327 fluid.mountStrategy = function (prefix, root, toMount) {
4328 var offset = prefix.length;
4329 return function (target, name, i, segs) {
4330 if (i <= prefix.length) { // Avoid OOB to not trigger deoptimisation!
4331 return;
4332 }
4333 for (var j = 0; j < prefix.length; ++j) {
4334 if (segs[j] !== prefix[j]) {
4335 return;
4336 }
4337 }
4338 return toMount(target, name, i - prefix.length, segs.slice(offset));
4339 };
4340 };
4341
4342 fluid.invokerFromRecord = function (invokerec, name, that) {
4343 fluid.pushActivity("makeInvoker", "beginning instantiation of invoker with name %name and record %record as child of %that",
4344 {name: name, record: invokerec, that: that});
4345 var invoker = invokerec ? fluid.makeInvoker(that, invokerec, name) : undefined;
4346 fluid.popActivity();
4347 return invoker;
4348 };
4349
4350 fluid.memberFromRecord = function (memberrecs, name, that) {
4351 var togo;
4352 for (var i = 0; i < memberrecs.length; ++i) { // memberrecs is the special "fluid.mergingArray" type which is not Arrayable
4353 var expanded = fluid.expandImmediate(memberrecs[i], that);
4354 if (!fluid.isPlainObject(togo)) { // poor man's "merge" algorithm to hack FLUID-5668 for now
4355 togo = expanded;
4356 } else {
4357 togo = $.extend(true, togo, expanded);
4358 }
4359 }
4360 return togo;
4361 };
4362
4363 fluid.recordStrategy = function (that, options, optionsStrategy, recordPath, recordMaker, prefix, exceptions) {
4364 prefix = prefix || [];
4365 return {
4366 strategy: function (target, name, i) {
4367 if (i !== 1) {
4368 return;
4369 }
4370 var record = fluid.driveStrategy(options, [recordPath, name], optionsStrategy);
4371 if (record === undefined) {
4372 return;
4373 }
4374 fluid.set(target, [name], fluid.inEvaluationMarker);
4375 var member = recordMaker(record, name, that);
4376 fluid.set(target, [name], member);
4377 return member;
4378 },
4379 initter: function () {
4380 var records = fluid.driveStrategy(options, recordPath, optionsStrategy) || {};
4381 for (var name in records) {
4382 if (!exceptions || !exceptions[name]) {
4383 fluid.getForComponent(that, prefix.concat([name]));
4384 }
4385 }
4386 }
4387 };
4388 };
4389
4390 // patch Fluid.js version for timing
4391 fluid.instantiateFirers = function (that) {
4392 var shadow = fluid.shadowForComponent(that);
4393 var initter = fluid.get(shadow, ["eventStrategyBlock", "initter"]) || fluid.identity;
4394 initter();
4395 };
4396
4397 fluid.makeDistributionRecord = function (contextThat, sourceRecord, sourcePath, targetSegs, exclusions, sourceType) {
4398 sourceType = sourceType || "distribution";
4399 fluid.pushActivity("makeDistributionRecord", "Making distribution record from source record %sourceRecord path %sourcePath to target path %targetSegs", {sourceRecord: sourceRecord, sourcePath: sourcePath, targetSegs: targetSegs});
4400
4401 var source = fluid.copy(fluid.get(sourceRecord, sourcePath));
4402 fluid.each(exclusions, function (exclusion) {
4403 fluid.model.applyChangeRequest(source, {segs: exclusion, type: "DELETE"});
4404 });
4405
4406 var record = {options: {}};
4407 fluid.model.applyChangeRequest(record, {segs: targetSegs, type: "ADD", value: source});
4408 fluid.checkComponentRecord(record);
4409 fluid.popActivity();
4410 return $.extend(record, {contextThat: contextThat, recordType: sourceType});
4411 };
4412
4413 // Part of the early "distributeOptions" workflow. Given the description of the blocks to be distributed, assembles "canned" records
4414 // suitable to be either registered into the shadow record for later or directly pushed to an existing component, as well as honouring
4415 // any "removeSource" annotations by removing these options from the source block.
4416 fluid.filterBlocks = function (contextThat, sourceBlocks, sourceSegs, targetSegs, exclusions, removeSource) {
4417 var togo = [];
4418 fluid.each(sourceBlocks, function (block) {
4419 var source = fluid.get(block.source, sourceSegs);
4420 if (source !== undefined) {
4421 togo.push(fluid.makeDistributionRecord(contextThat, block.source, sourceSegs, targetSegs, exclusions, block.recordType));
4422 var rescued = $.extend({}, source);
4423 if (removeSource) {
4424 fluid.model.applyChangeRequest(block.source, {segs: sourceSegs, type: "DELETE"});
4425 }
4426 fluid.each(exclusions, function (exclusion) {
4427 var orig = fluid.get(rescued, exclusion);
4428 fluid.set(block.source, sourceSegs.concat(exclusion), orig);
4429 });
4430 }
4431 });
4432 return togo;
4433 };
4434
4435 // Use this peculiar signature since the actual component and shadow itself may not exist yet. Perhaps clean up with FLUID-4925
4436 fluid.noteCollectedDistribution = function (parentShadow, memberName, distribution) {
4437 fluid.model.setSimple(parentShadow, ["collectedDistributions", memberName, distribution.id], true);
4438 };
4439
4440 fluid.isCollectedDistribution = function (parentShadow, memberName, distribution) {
4441 return fluid.model.getSimple(parentShadow, ["collectedDistributions", memberName, distribution.id]);
4442 };
4443
4444 fluid.clearCollectedDistributions = function (parentShadow, memberName) {
4445 fluid.model.applyChangeRequest(parentShadow, {segs: ["collectedDistributions", memberName], type: "DELETE"});
4446 };
4447
4448 fluid.collectDistributions = function (distributedBlocks, parentShadow, distribution, thatStack, contextHashes, memberNames, i) {
4449 var lastMember = memberNames[memberNames.length - 1];
4450 if (!fluid.isCollectedDistribution(parentShadow, lastMember, distribution) &&
4451 fluid.matchIoCSelector(distribution.selector, thatStack, contextHashes, memberNames, i)) {
4452 distributedBlocks.push.apply(distributedBlocks, distribution.blocks);
4453 fluid.noteCollectedDistribution(parentShadow, lastMember, distribution);
4454 }
4455 };
4456
4457 // Slightly silly function to clean up the "appliedDistributions" records. In general we need to be much more aggressive both
4458 // about clearing instantiation garbage (e.g. onCreate and most of the shadow)
4459 // as well as caching frequently-used records such as the "thatStack" which
4460 // would mean this function could be written in a sensible way
4461 fluid.registerCollectedClearer = function (shadow, parentShadow, memberName) {
4462 if (!shadow.collectedClearer && parentShadow) {
4463 shadow.collectedClearer = function () {
4464 fluid.clearCollectedDistributions(parentShadow, memberName);
4465 };
4466 }
4467 };
4468
4469 fluid.receiveDistributions = function (parentThat, gradeNames, memberName, that) {
4470 var instantiator = fluid.getInstantiator(parentThat || that);
4471 var thatStack = instantiator.getThatStack(parentThat || that); // most specific is at end
4472 thatStack.unshift(fluid.rootComponent);
4473 var memberNames = fluid.getMemberNames(instantiator, thatStack);
4474 var shadows = fluid.transform(thatStack, function (thisThat) {
4475 return instantiator.idToShadow[thisThat.id];
4476 });
4477 var parentShadow = shadows[shadows.length - (parentThat ? 1 : 2)];
4478 var contextHashes = fluid.getMembers(shadows, "contextHash");
4479 if (parentThat) { // if called before construction of component from assembleCreatorArguments - NB this path will be abolished/amalgamated
4480 memberNames.push(memberName);
4481 contextHashes.push(fluid.gradeNamesToHash(gradeNames));
4482 thatStack.push(that);
4483 } else {
4484 fluid.registerCollectedClearer(shadows[shadows.length - 1], parentShadow, memberNames[memberNames.length - 1]);
4485 }
4486 var distributedBlocks = [];
4487 for (var i = 0; i < thatStack.length - 1; ++i) {
4488 fluid.each(shadows[i].distributions, function (distribution) { // eslint-disable-line no-loop-func
4489 fluid.collectDistributions(distributedBlocks, parentShadow, distribution, thatStack, contextHashes, memberNames, i);
4490 });
4491 }
4492 return distributedBlocks;
4493 };
4494
4495 fluid.computeTreeDistance = function (path1, path2) {
4496 var i = 0;
4497 while (i < path1.length && i < path2.length && path1[i] === path2[i]) {
4498 ++i;
4499 }
4500 return path1.length + path2.length - 2*i; // eslint-disable-line space-infix-ops
4501 };
4502
4503 // Called from applyDistributions (immediate application route) as well as mergeRecordsToList (pre-instantiation route) AS WELL AS assembleCreatorArguments (pre-pre-instantiation route)
4504 fluid.computeDistributionPriority = function (targetThat, distributedBlock) {
4505 if (!distributedBlock.priority) {
4506 var instantiator = fluid.getInstantiator(targetThat);
4507 var targetStack = instantiator.getThatStack(targetThat);
4508 var targetPath = fluid.getMemberNames(instantiator, targetStack);
4509 var sourceStack = instantiator.getThatStack(distributedBlock.contextThat);
4510 var sourcePath = fluid.getMemberNames(instantiator, sourceStack);
4511 var distance = fluid.computeTreeDistance(targetPath, sourcePath);
4512 distributedBlock.priority = fluid.mergeRecordTypes.distribution - distance;
4513 }
4514 return distributedBlock;
4515 };
4516
4517 // convert "preBlocks" as produced from fluid.filterBlocks into "real blocks" suitable to be used by the expansion machinery.
4518 fluid.applyDistributions = function (that, preBlocks, targetShadow) {
4519 var distributedBlocks = fluid.transform(preBlocks, function (preBlock) {
4520 return fluid.generateExpandBlock(preBlock, that, targetShadow.mergePolicy);
4521 }, function (distributedBlock) {
4522 return fluid.computeDistributionPriority(that, distributedBlock);
4523 });
4524 var mergeOptions = targetShadow.mergeOptions;
4525 mergeOptions.mergeBlocks.push.apply(mergeOptions.mergeBlocks, distributedBlocks);
4526 mergeOptions.updateBlocks();
4527 return distributedBlocks;
4528 };
4529
4530 // TODO: This implementation is obviously poor and has numerous flaws - in particular it does no backtracking as well as matching backwards through the selector
4531 /** Match a parsed IoC selector against a selection of data structures representing a component's tree context.
4532 * @param {ParsedSelector} selector - A parsed selector structure as returned from `fluid.parseSelector`.
4533 * @param {Component[]} thatStack - An array of components ascending up the tree from the component being matched,
4534 * which will be held in the last position.
4535 * @param {Object[]} contextHashes - An array of context hashes as cached in the component's shadows - a hash to
4536 * `true`/"memberName" depending on the reason the context matches
4537 * @param {String[]} [memberNames] - An array of member names of components in their parents. This is only used in the distributeOptions route.
4538 * @param {Number} i - One plus the index of the IoCSS head component within `thatStack` - all components before this
4539 * index will be ignored for matching. Will have value `1` in the queryIoCSelector route.
4540 * @return {Boolean} `true` if the selector matches the leaf component at the end of `thatStack`
4541 */
4542 fluid.matchIoCSelector = function (selector, thatStack, contextHashes, memberNames, i) {
4543 var thatpos = thatStack.length - 1;
4544 var selpos = selector.length - 1;
4545 while (true) {
4546 var isChild = selector[selpos].child;
4547 var mustMatchHere = thatpos === thatStack.length - 1 || isChild;
4548
4549 var that = thatStack[thatpos];
4550 var selel = selector[selpos];
4551 var match = true;
4552 for (var j = 0; j < selel.predList.length; ++j) {
4553 var pred = selel.predList[j];
4554 var context = pred.context;
4555 if (context && context !== "*" && !(contextHashes[thatpos][context] || memberNames[thatpos] === context)) {
4556 match = false;
4557 break;
4558 }
4559 if (pred.id && that.id !== pred.id) {
4560 match = false;
4561 break;
4562 }
4563 }
4564 if (selpos === 0 && thatpos > i && mustMatchHere && isChild) {
4565 match = false; // child selector must exhaust stack completely - FLUID-5029
4566 }
4567 if (match) {
4568 if (selpos === 0) {
4569 return true;
4570 }
4571 --thatpos;
4572 --selpos;
4573 }
4574 else {
4575 if (mustMatchHere) {
4576 return false;
4577 }
4578 else {
4579 --thatpos;
4580 }
4581 }
4582 if (thatpos < i) {
4583 return false;
4584 }
4585 }
4586 };
4587
4588 /** Query for all components matching a selector in a particular tree
4589 * @param {Component} root - The root component at which to start the search
4590 * @param {String} selector - An IoCSS selector, in form of a string. Note that since selectors supplied to this function implicitly
4591 * match downwards, they need not contain the "head context" followed by whitespace required in the distributeOptions form. E.g.
4592 * simply <code>"fluid.viewComponent"</code> will match all viewComponents below the root.
4593 * @param {Boolean} flat - [Optional] <code>true</code> if the search should just be performed at top level of the component tree
4594 * Note that with <code>flat=true</code> this search will scan every component in the tree and may well be very slow.
4595 * @return {Component[]} The list of all components matching the selector
4596 */
4597 // supported, PUBLIC API function
4598 fluid.queryIoCSelector = function (root, selector, flat) {
4599 var parsed = fluid.parseSelector(selector, fluid.IoCSSMatcher);
4600 var togo = [];
4601
4602 fluid.visitComponentsForMatching(root, {flat: flat}, function (that, thatStack, contextHashes) {
4603 if (fluid.matchIoCSelector(parsed, thatStack, contextHashes, [], 1)) {
4604 togo.push(that);
4605 }
4606 });
4607 return togo;
4608 };
4609
4610 fluid.isIoCSSSelector = function (context) {
4611 return context.indexOf(" ") !== -1; // simple-minded check for an IoCSS reference
4612 };
4613
4614 fluid.pushDistributions = function (targetHead, selector, target, blocks) {
4615 var targetShadow = fluid.shadowForComponent(targetHead);
4616 var id = fluid.allocateGuid();
4617 var distribution = {
4618 id: id, // This id is used in clearDistributions
4619 target: target, // Here for improved debuggability - info is duplicated in "selector"
4620 selector: selector,
4621 blocks: blocks
4622 };
4623 Object.freeze(distribution);
4624 Object.freeze(distribution.blocks);
4625 fluid.pushArray(targetShadow, "distributions", distribution);
4626 return id;
4627 };
4628
4629 fluid.clearDistribution = function (targetHeadId, id) {
4630 var targetHeadShadow = fluid.globalInstantiator.idToShadow[targetHeadId];
4631 // By FLUID-6193, the head component may already have been destroyed, in which case the distributions are gone,
4632 // and we have leaked only its id. In theory we may want to re-establish the distribution if the head is
4633 // re-created, but that is a far wider issue.
4634 if (targetHeadShadow) {
4635 fluid.remove_if(targetHeadShadow.distributions, function (distribution) {
4636 return distribution.id === id;
4637 });
4638 }
4639 };
4640
4641 fluid.clearDistributions = function (shadow) {
4642 fluid.each(shadow.outDistributions, function (outDist) {
4643 fluid.clearDistribution(outDist.targetHeadId, outDist.distributionId);
4644 });
4645 };
4646
4647 // Modifies a parsed selector to extract and remove its head context which will be matched upwards
4648 fluid.extractSelectorHead = function (parsedSelector) {
4649 var predList = parsedSelector[0].predList;
4650 var context = predList[0].context;
4651 predList.length = 0;
4652 return context;
4653 };
4654
4655 fluid.parseExpectedOptionsPath = function (path, role) {
4656 var segs = fluid.model.parseEL(path);
4657 if (segs[0] !== "options") {
4658 fluid.fail("Error in options distribution path ", path, " - only " + role + " paths beginning with \"options\" are supported");
4659 }
4660 return segs.slice(1);
4661 };
4662
4663 fluid.replicateProperty = function (source, property, targets) {
4664 if (source[property] !== undefined) {
4665 fluid.each(targets, function (target) {
4666 target[property] = source[property];
4667 });
4668 }
4669 };
4670
4671 fluid.undistributableOptions = ["gradeNames", "distributeOptions", "argumentMap", "initFunction", "mergePolicy", "progressiveCheckerOptions"]; // automatically added to "exclusions" of every distribution
4672
4673 fluid.distributeOptions = function (that, optionsStrategy) {
4674 var thatShadow = fluid.shadowForComponent(that);
4675 var records = fluid.driveStrategy(that.options, "distributeOptions", optionsStrategy);
4676 fluid.each(records, function distributeOptionsOne(record) {
4677 fluid.pushActivity("distributeOptions", "parsing distributeOptions block %record %that ", {that: that, record: record});
4678 if (typeof(record.target) !== "string") {
4679 fluid.fail("Error in options distribution record ", record, " a member named \"target\" must be supplied holding an IoC reference");
4680 }
4681 if (typeof(record.source) === "string" ^ record.record === undefined) {
4682 fluid.fail("Error in options distribution record ", record, ": must supply either a member \"source\" holding an IoC reference or a member \"record\" holding a literal record");
4683 }
4684 var targetRef = fluid.parseContextReference(record.target);
4685 var targetHead, selector, context;
4686 if (fluid.isIoCSSSelector(targetRef.context)) {
4687 selector = fluid.parseSelector(targetRef.context, fluid.IoCSSMatcher);
4688 var headContext = fluid.extractSelectorHead(selector);
4689 if (headContext === "/") {
4690 targetHead = fluid.rootComponent;
4691 } else {
4692 context = headContext;
4693 }
4694 }
4695 else {
4696 context = targetRef.context;
4697 }
4698 targetHead = targetHead || fluid.resolveContext(context, that);
4699 if (!targetHead) {
4700 fluid.fail("Error in options distribution record ", record, " - could not resolve context {" + context + "} to a head component");
4701 }
4702 var targetSegs = fluid.model.parseEL(targetRef.path);
4703 var preBlocks;
4704 if (record.record !== undefined) {
4705 preBlocks = [(fluid.makeDistributionRecord(that, record.record, [], targetSegs, []))];
4706 }
4707 else {
4708 var source = fluid.parseContextReference(record.source);
4709 if (source.context !== "that") {
4710 fluid.fail("Error in options distribution record ", record, " only a context of {that} is supported");
4711 }
4712 var sourceSegs = fluid.parseExpectedOptionsPath(source.path, "source");
4713 var fullExclusions = fluid.makeArray(record.exclusions).concat(sourceSegs.length === 0 ? fluid.undistributableOptions : []);
4714
4715 var exclusions = fluid.transform(fullExclusions, function (exclusion) {
4716 return fluid.model.parseEL(exclusion);
4717 });
4718
4719 preBlocks = fluid.filterBlocks(that, thatShadow.mergeOptions.mergeBlocks, sourceSegs, targetSegs, exclusions, record.removeSource);
4720 thatShadow.mergeOptions.updateBlocks(); // perhaps unnecessary
4721 }
4722 fluid.replicateProperty(record, "priority", preBlocks);
4723 fluid.replicateProperty(record, "namespace", preBlocks);
4724 // TODO: inline material has to be expanded in its original context!
4725
4726 if (selector) {
4727 var distributionId = fluid.pushDistributions(targetHead, selector, record.target, preBlocks);
4728 thatShadow.outDistributions = thatShadow.outDistributions || [];
4729 thatShadow.outDistributions.push({
4730 targetHeadId: targetHead.id,
4731 distributionId: distributionId
4732 });
4733 }
4734 else { // The component exists now, we must rebalance it
4735 var targetShadow = fluid.shadowForComponent(targetHead);
4736 fluid.applyDistributions(that, preBlocks, targetShadow);
4737 }
4738 fluid.popActivity();
4739 });
4740 };
4741
4742 fluid.gradeNamesToHash = function (gradeNames) {
4743 var contextHash = {};
4744 fluid.each(gradeNames, function (gradeName) {
4745 contextHash[gradeName] = true;
4746 contextHash[fluid.computeNickName(gradeName)] = true;
4747 });
4748 return contextHash;
4749 };
4750
4751 fluid.cacheShadowGrades = function (that, shadow) {
4752 var contextHash = fluid.gradeNamesToHash(that.options.gradeNames);
4753 if (!contextHash[shadow.memberName]) {
4754 contextHash[shadow.memberName] = "memberName"; // This is filtered out again in recordComponent - TODO: Ensure that ALL resolution uses the scope chain eventually
4755 }
4756 shadow.contextHash = contextHash;
4757 fluid.each(contextHash, function (troo, context) {
4758 shadow.ownScope[context] = that;
4759 if (shadow.parentShadow && shadow.parentShadow.that.type !== "fluid.rootComponent") {
4760 shadow.parentShadow.childrenScope[context] = that;
4761 }
4762 });
4763 };
4764
4765 // First sequence point where the mergeOptions strategy is delivered from Fluid.js - here we take care
4766 // of both receiving and transmitting options distributions
4767 fluid.deliverOptionsStrategy = function (that, target, mergeOptions) {
4768 var shadow = fluid.shadowForComponent(that, shadow);
4769 fluid.cacheShadowGrades(that, shadow);
4770 shadow.mergeOptions = mergeOptions;
4771 };
4772
4773 /** Dynamic grade closure algorithm - the following 4 functions share access to a small record structure "rec" which is
4774 * constructed at the start of fluid.computeDynamicGrades
4775 */
4776
4777 fluid.collectDistributedGrades = function (rec) {
4778 // Receive distributions first since these may cause arrival of more contextAwareness blocks.
4779 var distributedBlocks = fluid.receiveDistributions(null, null, null, rec.that);
4780 if (distributedBlocks.length > 0) {
4781 var readyBlocks = fluid.applyDistributions(rec.that, distributedBlocks, rec.shadow);
4782 var gradeNamesList = fluid.transform(fluid.getMembers(readyBlocks, ["source", "gradeNames"]), fluid.makeArray);
4783 fluid.accumulateDynamicGrades(rec, fluid.flatten(gradeNamesList));
4784 }
4785 };
4786
4787 // Apply a batch of freshly acquired plain dynamic grades to the target component and recompute its options
4788 fluid.applyDynamicGrades = function (rec) {
4789 rec.oldGradeNames = fluid.makeArray(rec.gradeNames);
4790 // Note that this crude algorithm doesn't allow us to determine which grades are "new" and which not // TODO: can no longer interpret comment
4791 var newDefaults = fluid.copy(fluid.getMergedDefaults(rec.that.typeName, rec.gradeNames));
4792 rec.gradeNames.length = 0; // acquire derivatives of dynamic grades (FLUID-5054)
4793 rec.gradeNames.push.apply(rec.gradeNames, newDefaults.gradeNames);
4794
4795 fluid.each(rec.gradeNames, function (gradeName) {
4796 if (!fluid.isIoCReference(gradeName)) {
4797 rec.seenGrades[gradeName] = true;
4798 }
4799 });
4800
4801 var shadow = rec.shadow;
4802 fluid.cacheShadowGrades(rec.that, shadow);
4803 // This cheap strategy patches FLUID-5091 for now - some more sophisticated activity will take place
4804 // at this site when we have a full fix for FLUID-5028
4805 shadow.mergeOptions.destroyValue(["mergePolicy"]);
4806 shadow.mergeOptions.destroyValue(["components"]);
4807 shadow.mergeOptions.destroyValue(["invokers"]);
4808
4809 rec.defaultsBlock.source = newDefaults;
4810 shadow.mergeOptions.updateBlocks();
4811 shadow.mergeOptions.computeMergePolicy(); // TODO: we should really only do this if its content changed - this implies moving all options evaluation over to some (cheap) variety of the ChangeApplier
4812
4813 fluid.accumulateDynamicGrades(rec, newDefaults.gradeNames);
4814 };
4815
4816 // Filter some newly discovered grades into their plain and dynamic queues
4817 fluid.accumulateDynamicGrades = function (rec, newGradeNames) {
4818 fluid.each(newGradeNames, function (gradeName) {
4819 if (!rec.seenGrades[gradeName]) {
4820 if (fluid.isIoCReference(gradeName)) {
4821 rec.rawDynamic.push(gradeName);
4822 rec.seenGrades[gradeName] = true;
4823 } else if (!fluid.contains(rec.oldGradeNames, gradeName)) {
4824 rec.plainDynamic.push(gradeName);
4825 }
4826 }
4827 });
4828 };
4829
4830 fluid.computeDynamicGrades = function (that, shadow, strategy) {
4831 delete that.options.gradeNames; // Recompute gradeNames for FLUID-5012 and others
4832 var gradeNames = fluid.driveStrategy(that.options, "gradeNames", strategy); // Just acquire the reference and force eval of mergeBlocks "target", contents are wrong
4833 gradeNames.length = 0;
4834 // TODO: In complex distribution cases, a component might end up with multiple default blocks
4835 var defaultsBlock = fluid.findMergeBlocks(shadow.mergeOptions.mergeBlocks, "defaults")[0];
4836
4837 var rec = {
4838 that: that,
4839 shadow: shadow,
4840 defaultsBlock: defaultsBlock,
4841 gradeNames: gradeNames, // remember that this array is globally shared
4842 seenGrades: {},
4843 plainDynamic: [],
4844 rawDynamic: []
4845 };
4846 fluid.each(shadow.mergeOptions.mergeBlocks, function (block) { // acquire parents of earlier blocks before applying later ones
4847 gradeNames.push.apply(gradeNames, fluid.makeArray(block.target && block.target.gradeNames));
4848 fluid.applyDynamicGrades(rec);
4849 });
4850 fluid.collectDistributedGrades(rec);
4851 while (true) {
4852 while (rec.plainDynamic.length > 0) {
4853 gradeNames.push.apply(gradeNames, rec.plainDynamic);
4854 rec.plainDynamic.length = 0;
4855 fluid.applyDynamicGrades(rec);
4856 fluid.collectDistributedGrades(rec);
4857 }
4858 if (rec.rawDynamic.length > 0) {
4859 var expanded = fluid.expandImmediate(rec.rawDynamic.shift(), that, shadow.localDynamic);
4860 if (typeof(expanded) === "function") {
4861 expanded = expanded();
4862 }
4863 if (expanded) {
4864 rec.plainDynamic = rec.plainDynamic.concat(expanded);
4865 }
4866 } else {
4867 break;
4868 }
4869 }
4870
4871 if (shadow.collectedClearer) {
4872 shadow.collectedClearer();
4873 delete shadow.collectedClearer;
4874 }
4875 };
4876
4877 fluid.computeDynamicComponentKey = function (recordKey, sourceKey) {
4878 return recordKey + (sourceKey === 0 ? "" : "-" + sourceKey); // TODO: configurable name strategies
4879 };
4880 // Hacked resolution of FLUID-6371 - we can't add a listener because this version of the framework doesn't
4881 // support multiple records as subcomponents, and there may have been a total options injection
4882 fluid.hasDynamicComponentCount = function (shadow, key) {
4883 var hypos = key.indexOf("-");
4884 if (hypos !== -1) {
4885 var recordKey = key.substring(0, hypos);
4886 return shadow.dynamicComponentCount !== undefined && shadow.dynamicComponentCount[recordKey] !== undefined;
4887 }
4888 };
4889
4890 fluid.clearDynamicParentRecord = function (shadow, key) {
4891 if (fluid.hasDynamicComponentCount(shadow, key)) {
4892 var holder = fluid.get(shadow.that, ["options", "components"]);
4893 if (holder) {
4894 delete holder[key];
4895 }
4896 }
4897 };
4898
4899 fluid.registerDynamicRecord = function (that, recordKey, sourceKey, record, toCensor) {
4900 var key = fluid.computeDynamicComponentKey(recordKey, sourceKey);
4901 var recordCopy = fluid.copy(record);
4902 delete recordCopy[toCensor];
4903 fluid.set(that.options, ["components", key], recordCopy);
4904 return key;
4905 };
4906
4907 fluid.computeDynamicComponents = function (that, mergeOptions) {
4908 var shadow = fluid.shadowForComponent(that);
4909 var localSub = shadow.subcomponentLocal = {};
4910 var records = fluid.driveStrategy(that.options, "dynamicComponents", mergeOptions.strategy);
4911 fluid.each(records, function (record, recordKey) {
4912 if (!record.sources && !record.createOnEvent) {
4913 fluid.fail("Cannot process dynamicComponents record ", record, " without a \"sources\" or \"createOnEvent\" entry");
4914 }
4915 if (record.sources) {
4916 var sources = fluid.expandOptions(record.sources, that);
4917 fluid.each(sources, function (source, sourceKey) {
4918 var key = fluid.registerDynamicRecord(that, recordKey, sourceKey, record, "sources");
4919 localSub[key] = {"source": source, "sourcePath": sourceKey};
4920 });
4921 }
4922 else if (record.createOnEvent) {
4923 var event = fluid.event.expandOneEvent(that, record.createOnEvent);
4924 fluid.set(shadow, ["dynamicComponentCount", recordKey], 0);
4925 var listener = function () {
4926 var key = fluid.registerDynamicRecord(that, recordKey, shadow.dynamicComponentCount[recordKey]++, record, "createOnEvent");
4927 var localRecord = {"arguments": fluid.makeArray(arguments)};
4928 fluid.initDependent(that, key, localRecord);
4929 };
4930 event.addListener(listener);
4931 fluid.recordListener(event, listener, shadow);
4932 }
4933 });
4934 };
4935
4936 // Second sequence point for mergeOptions from Fluid.js - here we construct all further
4937 // strategies required on the IoC side and mount them into the shadow's getConfig for universal use
4938 fluid.computeComponentAccessor = function (that, localRecord) {
4939 var instantiator = fluid.globalInstantiator;
4940 var shadow = fluid.shadowForComponent(that);
4941 shadow.localDynamic = localRecord; // for signalling to dynamic grades from dynamic components
4942 var options = that.options;
4943 var strategy = shadow.mergeOptions.strategy;
4944 var optionsStrategy = fluid.mountStrategy(["options"], options, strategy);
4945 shadow.invokerStrategy = fluid.recordStrategy(that, options, strategy, "invokers", fluid.invokerFromRecord);
4946 shadow.eventStrategyBlock = fluid.recordStrategy(that, options, strategy, "events", fluid.eventFromRecord, ["events"]);
4947 var eventStrategy = fluid.mountStrategy(["events"], that, shadow.eventStrategyBlock.strategy, ["events"]);
4948 shadow.memberStrategy = fluid.recordStrategy(that, options, strategy, "members", fluid.memberFromRecord, null, {model: true, modelRelay: true});
4949 // NB - ginger strategy handles concrete, rationalise
4950 shadow.getConfig = {strategies: [fluid.model.funcResolverStrategy, fluid.makeGingerStrategy(that),
4951 optionsStrategy, shadow.invokerStrategy.strategy, shadow.memberStrategy.strategy, eventStrategy]};
4952
4953 fluid.computeDynamicGrades(that, shadow, strategy, shadow.mergeOptions.mergeBlocks);
4954 fluid.distributeOptions(that, strategy);
4955 if (shadow.contextHash["fluid.resolveRoot"]) {
4956 var memberName;
4957 if (shadow.contextHash["fluid.resolveRootSingle"]) {
4958 var singleRootType = fluid.getForComponent(that, ["options", "singleRootType"]);
4959 if (!singleRootType) {
4960 fluid.fail("Cannot register object with grades " + Object.keys(shadow.contextHash).join(", ") + " as fluid.resolveRootSingle since it has not defined option singleRootType");
4961 }
4962 memberName = fluid.typeNameToMemberName(singleRootType);
4963 } else {
4964 memberName = fluid.computeGlobalMemberName(that);
4965 }
4966 var parent = fluid.resolveRootComponent;
4967 if (parent[memberName]) {
4968 instantiator.clearComponent(parent, memberName);
4969 }
4970 instantiator.recordKnownComponent(parent, that, memberName, false);
4971 }
4972
4973 return shadow.getConfig;
4974 };
4975
4976 // About the SHADOW:
4977 // Allocated at: instantiator's "recordComponent"
4978 // Contents:
4979 // path {String} Principal allocated path (point of construction) in tree
4980 // that {Component} The component itself
4981 // contextHash {String to Boolean} Map of context names which this component matches
4982 // mergePolicy, mergeOptions: Machinery for last phase of options merging
4983 // invokerStrategy, eventStrategyBlock, memberStrategy, getConfig: Junk required to operate the accessor
4984 // listeners: Listeners registered during this component's construction, to be cleared during clearListeners
4985 // distributions, collectedClearer: Managing options distributions
4986 // outDistributions: A list of distributions registered from this component, signalling from distributeOptions to clearDistributions
4987 // subcomponentLocal: Signalling local record from computeDynamicComponents to assembleCreatorArguments
4988 // dynamicLocal: Local signalling for dynamic grades
4989 // ownScope: A hash of names to components which are in scope from this component - populated in cacheShadowGrades
4990 // childrenScope: A hash of names to components which are in scope because they are children of this component (BELOW own ownScope in resolution order)
4991
4992 fluid.shadowForComponent = function (component) {
4993 var instantiator = fluid.getInstantiator(component);
4994 return instantiator && component ? instantiator.idToShadow[component.id] : null;
4995 };
4996
4997 // Access the member at a particular path in a component, forcing it to be constructed gingerly if necessary
4998 // supported, PUBLIC API function
4999 fluid.getForComponent = function (component, path) {
5000 var shadow = fluid.shadowForComponent(component);
5001 var getConfig = shadow ? shadow.getConfig : undefined;
5002 return fluid.get(component, path, getConfig);
5003 };
5004
5005 // An EL segment resolver strategy that will attempt to trigger creation of
5006 // components that it discovers along the EL path, if they have been defined but not yet
5007 // constructed.
5008 fluid.makeGingerStrategy = function (that) {
5009 var instantiator = fluid.getInstantiator(that);
5010 return function (component, thisSeg, index, segs) {
5011 var atval = component[thisSeg];
5012 if (atval === fluid.inEvaluationMarker && index === segs.length) {
5013 fluid.fail("Error in component configuration - a circular reference was found during evaluation of path segment \"" + thisSeg +
5014 "\": for more details, see the activity records following this message in the console, or issue fluid.setLogging(fluid.logLevel.TRACE) when running your application");
5015 }
5016 if (index > 1) {
5017 return atval;
5018 }
5019 if (atval === undefined && component.hasOwnProperty(thisSeg)) { // avoid recomputing properties that have been explicitly evaluated to undefined
5020 return fluid.NO_VALUE;
5021 }
5022 if (atval === undefined) { // pick up components in instantiation here - we can cut this branch by attaching early
5023 var parentPath = instantiator.idToShadow[component.id].path;
5024 var childPath = instantiator.composePath(parentPath, thisSeg);
5025 atval = instantiator.pathToComponent[childPath];
5026 }
5027 if (atval === undefined) {
5028 // TODO: This check is very expensive - once gingerness is stable, we ought to be able to
5029 // eagerly compute and cache the value of options.components - check is also incorrect and will miss injections
5030 var subRecord = fluid.getForComponent(component, ["options", "components", thisSeg]);
5031 if (subRecord) {
5032 if (subRecord.createOnEvent) {
5033 fluid.fail("Error resolving path segment \"" + thisSeg + "\" of path " + segs.join(".") + " since component with record ", subRecord,
5034 " has annotation \"createOnEvent\" - this very likely represents an implementation error. Either alter the reference so it does not " +
5035 " match this component, or alter your workflow to ensure that the component is instantiated by the time this reference resolves");
5036 }
5037 fluid.initDependent(component, thisSeg);
5038 atval = component[thisSeg];
5039 }
5040 }
5041 return atval;
5042 };
5043 };
5044
5045 // Listed in dependence order
5046 fluid.frameworkGrades = ["fluid.component", "fluid.modelComponent", "fluid.viewComponent", "fluid.rendererComponent"];
5047
5048 fluid.filterBuiltinGrades = function (gradeNames) {
5049 return fluid.remove_if(fluid.makeArray(gradeNames), function (gradeName) {
5050 return fluid.frameworkGrades.indexOf(gradeName) !== -1;
5051 });
5052 };
5053
5054 fluid.dumpGradeNames = function (that) {
5055 return that.options && that.options.gradeNames ?
5056 " gradeNames: " + JSON.stringify(fluid.filterBuiltinGrades(that.options.gradeNames)) : "";
5057 };
5058
5059 fluid.dumpThat = function (that) {
5060 return "{ typeName: \"" + that.typeName + "\"" + fluid.dumpGradeNames(that) + " id: " + that.id + "}";
5061 };
5062
5063 fluid.dumpThatStack = function (thatStack, instantiator) {
5064 var togo = fluid.transform(thatStack, function (that) {
5065 var path = instantiator.idToPath(that.id);
5066 return fluid.dumpThat(that) + (path ? (" - path: " + path) : "");
5067 });
5068 return togo.join("\n");
5069 };
5070
5071 fluid.dumpComponentPath = function (that) {
5072 var path = fluid.pathForComponent(that);
5073 return path ? fluid.pathUtil.composeSegments(path) : "** no path registered for component **";
5074 };
5075
5076 fluid.resolveContext = function (context, that, fast) {
5077 if (context === "that") {
5078 return that;
5079 }
5080 // TODO: Check performance impact of this type check introduced for FLUID-5903 in a very sensitive corner
5081 if (typeof(context) === "object") {
5082 var innerContext = fluid.resolveContext(context.context, that, fast);
5083 if (!fluid.isComponent(innerContext)) {
5084 fluid.triggerMismatchedPathError(context.context, that);
5085 }
5086 var rawValue = fluid.getForComponent(innerContext, context.path);
5087 // TODO: Terrible, slow dispatch for this route
5088 var expanded = fluid.expandOptions(rawValue, that);
5089 if (!fluid.isComponent(expanded)) {
5090 fluid.fail("Unable to resolve recursive context expression " + fluid.renderContextReference(context) + ": the directly resolved value of " + rawValue +
5091 " did not resolve to a component in the scope of component ", that, ": got ", expanded);
5092 }
5093 return expanded;
5094 } else {
5095 var foundComponent;
5096 var instantiator = fluid.globalInstantiator; // fluid.getInstantiator(that); // this hash lookup takes over 1us!
5097 if (fast) {
5098 var shadow = instantiator.idToShadow[that.id];
5099 return shadow.ownScope[context];
5100 } else {
5101 var thatStack = instantiator.getFullStack(that);
5102 fluid.visitComponentsForVisibility(instantiator, thatStack, function (component, name) {
5103 var shadow = fluid.shadowForComponent(component);
5104 // TODO: Some components, e.g. the static environment and typeTags do not have a shadow, which slows us down here
5105 if (context === name || shadow && shadow.contextHash && shadow.contextHash[context] || context === component.typeName) {
5106 foundComponent = component;
5107 return true; // YOUR VISIT IS AT AN END!!
5108 }
5109 if (fluid.getForComponent(component, ["options", "components", context]) && !component[context]) {
5110 // This is an expensive guess since we make it for every component up the stack - must apply the WAVE OF EXPLOSIONS (FLUID-4925) to discover all components first
5111 // This line attempts a hopeful construction of components that could be guessed by nickname through finding them unconstructed
5112 // in options. In the near future we should eagerly BEGIN the process of constructing components, discovering their
5113 // types and then attaching them to the tree VERY EARLY so that we get consistent results from different strategies.
5114 foundComponent = fluid.getForComponent(component, context);
5115 return true;
5116 }
5117 });
5118 return foundComponent;
5119 }
5120 }
5121 };
5122
5123 fluid.triggerMismatchedPathError = function (parsed, parentThat) {
5124 var ref = fluid.renderContextReference(parsed);
5125 fluid.fail("Failed to resolve reference " + ref + " - could not match context with name " +
5126 parsed.context + " from component " + fluid.dumpThat(parentThat) + " at path " + fluid.dumpComponentPath(parentThat) + " component: " , parentThat);
5127 };
5128
5129 fluid.makeStackFetcher = function (parentThat, localRecord, fast) {
5130 var fetcher = function (parsed) {
5131 if (parentThat && parentThat.lifecycleStatus === "destroyed") {
5132 fluid.fail("Cannot resolve reference " + fluid.renderContextReference(parsed) + " from component " + fluid.dumpThat(parentThat) + " which has been destroyed");
5133 }
5134 var context = parsed.context;
5135 if (localRecord && context in localRecord) {
5136 return fluid.get(localRecord[context], parsed.path);
5137 }
5138 var foundComponent = fluid.resolveContext(context, parentThat, fast);
5139 if (!foundComponent && parsed.path !== "") {
5140 fluid.triggerMismatchedPathError(parsed, parentThat);
5141 }
5142 return fluid.getForComponent(foundComponent, parsed.path);
5143 };
5144 return fetcher;
5145 };
5146
5147 fluid.makeStackResolverOptions = function (parentThat, localRecord, fast) {
5148 return $.extend(fluid.copy(fluid.rawDefaults("fluid.makeExpandOptions")), {
5149 localRecord: localRecord || {},
5150 fetcher: fluid.makeStackFetcher(parentThat, localRecord, fast),
5151 contextThat: parentThat,
5152 exceptions: {members: {model: true, modelRelay: true}}
5153 });
5154 };
5155
5156 fluid.clearListeners = function (shadow) {
5157 // TODO: bug here - "afterDestroy" listeners will be unregistered already unless they come from this component
5158 fluid.each(shadow.listeners, function (rec) {
5159 rec.event.removeListener(rec.listenerId || rec.listener);
5160 });
5161 delete shadow.listeners;
5162 };
5163
5164 fluid.recordListener = function (event, listener, shadow, listenerId) {
5165 if (event.ownerId !== shadow.that.id) { // don't bother recording listeners registered from this component itself
5166 fluid.pushArray(shadow, "listeners", {event: event, listener: listener, listenerId: listenerId});
5167 }
5168 };
5169
5170 fluid.constructScopeObjects = function (instantiator, parent, child, childShadow) {
5171 var parentShadow = parent ? instantiator.idToShadow[parent.id] : null;
5172 childShadow.childrenScope = parentShadow ? Object.create(parentShadow.ownScope) : {};
5173 childShadow.ownScope = Object.create(childShadow.childrenScope);
5174 childShadow.parentShadow = parentShadow;
5175 };
5176
5177 fluid.clearChildrenScope = function (instantiator, parentShadow, child, childShadow) {
5178 fluid.each(childShadow.contextHash, function (troo, context) {
5179 if (parentShadow.childrenScope[context] === child) {
5180 delete parentShadow.childrenScope[context]; // TODO: ambiguous resolution
5181 }
5182 });
5183 };
5184
5185 // unsupported, non-API function - however, this structure is of considerable interest to those debugging
5186 // into IoC issues. The structures idToShadow and pathToComponent contain a complete map of the component tree
5187 // forming the surrounding scope
5188 fluid.instantiator = function () {
5189 var that = fluid.typeTag("instantiator");
5190 $.extend(that, {
5191 lifecycleStatus: "constructed",
5192 pathToComponent: {},
5193 idToShadow: {},
5194 modelTransactions: {init: {}}, // a map of transaction id to map of component id to records of components enlisted in a current model initialisation transaction
5195 composePath: fluid.model.composePath, // For speed, we declare that no component's name may contain a period
5196 composeSegments: fluid.model.composeSegments,
5197 parseEL: fluid.model.parseEL,
5198 events: {
5199 onComponentAttach: fluid.makeEventFirer({name: "instantiator's onComponentAttach event"}),
5200 onComponentClear: fluid.makeEventFirer({name: "instantiator's onComponentClear event"})
5201 }
5202 });
5203 // TODO: this API can shortly be removed
5204 that.idToPath = function (id) {
5205 var shadow = that.idToShadow[id];
5206 return shadow ? shadow.path : "";
5207 };
5208 // Note - the returned stack is assumed writeable and does not include the root
5209 that.getThatStack = function (component) {
5210 var shadow = that.idToShadow[component.id];
5211 if (shadow) {
5212 var path = shadow.path;
5213 var parsed = that.parseEL(path);
5214 var root = that.pathToComponent[""], togo = [];
5215 for (var i = 0; i < parsed.length; ++i) {
5216 root = root[parsed[i]];
5217 togo.push(root);
5218 }
5219 return togo;
5220 }
5221 else { return [];}
5222 };
5223 that.getFullStack = function (component) {
5224 var thatStack = component ? that.getThatStack(component) : [];
5225 thatStack.unshift(fluid.resolveRootComponent);
5226 return thatStack;
5227 };
5228 function recordComponent(parent, component, path, name, created) {
5229 var shadow;
5230 if (created) {
5231 shadow = that.idToShadow[component.id] = {};
5232 shadow.that = component;
5233 shadow.path = path;
5234 shadow.memberName = name;
5235 fluid.constructScopeObjects(that, parent, component, shadow);
5236 } else {
5237 shadow = that.idToShadow[component.id];
5238 shadow.injectedPaths = shadow.injectedPaths || {}; // a hash since we will modify whilst iterating
5239 shadow.injectedPaths[path] = true;
5240 var parentShadow = that.idToShadow[parent.id]; // structural parent shadow - e.g. resolveRootComponent
5241 var keys = fluid.keys(shadow.contextHash);
5242 fluid.remove_if(keys, function (key) {
5243 return shadow.contextHash && shadow.contextHash[key] === "memberName";
5244 });
5245 keys.push(name); // add local name - FLUID-5696 and FLUID-5820
5246 fluid.each(keys, function (context) {
5247 if (!parentShadow.childrenScope[context]) {
5248 parentShadow.childrenScope[context] = component;
5249 }
5250 });
5251 }
5252 if (that.pathToComponent[path]) {
5253 fluid.fail("Error during instantiation - path " + path + " which has just created component " + fluid.dumpThat(component) +
5254 " has already been used for component " + fluid.dumpThat(that.pathToComponent[path]) + " - this is a circular instantiation or other oversight." +
5255 " Please clear the component using instantiator.clearComponent() before reusing the path.");
5256 }
5257 that.pathToComponent[path] = component;
5258 }
5259 that.recordRoot = function (component) {
5260 recordComponent(null, component, "", "", true);
5261 };
5262 that.recordKnownComponent = function (parent, component, name, created) {
5263 parent[name] = component;
5264 if (fluid.isComponent(component) || component.type === "instantiator") {
5265 var parentPath = that.idToShadow[parent.id].path;
5266 var path = that.composePath(parentPath, name);
5267 recordComponent(parent, component, path, name, created);
5268 that.events.onComponentAttach.fire(component, path, that, created);
5269 } else {
5270 fluid.fail("Cannot record non-component with value ", component, " at path \"" + name + "\" of parent ", parent);
5271 }
5272 };
5273 that.clearConcreteComponent = function (destroyRec) {
5274 // Clear injected instance of this component from all other paths - historically we didn't bother
5275 // to do this since injecting into a shorter scope is an error - but now we have resolveRoot area
5276 fluid.each(destroyRec.childShadow.injectedPaths, function (troo, injectedPath) {
5277 var parentPath = fluid.model.getToTailPath(injectedPath);
5278 var otherParent = that.pathToComponent[parentPath];
5279 that.clearComponent(otherParent, fluid.model.getTailPath(injectedPath), destroyRec.child);
5280 });
5281 fluid.clearDistributions(destroyRec.childShadow);
5282 fluid.clearListeners(destroyRec.childShadow);
5283 fluid.clearDynamicParentRecord(destroyRec.shadow, destroyRec.name);
5284 fluid.fireEvent(destroyRec.child, "afterDestroy", [destroyRec.child, destroyRec.name, destroyRec.component]);
5285 delete that.idToShadow[destroyRec.child.id];
5286 };
5287 that.clearComponent = function (component, name, child, options, nested, path) {
5288 // options are visitor options for recursive driving
5289 var shadow = that.idToShadow[component.id];
5290 // use flat recursion since we want to use our own recursion rather than rely on "visited" records
5291 options = options || {flat: true, instantiator: that, destroyRecs: []};
5292 child = child || component[name];
5293 path = path || shadow.path;
5294 if (path === undefined) {
5295 fluid.fail("Cannot clear component " + name + " from component ", component,
5296 " which was not created by this instantiator");
5297 }
5298
5299 var childPath = that.composePath(path, name);
5300 var childShadow = that.idToShadow[child.id];
5301 if (!childShadow) { // Explicit FLUID-5812 check - this can be eliminated once we move visitComponentChildren to instantiator's records
5302 return;
5303 }
5304 var created = childShadow.path === childPath;
5305 that.events.onComponentClear.fire(child, childPath, component, created);
5306
5307 // only recurse on components which were created in place - if the id record disagrees with the
5308 // recurse path, it must have been injected
5309 if (created) {
5310 fluid.visitComponentChildren(child, function (gchild, gchildname, segs, i) {
5311 var parentPath = that.composeSegments.apply(null, segs.slice(0, i));
5312 that.clearComponent(child, gchildname, null, options, true, parentPath);
5313 }, options, that.parseEL(childPath));
5314 fluid.doDestroy(child, name, component); // call "onDestroy", null out events and invokers, setting lifecycleStatus to "destroyed"
5315 options.destroyRecs.push({child: child, childShadow: childShadow, name: name, component: component, shadow: shadow});
5316 } else {
5317 fluid.remove_if(childShadow.injectedPaths, function (troo, path) {
5318 return path === childPath;
5319 });
5320 }
5321 fluid.clearChildrenScope(that, shadow, child, childShadow);
5322 // Note that "pathToComponent" will not be available during afterDestroy. This is so that we can synchronously recreate the component
5323 // in an afterDestroy listener (FLUID-5931). We don't clear up the shadow itself until after afterDestroy.
5324 delete that.pathToComponent[childPath];
5325 if (!nested) {
5326 delete component[name]; // there may be no entry - if creation is not concluded
5327 // Do actual destruction for the whole tree here, including "afterDestroy" and deleting shadows
5328 fluid.each(options.destroyRecs, that.clearConcreteComponent);
5329 }
5330 };
5331 return that;
5332 };
5333
5334 // The global instantiator, holding all components instantiated in this context (instance of Infusion)
5335 fluid.globalInstantiator = fluid.instantiator();
5336
5337 // Look up the globally registered instantiator for a particular component - we now only really support a
5338 // single, global instantiator, but this method is left as a notation point in case this ever reverts
5339 // Returns null if argument is a noncomponent or has no shadow
5340 fluid.getInstantiator = function (component) {
5341 var instantiator = fluid.globalInstantiator;
5342 return component && instantiator.idToShadow[component.id] ? instantiator : null;
5343 };
5344
5345 // The grade supplied to components which will be resolvable from all parts of the component tree
5346 fluid.defaults("fluid.resolveRoot");
5347 // In addition to being resolvable at the root, "resolveRootSingle" component will have just a single instance available. Fresh
5348 // instances will displace older ones.
5349 fluid.defaults("fluid.resolveRootSingle", {
5350 gradeNames: "fluid.resolveRoot"
5351 });
5352
5353 fluid.constructRootComponents = function (instantiator) {
5354 // Instantiate the primordial components at the root of each context tree
5355 fluid.rootComponent = instantiator.rootComponent = fluid.typeTag("fluid.rootComponent");
5356 instantiator.recordRoot(fluid.rootComponent);
5357
5358 // The component which for convenience holds injected instances of all components with fluid.resolveRoot grade
5359 fluid.resolveRootComponent = instantiator.resolveRootComponent = fluid.typeTag("fluid.resolveRootComponent");
5360 instantiator.recordKnownComponent(fluid.rootComponent, fluid.resolveRootComponent, "resolveRootComponent", true);
5361
5362 // obliterate resolveRoot's scope objects and replace by the real root scope - which is unused by its own children
5363 var rootShadow = instantiator.idToShadow[fluid.rootComponent.id];
5364 rootShadow.contextHash = {}; // Fix for FLUID-6128
5365 var resolveRootShadow = instantiator.idToShadow[fluid.resolveRootComponent.id];
5366 resolveRootShadow.ownScope = rootShadow.ownScope;
5367 resolveRootShadow.childrenScope = rootShadow.childrenScope;
5368
5369 instantiator.recordKnownComponent(fluid.resolveRootComponent, instantiator, "instantiator", true); // needs to have a shadow so it can be injected
5370 resolveRootShadow.childrenScope.instantiator = instantiator; // needs to be mounted since it never passes through cacheShadowGrades
5371 };
5372
5373 fluid.constructRootComponents(fluid.globalInstantiator); // currently a singleton - in future, alternative instantiators might come back
5374
5375 /** Expand a set of component options either immediately, or with deferred effect.
5376 * The current policy is to expand immediately function arguments within fluid.assembleCreatorArguments which are not the main options of a
5377 * component. The component's own options take <code>{defer: true}</code> as part of
5378 * <code>outerExpandOptions</code> which produces an "expandOptions" structure holding the "strategy" and "initter" pattern
5379 * common to ginger participants.
5380 * Probably not to be advertised as part of a public API, but is considerably more stable than most of the rest
5381 * of the IoC API structure especially with respect to the first arguments.
5382 */
5383
5384// TODO: Can we move outerExpandOptions to 2nd place? only user of 3 and 4 is fluid.makeExpandBlock
5385// TODO: Actually we want localRecord in 2nd place since outerExpandOptions is now almost disused
5386 fluid.expandOptions = function (args, that, mergePolicy, localRecord, outerExpandOptions) {
5387 if (!args) {
5388 return args;
5389 }
5390 fluid.pushActivity("expandOptions", "expanding options %args for component %that ", {that: that, args: args});
5391 var expandOptions = fluid.makeStackResolverOptions(that, localRecord);
5392 expandOptions.mergePolicy = mergePolicy;
5393 expandOptions.defer = outerExpandOptions && outerExpandOptions.defer;
5394 var expanded = expandOptions.defer ?
5395 fluid.makeExpandOptions(args, expandOptions) : fluid.expand(args, expandOptions);
5396 fluid.popActivity();
5397 return expanded;
5398 };
5399
5400 fluid.localRecordExpected = fluid.arrayToHash(["type", "options", "container", "createOnEvent", "priority", "recordType"]); // last element unavoidably polluting
5401
5402 fluid.checkComponentRecord = function (localRecord) {
5403 fluid.each(localRecord, function (value, key) {
5404 if (!fluid.localRecordExpected[key]) {
5405 fluid.fail("Probable error in subcomponent record ", localRecord, " - key \"" + key +
5406 "\" found, where the only legal options are " +
5407 fluid.keys(fluid.localRecordExpected).join(", "));
5408 }
5409 });
5410 };
5411
5412 fluid.mergeRecordsToList = function (that, mergeRecords) {
5413 var list = [];
5414 fluid.each(mergeRecords, function (value, key) {
5415 value.recordType = key;
5416 if (key === "distributions") {
5417 list.push.apply(list, fluid.transform(value, function (distributedBlock) {
5418 return fluid.computeDistributionPriority(that, distributedBlock);
5419 }));
5420 }
5421 else {
5422 if (!value.options) { return; }
5423 value.priority = fluid.mergeRecordTypes[key];
5424 if (value.priority === undefined) {
5425 fluid.fail("Merge record with unrecognised type " + key + ": ", value);
5426 }
5427 list.push(value);
5428 }
5429 });
5430 return list;
5431 };
5432
5433 // TODO: overall efficiency could huge be improved by resorting to the hated PROTOTYPALISM as an optimisation
5434 // for this mergePolicy which occurs in every component. Although it is a deep structure, the root keys are all we need
5435 var addPolicyBuiltins = function (policy) {
5436 fluid.each(["gradeNames", "mergePolicy", "argumentMap", "components", "dynamicComponents", "events", "listeners", "modelListeners", "modelRelay", "distributeOptions", "transformOptions"], function (key) {
5437 fluid.set(policy, [key, "*", "noexpand"], true);
5438 });
5439 return policy;
5440 };
5441
5442 // used from Fluid.js
5443 fluid.generateExpandBlock = function (record, that, mergePolicy, localRecord) {
5444 var expanded = fluid.expandOptions(record.options, record.contextThat || that, mergePolicy, localRecord, {defer: true});
5445 expanded.priority = record.priority;
5446 expanded.namespace = record.namespace;
5447 expanded.recordType = record.recordType;
5448 return expanded;
5449 };
5450
5451 var expandComponentOptionsImpl = function (mergePolicy, defaults, initRecord, that) {
5452 var defaultCopy = fluid.copy(defaults);
5453 addPolicyBuiltins(mergePolicy);
5454 var shadow = fluid.shadowForComponent(that);
5455 shadow.mergePolicy = mergePolicy;
5456 var mergeRecords = {
5457 defaults: {options: defaultCopy}
5458 };
5459
5460 $.extend(mergeRecords, initRecord.mergeRecords);
5461 // Do this here for gradeless components that were corrected by "localOptions"
5462 if (mergeRecords.subcomponentRecord) {
5463 fluid.checkComponentRecord(mergeRecords.subcomponentRecord);
5464 }
5465
5466 var expandList = fluid.mergeRecordsToList(that, mergeRecords);
5467
5468 var togo = fluid.transform(expandList, function (value) {
5469 return fluid.generateExpandBlock(value, that, mergePolicy, initRecord.localRecord);
5470 });
5471 return togo;
5472 };
5473
5474 fluid.fabricateDestroyMethod = function (that, name, instantiator, child) {
5475 return function () {
5476 instantiator.clearComponent(that, name, child);
5477 };
5478 };
5479
5480 // Computes a name for a component appearing at the global root which is globally unique, from its nickName and id
5481 fluid.computeGlobalMemberName = function (that) {
5482 var nickName = fluid.computeNickName(that.typeName);
5483 return nickName + "-" + that.id;
5484 };
5485
5486 // Maps a type name to the member name to be used for it at a particular path level where it is intended to be unique
5487 // Note that "." is still not supported within a member name
5488 // supported, PUBLIC API function
5489 fluid.typeNameToMemberName = function (typeName) {
5490 return typeName.replace(/\./g, "_");
5491 };
5492
5493 // This is the initial entry point from the non-IoC side reporting the first presence of a new component - called from fluid.mergeComponentOptions
5494 fluid.expandComponentOptions = function (mergePolicy, defaults, userOptions, that) {
5495 var initRecord = userOptions; // might have been tunnelled through "userOptions" from "assembleCreatorArguments"
5496 var instantiator = userOptions && userOptions.marker === fluid.EXPAND ? userOptions.instantiator : null;
5497 fluid.pushActivity("expandComponentOptions", "expanding component options %options with record %record for component %that",
5498 {options: instantiator ? userOptions.mergeRecords.user : userOptions, record: initRecord, that: that});
5499 if (!instantiator) { // it is a top-level component which needs to be attached to the global root
5500 instantiator = fluid.globalInstantiator;
5501 initRecord = { // upgrade "userOptions" to the same format produced by fluid.assembleCreatorArguments via the subcomponent route
5502 mergeRecords: {user: {options: fluid.expandCompact(userOptions, true)}},
5503 memberName: fluid.computeGlobalMemberName(that),
5504 instantiator: instantiator,
5505 parentThat: fluid.rootComponent
5506 };
5507 }
5508 that.destroy = fluid.fabricateDestroyMethod(initRecord.parentThat, initRecord.memberName, instantiator, that);
5509
5510 instantiator.recordKnownComponent(initRecord.parentThat, that, initRecord.memberName, true);
5511 var togo = expandComponentOptionsImpl(mergePolicy, defaults, initRecord, that);
5512
5513 fluid.popActivity();
5514 return togo;
5515 };
5516
5517 /** Given a typeName, determine the final concrete
5518 * "invocation specification" consisting of a concrete global function name
5519 * and argument list which is suitable to be executed directly by fluid.invokeGlobalFunction.
5520 */
5521 // options is just a disposition record containing memberName, componentRecord
5522 fluid.assembleCreatorArguments = function (parentThat, typeName, options) {
5523 var upDefaults = fluid.defaults(typeName); // we're not responsive to dynamic changes in argMap, but we don't believe in these anyway
5524 if (!upDefaults || !upDefaults.argumentMap) {
5525 fluid.fail("Error in assembleCreatorArguments: cannot look up component type name " + typeName + " to a component creator grade with an argumentMap");
5526 }
5527
5528 var fakeThat = {}; // fake "that" for receiveDistributions since we try to match selectors before creation for FLUID-5013
5529 var distributions = parentThat ? fluid.receiveDistributions(parentThat, upDefaults.gradeNames, options.memberName, fakeThat) : [];
5530 fluid.each(distributions, function (distribution) { // TODO: The duplicated route for this is in fluid.mergeComponentOptions
5531 fluid.computeDistributionPriority(parentThat, distribution);
5532 if (fluid.isPrimitive(distribution.priority)) { // TODO: These should be immutable and parsed just once on registration - but we can't because of crazy target-dependent distance system
5533 distribution.priority = fluid.parsePriority(distribution.priority, 0, false, "options distribution");
5534 }
5535 });
5536 fluid.sortByPriority(distributions);
5537
5538 var localDynamic = options.localDynamic;
5539 var localRecord = $.extend({}, fluid.censorKeys(options.componentRecord, ["type"]), localDynamic);
5540
5541 var argMap = upDefaults.argumentMap;
5542 var findKeys = Object.keys(argMap).concat(["type"]);
5543
5544 fluid.each(findKeys, function (name) {
5545 for (var i = 0; i < distributions.length; ++i) { // Apply non-options material from distributions (FLUID-5013)
5546 if (distributions[i][name] !== undefined) {
5547 localRecord[name] = distributions[i][name];
5548 }
5549 }
5550 });
5551 typeName = localRecord.type || typeName;
5552
5553 delete localRecord.type;
5554 delete localRecord.options;
5555
5556 var mergeRecords = {distributions: distributions};
5557
5558 if (options.componentRecord !== undefined) {
5559 // Deliberately put too many things here so they can be checked in expandComponentOptions (FLUID-4285)
5560 mergeRecords.subcomponentRecord = $.extend({}, options.componentRecord);
5561 }
5562 var args = [];
5563 fluid.each(argMap, function (index, name) {
5564 var arg;
5565 if (name === "options") {
5566 arg = {marker: fluid.EXPAND,
5567 localRecord: localDynamic,
5568 mergeRecords: mergeRecords,
5569 instantiator: fluid.getInstantiator(parentThat),
5570 parentThat: parentThat,
5571 memberName: options.memberName};
5572 } else {
5573 var value = localRecord[name];
5574 arg = fluid.expandImmediate(value, parentThat, localRecord);
5575 }
5576 args[index] = arg;
5577 });
5578
5579 var togo = {
5580 args: args,
5581 funcName: typeName
5582 };
5583 return togo;
5584 };
5585
5586 /** Instantiate the subcomponent with the supplied name of the supplied top-level component. Although this method
5587 * is published as part of the Fluid API, it should not be called by general users and may not remain stable. It is
5588 * currently the only mechanism provided for instantiating components whose definitions are dynamic, and will be
5589 * replaced in time by dedicated declarative framework described by FLUID-5022.
5590 * @param {Component} that - The parent component for which the subcomponent is to be instantiated
5591 * @param {String} name - The name of the component - the index of the options block which configures it as part of the
5592 * <code>components</code> section of its parent's options
5593 * @param {Object} [localRecord] - A local scope record keyed by context names which should specially be in scope for this
5594 * construction, e.g. `arguments`. Primarily for internal framework use.
5595 * @return {Component} The constructed subcomponent
5596 */
5597 fluid.initDependent = function (that, name, localRecord) {
5598 if (that[name]) { return; } // TODO: move this into strategy
5599 var component = that.options.components[name];
5600 var instance;
5601 var instantiator = fluid.globalInstantiator;
5602 var shadow = instantiator.idToShadow[that.id];
5603 var localDynamic = localRecord || shadow.subcomponentLocal && shadow.subcomponentLocal[name];
5604 fluid.pushActivity("initDependent", "instantiating dependent component at path \"%path\" with record %record as child of %parent",
5605 {path: shadow.path + "." + name, record: component, parent: that});
5606
5607 if (typeof(component) === "string" || component.expander) {
5608 that[name] = fluid.inEvaluationMarker;
5609 instance = fluid.expandImmediate(component, that);
5610 if (instance) {
5611 instantiator.recordKnownComponent(that, instance, name, false);
5612 } else {
5613 delete that[name];
5614 }
5615 }
5616 else if (component.type) {
5617 var type = fluid.expandImmediate(component.type, that, localDynamic);
5618 if (!type) {
5619 fluid.fail("Error in subcomponent record: ", component.type, " could not be resolved to a type for component ", name,
5620 " of parent ", that);
5621 }
5622 var invokeSpec = fluid.assembleCreatorArguments(that, type, {componentRecord: component, memberName: name, localDynamic: localDynamic});
5623 instance = fluid.initSubcomponentImpl(that, {type: invokeSpec.funcName}, invokeSpec.args);
5624 }
5625 else {
5626 fluid.fail("Unrecognised material in place of subcomponent " + name + " - no \"type\" field found");
5627 }
5628 fluid.popActivity();
5629 return instance;
5630 };
5631
5632 fluid.bindDeferredComponent = function (that, componentName, component) {
5633 var events = fluid.makeArray(component.createOnEvent);
5634 fluid.each(events, function (eventName) {
5635 var event = fluid.isIoCReference(eventName) ? fluid.expandOptions(eventName, that) : that.events[eventName];
5636 if (!event || !event.addListener) {
5637 fluid.fail("Error instantiating createOnEvent component with name " + componentName + " of parent ", that, " since event specification " +
5638 eventName + " could not be expanded to an event - got ", event);
5639 }
5640 event.addListener(function () {
5641 fluid.pushActivity("initDeferred", "instantiating deferred component %componentName of parent %that due to event %eventName",
5642 {componentName: componentName, that: that, eventName: eventName});
5643 if (that[componentName]) {
5644 fluid.globalInstantiator.clearComponent(that, componentName);
5645 }
5646 var localRecord = {"arguments": fluid.makeArray(arguments)};
5647 fluid.initDependent(that, componentName, localRecord);
5648 fluid.popActivity();
5649 }, null, component.priority);
5650 });
5651 };
5652
5653 fluid.priorityForComponent = function (component) {
5654 return component.priority ? component.priority :
5655 (component.type === "fluid.typeFount" || fluid.hasGrade(fluid.defaults(component.type), "fluid.typeFount")) ?
5656 "first" : undefined;
5657 };
5658
5659 fluid.initDependents = function (that) {
5660 fluid.pushActivity("initDependents", "instantiating dependent components for component %that", {that: that});
5661 var shadow = fluid.shadowForComponent(that);
5662 shadow.memberStrategy.initter();
5663 shadow.invokerStrategy.initter();
5664
5665 fluid.getForComponent(that, "modelRelay");
5666 fluid.getForComponent(that, "model"); // trigger this as late as possible - but must be before components so that child component has model on its onCreate
5667 if (fluid.isDestroyed(that)) {
5668 return; // Further fix for FLUID-5869 - if we managed to destroy ourselves through some bizarre model self-reaction, bail out here
5669 }
5670
5671 var options = that.options;
5672 var components = options.components || {};
5673 var componentSort = [];
5674
5675 fluid.each(components, function (component, name) {
5676 if (!component.createOnEvent) {
5677 var priority = fluid.priorityForComponent(component);
5678 componentSort.push({namespace: name, priority: fluid.parsePriority(priority)});
5679 }
5680 else {
5681 fluid.bindDeferredComponent(that, name, component);
5682 }
5683 });
5684 fluid.sortByPriority(componentSort);
5685 fluid.each(componentSort, function (entry) {
5686 fluid.initDependent(that, entry.namespace);
5687 });
5688 if (shadow.subcomponentLocal) {
5689 fluid.clear(shadow.subcomponentLocal); // still need repo for event-driven dynamic components - abolish these in time
5690 }
5691 that.lifecycleStatus = "constructed";
5692 fluid.assessTreeConstruction(that, shadow);
5693
5694 fluid.popActivity();
5695 };
5696
5697 fluid.assessTreeConstruction = function (that, shadow) {
5698 var instantiator = fluid.globalInstantiator;
5699 var thatStack = instantiator.getThatStack(that);
5700 var unstableUp = fluid.find_if(thatStack, function (that) {
5701 return that.lifecycleStatus === "constructing";
5702 });
5703 if (unstableUp) {
5704 that.lifecycleStatus = "constructed";
5705 } else {
5706 fluid.markSubtree(instantiator, that, shadow.path, "treeConstructed");
5707 }
5708 };
5709
5710 fluid.markSubtree = function (instantiator, that, path, state) {
5711 that.lifecycleStatus = state;
5712 fluid.visitComponentChildren(that, function (child, name) {
5713 var childPath = instantiator.composePath(path, name);
5714 var childShadow = instantiator.idToShadow[child.id];
5715 var created = childShadow && childShadow.path === childPath;
5716 if (created) {
5717 fluid.markSubtree(instantiator, child, childPath, state);
5718 }
5719 }, {flat: true});
5720 };
5721
5722
5723 /* == BEGIN NEXUS METHODS == */
5724
5725 /**
5726 * Given a component reference, returns the path of that component within its component tree.
5727 *
5728 * @param {Component} component - A reference to a component.
5729 * @param {Instantiator} [instantiator] - (optional) An instantiator to use for the lookup.
5730 * @return {String[]} An array of {String} path segments of the component within its tree, or `null` if the reference does not hold a live component.
5731 */
5732 fluid.pathForComponent = function (component, instantiator) {
5733 instantiator = instantiator || fluid.getInstantiator(component) || fluid.globalInstantiator;
5734 var shadow = instantiator.idToShadow[component.id];
5735 if (!shadow) {
5736 return null;
5737 }
5738 return instantiator.parseEL(shadow.path);
5739 };
5740
5741 /** Construct a component with the supplied options at the specified path in the component tree. The parent path of the location must already be a component.
5742 * @param {String|String[]} path - Path where the new component is to be constructed, represented as a string or array of string segments
5743 * @param {Object} options - Top-level options supplied to the component - must at the very least include a field <code>type</code> holding the component's type
5744 * @param {Instantiator} [instantiator] - [optional] The instantiator holding the component to be created - if blank, the global instantiator will be used
5745 * @return {Object} The constructed component.
5746 */
5747 fluid.construct = function (path, options, instantiator) {
5748 var record = fluid.destroy(path, instantiator);
5749 // TODO: We must construct a more principled scheme for designating child components than this - especially once options become immutable
5750 fluid.set(record.parent, ["options", "components", record.memberName], {
5751 type: options.type,
5752 options: options
5753 });
5754 return fluid.initDependent(record.parent, record.memberName);
5755 };
5756
5757 /** Destroys a component held at the specified path. The parent path must represent a component, although the component itself may be nonexistent
5758 * @param {String|String[]} path - Path where the new component is to be destroyed, represented as a string or array of string segments
5759 * @param {Instantiator} [instantiator] - [optional] The instantiator holding the component to be destroyed - if blank, the global instantiator will be used.
5760 * @return {Object} - An object containing a reference to the parent of the destroyed element, and the member name of the destroyed component.
5761 */
5762 fluid.destroy = function (path, instantiator) {
5763 instantiator = instantiator || fluid.globalInstantiator;
5764 var segs = fluid.model.parseToSegments(path, instantiator.parseEL, true);
5765 if (segs.length === 0) {
5766 fluid.fail("Cannot destroy the root component");
5767 }
5768 var memberName = segs.pop(), parentPath = instantiator.composeSegments.apply(null, segs);
5769 var parent = instantiator.pathToComponent[parentPath];
5770 if (!parent) {
5771 fluid.fail("Cannot modify component with nonexistent parent at path ", path);
5772 }
5773 if (parent[memberName]) {
5774 parent[memberName].destroy();
5775 }
5776 return {
5777 parent: parent,
5778 memberName: memberName
5779 };
5780 };
5781
5782 /** Construct an instance of a component as a child of the specified parent, with a well-known, unique name derived from its typeName
5783 * @param {String|String[]} parentPath - Parent of path where the new component is to be constructed, represented as a {String} or array of {String} segments
5784 * @param {String|Object} options - Options encoding the component to be constructed. If this is of type String, it is assumed to represent the component's typeName with no options
5785 * @param {Instantiator} [instantiator] - [optional] The instantiator holding the component to be created - if blank, the global instantiator will be used
5786 */
5787 fluid.constructSingle = function (parentPath, options, instantiator) {
5788 instantiator = instantiator || fluid.globalInstantiator;
5789 parentPath = parentPath || "";
5790 var segs = fluid.model.parseToSegments(parentPath, instantiator.parseEL, true);
5791 if (typeof(options) === "string") {
5792 options = {type: options};
5793 }
5794 var type = options.type;
5795 if (!type) {
5796 fluid.fail("Cannot construct singleton object without a type entry");
5797 }
5798 options = $.extend({}, options);
5799 var gradeNames = options.gradeNames = fluid.makeArray(options.gradeNames);
5800 gradeNames.unshift(type); // principal type may be noninstantiable
5801 options.type = "fluid.component";
5802 var root = segs.length === 0;
5803 if (root) {
5804 gradeNames.push("fluid.resolveRoot");
5805 }
5806 var memberName = fluid.typeNameToMemberName(options.singleRootType || type);
5807 segs.push(memberName);
5808 fluid.construct(segs, options, instantiator);
5809 };
5810
5811 /** Destroy an instance created by `fluid.constructSingle`
5812 * @param {String|String[]} parentPath - Parent of path where the new component is to be constructed, represented as a {String} or array of {String} segments
5813 * @param {String} typeName - The type name used to construct the component (either `type` or `singleRootType` of the `options` argument to `fluid.constructSingle`
5814 * @param {Instantiator} [instantiator] - [optional] The instantiator holding the component to be created - if blank, the global instantiator will be used
5815 */
5816 fluid.destroySingle = function (parentPath, typeName, instantiator) {
5817 instantiator = instantiator || fluid.globalInstantiator;
5818 var segs = fluid.model.parseToSegments(parentPath, instantiator.parseEL, true);
5819 var memberName = fluid.typeNameToMemberName(typeName);
5820 segs.push(memberName);
5821 fluid.destroy(segs, instantiator);
5822 };
5823
5824 /** Registers and constructs a "linkage distribution" which will ensure that wherever a set of "input grades" co-occur, they will
5825 * always result in a supplied "output grades" in the component where they co-occur.
5826 * @param {String} linkageName - The name of the grade which will broadcast the resulting linkage. If required, this linkage can be destroyed by supplying this name to `fluid.destroySingle`.
5827 * @param {String[]} inputNames - An array of grade names which will be tested globally for co-occurrence
5828 * @param {String|String[]} outputNames - A single grade name or array of grade names which will be output into the co-occuring component
5829 */
5830 fluid.makeGradeLinkage = function (linkageName, inputNames, outputNames) {
5831 fluid.defaults(linkageName, {
5832 gradeNames: "fluid.component",
5833 distributeOptions: {
5834 record: outputNames,
5835 target: "{/ " + inputNames.join("&") + "}.options.gradeNames"
5836 }
5837 });
5838 fluid.constructSingle([], linkageName);
5839 };
5840
5841 /** Retrieves a component by global path.
5842 * @param {String|String[]} path - The global path of the component to look up, expressed as a string or as an array of segments.
5843 * @return {Object} - The component at the specified path, or undefined if none is found.
5844 */
5845 fluid.componentForPath = function (path) {
5846 return fluid.globalInstantiator.pathToComponent[fluid.isArrayable(path) ? path.join(".") : path];
5847 };
5848
5849 /** END NEXUS METHODS **/
5850
5851 /** BEGIN IOC DEBUGGING METHODS **/
5852 fluid["debugger"] = function () {
5853 debugger; // eslint-disable-line no-debugger
5854 };
5855
5856 fluid.defaults("fluid.debuggingProbe", {
5857 gradeNames: ["fluid.component"]
5858 });
5859
5860 // probe looks like:
5861 // target: {preview other}.listeners.eventName
5862 // priority: first/last
5863 // func: console.log/fluid.log/fluid.debugger
5864 fluid.probeToDistribution = function (probe) {
5865 var instantiator = fluid.globalInstantiator;
5866 var parsed = fluid.parseContextReference(probe.target);
5867 var segs = fluid.model.parseToSegments(parsed.path, instantiator.parseEL, true);
5868 if (segs[0] !== "options") {
5869 segs.unshift("options"); // compensate for this insanity until we have the great options flattening
5870 }
5871 var parsedPriority = fluid.parsePriority(probe.priority);
5872 if (parsedPriority.constraint && !parsedPriority.constraint.target) {
5873 parsedPriority.constraint.target = "authoring";
5874 }
5875 return {
5876 target: "{/ " + parsed.context + "}." + instantiator.composeSegments.apply(null, segs),
5877 record: {
5878 func: probe.func,
5879 funcName: probe.funcName,
5880 args: probe.args,
5881 priority: fluid.renderPriority(parsedPriority)
5882 }
5883 };
5884 };
5885
5886 fluid.registerProbes = function (probes) {
5887 var probeDistribution = fluid.transform(probes, fluid.probeToDistribution);
5888 var memberName = "fluid_debuggingProbe_" + fluid.allocateGuid();
5889 fluid.construct([memberName], {
5890 type: "fluid.debuggingProbe",
5891 distributeOptions: probeDistribution
5892 });
5893 return memberName;
5894 };
5895
5896 fluid.deregisterProbes = function (probeName) {
5897 fluid.destroy([probeName]);
5898 };
5899
5900 /** END IOC DEBUGGING METHODS **/
5901
5902 fluid.thisistToApplicable = function (record, recthis, that) {
5903 return {
5904 apply: function (noThis, args) {
5905 // Resolve this material late, to deal with cases where the target has only just been brought into existence
5906 // (e.g. a jQuery target for rendered material) - TODO: Possibly implement cached versions of these as we might do for invokers
5907 var resolvedThis = fluid.expandOptions(recthis, that);
5908 if (typeof(resolvedThis) === "string") {
5909 resolvedThis = fluid.getGlobalValue(resolvedThis);
5910 }
5911 if (!resolvedThis) {
5912 fluid.fail("Could not resolve reference " + recthis + " to a value");
5913 }
5914 var resolvedFunc = resolvedThis[record.method];
5915 if (typeof(resolvedFunc) !== "function") {
5916 fluid.fail("Object ", resolvedThis, " at reference " + recthis + " has no member named " + record.method + " which is a function ");
5917 }
5918 if (fluid.passLogLevel(fluid.logLevel.TRACE)) {
5919 fluid.log(fluid.logLevel.TRACE, "Applying arguments ", args, " to method " + record.method + " of instance ", resolvedThis);
5920 }
5921 return resolvedFunc.apply(resolvedThis, args);
5922 }
5923 };
5924 };
5925
5926 fluid.changeToApplicable = function (record, that) {
5927 return {
5928 apply: function (noThis, args, localRecord, mergeRecord) {
5929 var parsed = fluid.parseValidModelReference(that, "changePath listener record", record.changePath);
5930 var value = fluid.expandOptions(record.value, that, {}, fluid.extend(localRecord, {"arguments": args}));
5931 var sources = mergeRecord && mergeRecord.source && mergeRecord.source.length ? fluid.makeArray(record.source).concat(mergeRecord.source) : record.source;
5932 parsed.applier.change(parsed.modelSegs, value, record.type, sources); // FLUID-5586 now resolved
5933 }
5934 };
5935 };
5936
5937 // Convert "exotic records" into an applicable form ("this/method" for FLUID-4878 or "changePath" for FLUID-3674)
5938 fluid.recordToApplicable = function (record, that, standard) {
5939 if (record.changePath !== undefined) { // Allow falsy paths for FLUID-5586
5940 return fluid.changeToApplicable(record, that, standard);
5941 }
5942 var recthis = record["this"];
5943 if (record.method ^ recthis) {
5944 fluid.fail("Record ", that, " must contain both entries \"method\" and \"this\" if it contains either");
5945 }
5946 return record.method ? fluid.thisistToApplicable(record, recthis, that) : null;
5947 };
5948
5949 fluid.getGlobalValueNonComponent = function (funcName, context) { // TODO: Guard this in listeners as well
5950 var defaults = fluid.defaults(funcName);
5951 if (defaults && fluid.hasGrade(defaults, "fluid.component")) {
5952 fluid.fail("Error in function specification - cannot invoke function " + funcName + " in the context of " + context + ": component creator functions can only be used as subcomponents");
5953 }
5954 return fluid.getGlobalValue(funcName);
5955 };
5956
5957 fluid.makeInvoker = function (that, invokerec, name) {
5958 invokerec = fluid.upgradePrimitiveFunc(invokerec); // shorthand case for direct function invokers (FLUID-4926)
5959 if (invokerec.args !== undefined && invokerec.args !== fluid.NO_VALUE && !fluid.isArrayable(invokerec.args)) {
5960 invokerec.args = fluid.makeArray(invokerec.args);
5961 }
5962 var func = fluid.recordToApplicable(invokerec, that);
5963 var invokePre = fluid.preExpand(invokerec.args);
5964 var localRecord = {};
5965 var expandOptions = fluid.makeStackResolverOptions(that, localRecord, true);
5966 func = func || (invokerec.funcName ? fluid.getGlobalValueNonComponent(invokerec.funcName, "an invoker") : fluid.expandImmediate(invokerec.func, that));
5967 if (!func || !func.apply) {
5968 fluid.fail("Error in invoker record: could not resolve members func, funcName or method to a function implementation - got " + func + " from ", invokerec);
5969 } else if (func === fluid.notImplemented) {
5970 fluid.fail("Error constructing component ", that, " - the invoker named " + name + " which was defined in grade " + invokerec.componentSource + " needs to be overridden with a concrete implementation");
5971 }
5972 return function invokeInvoker() {
5973 if (fluid.defeatLogging === false) {
5974 fluid.pushActivity("invokeInvoker", "invoking invoker with name %name and record %record from path %path holding component %that",
5975 {name: name, record: invokerec, path: fluid.dumpComponentPath(that), that: that});
5976 }
5977 var togo, finalArgs;
5978 if (that.lifecycleStatus === "destroyed") {
5979 fluid.log(fluid.logLevel.WARN, "Ignoring call to invoker " + name + " of component ", that, " which has been destroyed");
5980 } else {
5981 localRecord.arguments = arguments;
5982 if (invokerec.args === undefined || invokerec.args === fluid.NO_VALUE) {
5983 finalArgs = arguments;
5984 } else {
5985 fluid.expandImmediateImpl(invokePre, expandOptions);
5986 finalArgs = invokePre.source;
5987 }
5988 togo = func.apply(null, finalArgs);
5989 }
5990 if (fluid.defeatLogging === false) {
5991 fluid.popActivity();
5992 }
5993 return togo;
5994 };
5995 };
5996
5997 // weird higher-order function so that we can staightforwardly dispatch original args back onto listener
5998 fluid.event.makeTrackedListenerAdder = function (source) {
5999 var shadow = fluid.shadowForComponent(source);
6000 return function (event) {
6001 return {addListener: function (listener, namespace, priority, softNamespace, listenerId) {
6002 fluid.recordListener(event, listener, shadow, listenerId);
6003 event.addListener.apply(null, arguments);
6004 }};
6005 };
6006 };
6007
6008 fluid.event.listenerEngine = function (eventSpec, callback, adder) {
6009 var argstruc = {};
6010 function checkFire() {
6011 var notall = fluid.find(eventSpec, function (value, key) {
6012 if (argstruc[key] === undefined) {
6013 return true;
6014 }
6015 });
6016 if (!notall) {
6017 var oldstruc = argstruc;
6018 argstruc = {}; // guard against the case the callback perversely fires one of its prerequisites (FLUID-5112)
6019 callback(oldstruc);
6020 }
6021 }
6022 fluid.each(eventSpec, function (event, eventName) {
6023 adder(event).addListener(function () {
6024 argstruc[eventName] = fluid.makeArray(arguments);
6025 checkFire();
6026 });
6027 });
6028 };
6029
6030 fluid.event.dispatchListener = function (that, listener, eventName, eventSpec, wrappedArgs) {
6031 if (eventSpec.args !== undefined && eventSpec.args !== fluid.NO_VALUE && !fluid.isArrayable(eventSpec.args)) {
6032 eventSpec.args = fluid.makeArray(eventSpec.args);
6033 }
6034 listener = fluid.event.resolveListener(listener); // In theory this optimisation is too aggressive if global name is not defined yet
6035 var dispatchPre = fluid.preExpand(eventSpec.args);
6036 var localRecord = {};
6037 var expandOptions = fluid.makeStackResolverOptions(that, localRecord, true);
6038 var togo = function () {
6039 if (fluid.defeatLogging === false) {
6040 fluid.pushActivity("dispatchListener", "firing to listener to event named %eventName of component %that",
6041 {eventName: eventName, that: that});
6042 }
6043
6044 var args = wrappedArgs ? arguments[0] : arguments, finalArgs;
6045 localRecord.arguments = args;
6046 if (eventSpec.args !== undefined && eventSpec.args !== fluid.NO_VALUE) {
6047 // In theory something more exotic happens here, and in makeInvoker - where "source" is an array we want to
6048 // keep its base reference stable since Function.apply will fork it sufficiently, but we really need to
6049 // clone each structured argument. Implies that expandImmediateImpl needs to be split in two, and operate
6050 // reference by "segs" rather than by "holder"
6051 fluid.expandImmediateImpl(dispatchPre, expandOptions);
6052 finalArgs = dispatchPre.source;
6053 } else {
6054 finalArgs = args;
6055 }
6056 var togo = listener.apply(null, finalArgs);
6057 if (fluid.defeatLogging === false) {
6058 fluid.popActivity();
6059 }
6060 return togo;
6061 };
6062 fluid.event.impersonateListener(listener, togo); // still necessary for FLUID-5254 even though framework's listeners now get explicit guids
6063 return togo;
6064 };
6065
6066 fluid.event.resolveSoftNamespace = function (key) {
6067 if (typeof(key) !== "string") {
6068 return null;
6069 } else {
6070 var lastpos = Math.max(key.lastIndexOf("."), key.lastIndexOf("}"));
6071 return key.substring(lastpos + 1);
6072 }
6073 };
6074
6075 fluid.event.resolveListenerRecord = function (lisrec, that, eventName, namespace, standard) {
6076 var badRec = function (record, extra) {
6077 fluid.fail("Error in listener record - could not resolve reference ", record, " to a listener or firer. " +
6078 "Did you miss out \"events.\" when referring to an event firer?" + extra);
6079 };
6080 fluid.pushActivity("resolveListenerRecord", "resolving listener record for event named %eventName for component %that",
6081 {eventName: eventName, that: that});
6082 var records = fluid.makeArray(lisrec);
6083 var transRecs = fluid.transform(records, function (record) {
6084 // TODO: FLUID-5242 fix - we copy here since distributeOptions does not copy options blocks that it distributes and we can hence corrupt them.
6085 // need to clarify policy on options sharing - for slightly better efficiency, copy should happen during distribution and not here
6086 // Note that fluid.mergeModelListeners expects to write to these too
6087 var expanded = fluid.isPrimitive(record) || record.expander ? {listener: record} : fluid.copy(record);
6088 var methodist = fluid.recordToApplicable(record, that, standard);
6089 if (methodist) {
6090 expanded.listener = methodist;
6091 }
6092 else {
6093 expanded.listener = expanded.listener || expanded.func || expanded.funcName;
6094 }
6095 if (!expanded.listener) {
6096 badRec(record, " Listener record must contain a member named \"listener\", \"func\", \"funcName\" or \"method\"");
6097 }
6098 var softNamespace = record.method ?
6099 fluid.event.resolveSoftNamespace(record["this"]) + "." + record.method :
6100 fluid.event.resolveSoftNamespace(expanded.listener);
6101 if (!expanded.namespace && !namespace && softNamespace) {
6102 expanded.softNamespace = true;
6103 expanded.namespace = (record.componentSource ? record.componentSource : that.typeName) + "." + softNamespace;
6104 }
6105 var listener = expanded.listener = fluid.expandOptions(expanded.listener, that);
6106 if (!listener) {
6107 badRec(record, "");
6108 }
6109 var firer = false;
6110 if (listener.typeName === "fluid.event.firer") {
6111 listener = listener.fire;
6112 firer = true;
6113 }
6114 expanded.listener = (standard && (expanded.args && listener !== "fluid.notImplemented" || firer)) ? fluid.event.dispatchListener(that, listener, eventName, expanded) : listener;
6115 expanded.listenerId = fluid.allocateGuid();
6116 return expanded;
6117 });
6118 var togo = {
6119 records: transRecs,
6120 adderWrapper: standard ? fluid.event.makeTrackedListenerAdder(that) : null
6121 };
6122 fluid.popActivity();
6123 return togo;
6124 };
6125
6126 fluid.event.expandOneEvent = function (that, event) {
6127 var origin;
6128 if (typeof(event) === "string" && event.charAt(0) !== "{") {
6129 // Shorthand for resolving onto our own events, but with GINGER WORLD!
6130 origin = fluid.getForComponent(that, ["events", event]);
6131 }
6132 else {
6133 origin = fluid.expandOptions(event, that);
6134 }
6135 if (!origin || origin.typeName !== "fluid.event.firer") {
6136 fluid.fail("Error in event specification - could not resolve base event reference ", event, " to an event firer: got ", origin);
6137 }
6138 return origin;
6139 };
6140
6141 fluid.event.expandEvents = function (that, event) {
6142 return typeof(event) === "string" ?
6143 fluid.event.expandOneEvent(that, event) :
6144 fluid.transform(event, function (oneEvent) {
6145 return fluid.event.expandOneEvent(that, oneEvent);
6146 });
6147 };
6148
6149 fluid.event.resolveEvent = function (that, eventName, eventSpec) {
6150 fluid.pushActivity("resolveEvent", "resolving event with name %eventName attached to component %that",
6151 {eventName: eventName, that: that});
6152 var adder = fluid.event.makeTrackedListenerAdder(that);
6153 if (typeof(eventSpec) === "string") {
6154 eventSpec = {event: eventSpec};
6155 }
6156 var event = eventSpec.typeName === "fluid.event.firer" ? eventSpec : eventSpec.event || eventSpec.events;
6157 if (!event) {
6158 fluid.fail("Event specification for event with name " + eventName + " does not include a base event specification: ", eventSpec);
6159 }
6160
6161 var origin = event.typeName === "fluid.event.firer" ? event : fluid.event.expandEvents(that, event);
6162
6163 var isMultiple = origin.typeName !== "fluid.event.firer";
6164 var isComposite = eventSpec.args || isMultiple;
6165 // If "event" is not composite, we want to share the listener list and FIRE method with the original
6166 // If "event" is composite, we need to create a new firer. "composite" includes case where any boiling
6167 // occurred - this was implemented wrongly in 1.4.
6168 var firer;
6169 if (isComposite) {
6170 firer = fluid.makeEventFirer({name: " [composite] " + fluid.event.nameEvent(that, eventName)});
6171 var dispatcher = fluid.event.dispatchListener(that, firer.fire, eventName, eventSpec, isMultiple);
6172 if (isMultiple) {
6173 fluid.event.listenerEngine(origin, dispatcher, adder);
6174 }
6175 else {
6176 adder(origin).addListener(dispatcher);
6177 }
6178 }
6179 else {
6180 firer = {typeName: "fluid.event.firer"};
6181 firer.fire = function () {
6182 var outerArgs = fluid.makeArray(arguments);
6183 fluid.pushActivity("fireSynthetic", "firing synthetic event %eventName ", {eventName: eventName});
6184 var togo = origin.fire.apply(null, outerArgs);
6185 fluid.popActivity();
6186 return togo;
6187 };
6188 firer.addListener = function (listener, namespace, priority, softNamespace, listenerId) {
6189 var dispatcher = fluid.event.dispatchListener(that, listener, eventName, eventSpec);
6190 adder(origin).addListener(dispatcher, namespace, priority, softNamespace, listenerId);
6191 };
6192 firer.removeListener = function (listener) {
6193 origin.removeListener(listener);
6194 };
6195 // To allow introspection on listeners in cases such as fluid.test.findListenerId
6196 firer.originEvent = origin;
6197 }
6198 fluid.popActivity();
6199 return firer;
6200 };
6201
6202 /** BEGIN unofficial IoC material **/
6203 // The following three functions are unsupported ane only used in the renderer expander.
6204 // The material they produce is no longer recognised for component resolution.
6205
6206 fluid.withEnvironment = function (envAdd, func, root) {
6207 var key;
6208 root = root || fluid.globalThreadLocal();
6209 try {
6210 for (key in envAdd) {
6211 root[key] = envAdd[key];
6212 }
6213 $.extend(root, envAdd);
6214 return func();
6215 } finally {
6216 for (key in envAdd) {
6217 delete root[key]; // TODO: users may want a recursive "scoping" model
6218 }
6219 }
6220 };
6221
6222 fluid.fetchContextReference = function (parsed, directModel, env, elResolver, externalFetcher) {
6223 // The "elResolver" is a hack to make certain common idioms in protoTrees work correctly, where a contextualised EL
6224 // path actually resolves onto a further EL reference rather than directly onto a value target
6225 if (elResolver) {
6226 parsed = elResolver(parsed, env);
6227 }
6228 var base = parsed.context ? env[parsed.context] : directModel;
6229 if (!base) {
6230 var resolveExternal = externalFetcher && externalFetcher(parsed);
6231 return resolveExternal || base;
6232 }
6233 return parsed.noDereference ? parsed.path : fluid.get(base, parsed.path);
6234 };
6235
6236 fluid.makeEnvironmentFetcher = function (directModel, elResolver, envGetter, externalFetcher) {
6237 envGetter = envGetter || fluid.globalThreadLocal;
6238 return function (parsed) {
6239 var env = envGetter();
6240 return fluid.fetchContextReference(parsed, directModel, env, elResolver, externalFetcher);
6241 };
6242 };
6243
6244 /** END of unofficial IoC material **/
6245
6246 /* Compact expansion machinery - for short form invoker and expander references such as @expand:func(arg) and func(arg) */
6247
6248 fluid.coerceToPrimitive = function (string) {
6249 return string === "false" ? false : (string === "true" ? true :
6250 (isFinite(string) ? Number(string) : string));
6251 };
6252
6253 fluid.compactStringToRec = function (string, type) {
6254 var openPos = string.indexOf("(");
6255 var closePos = string.indexOf(")");
6256 if (openPos === -1 ^ closePos === -1 || openPos > closePos) {
6257 fluid.fail("Badly-formed compact " + type + " record without matching parentheses: " + string);
6258 }
6259 if (openPos !== -1 && closePos !== -1) {
6260 var trail = string.substring(closePos + 1);
6261 if ($.trim(trail) !== "") {
6262 fluid.fail("Badly-formed compact " + type + " record " + string + " - unexpected material following close parenthesis: " + trail);
6263 }
6264 var prefix = string.substring(0, openPos);
6265 var body = $.trim(string.substring(openPos + 1, closePos));
6266 var args = body === "" ? [] : fluid.transform(body.split(","), $.trim, fluid.coerceToPrimitive);
6267 var togo = fluid.upgradePrimitiveFunc(prefix, null);
6268 togo.args = args;
6269 return togo;
6270 }
6271 else if (type === "expander") {
6272 fluid.fail("Badly-formed compact expander record without parentheses: " + string);
6273 }
6274 return string;
6275 };
6276
6277 fluid.expandPrefix = "@expand:";
6278
6279 fluid.expandCompactString = function (string, active) {
6280 var rec = string;
6281 if (string.indexOf(fluid.expandPrefix) === 0) {
6282 var rem = string.substring(fluid.expandPrefix.length);
6283 rec = {
6284 expander: fluid.compactStringToRec(rem, "expander")
6285 };
6286 }
6287 else if (active) {
6288 rec = fluid.compactStringToRec(string, active);
6289 }
6290 return rec;
6291 };
6292
6293 var singularPenRecord = {
6294 listeners: "listener",
6295 modelListeners: "modelListener"
6296 };
6297
6298 var singularRecord = $.extend({
6299 invokers: "invoker"
6300 }, singularPenRecord);
6301
6302 fluid.expandCompactRec = function (segs, target, source) {
6303 fluid.guardCircularExpansion(segs, segs.length);
6304 var pen = segs.length > 0 ? segs[segs.length - 1] : "";
6305 var active = singularRecord[pen];
6306 if (!active && segs.length > 1) {
6307 active = singularPenRecord[segs[segs.length - 2]]; // support array of listeners and modelListeners
6308 }
6309 fluid.each(source, function (value, key) {
6310 if (fluid.isPlainObject(value)) {
6311 target[key] = fluid.freshContainer(value);
6312 segs.push(key);
6313 fluid.expandCompactRec(segs, target[key], value);
6314 segs.pop();
6315 return;
6316 }
6317 else if (typeof(value) === "string") {
6318 value = fluid.expandCompactString(value, active);
6319 }
6320 target[key] = value;
6321 });
6322 };
6323
6324 fluid.expandCompact = function (options) {
6325 var togo = {};
6326 fluid.expandCompactRec([], togo, options);
6327 return togo;
6328 };
6329
6330 /** End compact record expansion machinery **/
6331
6332 fluid.extractEL = function (string, options) {
6333 if (options.ELstyle === "ALL") {
6334 return string;
6335 }
6336 else if (options.ELstyle.length === 1) {
6337 if (string.charAt(0) === options.ELstyle) {
6338 return string.substring(1);
6339 }
6340 }
6341 else if (options.ELstyle === "${}") {
6342 var i1 = string.indexOf("${");
6343 var i2 = string.lastIndexOf("}");
6344 if (i1 === 0 && i2 !== -1) {
6345 return string.substring(2, i2);
6346 }
6347 }
6348 };
6349
6350 fluid.extractELWithContext = function (string, options) {
6351 var EL = fluid.extractEL(string, options);
6352 if (fluid.isIoCReference(EL)) {
6353 return fluid.parseContextReference(EL);
6354 }
6355 return EL ? {path: EL} : EL;
6356 };
6357
6358 /** Parse the string form of a contextualised IoC reference into an object.
6359 * @param {String} reference - The reference to be parsed. The character at position `index` is assumed to be `{`
6360 * @param {String} [index] - [optional] The index into the string to start parsing at, if omitted, defaults to 0
6361 * @param {Character} [delimiter] - [optional] A character which will delimit the end of the context expression. If omitted, the expression continues to the end of the string.
6362 * @return {ParsedContext} A structure holding the parsed structure, with members
6363 * context {String|ParsedContext} The context portion of the reference. This will be a `string` for a flat reference, or a further `ParsedContext` for a recursive reference
6364 * path {String} The string portion of the reference
6365 * endpos {Integer} The position in the string where parsing stopped [this member is not supported and will be removed in a future release]
6366 */
6367 fluid.parseContextReference = function (reference, index, delimiter) {
6368 index = index || 0;
6369 var isNested = reference.charAt(index + 1) === "{", endcpos, context, nested;
6370 if (isNested) {
6371 nested = fluid.parseContextReference(reference, index + 1, "}");
6372 endcpos = nested.endpos;
6373 } else {
6374 endcpos = reference.indexOf("}", index + 1);
6375 }
6376 if (endcpos === -1) {
6377 fluid.fail("Cannot parse context reference \"" + reference + "\": Malformed context reference without }");
6378 }
6379 if (isNested) {
6380 context = nested;
6381 } else {
6382 context = reference.substring(index + 1, endcpos);
6383 }
6384 var endpos = delimiter ? reference.indexOf(delimiter, endcpos + 1) : reference.length;
6385 var path = reference.substring(endcpos + 1, endpos);
6386 if (path.charAt(0) === ".") {
6387 path = path.substring(1);
6388 }
6389 return {context: context, path: path, endpos: endpos};
6390 };
6391
6392 fluid.renderContextReference = function (parsed) {
6393 var context = parsed.context;
6394 return "{" + (typeof(context) === "string" ? context : fluid.renderContextReference(context)) + "}" + (parsed.path ? "." + parsed.path : "");
6395 };
6396
6397 // TODO: Once we eliminate expandSource (in favour of fluid.expander.fetch), all of this tree of functions can be hived off to RendererUtilities
6398 fluid.resolveContextValue = function (string, options) {
6399 function fetch(parsed) {
6400 fluid.pushActivity("resolveContextValue", "resolving context value %parsed", {parsed: parsed});
6401 var togo = options.fetcher(parsed);
6402 fluid.pushActivity("resolvedContextValue", "resolved value %parsed to value %value", {parsed: parsed, value: togo});
6403 fluid.popActivity(2);
6404 return togo;
6405 }
6406 var parsed;
6407 if (options.bareContextRefs && fluid.isIoCReference(string)) {
6408 parsed = fluid.parseContextReference(string);
6409 return fetch(parsed);
6410 }
6411 else if (options.ELstyle && options.ELstyle !== "${}") {
6412 parsed = fluid.extractELWithContext(string, options);
6413 if (parsed) {
6414 return fetch(parsed);
6415 }
6416 }
6417 while (typeof(string) === "string") {
6418 var i1 = string.indexOf("${");
6419 var i2 = string.indexOf("}", i1 + 2);
6420 if (i1 !== -1 && i2 !== -1) {
6421 if (string.charAt(i1 + 2) === "{") {
6422 parsed = fluid.parseContextReference(string, i1 + 2, "}");
6423 i2 = parsed.endpos;
6424 }
6425 else {
6426 parsed = {path: string.substring(i1 + 2, i2)};
6427 }
6428 var subs = fetch(parsed);
6429 var all = (i1 === 0 && i2 === string.length - 1);
6430 // TODO: test case for all undefined substitution
6431 if (subs === undefined || subs === null) {
6432 return subs;
6433 }
6434 string = all ? subs : string.substring(0, i1) + subs + string.substring(i2 + 1);
6435 }
6436 else {
6437 break;
6438 }
6439 }
6440 return string;
6441 };
6442
6443 // This function appears somewhat reusable, but not entirely - it probably needs to be packaged
6444 // along with the particular "strategy". Very similar to the old "filter"... the "outer driver" needs
6445 // to execute it to get the first recursion going at top level. This was one of the most odd results
6446 // of the reorganisation, since the "old work" seemed much more naturally expressed in terms of values
6447 // and what happened to them. The "new work" is expressed in terms of paths and how to move amongst them.
6448 fluid.fetchExpandChildren = function (target, i, segs, source, mergePolicy, options) {
6449 if (source.expander) { // possible expander at top level
6450 var expanded = fluid.expandExpander(target, source, options);
6451 if (fluid.isPrimitive(expanded) || !fluid.isPlainObject(expanded) || (fluid.isArrayable(expanded) ^ fluid.isArrayable(target))) {
6452 return expanded;
6453 }
6454 else { // make an attempt to preserve the root reference if possible
6455 $.extend(true, target, expanded);
6456 }
6457 }
6458 // NOTE! This expects that RHS is concrete! For material input to "expansion" this happens to be the case, but is not
6459 // true for other algorithms. Inconsistently, this algorithm uses "sourceStrategy" below. In fact, this "fetchChildren"
6460 // operation looks like it is a fundamental primitive of the system. We do call "deliverer" early which enables correct
6461 // reference to parent nodes up the tree - however, anyone processing a tree IN THE CHAIN requires that it is produced
6462 // concretely at the point STRATEGY returns. Which in fact it is...............
6463 fluid.each(source, function (newSource, key) {
6464 if (newSource === undefined) {
6465 target[key] = undefined; // avoid ever dispatching to ourselves with undefined source
6466 }
6467 else if (key !== "expander") {
6468 segs[i] = key;
6469 if (fluid.getImmediate(options.exceptions, segs, i) !== true) {
6470 options.strategy(target, key, i + 1, segs, source, mergePolicy);
6471 }
6472 }
6473 });
6474 return target;
6475 };
6476
6477 // TODO: This method is unnecessary and will quadratic inefficiency if RHS block is not concrete.
6478 // The driver should detect "homogeneous uni-strategy trundling" and agree to preserve the extra
6479 // "cursor arguments" which should be advertised somehow (at least their number)
6480 function regenerateCursor(source, segs, limit, sourceStrategy) {
6481 for (var i = 0; i < limit; ++i) {
6482 // copy segs to avoid aliasing with FLUID-5243
6483 source = sourceStrategy(source, segs[i], i, fluid.makeArray(segs));
6484 }
6485 return source;
6486 }
6487
6488 fluid.isUnexpandable = function (source) { // slightly more efficient compound of fluid.isCopyable and fluid.isComponent - review performance
6489 return fluid.isPrimitive(source) || !fluid.isPlainObject(source);
6490 };
6491
6492 fluid.expandSource = function (options, target, i, segs, deliverer, source, policy, recurse) {
6493 var expanded, isTrunk;
6494 var thisPolicy = fluid.derefMergePolicy(policy);
6495 if (typeof (source) === "string" && !thisPolicy.noexpand) {
6496 if (!options.defaultEL || source.charAt(0) === "{") { // hard-code this for performance
6497 fluid.pushActivity("expandContextValue", "expanding context value %source held at path %path", {source: source, path: fluid.path.apply(null, segs.slice(0, i))});
6498 expanded = fluid.resolveContextValue(source, options);
6499 fluid.popActivity(1);
6500 } else {
6501 expanded = source;
6502 }
6503 }
6504 else if (thisPolicy.noexpand || fluid.isUnexpandable(source)) {
6505 expanded = source;
6506 }
6507 else if (source.expander) {
6508 expanded = fluid.expandExpander(deliverer, source, options);
6509 }
6510 else {
6511 expanded = fluid.freshContainer(source);
6512 isTrunk = true;
6513 }
6514 if (expanded !== fluid.NO_VALUE) {
6515 deliverer(expanded);
6516 }
6517 if (isTrunk) {
6518 recurse(expanded, source, i, segs, policy);
6519 }
6520 return expanded;
6521 };
6522
6523 fluid.guardCircularExpansion = function (segs, i) {
6524 if (i > fluid.strategyRecursionBailout) {
6525 fluid.fail("Overflow/circularity in options expansion, current path is ", segs, " at depth " , i, " - please ensure options are not circularly connected, or protect from expansion using the \"noexpand\" policy or expander");
6526 }
6527 };
6528
6529 fluid.makeExpandStrategy = function (options) {
6530 var recurse = function (target, source, i, segs, policy) {
6531 return fluid.fetchExpandChildren(target, i || 0, segs || [], source, policy, options);
6532 };
6533 var strategy = function (target, name, i, segs, source, policy) {
6534 fluid.guardCircularExpansion(segs, i);
6535 if (!target) {
6536 return;
6537 }
6538 if (target.hasOwnProperty(name)) { // bail out if our work has already been done
6539 return target[name];
6540 }
6541 if (source === undefined) { // recover our state in case this is an external entry point
6542 source = regenerateCursor(options.source, segs, i - 1, options.sourceStrategy);
6543 policy = regenerateCursor(options.mergePolicy, segs, i - 1, fluid.concreteTrundler);
6544 }
6545 var thisSource = options.sourceStrategy(source, name, i, segs);
6546 var thisPolicy = fluid.concreteTrundler(policy, name);
6547 function deliverer(value) {
6548 target[name] = value;
6549 }
6550 return fluid.expandSource(options, target, i, segs, deliverer, thisSource, thisPolicy, recurse);
6551 };
6552 options.recurse = recurse;
6553 options.strategy = strategy;
6554 return strategy;
6555 };
6556
6557 fluid.defaults("fluid.makeExpandOptions", {
6558 ELstyle: "${}",
6559 bareContextRefs: true,
6560 target: fluid.inCreationMarker
6561 });
6562
6563 fluid.makeExpandOptions = function (source, options) {
6564 options = $.extend({}, fluid.rawDefaults("fluid.makeExpandOptions"), options);
6565 options.defaultEL = options.ELStyle === "${}" && options.bareContextRefs; // optimisation to help expander
6566 options.expandSource = function (source) {
6567 return fluid.expandSource(options, null, 0, [], fluid.identity, source, options.mergePolicy, false);
6568 };
6569 if (!fluid.isUnexpandable(source)) {
6570 options.source = source;
6571 options.target = fluid.freshContainer(source);
6572 options.sourceStrategy = options.sourceStrategy || fluid.concreteTrundler;
6573 fluid.makeExpandStrategy(options);
6574 options.initter = function () {
6575 options.target = fluid.fetchExpandChildren(options.target, 0, [], options.source, options.mergePolicy, options);
6576 };
6577 }
6578 else { // these init immediately since we must deliver a valid root target
6579 options.strategy = fluid.concreteTrundler;
6580 options.initter = fluid.identity;
6581 if (typeof(source) === "string") {
6582 // Copy is necessary to resolve FLUID-6213 since targets are regularly scrawled over with "undefined" by dim expansion pathway
6583 // However, we can't screw up object identity for uncloneable things like events resolved via local expansion
6584 options.target = (options.defer ? fluid.copy : fluid.identity)(options.expandSource(source));
6585 }
6586 else {
6587 options.target = source;
6588 }
6589 options.immutableTarget = true;
6590 }
6591 return options;
6592 };
6593
6594 // supported, PUBLIC API function
6595 fluid.expand = function (source, options) {
6596 var expandOptions = fluid.makeExpandOptions(source, options);
6597 expandOptions.initter();
6598 return expandOptions.target;
6599 };
6600
6601 fluid.preExpandRecurse = function (root, source, holder, member, rootSegs) { // on entry, holder[member] = source
6602 fluid.guardCircularExpansion(rootSegs, rootSegs.length);
6603 function pushExpander(expander) {
6604 root.expanders.push({expander: expander, holder: holder, member: member});
6605 delete holder[member];
6606 }
6607 if (fluid.isIoCReference(source)) {
6608 var parsed = fluid.parseContextReference(source);
6609 var segs = fluid.model.parseEL(parsed.path);
6610 pushExpander({
6611 typeFunc: fluid.expander.fetch,
6612 context: parsed.context,
6613 segs: segs
6614 });
6615 } else if (fluid.isPlainObject(source)) {
6616 if (source.expander) {
6617 source.expander.typeFunc = fluid.getGlobalValue(source.expander.type || "fluid.invokeFunc");
6618 pushExpander(source.expander);
6619 } else {
6620 fluid.each(source, function (value, key) {
6621 rootSegs.push(key);
6622 fluid.preExpandRecurse(root, value, source, key, rootSegs);
6623 rootSegs.pop();
6624 });
6625 }
6626 }
6627 };
6628
6629 fluid.preExpand = function (source) {
6630 var root = {
6631 expanders: [],
6632 source: fluid.isUnexpandable(source) ? source : fluid.copy(source)
6633 };
6634 fluid.preExpandRecurse(root, root.source, root, "source", []);
6635 return root;
6636 };
6637
6638 // Main pathway for freestanding material that is not part of a component's options
6639 fluid.expandImmediate = function (source, that, localRecord) {
6640 var options = fluid.makeStackResolverOptions(that, localRecord, true); // TODO: ELstyle and target are now ignored
6641 var root = fluid.preExpand(source);
6642 fluid.expandImmediateImpl(root, options);
6643 return root.source;
6644 };
6645
6646 // High performance expander for situations such as invokers, listeners, where raw materials can be cached - consumes "root" structure produced by preExpand
6647 fluid.expandImmediateImpl = function (root, options) {
6648 var expanders = root.expanders;
6649 for (var i = 0; i < expanders.length; ++i) {
6650 var expander = expanders[i];
6651 expander.holder[expander.member] = expander.expander.typeFunc(null, expander, options);
6652 }
6653 };
6654
6655 fluid.expandExpander = function (deliverer, source, options) {
6656 var expander = fluid.getGlobalValue(source.expander.type || "fluid.invokeFunc");
6657 if (!expander) {
6658 fluid.fail("Unknown expander with type " + source.expander.type);
6659 }
6660 return expander(deliverer, source, options);
6661 };
6662
6663 fluid.registerNamespace("fluid.expander");
6664
6665 // "deliverer" is null in the new (fast) pathway, this is a relic of the old "source expander" signature. It appears we can already globally remove this
6666 fluid.expander.fetch = function (deliverer, source, options) {
6667 var localRecord = options.localRecord, context = source.expander.context, segs = source.expander.segs;
6668 // TODO: Either type-check on context as string or else create fetchSlow
6669 var inLocal = localRecord[context] !== undefined;
6670 var contextStatus = options.contextThat.lifecycleStatus;
6671 // somewhat hack to anticipate "fits" for FLUID-4925 - we assume that if THIS component is in construction, its reference target might be too
6672 // if context is destroyed, we are most likely in an afterDestroy listener and so path records have been destroyed
6673 var fast = contextStatus === "treeConstructed" || contextStatus === "destroyed";
6674 var component = inLocal ? localRecord[context] : fluid.resolveContext(context, options.contextThat, fast);
6675 if (component) {
6676 var root = component;
6677 if (inLocal || component.lifecycleStatus !== "constructing") {
6678 for (var i = 0; i < segs.length; ++i) { // fast resolution of paths when no ginger process active
6679 root = root ? root[segs[i]] : undefined;
6680 }
6681 } else {
6682 root = fluid.getForComponent(component, segs);
6683 }
6684 if (root === undefined && !inLocal) { // last-ditch attempt to get exotic EL value from component
6685 root = fluid.getForComponent(component, segs);
6686 }
6687 return root;
6688 } else if (segs.length > 0) {
6689 fluid.triggerMismatchedPathError(source.expander, options.contextThat);
6690 }
6691 };
6692
6693 /* "light" expanders, starting with the default expander invokeFunc,
6694 which makes an arbitrary function call (after expanding arguments) and are then replaced in
6695 the configuration with the call results. These will probably be abolished and replaced with
6696 equivalent model transformation machinery */
6697
6698 // This one is now positioned as the "universal expander" - default if no type supplied
6699 fluid.invokeFunc = function (deliverer, source, options) {
6700 var expander = source.expander;
6701 var args = fluid.makeArray(expander.args);
6702 expander.args = args; // head off case where args is an EL reference which resolves to an array
6703 if (options.recurse) { // only available in the path from fluid.expandOptions - this will be abolished in the end
6704 args = options.recurse([], args);
6705 } else {
6706 expander = fluid.expandImmediate(expander, options.contextThat, options.localRecord);
6707 args = expander.args;
6708 }
6709 var funcEntry = expander.func || expander.funcName;
6710 var func = (options.expandSource ? options.expandSource(funcEntry) : funcEntry) || fluid.recordToApplicable(expander, options.contextThat);
6711 if (typeof(func) === "string") {
6712 func = fluid.getGlobalValue(func);
6713 }
6714 if (!func) {
6715 fluid.fail("Error in expander record ", expander, ": " + funcEntry + " could not be resolved to a function for component ", options.contextThat);
6716 }
6717 return func.apply(null, args);
6718 };
6719
6720 // The "noexpand" expander which simply unwraps one level of expansion and ceases.
6721 fluid.noexpand = function (deliverer, source) {
6722 return source.expander.value ? source.expander.value : source.expander.tree;
6723 };
6724
6725})(jQuery, fluid_3_0_0);
6726;
6727/*
6728Copyright The Infusion copyright holders
6729See the AUTHORS.md file at the top-level directory of this distribution and at
6730https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
6731
6732Licensed under the Educational Community License (ECL), Version 2.0 or the New
6733BSD license. You may not use this file except in compliance with one these
6734Licenses.
6735
6736You may obtain a copy of the ECL 2.0 License and BSD License at
6737https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
6738*/
6739
6740var fluid_3_0_0 = fluid_3_0_0 || {};
6741
6742(function ($, fluid) {
6743 "use strict";
6744
6745 /** NOTE: The contents of this file are by default NOT PART OF THE PUBLIC FLUID API unless explicitly annotated before the function **/
6746
6747 /** MODEL ACCESSOR ENGINE **/
6748
6749 /** Standard strategies for resolving path segments **/
6750
6751 fluid.model.makeEnvironmentStrategy = function (environment) {
6752 return function (root, segment, index) {
6753 return index === 0 && environment[segment] ?
6754 environment[segment] : undefined;
6755 };
6756 };
6757
6758 fluid.model.defaultCreatorStrategy = function (root, segment) {
6759 if (root[segment] === undefined) {
6760 root[segment] = {};
6761 return root[segment];
6762 }
6763 };
6764
6765 fluid.model.defaultFetchStrategy = function (root, segment) {
6766 return root[segment];
6767 };
6768
6769 fluid.model.funcResolverStrategy = function (root, segment) {
6770 if (root.resolvePathSegment) {
6771 return root.resolvePathSegment(segment);
6772 }
6773 };
6774
6775 fluid.model.traverseWithStrategy = function (root, segs, initPos, config, uncess) {
6776 var strategies = config.strategies;
6777 var limit = segs.length - uncess;
6778 for (var i = initPos; i < limit; ++i) {
6779 if (!root) {
6780 return root;
6781 }
6782 var accepted;
6783 for (var j = 0; j < strategies.length; ++j) {
6784 accepted = strategies[j](root, segs[i], i + 1, segs);
6785 if (accepted !== undefined) {
6786 break; // May now short-circuit with stateless strategies
6787 }
6788 }
6789 if (accepted === fluid.NO_VALUE) {
6790 accepted = undefined;
6791 }
6792 root = accepted;
6793 }
6794 return root;
6795 };
6796
6797 /* Returns both the value and the path of the value held at the supplied EL path */
6798 fluid.model.getValueAndSegments = function (root, EL, config, initSegs) {
6799 return fluid.model.accessWithStrategy(root, EL, fluid.NO_VALUE, config, initSegs, true);
6800 };
6801
6802 // Very lightweight remnant of trundler, only used in resolvers
6803 fluid.model.makeTrundler = function (config) {
6804 return function (valueSeg, EL) {
6805 return fluid.model.getValueAndSegments(valueSeg.root, EL, config, valueSeg.segs);
6806 };
6807 };
6808
6809 fluid.model.getWithStrategy = function (root, EL, config, initSegs) {
6810 return fluid.model.accessWithStrategy(root, EL, fluid.NO_VALUE, config, initSegs);
6811 };
6812
6813 fluid.model.setWithStrategy = function (root, EL, newValue, config, initSegs) {
6814 fluid.model.accessWithStrategy(root, EL, newValue, config, initSegs);
6815 };
6816
6817 fluid.model.accessWithStrategy = function (root, EL, newValue, config, initSegs, returnSegs) {
6818 // This function is written in this unfortunate style largely for efficiency reasons. In many cases
6819 // it should be capable of running with 0 allocations (EL is preparsed, initSegs is empty)
6820 if (!fluid.isPrimitive(EL) && !fluid.isArrayable(EL)) {
6821 var key = EL.type || "default";
6822 var resolver = config.resolvers[key];
6823 if (!resolver) {
6824 fluid.fail("Unable to find resolver of type " + key);
6825 }
6826 var trundler = fluid.model.makeTrundler(config); // very lightweight trundler for resolvers
6827 var valueSeg = {root: root, segs: initSegs};
6828 valueSeg = resolver(valueSeg, EL, trundler);
6829 if (EL.path && valueSeg) { // every resolver supports this piece of output resolution
6830 valueSeg = trundler(valueSeg, EL.path);
6831 }
6832 return returnSegs ? valueSeg : (valueSeg ? valueSeg.root : undefined);
6833 }
6834 else {
6835 return fluid.model.accessImpl(root, EL, newValue, config, initSegs, returnSegs, fluid.model.traverseWithStrategy);
6836 }
6837 };
6838
6839 // Implementation notes: The EL path manipulation utilities here are equivalents of the simpler ones
6840 // that are provided in Fluid.js and elsewhere - they apply escaping rules to parse characters .
6841 // as \. and \ as \\ - allowing us to process member names containing periods. These versions are mostly
6842 // in use within model machinery, whereas the cheaper versions based on String.split(".") are mostly used
6843 // within the IoC machinery.
6844 // Performance testing in early 2015 suggests that modern browsers now allow these to execute slightly faster
6845 // than the equivalent machinery written using complex regexps - therefore they will continue to be maintained
6846 // here. However, there is still a significant performance gap with respect to the performance of String.split(".")
6847 // especially on Chrome, so we will continue to insist that component member names do not contain a "." character
6848 // for the time being.
6849 // See http://jsperf.com/parsing-escaped-el for some experiments
6850
6851 fluid.registerNamespace("fluid.pathUtil");
6852
6853 fluid.pathUtil.getPathSegmentImpl = function (accept, path, i) {
6854 var segment = null;
6855 if (accept) {
6856 segment = "";
6857 }
6858 var escaped = false;
6859 var limit = path.length;
6860 for (; i < limit; ++i) {
6861 var c = path.charAt(i);
6862 if (!escaped) {
6863 if (c === ".") {
6864 break;
6865 }
6866 else if (c === "\\") {
6867 escaped = true;
6868 }
6869 else if (segment !== null) {
6870 segment += c;
6871 }
6872 }
6873 else {
6874 escaped = false;
6875 if (segment !== null) {
6876 segment += c;
6877 }
6878 }
6879 }
6880 if (segment !== null) {
6881 accept[0] = segment;
6882 }
6883 return i;
6884 };
6885
6886 var globalAccept = []; // TODO: reentrancy risk here. This holder is here to allow parseEL to make two returns without an allocation.
6887
6888 /* A version of fluid.model.parseEL that apples escaping rules - this allows path segments
6889 * to contain period characters . - characters "\" and "}" will also be escaped. WARNING -
6890 * this current implementation is EXTREMELY slow compared to fluid.model.parseEL and should
6891 * not be used in performance-sensitive applications */
6892 // supported, PUBLIC API function
6893 fluid.pathUtil.parseEL = function (path) {
6894 var togo = [];
6895 var index = 0;
6896 var limit = path.length;
6897 while (index < limit) {
6898 var firstdot = fluid.pathUtil.getPathSegmentImpl(globalAccept, path, index);
6899 togo.push(globalAccept[0]);
6900 index = firstdot + 1;
6901 }
6902 return togo;
6903 };
6904
6905 // supported, PUBLIC API function
6906 fluid.pathUtil.composeSegment = function (prefix, toappend) {
6907 toappend = toappend.toString();
6908 for (var i = 0; i < toappend.length; ++i) {
6909 var c = toappend.charAt(i);
6910 if (c === "." || c === "\\" || c === "}") {
6911 prefix += "\\";
6912 }
6913 prefix += c;
6914 }
6915 return prefix;
6916 };
6917
6918 /* Escapes a single path segment by replacing any character ".", "\" or "}" with itself prepended by \ */
6919 // supported, PUBLIC API function
6920 fluid.pathUtil.escapeSegment = function (segment) {
6921 return fluid.pathUtil.composeSegment("", segment);
6922 };
6923
6924 /*
6925 * Compose a prefix and suffix EL path, where the prefix is already escaped.
6926 * Prefix may be empty, but not null. The suffix will become escaped.
6927 */
6928 // supported, PUBLIC API function
6929 fluid.pathUtil.composePath = function (prefix, suffix) {
6930 if (prefix.length !== 0) {
6931 prefix += ".";
6932 }
6933 return fluid.pathUtil.composeSegment(prefix, suffix);
6934 };
6935
6936 /*
6937 * Compose a set of path segments supplied as arguments into an escaped EL expression. Escaped version
6938 * of fluid.model.composeSegments
6939 */
6940
6941 // supported, PUBLIC API function
6942 fluid.pathUtil.composeSegments = function () {
6943 var path = "";
6944 for (var i = 0; i < arguments.length; ++i) {
6945 path = fluid.pathUtil.composePath(path, arguments[i]);
6946 }
6947 return path;
6948 };
6949
6950 /* Helpful utility for use in resolvers - matches a path which has already been parsed into segments */
6951 fluid.pathUtil.matchSegments = function (toMatch, segs, start, end) {
6952 if (end - start !== toMatch.length) {
6953 return false;
6954 }
6955 for (var i = start; i < end; ++i) {
6956 if (segs[i] !== toMatch[i - start]) {
6957 return false;
6958 }
6959 }
6960 return true;
6961 };
6962
6963 fluid.model.unescapedParser = {
6964 parse: fluid.model.parseEL,
6965 compose: fluid.model.composeSegments
6966 };
6967
6968 // supported, PUBLIC API record
6969 fluid.model.defaultGetConfig = {
6970 parser: fluid.model.unescapedParser,
6971 strategies: [fluid.model.funcResolverStrategy, fluid.model.defaultFetchStrategy]
6972 };
6973
6974 // supported, PUBLIC API record
6975 fluid.model.defaultSetConfig = {
6976 parser: fluid.model.unescapedParser,
6977 strategies: [fluid.model.funcResolverStrategy, fluid.model.defaultFetchStrategy, fluid.model.defaultCreatorStrategy]
6978 };
6979
6980 fluid.model.escapedParser = {
6981 parse: fluid.pathUtil.parseEL,
6982 compose: fluid.pathUtil.composeSegments
6983 };
6984
6985 // supported, PUBLIC API record
6986 fluid.model.escapedGetConfig = {
6987 parser: fluid.model.escapedParser,
6988 strategies: [fluid.model.defaultFetchStrategy]
6989 };
6990
6991 // supported, PUBLIC API record
6992 fluid.model.escapedSetConfig = {
6993 parser: fluid.model.escapedParser,
6994 strategies: [fluid.model.defaultFetchStrategy, fluid.model.defaultCreatorStrategy]
6995 };
6996
6997 /** CONNECTED COMPONENTS AND TOPOLOGICAL SORTING **/
6998
6999 // Following "tarjan" at https://en.wikipedia.org/wiki/Tarjan%27s_strongly_connected_components_algorithm
7000
7001 /** Compute the strongly connected components of a graph, specified as a list of vertices and an accessor function.
7002 * Returns an array of arrays of strongly connected vertices, with each component in topologically sorted order.
7003 * @param {Vertex[]} vertices - An array of vertices of the graph to be processed. Each vertex object will be polluted
7004 * with three extra fields: `tarjanIndex`, `lowIndex` and `onStack`.
7005 * @param {Function} accessor - A function that returns the accessor vertex or vertices.
7006 * @return {Array.<Vertex[]>} - An array of arrays of vertices.
7007 */
7008 fluid.stronglyConnected = function (vertices, accessor) {
7009 var that = {
7010 stack: [],
7011 accessor: accessor,
7012 components: [],
7013 index: 0
7014 };
7015 vertices.forEach(function (vertex) {
7016 if (vertex.tarjanIndex === undefined) {
7017 fluid.stronglyConnectedOne(vertex, that);
7018 }
7019 });
7020 return that.components;
7021 };
7022
7023 // Perform one round of the Tarjan search algorithm using the state structure generated in fluid.stronglyConnected
7024 fluid.stronglyConnectedOne = function (vertex, that) {
7025 vertex.tarjanIndex = that.index;
7026 vertex.lowIndex = that.index;
7027 ++that.index;
7028 that.stack.push(vertex);
7029 vertex.onStack = true;
7030 var outEdges = that.accessor(vertex);
7031 outEdges.forEach(function (outVertex) {
7032 if (outVertex.tarjanIndex === undefined) {
7033 // Successor has not yet been visited; recurse on it
7034 fluid.stronglyConnectedOne(outVertex, that);
7035 vertex.lowIndex = Math.min(vertex.lowIndex, outVertex.lowIndex);
7036 } else if (outVertex.onStack) {
7037 // Successor is on the stack and hence in the current component
7038 vertex.lowIndex = Math.min(vertex.lowIndex, outVertex.tarjanIndex);
7039 }
7040 });
7041 // If vertex is a root node, pop the stack back as far as it and generate a component
7042 if (vertex.lowIndex === vertex.tarjanIndex) {
7043 var component = [], outVertex;
7044 do {
7045 outVertex = that.stack.pop();
7046 outVertex.onStack = false;
7047 component.push(outVertex);
7048 } while (outVertex !== vertex);
7049 that.components.push(component);
7050 }
7051 };
7052
7053 /** MODEL COMPONENT HIERARCHY AND RELAY SYSTEM **/
7054
7055 fluid.initRelayModel = function (that) {
7056 fluid.deenlistModelComponent(that);
7057 return that.model;
7058 };
7059
7060 // TODO: This utility compensates for our lack of control over "wave of explosions" initialisation - we may
7061 // catch a model when it is apparently "completely initialised" and that's the best we can do, since we have
7062 // missed its own initial transaction
7063
7064 fluid.isModelComplete = function (that) {
7065 return "model" in that && that.model !== fluid.inEvaluationMarker;
7066 };
7067
7068 // Enlist this model component as part of the "initial transaction" wave - note that "special transaction" init
7069 // is indexed by component, not by applier, and has special record type (complete + initModel), not transaction
7070 fluid.enlistModelComponent = function (that) {
7071 var instantiator = fluid.getInstantiator(that);
7072 var enlist = instantiator.modelTransactions.init[that.id];
7073 if (!enlist) {
7074 enlist = {
7075 that: that,
7076 applier: fluid.getForComponent(that, "applier"), // required for FLUID-5504 even though currently unused
7077 complete: fluid.isModelComplete(that)
7078 };
7079 instantiator.modelTransactions.init[that.id] = enlist;
7080 }
7081 return enlist;
7082 };
7083
7084 fluid.clearTransactions = function () {
7085 var instantiator = fluid.globalInstantiator;
7086 fluid.clear(instantiator.modelTransactions);
7087 instantiator.modelTransactions.init = {};
7088 };
7089
7090 fluid.failureEvent.addListener(fluid.clearTransactions, "clearTransactions", "before:fail");
7091
7092 // Utility to coordinate with our crude "oscillation prevention system" which limits each link to 2 updates (presumably
7093 // in opposite directions). In the case of the initial transaction, we need to reset the count given that genuine
7094 // changes are arising in the system with each new enlisted model. TODO: if we ever get users operating their own
7095 // transactions, think of a way to incorporate this into that workflow
7096 fluid.clearLinkCounts = function (transRec, relaysAlso) {
7097 // TODO: Separate this record out into different types of records (relays are already in their own area)
7098 fluid.each(transRec, function (value, key) {
7099 if (typeof(value) === "number") {
7100 transRec[key] = 0;
7101 } else if (relaysAlso && value.options && typeof(value.relayCount) === "number") {
7102 value.relayCount = 0;
7103 }
7104 });
7105 };
7106
7107 /** Compute relay dependency out arcs for a group of initialising components.
7108 * @param {Object} transacs - Hash of component id to local ChangeApplier transaction.
7109 * @param {Object} mrec - Hash of component id to enlisted component record.
7110 * @return {Object} - Hash of component id to list of enlisted component record.
7111 */
7112 fluid.computeInitialOutArcs = function (transacs, mrec) {
7113 return fluid.transform(mrec, function (recel, id) {
7114 var oneOutArcs = {};
7115 var listeners = recel.that.applier.listeners.sortedListeners;
7116 fluid.each(listeners, function (listener) {
7117 if (listener.isRelay && !fluid.isExcludedChangeSource(transacs[id], listener.cond)) {
7118 var targetId = listener.targetId;
7119 if (targetId !== id) {
7120 oneOutArcs[targetId] = true;
7121 }
7122 }
7123 });
7124 var oneOutArcList = Object.keys(oneOutArcs);
7125 var togo = oneOutArcList.map(function (id) {
7126 return mrec[id];
7127 });
7128 // No edge if the component is not enlisted - it will sort to the end via "completeOnInit"
7129 fluid.remove_if(togo, function (rec) {
7130 return rec === undefined;
7131 });
7132 return togo;
7133 });
7134 };
7135
7136 fluid.sortCompleteLast = function (reca, recb) {
7137 return (reca.completeOnInit ? 1 : 0) - (recb.completeOnInit ? 1 : 0);
7138 };
7139
7140
7141 /** Operate all coordinated transactions by bringing models to their respective initial values, and then commit them all
7142 * @param {Component} that - A representative component of the collection for which the initial transaction is to be operated
7143 * @param {Object} mrec - The global model transaction record for the init transaction. This is a hash indexed by component id
7144 * to a model transaction record, as registered in `fluid.enlistModelComponent`. This has members `that`, `applier`, `complete`.
7145 */
7146 fluid.operateInitialTransaction = function (that, mrec) {
7147 var transId = fluid.allocateGuid();
7148 var transRec = fluid.getModelTransactionRec(that, transId);
7149 var transac;
7150 var transacs = fluid.transform(mrec, function (recel) {
7151 transac = recel.that.applier.initiate(null, "init", transId);
7152 transRec[recel.that.applier.applierId] = {transaction: transac};
7153 return transac;
7154 });
7155 // TODO: This sort has very little effect in any current test (can be replaced by no-op - see FLUID-5339) - but
7156 // at least can't be performed in reverse order ("FLUID-3674 event coordination test" will fail) - need more cases
7157
7158 // Compute the graph of init transaction relays for FLUID-6234 - one day we will have to do better than this, since there
7159 // may be finer structure than per-component - it may be that each piece of model area participates in this relation
7160 // differently. But this will require even more ambitious work such as fragmenting all the initial model values along
7161 // these boundaries.
7162 var outArcs = fluid.computeInitialOutArcs(transacs, mrec);
7163 var arcAccessor = function (mrec) {
7164 return outArcs[mrec.that.id];
7165 };
7166 var recs = fluid.values(mrec);
7167 var components = fluid.stronglyConnected(recs, arcAccessor);
7168 var priorityIndex = 0;
7169 components.forEach(function (component) {
7170 component.forEach(function (recel) {
7171 recel.initPriority = recel.completeOnInit ? Math.Infinity : priorityIndex++;
7172 });
7173 });
7174
7175 recs.sort(function (reca, recb) {
7176 return reca.initPriority - recb.initPriority;
7177 });
7178 recs.forEach(function (recel) {
7179 var that = recel.that;
7180 var transac = transacs[that.id];
7181 if (recel.completeOnInit) {
7182 fluid.initModelEvent(that, that.applier, transac, that.applier.listeners.sortedListeners);
7183 } else {
7184 fluid.each(recel.initModels, function (initModel) {
7185 transac.fireChangeRequest({type: "ADD", segs: [], value: initModel});
7186 fluid.clearLinkCounts(transRec, true);
7187 });
7188 }
7189 var shadow = fluid.shadowForComponent(that);
7190 if (shadow) { // Fix for FLUID-5869 - the component may have been destroyed during its own init transaction
7191 shadow.modelComplete = true; // technically this is a little early, but this flag is only read in fluid.connectModelRelay
7192 }
7193 });
7194
7195 transac.commit(); // committing one representative transaction will commit them all
7196 };
7197
7198 // This modelComponent has now concluded initialisation - commit its initialisation transaction if it is the last such in the wave
7199 fluid.deenlistModelComponent = function (that) {
7200 var instantiator = fluid.getInstantiator(that);
7201 var mrec = instantiator.modelTransactions.init;
7202 if (!mrec[that.id]) { // avoid double evaluation through currently hacked "members" implementation
7203 return;
7204 }
7205 that.model = undefined; // Abuse of the ginger system - in fact it is "currently in evaluation" - we need to return a proper initial model value even if no init occurred yet
7206 mrec[that.id].complete = true; // flag means - "complete as in ready to participate in this transaction"
7207 var incomplete = fluid.find_if(mrec, function (recel) {
7208 return recel.complete !== true;
7209 });
7210 if (!incomplete) {
7211 try { // For FLUID-6195 ensure that exceptions during init relay don't leave the framework unusable
7212 fluid.operateInitialTransaction(that, mrec);
7213 } catch (e) {
7214 fluid.clearTransactions();
7215 throw e;
7216 }
7217 // NB: Don't call fluid.concludeTransaction since "init" is not a standard record - this occurs in commitRelays for the corresponding genuine record as usual
7218 instantiator.modelTransactions.init = {};
7219 }
7220 };
7221
7222 fluid.parseModelReference = function (that, ref) {
7223 var parsed = fluid.parseContextReference(ref);
7224 parsed.segs = that.applier.parseEL(parsed.path);
7225 return parsed;
7226 };
7227
7228 /** Given a string which may represent a reference into a model, parses it into a structure holding the coordinates for resolving the reference. It specially
7229 * detects "references into model material" by looking for the first path segment in the path reference which holds the value "model". Some of its workflow is bypassed
7230 * in the special case of a reference representing an implicit model relay. In this case, ref will definitely be a String, and if it does not refer to model material, rather than
7231 * raising an error, the return structure will include a field <code>nonModel: true</code>
7232 * @param {Component} that - The component holding the reference
7233 * @param {String} name - A human-readable string representing the type of block holding the reference - e.g. "modelListeners"
7234 * @param {String|ModelReference} ref - The model reference to be parsed. This may have already been partially parsed at the original site - that is, a ModelReference is a
7235 * structure containing
7236 * segs: {String[]} An array of model path segments to be dereferenced in the target component (will become `modelSegs` in the final return)
7237 * context: {String} An IoC reference to the component holding the model
7238 * @param {Boolean} implicitRelay - <code>true</code> if the reference was being resolved for an implicit model relay - that is,
7239 * whether it occured within the `model` block itself. In this case, references to non-model material are not a failure and will simply be resolved
7240 * (by the caller) onto their targets (as constants). Otherwise, this function will issue a failure on discovering a reference to non-model material.
7241 * @return {Object} - A structure holding:
7242 * that {Component} The component whose model is the target of the reference. This may end up being constructed as part of the act of resolving the reference
7243 * applier {Component} The changeApplier for the component <code>that</code>. This may end up being constructed as part of the act of resolving the reference
7244 * modelSegs {String[]} An array of path segments into the model of the component
7245 * path {String} the value of <code>modelSegs</code> encoded as an EL path (remove client uses of this in time)
7246 * nonModel {Boolean} Set if <code>implicitRelay</code> was true and the reference was not into a model (modelSegs/path will not be set in this case)
7247 * segs {String[]} Holds the full array of path segments found by parsing the original reference - only useful in <code>nonModel</code> case
7248 */
7249 fluid.parseValidModelReference = function (that, name, ref, implicitRelay) {
7250 var reject = function () {
7251 var failArgs = ["Error in " + name + ": ", ref].concat(fluid.makeArray(arguments));
7252 fluid.fail.apply(null, failArgs);
7253 };
7254 var rejectNonModel = function (value) {
7255 reject(" must be a reference to a component with a ChangeApplier (descended from fluid.modelComponent), instead got ", value);
7256 };
7257 var parsed; // resolve ref into context and modelSegs
7258 if (typeof(ref) === "string") {
7259 if (fluid.isIoCReference(ref)) {
7260 parsed = fluid.parseModelReference(that, ref);
7261 var modelPoint = parsed.segs.indexOf("model");
7262 if (modelPoint === -1) {
7263 if (implicitRelay) {
7264 parsed.nonModel = true;
7265 } else {
7266 reject(" must be a reference into a component model via a path including the segment \"model\"");
7267 }
7268 } else {
7269 parsed.modelSegs = parsed.segs.slice(modelPoint + 1);
7270 parsed.contextSegs = parsed.segs.slice(0, modelPoint);
7271 delete parsed.path;
7272 }
7273 } else {
7274 parsed = {
7275 path: ref,
7276 modelSegs: that.applier.parseEL(ref)
7277 };
7278 }
7279 } else {
7280 if (!fluid.isArrayable(ref.segs)) {
7281 reject(" must contain an entry \"segs\" holding path segments referring a model path within a component");
7282 }
7283 parsed = {
7284 context: ref.context,
7285 modelSegs: fluid.expandOptions(ref.segs, that)
7286 };
7287 }
7288 var contextTarget, target; // resolve target component, which defaults to "that"
7289 if (parsed.context) {
7290 contextTarget = fluid.resolveContext(parsed.context, that);
7291 if (!contextTarget) {
7292 reject(" context must be a reference to an existing component");
7293 }
7294 target = parsed.contextSegs ? fluid.getForComponent(contextTarget, parsed.contextSegs) : contextTarget;
7295 } else {
7296 target = that;
7297 }
7298 if (!parsed.nonModel) {
7299 if (!fluid.isComponent(target)) {
7300 rejectNonModel(target);
7301 }
7302 if (!target.applier) {
7303 fluid.getForComponent(target, ["applier"]);
7304 }
7305 if (!target.applier) {
7306 rejectNonModel(target);
7307 }
7308 }
7309 parsed.that = target;
7310 parsed.applier = target && target.applier;
7311 if (!parsed.path) { // ChangeToApplicable amongst others rely on this
7312 parsed.path = target && target.applier.composeSegments.apply(null, parsed.modelSegs);
7313 }
7314 return parsed;
7315 };
7316
7317 // Gets global record for a particular transaction id, allocating if necessary - looks up applier id to transaction,
7318 // as well as looking up source id (linkId in below) to count/true
7319 // Through poor implementation quality, not every access passes through this function - some look up instantiator.modelTransactions directly
7320 fluid.getModelTransactionRec = function (that, transId) {
7321 var instantiator = fluid.getInstantiator(that);
7322 if (!transId) {
7323 fluid.fail("Cannot get transaction record without transaction id");
7324 }
7325 if (!instantiator) {
7326 return null;
7327 }
7328 var transRec = instantiator.modelTransactions[transId];
7329 if (!transRec) {
7330 transRec = instantiator.modelTransactions[transId] = {
7331 relays: [], // sorted array of relay elements (also appear at top level index by transaction id)
7332 sources: {}, // hash of the global transaction sources (includes "init" but excludes "relay" and "local")
7333 externalChanges: {} // index by applierId to changePath to listener record
7334 };
7335 }
7336 return transRec;
7337 };
7338
7339 fluid.recordChangeListener = function (component, applier, sourceListener, listenerId) {
7340 var shadow = fluid.shadowForComponent(component);
7341 fluid.recordListener(applier.modelChanged, sourceListener, shadow, listenerId);
7342 };
7343
7344 /** Called when a relay listener registered using `fluid.registerDirectChangeRelay` enlists in a transaction. Opens a local
7345 * representative of this transaction on `targetApplier`, creates and stores a "transaction element" within the global transaction
7346 * record keyed by the target applier's id. The transaction element is also pushed onto the `relays` member of the global transaction record - they
7347 * will be sorted by priority here when changes are fired.
7348 * @param {TransactionRecord} transRec - The global record for the current ChangeApplier transaction as retrieved from `fluid.getModelTransactionRec`
7349 * @param {ChangeApplier} targetApplier - The ChangeApplier to which outgoing changes will be applied. A local representative of the transaction will be opened on this applier and returned.
7350 * @param {String} transId - The global id of this transaction
7351 * @param {Object} options - The `options` argument supplied to `fluid.registerDirectChangeRelay`. This will be stored in the returned transaction element
7352 * - note that only the member `update` is ever used in `fluid.model.updateRelays` - TODO: We should thin this out
7353 * @param {Object} npOptions - Namespace and priority options
7354 * namespace {String} [optional] The namespace attached to this relay definition
7355 * priority {String} [optional] The (unparsed) priority attached to this relay definition
7356 * @return {Object} A "transaction element" holding information relevant to this relay's enlistment in the current transaction. This includes fields:
7357 * transaction {Transaction} The local representative of this transaction created on `targetApplier`
7358 * relayCount {Integer} The number of times this relay has been activated in this transaction
7359 * namespace {String} [optional] Namespace for this relay definition
7360 * priority {Priority} The parsed priority definition for this relay
7361 */
7362 fluid.registerRelayTransaction = function (transRec, targetApplier, transId, options, npOptions) {
7363 var newTrans = targetApplier.initiate("relay", null, transId); // non-top-level transaction will defeat postCommit
7364 var transEl = transRec[targetApplier.applierId] = {transaction: newTrans, relayCount: 0, namespace: npOptions.namespace, priority: npOptions.priority, options: options};
7365 transEl.priority = fluid.parsePriority(transEl.priority, transRec.relays.length, false, "model relay");
7366 transRec.relays.push(transEl);
7367 return transEl;
7368 };
7369
7370 // Configure this parameter to tweak the number of relays the model will attempt per transaction before bailing out with an error
7371 fluid.relayRecursionBailout = 100;
7372
7373 // Used with various arg combinations from different sources. For standard "implicit relay" or fully lensed relay,
7374 // the first 4 args will be set, and "options" will be empty
7375
7376 // For a model-dependent relay, this will be used in two halves - firstly, all of the model
7377 // sources will bind to the relay transform document itself. In this case the argument "targetApplier" within "options" will be set.
7378 // In this case, the component known as "target" is really the source - it is a component reference discovered by parsing the
7379 // relay document.
7380
7381 // Secondly, the relay itself will schedule an invalidation (as if receiving change to "*" of its source - which may in most
7382 // cases actually be empty) and play through its transducer. "Source" component itself is never empty, since it is used for listener
7383 // degistration on destruction (check this is correct for external model relay). However, "sourceSegs" may be empty in the case
7384 // there is no "source" component registered for the link. This change is played in a "half-transactional" way - that is, we wait
7385 // for all other changes in the system to settle before playing the relay document, in order to minimise the chances of multiple
7386 // firing and corruption. This is done via the "preCommit" hook registered at top level in establishModelRelay. This listener
7387 // is transactional but it does not require the transaction to conclude in order to fire - it may be reused as many times as
7388 // required within the "overall" transaction whilst genuine (external) changes continue to arrive.
7389
7390 // TODO: Vast overcomplication and generation of closure garbage. SURELY we should be able to convert this into an externalised, arg-ist form
7391 /** Registers a listener operating one leg of a model relay relation, connecting the source and target. Called once or twice from `fluid.connectModelRelay` -
7392 * see the comment there for the three cases involved. Note that in its case iii)B) the applier to bind to is not the one attached to `target` but is instead
7393 * held in `options.targetApplier`.
7394 * @param {Object} target - The target component at the end of the relay.
7395 * @param {String[]} targetSegs - String segments representing the path in the target where outgoing changes are to be fired
7396 * @param {Component|null} source - The source component from where changes will be listened to. May be null if the change source is a relay document.
7397 * @param {String[]} sourceSegs - String segments representing the path in the source component's model at which changes will be listened to
7398 * @param {String} linkId - The unique id of this relay arc. This will be used as a key within the active transaction record to look up dynamic information about
7399 * activation of the link within that transaction (currently just an activation count)
7400 * @param {Function|null} transducer - A function which will be invoked when a change is to be relayed. This is one of the adapters constructed in "makeTransformPackage"
7401 * and is set in all cases other than iii)B) (collecting changes to contextualised relay). Note that this will have a member `cond` as returned from
7402 * `fluid.model.parseRelayCondition` encoding the condition whereby changes should be excluded from the transaction. The rule encoded by the condition
7403 * will be applied by the function within `transducer`.
7404 * @param {Object} options -
7405 * transactional {Boolean} `true` in case iii) - although this only represents `half-transactions`, `false` in others since these are resolved immediately with no granularity
7406 * targetApplier {ChangeApplier} [optional] in case iii)B) holds the applier for the contextualised relay document which outgoing changes should be applied to
7407 * sourceApplier {ChangeApplier} [optional] in case ii) holds the applier for the contextualised relay document on which we listen for outgoing changes
7408 * @param {Object} npOptions - Namespace and priority options
7409 * namespace {String} [optional] The namespace attached to this relay definition
7410 * priority {String} [optional] The (unparsed) priority attached to this relay definition
7411 */
7412 fluid.registerDirectChangeRelay = function (target, targetSegs, source, sourceSegs, linkId, transducer, options, npOptions) {
7413 var targetApplier = options.targetApplier || target.applier; // first branch implies the target is a relay document
7414 var sourceApplier = options.sourceApplier || source.applier; // first branch implies the source is a relay document - listener will be transactional
7415 var applierId = targetApplier.applierId;
7416 targetSegs = fluid.makeArray(targetSegs);
7417 sourceSegs = fluid.makeArray(sourceSegs); // take copies since originals will be trashed
7418 var sourceListener = function (newValue, oldValue, path, changeRequest, trans, applier) {
7419 var transId = trans.id;
7420 var transRec = fluid.getModelTransactionRec(target, transId);
7421 if (applier && trans && !transRec[applier.applierId]) { // don't trash existing record which may contain "options" (FLUID-5397)
7422 transRec[applier.applierId] = {transaction: trans}; // enlist the outer user's original transaction
7423 }
7424 var existing = transRec[applierId];
7425 transRec[linkId] = transRec[linkId] || 0;
7426 // Crude "oscillation prevention" system limits each link to maximum of 2 operations per cycle (presumably in opposite directions)
7427 var relay = true; // TODO: See FLUID-5303 - we currently disable this check entirely to solve FLUID-5293 - perhaps we might remove link counts entirely
7428 if (relay) {
7429 ++transRec[linkId];
7430 if (transRec[linkId] > fluid.relayRecursionBailout) {
7431 fluid.fail("Error in model relay specification at component ", target, " - operated more than " + fluid.relayRecursionBailout + " relays without model value settling - current model contents are ", trans.newHolder.model);
7432 }
7433 if (!existing) {
7434 existing = fluid.registerRelayTransaction(transRec, targetApplier, transId, options, npOptions);
7435 }
7436 if (transducer && !options.targetApplier) {
7437 // TODO: This is just for safety but is still unusual and now abused. The transducer doesn't need the "newValue" since all the transform information
7438 // has been baked into the transform document itself. However, we now rely on this special signalling value to make sure we regenerate transforms in
7439 // the "forwardAdapter"
7440 transducer(existing.transaction, options.sourceApplier ? undefined : newValue, sourceSegs, targetSegs, changeRequest);
7441 } else {
7442 if (changeRequest && changeRequest.type === "DELETE") {
7443 existing.transaction.fireChangeRequest({type: "DELETE", segs: targetSegs});
7444 }
7445 if (newValue !== undefined) {
7446 existing.transaction.fireChangeRequest({type: "ADD", segs: targetSegs, value: newValue});
7447 }
7448 }
7449 }
7450 };
7451 var spec = sourceApplier.modelChanged.addListener({
7452 isRelay: true,
7453 cond: transducer && transducer.cond,
7454 targetId: target.id, // these two fields for debuggability
7455 targetApplierId: targetApplier.id,
7456 segs: sourceSegs,
7457 transactional: options.transactional
7458 }, sourceListener);
7459 if (fluid.passLogLevel(fluid.logLevel.TRACE)) {
7460 fluid.log(fluid.logLevel.TRACE, "Adding relay listener with listenerId " + spec.listenerId + " to source applier with id " +
7461 sourceApplier.applierId + " from target applier with id " + applierId + " for target component with id " + target.id);
7462 }
7463 if (source) { // TODO - we actually may require to register on THREE sources in the case modelRelay is attached to a
7464 // component which is neither source nor target. Note there will be problems if source, say, is destroyed and recreated,
7465 // and holder is not - relay will in that case be lost. Need to integrate relay expressions with IoCSS.
7466 fluid.recordChangeListener(source, sourceApplier, sourceListener, spec.listenerId);
7467 if (target !== source) {
7468 fluid.recordChangeListener(target, sourceApplier, sourceListener, spec.listenerId);
7469 }
7470 }
7471 };
7472
7473 /** Connect a model relay relation between model material. This is called in three scenarios:
7474 * i) from `fluid.parseModelRelay` when parsing an uncontextualised model relay (one with a static transform document), to
7475 * directly connect the source and target of the relay
7476 * ii) from `fluid.parseModelRelay` when parsing a contextualised model relay (one whose transform document depends on other model
7477 * material), to connect updates emitted from the transform document's applier onto the relay ends (both source and target)
7478 * iii) from `fluid.parseImplicitRelay` when parsing model references found within contextualised model relay to bind changes emitted
7479 * from the target of the reference onto the transform document's applier. These may apply directly to another component's model (in its case
7480 * A) or apply to a relay document (in its case B)
7481 *
7482 * This function will make one or two calls to `fluid.registerDirectChangeRelay` in order to set up each leg of any required relay.
7483 * Note that in case iii)B) the component referred to as our argument `target` is actually the "source" of the changes (that is, the one encountered
7484 * while traversing the transform document), and our argument `source` is the component holding the transform, and so
7485 * the call to `fluid.registerDirectChangeRelay` will have `source` and `target` reversed (`fluid.registerDirectChangeRelay` will bind to the `targetApplier`
7486 * in the options rather than source's applier).
7487 * @param {Component} source - The component holding the material giving rise to the relay, or the one referred to by the `source` member
7488 * of the configuration in case ii), if there is one
7489 * @param {Array|null} sourceSegs - An array of parsed string segments of the `source` relay reference in case i), or the offset into the transform
7490 * document of the reference component in case iii), otherwise `null` (case ii))
7491 * @param {Component} target - The component holding the model relay `target` in cases i) and ii), or the component at the other end of
7492 * the model reference in case iii) (in this case in fact a "source" for the changes.
7493 * @param {Array} targetSegs - An array of parsed string segments of the `target` reference in cases i) and ii), or of the model reference in
7494 * case iii)
7495 * @param {Object} options - A structure describing the relay, allowing discrimination of the various cases above. This is derived from the return from
7496 * `fluid.makeTransformPackage` but will have some members filtered in different cases. This contains members:
7497 * update {Function} A function to be called at the end of a "half-transaction" when all pending updates have been applied to the document's applier.
7498 * This discriminates case iii)
7499 * targetApplier {ChangeApplier} The ChangeApplier for the relay document, in case iii)B)
7500 * forwardApplier (ChangeApplier} The ChangeApplier for the relay document, in cases ii) and iii)B) (only used in latter case)
7501 * forwardAdapter {Adapter} A function accepting (transaction, newValue) to pass through the forward leg of the relay. Contains a member `cond` holding the parsed relay condition.
7502 * backwardAdapter {Adapter} A function accepting (transaction, newValue) to pass through the backward leg of the relay. Contains a member `cond` holding the parsed relay condition.
7503 * namespace {String} Namespace for any relay definition
7504 * priority {String} Priority for any relay definition or synthetic "first" for iii)A)
7505 */
7506 fluid.connectModelRelay = function (source, sourceSegs, target, targetSegs, options) {
7507 var linkId = fluid.allocateGuid();
7508 function enlistComponent(component) {
7509 var enlist = fluid.enlistModelComponent(component);
7510
7511 if (enlist.complete) {
7512 var shadow = fluid.shadowForComponent(component);
7513 if (shadow.modelComplete) {
7514 enlist.completeOnInit = true;
7515 }
7516 }
7517 }
7518 enlistComponent(target);
7519 enlistComponent(source); // role of "source" and "target" are swapped in case iii)B)
7520 var npOptions = fluid.filterKeys(options, ["namespace", "priority"]);
7521
7522 if (options.update) { // it is a call for a relay document - ii) or iii)B)
7523 if (options.targetApplier) { // case iii)B)
7524 // We are in the middle of parsing a contextualised relay, and this call has arrived via its parseImplicitRelay.
7525 // register changes from the target model onto changes to the model relay document
7526 fluid.registerDirectChangeRelay(source, sourceSegs, target, targetSegs, linkId, null, {
7527 transactional: false,
7528 targetApplier: options.targetApplier,
7529 update: options.update
7530 }, npOptions);
7531 } else { // case ii), contextualised relay overall output
7532 // Rather than bind source-source, instead register the "half-transactional" listener which binds changes
7533 // from the relay document itself onto the target
7534 fluid.registerDirectChangeRelay(target, targetSegs, source, [], linkId + "-transform", options.forwardAdapter, {transactional: true, sourceApplier: options.forwardApplier}, npOptions);
7535 }
7536 } else { // case i) or iii)A): more efficient, old-fashioned branch where relay is uncontextualised
7537 fluid.registerDirectChangeRelay(target, targetSegs, source, sourceSegs, linkId, options.forwardAdapter, {transactional: false}, npOptions);
7538 fluid.registerDirectChangeRelay(source, sourceSegs, target, targetSegs, linkId, options.backwardAdapter, {transactional: false}, npOptions);
7539 }
7540 };
7541
7542 fluid.parseSourceExclusionSpec = function (targetSpec, sourceSpec) {
7543 targetSpec.excludeSource = fluid.arrayToHash(fluid.makeArray(sourceSpec.excludeSource || (sourceSpec.includeSource ? "*" : undefined)));
7544 targetSpec.includeSource = fluid.arrayToHash(fluid.makeArray(sourceSpec.includeSource));
7545 return targetSpec;
7546 };
7547
7548 /** Determines whether the supplied transaction should have changes not propagated into it as a result of being excluded by a
7549 * condition specification.
7550 * @param {Transaction} transaction - A local ChangeApplier transaction, with member `fullSources` holding all currently active sources
7551 * @param {ConditionSpec} spec - A parsed relay condition specification, as returned from `fluid.model.parseRelayCondition`.
7552 * @return {Boolean} `true` if changes should be excluded from the supplied transaction according to the supplied specification
7553 */
7554 fluid.isExcludedChangeSource = function (transaction, spec) {
7555 if (!spec || !spec.excludeSource) { // mergeModelListeners initModelEvent fabricates a fake spec that bypasses processing
7556 return false;
7557 }
7558 var excluded = spec.excludeSource["*"];
7559 for (var source in transaction.fullSources) {
7560 if (spec.excludeSource[source]) {
7561 excluded = true;
7562 }
7563 if (spec.includeSource[source]) {
7564 excluded = false;
7565 }
7566 }
7567 return excluded;
7568 };
7569
7570 fluid.model.guardedAdapter = function (transaction, cond, func, args) {
7571 if (!fluid.isExcludedChangeSource(transaction, cond) && func !== fluid.model.transform.uninvertibleTransform) {
7572 func.apply(null, args);
7573 }
7574 };
7575
7576 // TODO: This rather crummy function is the only site with a hard use of "path" as String
7577 fluid.transformToAdapter = function (transform, targetPath) {
7578 var basedTransform = {};
7579 basedTransform[targetPath] = transform; // TODO: Faulty with respect to escaping rules
7580 return function (trans, newValue, sourceSegs, targetSegs, changeRequest) {
7581 if (changeRequest && changeRequest.type === "DELETE") {
7582 trans.fireChangeRequest({type: "DELETE", path: targetPath}); // avoid mouse droppings in target document for FLUID-5585
7583 }
7584 // TODO: More efficient model that can only run invalidated portion of transform (need to access changeMap of source transaction)
7585 fluid.model.transformWithRules(newValue, basedTransform, {finalApplier: trans});
7586 };
7587 };
7588
7589 // TODO: sourcePath and targetPath should really be converted to segs to avoid excess work in parseValidModelReference
7590 fluid.makeTransformPackage = function (componentThat, transform, sourcePath, targetPath, forwardCond, backwardCond, namespace, priority) {
7591 var that = {
7592 forwardHolder: {model: transform},
7593 backwardHolder: {model: null}
7594 };
7595 that.generateAdapters = function (trans) {
7596 // can't commit "half-transaction" or events will fire - violate encapsulation in this way
7597 that.forwardAdapterImpl = fluid.transformToAdapter(trans ? trans.newHolder.model : that.forwardHolder.model, targetPath);
7598 if (sourcePath !== null) {
7599 var inverted = fluid.model.transform.invertConfiguration(transform);
7600 if (inverted !== fluid.model.transform.uninvertibleTransform) {
7601 that.backwardHolder.model = inverted;
7602 that.backwardAdapterImpl = fluid.transformToAdapter(that.backwardHolder.model, sourcePath);
7603 } else {
7604 that.backwardAdapterImpl = inverted;
7605 }
7606 }
7607 };
7608 that.forwardAdapter = function (transaction, newValue) { // create a stable function reference for this possibly changing adapter
7609 if (newValue === undefined) {
7610 that.generateAdapters(); // TODO: Quick fix for incorrect scheduling of invalidation/transducing
7611 // "it so happens" that fluid.registerDirectChangeRelay invokes us with empty newValue in the case of invalidation -> transduction
7612 }
7613 fluid.model.guardedAdapter(transaction, forwardCond, that.forwardAdapterImpl, arguments);
7614 };
7615 that.forwardAdapter.cond = forwardCond; // Used when parsing graph in init transaction
7616 // fired from fluid.model.updateRelays via invalidator event
7617 that.runTransform = function (trans) {
7618 trans.commit(); // this will reach the special "half-transactional listener" registered in fluid.connectModelRelay,
7619 // branch with options.targetApplier - by committing the transaction, we update the relay document in bulk and then cause
7620 // it to execute (via "transducer")
7621 trans.reset();
7622 };
7623 that.forwardApplier = fluid.makeHolderChangeApplier(that.forwardHolder);
7624 that.forwardApplier.isRelayApplier = true; // special annotation so these can be discovered in the transaction record
7625 that.invalidator = fluid.makeEventFirer({name: "Invalidator for model relay with applier " + that.forwardApplier.applierId});
7626 if (sourcePath !== null) {
7627 // TODO: backwardApplier is unused
7628 that.backwardApplier = fluid.makeHolderChangeApplier(that.backwardHolder);
7629 that.backwardAdapter = function (transaction) {
7630 fluid.model.guardedAdapter(transaction, backwardCond, that.backwardAdapterImpl, arguments);
7631 };
7632 that.backwardAdapter.cond = backwardCond;
7633 }
7634 that.update = that.invalidator.fire; // necessary so that both routes to fluid.connectModelRelay from here hit the first branch
7635 var implicitOptions = {
7636 targetApplier: that.forwardApplier, // this special field identifies us to fluid.connectModelRelay
7637 update: that.update,
7638 namespace: namespace,
7639 priority: priority,
7640 refCount: 0
7641 };
7642 that.forwardHolder.model = fluid.parseImplicitRelay(componentThat, transform, [], implicitOptions);
7643 that.refCount = implicitOptions.refCount;
7644 that.namespace = namespace;
7645 that.priority = priority;
7646 that.generateAdapters();
7647 that.invalidator.addListener(that.generateAdapters);
7648 that.invalidator.addListener(that.runTransform);
7649 return that;
7650 };
7651
7652 fluid.singleTransformToFull = function (singleTransform) {
7653 var withPath = $.extend(true, {inputPath: ""}, singleTransform);
7654 return {
7655 "": {
7656 transform: withPath
7657 }
7658 };
7659 };
7660
7661 // Convert old-style "relay conditions" to source includes/excludes as used in model listeners
7662 fluid.model.relayConditions = {
7663 initOnly: {includeSource: "init"},
7664 liveOnly: {excludeSource: "init"},
7665 never: {includeSource: []},
7666 always: {}
7667 };
7668
7669 /** Parse a relay condition specification, e.g. of the form `{includeSource: "init"}` or `never` into a hash representation
7670 * suitable for rapid querying.
7671 * @param {String|Object} condition - A relay condition specification, appearing in the section `forward` or `backward` of a
7672 * relay definition
7673 * @return {RelayCondition} The parsed condition, holding members `includeSource` and `excludeSource` each with a hash to `true`
7674 * of referenced sources
7675 */
7676 fluid.model.parseRelayCondition = function (condition) {
7677 if (condition === "initOnly") {
7678 fluid.log(fluid.logLevel.WARN, "The relay condition \"initOnly\" is deprecated: Please use the form 'includeSource: \"init\"' instead");
7679 } else if (condition === "liveOnly") {
7680 fluid.log(fluid.logLevel.WARN, "The relay condition \"liveOnly\" is deprecated: Please use the form 'excludeSource: \"init\"' instead");
7681 }
7682 var exclusionRec;
7683 if (!condition) {
7684 exclusionRec = {};
7685 } else if (typeof(condition) === "string") {
7686 exclusionRec = fluid.model.relayConditions[condition];
7687 if (!exclusionRec) {
7688 fluid.fail("Unrecognised model relay condition string \"" + condition + "\": the supported values are \"never\" or a record with members \"includeSource\" and/or \"excludeSource\"");
7689 }
7690 } else {
7691 exclusionRec = condition;
7692 }
7693 return fluid.parseSourceExclusionSpec({}, exclusionRec);
7694 };
7695
7696 /** Parse a single model relay record as appearing nested within the `modelRelay` block in a model component's
7697 * options. By various calls to `fluid.connectModelRelay` this will set up the structure operating the live
7698 * relay during the component#s lifetime.
7699 * @param {Component} that - The component holding the record, currently instantiating
7700 * @param {Object} mrrec - The model relay record. This must contain either a member `singleTransform` or `transform` and may also contain
7701 * members `namespace`, `path`, `priority`, `forward` and `backward`
7702 * @param {String} key -
7703 */
7704 fluid.parseModelRelay = function (that, mrrec, key) {
7705 var parsedSource = mrrec.source !== undefined ? fluid.parseValidModelReference(that, "modelRelay record member \"source\"", mrrec.source) :
7706 {path: null, modelSegs: null};
7707 var parsedTarget = fluid.parseValidModelReference(that, "modelRelay record member \"target\"", mrrec.target);
7708 var namespace = mrrec.namespace || key;
7709
7710 var transform = mrrec.singleTransform ? fluid.singleTransformToFull(mrrec.singleTransform) : mrrec.transform;
7711 if (!transform) {
7712 fluid.fail("Cannot parse modelRelay record without element \"singleTransform\" or \"transform\":", mrrec);
7713 }
7714 var forwardCond = fluid.model.parseRelayCondition(mrrec.forward), backwardCond = fluid.model.parseRelayCondition(mrrec.backward);
7715
7716 var transformPackage = fluid.makeTransformPackage(that, transform, parsedSource.path, parsedTarget.path, forwardCond, backwardCond, namespace, mrrec.priority);
7717 if (transformPackage.refCount === 0) { // There were no implicit relay elements found in the relay document - it can be relayed directly
7718 // Case i): Bind changes emitted from the relay ends to each other, synchronously
7719 fluid.connectModelRelay(parsedSource.that || that, parsedSource.modelSegs, parsedTarget.that, parsedTarget.modelSegs,
7720 // Primarily, here, we want to get rid of "update" which is what signals to connectModelRelay that this is a invalidatable relay
7721 fluid.filterKeys(transformPackage, ["forwardAdapter", "backwardAdapter", "namespace", "priority"]));
7722 } else {
7723 if (parsedSource.modelSegs) {
7724 fluid.fail("Error in model relay definition: If a relay transform has a model dependency, you can not specify a \"source\" entry - please instead enter this as \"input\" in the transform specification. Definition was ", mrrec, " for component ", that);
7725 }
7726 // Case ii): Binds changes emitted from the relay document itself onto the relay ends (using the "half-transactional system")
7727 fluid.connectModelRelay(that, null, parsedTarget.that, parsedTarget.modelSegs, transformPackage);
7728 }
7729 };
7730
7731 /** Traverses a model document written within a component's options, parsing any IoC references looking for
7732 * i) references to general material, which will be fetched and interpolated now, and ii) "implicit relay" references to the
7733 * model areas of other components, which will be used to set up live synchronisation between the area in this component where
7734 * they appear and their target, as well as setting up initial synchronisation to compute the initial contents.
7735 * This is called in two situations: A) parsing the `model` configuration option for a model component, and B) parsing the
7736 * `transform` member (perhaps derived from `singleTransform`) of a `modelRelay` block for a model component. It calls itself
7737 * recursively as it progresses through the model document material with updated `segs`
7738 * @param {Component} that - The component holding the model document
7739 * @param {Any} modelRec - The model document specification to be parsed
7740 * @param {String[]} segs - The array of string path segments from the root of the entire model document to the point of current parsing
7741 * @param {Object} options - Configuration options (mutable) governing this parse. This is primarily used to hand as the 5th argument to
7742 * `fluid.connectModelRelay` for any model references found, and contains members
7743 * refCount {Integer} An count incremented for every call to `fluid.connectModelRelay` setting up a synchronizing relay for every
7744 * reference to model material encountered
7745 * priority {String} The unparsed priority member attached to this record, or `first` for a parse of `model` (case A)
7746 * namespace {String} [optional] A namespace attached to this transform, if we are parsing a transform
7747 * targetApplier {ChangeApplier} [optional] The ChangeApplier for this transform document, if it is a transform, empty otherwise
7748 * update {Function} [optional] A function to be called on conclusion of a "half-transaction" where all currently pending updates have been applied
7749 * to this transform document. This function will update/regenerate the relay transform functions used to relay changes between the transform
7750 * ends based on the updated document.
7751 * @return {Any} - The resulting model value.
7752 */
7753 fluid.parseImplicitRelay = function (that, modelRec, segs, options) {
7754 var value;
7755 if (fluid.isIoCReference(modelRec)) {
7756 var parsed = fluid.parseValidModelReference(that, "model reference from model (implicit relay)", modelRec, true);
7757 if (parsed.nonModel) {
7758 value = fluid.getForComponent(parsed.that, parsed.segs);
7759 } else {
7760 ++options.refCount; // This count is used from within fluid.makeTransformPackage
7761 fluid.connectModelRelay(that, segs, parsed.that, parsed.modelSegs, options);
7762 }
7763 } else if (fluid.isPrimitive(modelRec) || !fluid.isPlainObject(modelRec)) {
7764 value = modelRec;
7765 } else if (modelRec.expander && fluid.isPlainObject(modelRec.expander)) {
7766 value = fluid.expandOptions(modelRec, that);
7767 } else {
7768 value = fluid.freshContainer(modelRec);
7769 fluid.each(modelRec, function (innerValue, key) {
7770 segs.push(key);
7771 var innerTrans = fluid.parseImplicitRelay(that, innerValue, segs, options);
7772 if (innerTrans !== undefined) {
7773 value[key] = innerTrans;
7774 }
7775 segs.pop();
7776 });
7777 }
7778 return value;
7779 };
7780
7781
7782 // Conclude the transaction by firing to all external listeners in priority order
7783 fluid.model.notifyExternal = function (transRec) {
7784 var allChanges = transRec ? fluid.values(transRec.externalChanges) : [];
7785 fluid.sortByPriority(allChanges);
7786 for (var i = 0; i < allChanges.length; ++i) {
7787 var change = allChanges[i];
7788 var targetApplier = change.args[5]; // NOTE: This argument gets here via fluid.model.storeExternalChange from fluid.notifyModelChanges
7789 if (!targetApplier.destroyed) { // 3rd point of guarding for FLUID-5592
7790 change.listener.apply(null, change.args);
7791 }
7792 }
7793 fluid.clearLinkCounts(transRec, true); // "options" structures for relayCount are aliased
7794 };
7795
7796 fluid.model.commitRelays = function (instantiator, transactionId) {
7797 var transRec = instantiator.modelTransactions[transactionId];
7798 fluid.each(transRec, function (transEl) {
7799 // EXPLAIN: This must commit ALL current transactions, not just those for relays - why?
7800 if (transEl.transaction) { // some entries are links
7801 transEl.transaction.commit("relay");
7802 transEl.transaction.reset();
7803 }
7804 });
7805 };
7806
7807 // Listens to all invalidation to relays, and reruns/applies them if they have been invalidated
7808 fluid.model.updateRelays = function (instantiator, transactionId) {
7809 var transRec = instantiator.modelTransactions[transactionId];
7810 var updates = 0;
7811 fluid.sortByPriority(transRec.relays);
7812 fluid.each(transRec.relays, function (transEl) {
7813 // TODO: We have a bit of a problem here in that we only process updatable relays by priority - plain relays get to act non-transactionally
7814 if (transEl.transaction.changeRecord.changes > 0 && transEl.relayCount < 2 && transEl.options.update) {
7815 transEl.relayCount++;
7816 fluid.clearLinkCounts(transRec);
7817 transEl.options.update(transEl.transaction, transRec);
7818 ++updates;
7819 }
7820 });
7821 return updates;
7822 };
7823
7824 fluid.establishModelRelay = function (that, optionsModel, optionsML, optionsMR, applier) {
7825 var shadow = fluid.shadowForComponent(that);
7826 if (!shadow.modelRelayEstablished) {
7827 shadow.modelRelayEstablished = true;
7828 } else {
7829 fluid.fail("FLUID-5887 failure: Model relay initialised twice on component", that);
7830 }
7831 fluid.mergeModelListeners(that, optionsML);
7832
7833 var enlist = fluid.enlistModelComponent(that);
7834 fluid.each(optionsMR, function (mrrec, key) {
7835 for (var i = 0; i < mrrec.length; ++i) {
7836 fluid.parseModelRelay(that, mrrec[i], key);
7837 }
7838 });
7839
7840 // Note: this particular instance of "refCount" is disused. We only use the count made within fluid.makeTransformPackge
7841 var initModels = fluid.transform(optionsModel, function (modelRec) {
7842 return fluid.parseImplicitRelay(that, modelRec, [], {refCount: 0, priority: "first"});
7843 });
7844 enlist.initModels = initModels;
7845
7846 var instantiator = fluid.getInstantiator(that);
7847
7848 function updateRelays(transaction) {
7849 while (fluid.model.updateRelays(instantiator, transaction.id) > 0) {} // eslint-disable-line no-empty
7850 }
7851
7852 function commitRelays(transaction, applier, code) {
7853 if (code !== "relay") { // don't commit relays if this commit is already a relay commit
7854 fluid.model.commitRelays(instantiator, transaction.id);
7855 }
7856 }
7857
7858 function concludeTransaction(transaction, applier, code) {
7859 if (code !== "relay") {
7860 fluid.model.notifyExternal(instantiator.modelTransactions[transaction.id]);
7861 delete instantiator.modelTransactions[transaction.id];
7862 }
7863 }
7864
7865 applier.preCommit.addListener(updateRelays);
7866 applier.preCommit.addListener(commitRelays);
7867 applier.postCommit.addListener(concludeTransaction);
7868
7869 return null;
7870 };
7871
7872 // supported, PUBLIC API grade
7873 fluid.defaults("fluid.modelComponent", {
7874 gradeNames: ["fluid.component"],
7875 changeApplierOptions: {
7876 relayStyle: true,
7877 cullUnchanged: true
7878 },
7879 members: {
7880 model: "@expand:fluid.initRelayModel({that}, {that}.modelRelay)",
7881 applier: "@expand:fluid.makeHolderChangeApplier({that}, {that}.options.changeApplierOptions)",
7882 modelRelay: "@expand:fluid.establishModelRelay({that}, {that}.options.model, {that}.options.modelListeners, {that}.options.modelRelay, {that}.applier)"
7883 },
7884 mergePolicy: {
7885 model: {
7886 noexpand: true,
7887 func: fluid.arrayConcatPolicy // TODO: bug here in case a model consists of an array
7888 },
7889 modelListeners: fluid.makeMergeListenersPolicy(fluid.arrayConcatPolicy),
7890 modelRelay: fluid.makeMergeListenersPolicy(fluid.arrayConcatPolicy, true)
7891 }
7892 });
7893
7894 fluid.modelChangedToChange = function (args) {
7895 return {
7896 value: args[0],
7897 oldValue: args[1],
7898 path: args[2],
7899 transaction: args[4]
7900 };
7901 };
7902
7903 // Note - has only one call, from resolveModelListener
7904 fluid.event.invokeListener = function (listener, args, localRecord, mergeRecord) {
7905 if (typeof(listener) === "string") {
7906 listener = fluid.event.resolveListener(listener); // just resolves globals
7907 }
7908 return listener.apply(null, args, localRecord, mergeRecord); // can be "false apply" that requires extra context for expansion
7909 };
7910
7911 fluid.resolveModelListener = function (that, record) {
7912 var togo = function () {
7913 if (fluid.isDestroyed(that)) { // first guarding point to resolve FLUID-5592
7914 return;
7915 }
7916 var change = fluid.modelChangedToChange(arguments);
7917 var args = arguments;
7918 var localRecord = {change: change, "arguments": args};
7919 var mergeRecord = {source: Object.keys(change.transaction.sources)}; // cascade for FLUID-5490
7920 if (record.args) {
7921 args = fluid.expandOptions(record.args, that, {}, localRecord);
7922 }
7923 fluid.event.invokeListener(record.listener, fluid.makeArray(args), localRecord, mergeRecord);
7924 };
7925 fluid.event.impersonateListener(record.listener, togo);
7926 return togo;
7927 };
7928
7929 fluid.registerModelListeners = function (that, record, paths, namespace) {
7930 var func = fluid.resolveModelListener(that, record);
7931 fluid.each(record.byTarget, function (parsedArray) {
7932 var parsed = parsedArray[0]; // that, applier are common across all these elements
7933 var spec = {
7934 listener: func, // for initModelEvent
7935 listenerId: fluid.allocateGuid(), // external declarative listeners may often share listener handle, identify here
7936 segsArray: fluid.getMembers(parsedArray, "modelSegs"),
7937 pathArray: fluid.getMembers(parsedArray, "path"),
7938 includeSource: record.includeSource,
7939 excludeSource: record.excludeSource,
7940 priority: fluid.expandOptions(record.priority, that),
7941 transactional: true
7942 };
7943 // update "spec" so that we parse priority information just once
7944 spec = parsed.applier.modelChanged.addListener(spec, func, namespace, record.softNamespace);
7945
7946 fluid.recordChangeListener(that, parsed.applier, func, spec.listenerId);
7947 function initModelEvent() {
7948 if (fluid.isModelComplete(parsed.that)) {
7949 var trans = parsed.applier.initiate(null, "init");
7950 fluid.initModelEvent(that, parsed.applier, trans, [spec]);
7951 trans.commit();
7952 }
7953 }
7954 if (that !== parsed.that && !fluid.isModelComplete(that)) { // TODO: Use FLUID-4883 "latched events" when available
7955 // Don't confuse the end user by firing their listener before the component is constructed
7956 // TODO: Better detection than this is requred - we assume that the target component will not be discovered as part
7957 // of the initial transaction wave, but if it is, it will get a double notification - we really need "wave of explosions"
7958 // since we are currently too early in initialisation of THIS component in order to tell if other will be found
7959 // independently.
7960 var onCreate = fluid.getForComponent(that, ["events", "onCreate"]);
7961 onCreate.addListener(initModelEvent);
7962 }
7963 });
7964 };
7965
7966 fluid.mergeModelListeners = function (that, listeners) {
7967 fluid.each(listeners, function (value, key) {
7968 if (typeof(value) === "string") {
7969 value = {
7970 funcName: value
7971 };
7972 }
7973 // Bypass fluid.event.dispatchListener by means of "standard = false" and enter our custom workflow including expanding "change":
7974 var records = fluid.event.resolveListenerRecord(value, that, "modelListeners", null, false).records;
7975 fluid.each(records, function (record) {
7976 // Aggregate model listeners into groups referring to the same component target.
7977 // We do this so that a single entry will appear in its modelListeners so that they may
7978 // be notified just once per transaction, and also displaced by namespace
7979 record.byTarget = {};
7980 var paths = fluid.makeArray(record.path === undefined ? key : record.path);
7981 fluid.each(paths, function (path) {
7982 var parsed = fluid.parseValidModelReference(that, "modelListeners entry", path);
7983 fluid.pushArray(record.byTarget, parsed.that.id, parsed);
7984 });
7985 var namespace = (record.namespace && !record.softNamespace ? record.namespace : null) || (record.path !== undefined ? key : null);
7986 fluid.registerModelListeners(that, record, paths, namespace);
7987 });
7988 });
7989 };
7990
7991
7992 /** CHANGE APPLIER **/
7993
7994 /* Dispatches a list of changes to the supplied applier */
7995 fluid.fireChanges = function (applier, changes) {
7996 for (var i = 0; i < changes.length; ++i) {
7997 applier.fireChangeRequest(changes[i]);
7998 }
7999 };
8000
8001 fluid.model.isChangedPath = function (changeMap, segs) {
8002 for (var i = 0; i <= segs.length; ++i) {
8003 if (typeof(changeMap) === "string") {
8004 return true;
8005 }
8006 if (i < segs.length && changeMap) {
8007 changeMap = changeMap[segs[i]];
8008 }
8009 }
8010 return false;
8011 };
8012
8013 fluid.model.setChangedPath = function (options, segs, value) {
8014 var notePath = function (record) {
8015 segs.unshift(record);
8016 fluid.model.setSimple(options, segs, value);
8017 segs.shift();
8018 };
8019 if (!fluid.model.isChangedPath(options.changeMap, segs)) {
8020 ++options.changes;
8021 notePath("changeMap");
8022 }
8023 if (!fluid.model.isChangedPath(options.deltaMap, segs)) {
8024 ++options.deltas;
8025 notePath("deltaMap");
8026 }
8027 };
8028
8029 fluid.model.fetchChangeChildren = function (target, i, segs, source, options) {
8030 fluid.each(source, function (value, key) {
8031 segs[i] = key;
8032 fluid.model.applyChangeStrategy(target, key, i, segs, value, options);
8033 segs.length = i;
8034 });
8035 };
8036
8037 // Called with two primitives which are compared for equality. This takes account of "floating point slop" to avoid
8038 // continuing to propagate inverted values as changes
8039 // TODO: replace with a pluggable implementation
8040 fluid.model.isSameValue = function (a, b) {
8041 if (typeof(a) !== "number" || typeof(b) !== "number") {
8042 return a === b;
8043 } else {
8044 // Don't use isNaN because of https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/isNaN#Confusing_special-case_behavior
8045 if (a === b || a !== a && b !== b) { // Either the same concrete number or both NaN
8046 return true;
8047 } else {
8048 var relError = Math.abs((a - b) / b);
8049 return relError < 1e-12; // 64-bit floats have approx 16 digits accuracy, this should deal with most reasonable transforms
8050 }
8051 }
8052 };
8053
8054 fluid.model.applyChangeStrategy = function (target, name, i, segs, source, options) {
8055 var targetSlot = target[name];
8056 var sourceCode = fluid.typeCode(source);
8057 var targetCode = fluid.typeCode(targetSlot);
8058 var changedValue = fluid.NO_VALUE;
8059 if (sourceCode === "primitive") {
8060 if (!fluid.model.isSameValue(targetSlot, source)) {
8061 changedValue = source;
8062 ++options.unchanged;
8063 }
8064 } else if (targetCode !== sourceCode || sourceCode === "array" && source.length !== targetSlot.length) {
8065 // RH is not primitive - array or object and mismatching or any array rewrite
8066 changedValue = fluid.freshContainer(source);
8067 }
8068 if (changedValue !== fluid.NO_VALUE) {
8069 target[name] = changedValue;
8070 if (options.changeMap) {
8071 fluid.model.setChangedPath(options, segs, options.inverse ? "DELETE" : "ADD");
8072 }
8073 }
8074 if (sourceCode !== "primitive") {
8075 fluid.model.fetchChangeChildren(target[name], i + 1, segs, source, options);
8076 }
8077 };
8078
8079 fluid.model.stepTargetAccess = function (target, type, segs, startpos, endpos, options) {
8080 for (var i = startpos; i < endpos; ++i) {
8081 if (!target) {
8082 continue;
8083 }
8084 var oldTrunk = target[segs[i]];
8085 target = fluid.model.traverseWithStrategy(target, segs, i, options[type === "ADD" ? "resolverSetConfig" : "resolverGetConfig"],
8086 segs.length - i - 1);
8087 if (oldTrunk !== target && options.changeMap) {
8088 fluid.model.setChangedPath(options, segs.slice(0, i + 1), "ADD");
8089 }
8090 }
8091 return {root: target, last: segs[endpos]};
8092 };
8093
8094 fluid.model.defaultAccessorConfig = function (options) {
8095 options = options || {};
8096 options.resolverSetConfig = options.resolverSetConfig || fluid.model.escapedSetConfig;
8097 options.resolverGetConfig = options.resolverGetConfig || fluid.model.escapedGetConfig;
8098 return options;
8099 };
8100
8101 // Changes: "MERGE" action abolished
8102 // ADD/DELETE at root can be destructive
8103 // changes tracked in optional final argument holding "changeMap: {}, changes: 0, unchanged: 0"
8104 fluid.model.applyHolderChangeRequest = function (holder, request, options) {
8105 options = fluid.model.defaultAccessorConfig(options);
8106 options.deltaMap = options.changeMap ? {} : null;
8107 options.deltas = 0;
8108 var length = request.segs.length;
8109 var pen, atRoot = length === 0;
8110 if (atRoot) {
8111 pen = {root: holder, last: "model"};
8112 } else {
8113 if (!holder.model) {
8114 holder.model = {};
8115 fluid.model.setChangedPath(options, [], options.inverse ? "DELETE" : "ADD");
8116 }
8117 pen = fluid.model.stepTargetAccess(holder.model, request.type, request.segs, 0, length - 1, options);
8118 }
8119 if (request.type === "ADD") {
8120 var value = request.value;
8121 var segs = fluid.makeArray(request.segs);
8122 fluid.model.applyChangeStrategy(pen.root, pen.last, length - 1, segs, value, options, atRoot);
8123 } else if (request.type === "DELETE") {
8124 if (pen.root && pen.root[pen.last] !== undefined) {
8125 delete pen.root[pen.last];
8126 if (options.changeMap) {
8127 fluid.model.setChangedPath(options, request.segs, "DELETE");
8128 }
8129 }
8130 } else {
8131 fluid.fail("Unrecognised change type of " + request.type);
8132 }
8133 return options.deltas ? options.deltaMap : null;
8134 };
8135
8136 /** Compare two models for equality using a deep algorithm. It is assumed that both models are JSON-equivalent and do
8137 * not contain circular links.
8138 * @param modela The first model to be compared
8139 * @param modelb The second model to be compared
8140 * @param {Object} options - If supplied, will receive a map and summary of the change content between the objects. Structure is:
8141 * changeMap: {Object/String} An isomorphic map of the object structures to values "ADD" or "DELETE" indicating
8142 * that values have been added/removed at that location. Note that in the case the object structure differs at the root, <code>changeMap</code> will hold
8143 * the plain String value "ADD" or "DELETE"
8144 * changes: {Integer} Counts the number of changes between the objects - The two objects are identical iff <code>changes === 0</code>.
8145 * unchanged: {Integer} Counts the number of leaf (primitive) values at which the two objects are identical. Note that the current implementation will
8146 * double-count, this summary should be considered indicative rather than precise.
8147 * @return <code>true</code> if the models are identical
8148 */
8149 // TODO: This algorithm is quite inefficient in that both models will be copied once each
8150 // supported, PUBLIC API function
8151 fluid.model.diff = function (modela, modelb, options) {
8152 options = options || {changes: 0, unchanged: 0, changeMap: {}}; // current algorithm can't avoid the expense of changeMap
8153 var typea = fluid.typeCode(modela);
8154 var typeb = fluid.typeCode(modelb);
8155 var togo;
8156 if (typea === "primitive" && typeb === "primitive") {
8157 togo = fluid.model.isSameValue(modela, modelb);
8158 } else if (typea === "primitive" ^ typeb === "primitive") {
8159 togo = false;
8160 } else {
8161 // Apply both forward and reverse changes - if no changes either way, models are identical
8162 // "ADD" reported in the reverse direction must be accounted as a "DELETE"
8163 var holdera = {
8164 model: fluid.copy(modela)
8165 };
8166 fluid.model.applyHolderChangeRequest(holdera, {value: modelb, segs: [], type: "ADD"}, options);
8167 var holderb = {
8168 model: fluid.copy(modelb)
8169 };
8170 options.inverse = true;
8171 fluid.model.applyHolderChangeRequest(holderb, {value: modela, segs: [], type: "ADD"}, options);
8172 togo = options.changes === 0;
8173 }
8174 if (togo === false && options.changes === 0) { // catch all primitive cases
8175 options.changes = 1;
8176 options.changeMap = modelb === undefined ? "DELETE" : "ADD";
8177 } else if (togo === true && options.unchanged === 0) {
8178 options.unchanged = 1;
8179 }
8180 return togo;
8181 };
8182
8183 fluid.outputMatches = function (matches, outSegs, root) {
8184 fluid.each(root, function (value, key) {
8185 matches.push(outSegs.concat(key));
8186 });
8187 };
8188
8189 // Here we only support for now very simple expressions which have at most one
8190 // wildcard which must appear in the final segment
8191 fluid.matchChanges = function (changeMap, specSegs, newHolder, oldHolder) {
8192 var newRoot = newHolder.model;
8193 var oldRoot = oldHolder.model;
8194 var map = changeMap;
8195 var outSegs = ["model"];
8196 var wildcard = false;
8197 var togo = [];
8198 for (var i = 0; i < specSegs.length; ++i) {
8199 var seg = specSegs[i];
8200 if (seg === "*") {
8201 if (i === specSegs.length - 1) {
8202 wildcard = true;
8203 } else {
8204 fluid.fail("Wildcard specification in modelChanged listener is only supported for the final path segment: " + specSegs.join("."));
8205 }
8206 } else {
8207 outSegs.push(seg);
8208 map = fluid.isPrimitive(map) ? map : map[seg];
8209 newRoot = newRoot ? newRoot[seg] : undefined;
8210 oldRoot = oldRoot ? oldRoot[seg] : undefined;
8211 }
8212 }
8213 if (map) {
8214 if (wildcard) {
8215 if (map === "DELETE") {
8216 fluid.outputMatches(togo, outSegs, oldRoot);
8217 } else if (map === "ADD") {
8218 fluid.outputMatches(togo, outSegs, newRoot);
8219 } else {
8220 fluid.outputMatches(togo, outSegs, map);
8221 }
8222 } else {
8223 togo.push(outSegs);
8224 }
8225 }
8226 return togo;
8227 };
8228
8229 fluid.storeExternalChange = function (transRec, applier, invalidPath, spec, args) {
8230 var pathString = applier.composeSegments.apply(null, invalidPath);
8231 var keySegs = [applier.holder.id, spec.listenerId, (spec.wildcard ? pathString : "")];
8232 var keyString = keySegs.join("|");
8233 // TODO: We think we probably have a bug in that notifications destined for end of transaction are actually continuously emitted during the transaction
8234 // These are unbottled in fluid.concludeTransaction
8235 transRec.externalChanges[keyString] = {listener: spec.listener, namespace: spec.namespace, priority: spec.priority, args: args};
8236 };
8237
8238 fluid.notifyModelChanges = function (listeners, changeMap, newHolder, oldHolder, changeRequest, transaction, applier, that) {
8239 if (!listeners) {
8240 return;
8241 }
8242 var transRec = transaction && fluid.getModelTransactionRec(that, transaction.id);
8243 for (var i = 0; i < listeners.length; ++i) {
8244 var spec = listeners[i];
8245 var multiplePaths = spec.segsArray.length > 1; // does this spec listen on multiple paths? If so, don't rebase arguments and just report once per transaction
8246 for (var j = 0; j < spec.segsArray.length; ++j) {
8247 var invalidPaths = fluid.matchChanges(changeMap, spec.segsArray[j], newHolder, oldHolder);
8248 // We only have multiple invalidPaths here if there is a wildcard
8249 for (var k = 0; k < invalidPaths.length; ++k) {
8250 if (applier.destroyed) { // 2nd guarding point for FLUID-5592
8251 return;
8252 }
8253 var invalidPath = invalidPaths[k];
8254 spec.listener = fluid.event.resolveListener(spec.listener);
8255 var args = [multiplePaths ? newHolder.model : fluid.model.getSimple(newHolder, invalidPath),
8256 multiplePaths ? oldHolder.model : fluid.model.getSimple(oldHolder, invalidPath),
8257 multiplePaths ? [] : invalidPath.slice(1), changeRequest, transaction, applier];
8258 // FLUID-5489: Do not notify of null changes which were reported as a result of invalidating a higher path
8259 // TODO: We can improve greatly on efficiency by i) reporting a special code from fluid.matchChanges which signals the difference between invalidating a higher and lower path,
8260 // ii) improving fluid.model.diff to create fewer intermediate structures and no copies
8261 // TODO: The relay invalidation system is broken and must always be notified (branch 1) - since our old/new value detection is based on the wrong (global) timepoints in the transaction here,
8262 // rather than the "last received model" by the holder of the transform document
8263 if (!spec.isRelay) {
8264 var isNull = fluid.model.diff(args[0], args[1]);
8265 if (isNull) {
8266 continue;
8267 }
8268 var sourceExcluded = fluid.isExcludedChangeSource(transaction, spec);
8269 if (sourceExcluded) {
8270 continue;
8271 }
8272 }
8273 if (transRec && !spec.isRelay && spec.transactional) { // bottle up genuine external changes so we can sort and dedupe them later
8274 fluid.storeExternalChange(transRec, applier, invalidPath, spec, args);
8275 } else {
8276 spec.listener.apply(null, args);
8277 }
8278 }
8279 }
8280 }
8281 };
8282
8283 fluid.bindELMethods = function (applier) {
8284 applier.parseEL = function (EL) {
8285 return fluid.model.pathToSegments(EL, applier.options.resolverSetConfig);
8286 };
8287 applier.composeSegments = function () {
8288 return applier.options.resolverSetConfig.parser.compose.apply(null, arguments);
8289 };
8290 };
8291
8292 fluid.initModelEvent = function (that, applier, trans, listeners) {
8293 fluid.notifyModelChanges(listeners, "ADD", trans.oldHolder, fluid.emptyHolder, null, trans, applier, that);
8294 };
8295
8296 // A standard "empty model" for the purposes of comparing initial state during the primordial transaction
8297 fluid.emptyHolder = fluid.freezeRecursive({ model: undefined });
8298
8299 fluid.preFireChangeRequest = function (applier, changeRequest) {
8300 if (!changeRequest.type) {
8301 changeRequest.type = "ADD";
8302 }
8303 changeRequest.segs = changeRequest.segs || applier.parseEL(changeRequest.path);
8304 };
8305
8306 // Automatically adapts change onto fireChangeRequest
8307 fluid.bindRequestChange = function (that) {
8308 that.change = function (path, value, type, source) {
8309 var changeRequest = {
8310 path: path,
8311 value: value,
8312 type: type,
8313 source: source
8314 };
8315 that.fireChangeRequest(changeRequest);
8316 };
8317 };
8318
8319 // Quick n dirty test to cheaply detect Object versus other JSON types
8320 fluid.isObjectSimple = function (totest) {
8321 return Object.prototype.toString.call(totest) === "[object Object]";
8322 };
8323
8324 fluid.mergeChangeSources = function (target, globalSources) {
8325 if (fluid.isObjectSimple(globalSources)) { // TODO: No test for this branch!
8326 fluid.extend(target, globalSources);
8327 } else {
8328 fluid.each(fluid.makeArray(globalSources), function (globalSource) {
8329 target[globalSource] = true;
8330 });
8331 }
8332 };
8333
8334 fluid.ChangeApplier = function () {};
8335
8336 fluid.makeHolderChangeApplier = function (holder, options) {
8337 options = fluid.model.defaultAccessorConfig(options);
8338 var applierId = fluid.allocateGuid();
8339 var that = new fluid.ChangeApplier();
8340 var name = fluid.isComponent(holder) ? "ChangeApplier for component " + fluid.dumpThat(holder) : "ChangeApplier with id " + applierId;
8341 $.extend(that, {
8342 applierId: applierId,
8343 holder: holder,
8344 listeners: fluid.makeEventFirer({name: "Internal change listeners for " + name}),
8345 transListeners: fluid.makeEventFirer({name: "External change listeners for " + name}),
8346 options: options,
8347 modelChanged: {},
8348 preCommit: fluid.makeEventFirer({name: "preCommit event for " + name}),
8349 postCommit: fluid.makeEventFirer({name: "postCommit event for " + name})
8350 });
8351 that.destroy = function () {
8352 that.preCommit.destroy();
8353 that.postCommit.destroy();
8354 that.destroyed = true;
8355 };
8356 that.modelChanged.addListener = function (spec, listener, namespace, softNamespace) {
8357 if (typeof(spec) === "string") {
8358 spec = {
8359 path: spec
8360 };
8361 } else {
8362 spec = fluid.copy(spec);
8363 }
8364 spec.listenerId = spec.listenerId || fluid.allocateGuid(); // FLUID-5151: don't use identifyListener since event.addListener will use this as a namespace
8365 spec.namespace = namespace;
8366 spec.softNamespace = softNamespace;
8367 if (typeof(listener) === "string") { // The reason for "globalName" is so that listener names can be resolved on first use and not on registration
8368 listener = {globalName: listener};
8369 }
8370 spec.listener = listener;
8371 if (spec.transactional !== false) {
8372 spec.transactional = true;
8373 }
8374 if (!spec.segsArray) { // It's a manual registration
8375 if (spec.path !== undefined) {
8376 spec.segs = spec.segs || that.parseEL(spec.path);
8377 }
8378 if (!spec.segsArray) {
8379 spec.segsArray = [spec.segs];
8380 }
8381 }
8382 if (!spec.isRelay) {
8383 // This acts for listeners registered externally. For relays, the exclusion spec is stored in "cond"
8384 fluid.parseSourceExclusionSpec(spec, spec);
8385 spec.wildcard = fluid.accumulate(fluid.transform(spec.segsArray, function (segs) {
8386 return fluid.contains(segs, "*");
8387 }), fluid.add, 0);
8388 if (spec.wildcard && spec.segsArray.length > 1) {
8389 fluid.fail("Error in model listener specification ", spec, " - you may not supply a wildcard pattern as one of a set of multiple paths to be matched");
8390 }
8391 }
8392 var firer = that[spec.transactional ? "transListeners" : "listeners"];
8393 firer.addListener(spec);
8394 return spec; // return is used in registerModelListeners
8395 };
8396 that.modelChanged.removeListener = function (listener) {
8397 that.listeners.removeListener(listener);
8398 that.transListeners.removeListener(listener);
8399 };
8400 that.fireChangeRequest = function (changeRequest) {
8401 var ation = that.initiate("local", changeRequest.source);
8402 ation.fireChangeRequest(changeRequest);
8403 ation.commit();
8404 };
8405
8406 /**
8407 * Initiate a fresh transaction on this applier, perhaps coordinated with other transactions sharing the same id across the component tree
8408 * Arguments all optional
8409 * @param {String} localSource - "local", "relay" or null Local source identifiers only good for transaction's representative on this applier
8410 * @param {String|Array|Object} globalSources - Global source identifiers common across this transaction, expressed as a single string, an array of strings, or an object with a "toString" method.
8411 * @param {String} transactionId - Global transaction id to enlist with.
8412 * @return {Object} - The component initiating the change.
8413 */
8414 that.initiate = function (localSource, globalSources, transactionId) {
8415 localSource = globalSources === "init" ? null : (localSource || "local"); // supported values for localSource are "local" and "relay" - globalSource of "init" defeats defaulting of localSource to "local"
8416 var defeatPost = localSource === "relay"; // defeatPost is supplied for all non-top-level transactions
8417 var trans = {
8418 instanceId: fluid.allocateGuid(), // for debugging only - the representative of this transction on this applier
8419 id: transactionId || fluid.allocateGuid(), // The global transaction id across all appliers - allocate here if this is the starting point
8420 changeRecord: {
8421 resolverSetConfig: options.resolverSetConfig, // here to act as "options" in applyHolderChangeRequest
8422 resolverGetConfig: options.resolverGetConfig
8423 },
8424 reset: function () {
8425 trans.oldHolder = holder;
8426 trans.newHolder = { model: fluid.copy(holder.model) };
8427 trans.changeRecord.changes = 0;
8428 trans.changeRecord.unchanged = 0; // just for type consistency - we don't use these values in the ChangeApplier
8429 trans.changeRecord.changeMap = {};
8430 },
8431 commit: function (code) {
8432 that.preCommit.fire(trans, that, code);
8433 if (trans.changeRecord.changes > 0) {
8434 var oldHolder = {model: holder.model};
8435 holder.model = trans.newHolder.model;
8436 fluid.notifyModelChanges(that.transListeners.sortedListeners, trans.changeRecord.changeMap, holder, oldHolder, null, trans, that, holder);
8437 }
8438 if (!defeatPost) {
8439 that.postCommit.fire(trans, that, code);
8440 }
8441 },
8442 fireChangeRequest: function (changeRequest) {
8443 fluid.preFireChangeRequest(that, changeRequest);
8444 changeRequest.transactionId = trans.id;
8445 var deltaMap = fluid.model.applyHolderChangeRequest(trans.newHolder, changeRequest, trans.changeRecord);
8446 fluid.notifyModelChanges(that.listeners.sortedListeners, deltaMap, trans.newHolder, holder, changeRequest, trans, that, holder);
8447 },
8448 hasChangeSource: function (source) {
8449 return trans.fullSources[source];
8450 }
8451 };
8452 var transRec = fluid.getModelTransactionRec(holder, trans.id);
8453 if (transRec) {
8454 fluid.mergeChangeSources(transRec.sources, globalSources);
8455 trans.sources = transRec.sources;
8456 trans.fullSources = Object.create(transRec.sources);
8457 trans.fullSources[localSource] = true;
8458 }
8459 trans.reset();
8460 fluid.bindRequestChange(trans);
8461 return trans;
8462 };
8463
8464 fluid.bindRequestChange(that);
8465 fluid.bindELMethods(that);
8466 return that;
8467 };
8468
8469 /**
8470 * Calculates the changes between the model values 'value' and
8471 * 'oldValue' and returns an array of change records. The optional
8472 * argument 'changePathPrefix' is prepended to the change path of
8473 * each record (this is useful for generating change records to be
8474 * applied at a non-root path in a model). The returned array of
8475 * change records may be used with fluid.fireChanges().
8476 *
8477 * @param {Any} value - Model value to compare.
8478 * @param {Any} oldValue - Model value to compare.
8479 * @param {String|Array} [changePathPrefix] - [optional] Path prefix to prepend to change record paths, expressed as a string or an array of string segments.
8480 * @return {Array} - An array of change record objects.
8481 */
8482 fluid.modelPairToChanges = function (value, oldValue, changePathPrefix) {
8483 changePathPrefix = changePathPrefix || "";
8484
8485 // Calculate the diff between value and oldValue
8486 var diffOptions = {changes: 0, unchanged: 0, changeMap: {}};
8487 fluid.model.diff(oldValue, value, diffOptions);
8488
8489 var changes = [];
8490
8491 // Recursively process the diff to generate an array of change
8492 // records, stored in 'changes'
8493 fluid.modelPairToChangesImpl(value,
8494 fluid.pathUtil.parseEL(changePathPrefix),
8495 diffOptions.changeMap, [], changes);
8496
8497 return changes;
8498 };
8499
8500 /**
8501 * This function implements recursive processing for
8502 * fluid.modelPairToChanges(). It builds an array of change
8503 * records, accumulated in the 'changes' argument, by walking the
8504 * 'changeMap' structure and 'value' model value. As we walk down
8505 * the model, our path from the root of the model is recorded in
8506 * the 'changeSegs' argument.
8507 *
8508 * @param {Any} value - Model value
8509 * @param {String[]} changePathPrefixSegs - Path prefix to prepend to change record paths, expressed as an array of string segments.
8510 * @param {String|Object} changeMap - The changeMap structure from fluid.model.diff().
8511 * @param {String[]} changeSegs - Our path relative to the model value root, expressed as an array of string segments.
8512 * @param {Object[]} changes - The accumulated change record objects.
8513 */
8514 fluid.modelPairToChangesImpl = function (value, changePathPrefixSegs, changeMap, changeSegs, changes) {
8515 if (changeMap === "ADD") {
8516 // The whole model value is new
8517 changes.push({
8518 path: changePathPrefixSegs,
8519 value: value,
8520 type: "ADD"
8521 });
8522 } else if (changeMap === "DELETE") {
8523 // The whole model value has been deleted
8524 changes.push({
8525 path: changePathPrefixSegs,
8526 value: null,
8527 type: "DELETE"
8528 });
8529 } else if (fluid.isPlainObject(changeMap, true)) {
8530 // Something within the model value has changed
8531 fluid.each(changeMap, function (change, seg) {
8532 var currentChangeSegs = changeSegs.concat([seg]);
8533 if (change === "ADD") {
8534 changes.push({
8535 path: changePathPrefixSegs.concat(currentChangeSegs),
8536 value: fluid.get(value, currentChangeSegs),
8537 type: "ADD"
8538 });
8539 } else if (change === "DELETE") {
8540 changes.push({
8541 path: changePathPrefixSegs.concat(currentChangeSegs),
8542 value: null,
8543 type: "DELETE"
8544 });
8545 } else if (fluid.isPlainObject(change, true)) {
8546 // Recurse down the tree of changes
8547 fluid.modelPairToChangesImpl(value, changePathPrefixSegs,
8548 change, currentChangeSegs, changes);
8549 }
8550 });
8551 }
8552 };
8553
8554})(jQuery, fluid_3_0_0);
8555;
8556/*
8557Copyright The Infusion copyright holders
8558See the AUTHORS.md file at the top-level directory of this distribution and at
8559https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
8560
8561Licensed under the Educational Community License (ECL), Version 2.0 or the New
8562BSD license. You may not use this file except in compliance with one these
8563Licenses.
8564
8565You may obtain a copy of the ECL 2.0 License and BSD License at
8566https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
8567*/
8568
8569var fluid_3_0_0 = fluid_3_0_0 || {};
8570
8571(function ($, fluid) {
8572 "use strict";
8573
8574 /**
8575 * fluid.remoteModelComponent builds on top of fluid.modelComponent with the purpose of providing a buffer between a
8576 * local and remote model that are attempting to stay in sync. For example a local model is being updated by user
8577 * interaction, this is sent back to a remote server, which in turn tries to update the local model. If additional
8578 * user actions occur during the roundtrip, an infinite loop of updates may occur. fluid.remoteModelComponent solves
8579 * this by restricting reading and writing to a single request at a time, waiting for one request to complete
8580 * before operating the next.
8581 *
8582 * For more detailed documentation, including diagrams outlining the fetch and write workflows, see:
8583 * https://docs.fluidproject.org/infusion/development/RemoteModelAPI.html
8584 */
8585 fluid.defaults("fluid.remoteModelComponent", {
8586 gradeNames: ["fluid.modelComponent"],
8587 events: {
8588 afterFetch: null,
8589 onFetch: null,
8590 onFetchError: null,
8591 afterWrite: null,
8592 onWrite: null,
8593 onWriteError: null
8594 },
8595 members: {
8596 pendingRequests: {
8597 write: null,
8598 fetch: null
8599 }
8600 },
8601 model: {
8602 // an implementor must setup a model relay between the buffered local value and the portions of the
8603 // component's model state that should be updated with the remote source.
8604 local: {},
8605 remote: {},
8606 requestInFlight: false
8607 },
8608 modelListeners: {
8609 "requestInFlight": {
8610 listener: "fluid.remoteModelComponent.launchPendingRequest",
8611 args: ["{that}"]
8612 }
8613 },
8614 listeners: {
8615 "afterFetch.updateModel": {
8616 listener: "fluid.remoteModelComponent.updateModelFromFetch",
8617 args: ["{that}", "{arguments}.0"],
8618 priority: "before:unblock"
8619 },
8620 "afterFetch.unblock": {
8621 listener: "fluid.remoteModelComponent.unblockFetchReq",
8622 args: ["{that}"]
8623 },
8624 "onFetchError.unblock": {
8625 listener: "fluid.remoteModelComponent.unblockFetchReq",
8626 args: ["{that}"]
8627 },
8628 "afterWrite.updateRemoteModel": {
8629 listener: "fluid.remoteModelComponent.updateRemoteFromLocal",
8630 args: ["{that}"]
8631 },
8632 "afterWrite.unblock": {
8633 changePath: "requestInFlight",
8634 value: false,
8635 priority: "after:updateRemoteModel"
8636 },
8637 "onWriteError.unblock": {
8638 changePath: "requestInFlight",
8639 value: false
8640 }
8641 },
8642 invokers: {
8643 fetch: {
8644 funcName: "fluid.remoteModelComponent.fetch",
8645 args: ["{that}"]
8646 },
8647 fetchImpl: "fluid.notImplemented",
8648 write: {
8649 funcName: "fluid.remoteModelComponent.write",
8650 args: ["{that}"]
8651 },
8652 writeImpl: "fluid.notImplemented"
8653 }
8654 });
8655
8656 fluid.remoteModelComponent.launchPendingRequest = function (that) {
8657 if (!that.model.requestInFlight) {
8658 if (that.pendingRequests.fetch) {
8659 that.fetch();
8660 } else if (that.pendingRequests.write) {
8661 that.write();
8662 }
8663 }
8664 };
8665
8666 fluid.remoteModelComponent.updateModelFromFetch = function (that, fetchedModel) {
8667 var remoteChanges = fluid.modelPairToChanges(fetchedModel, that.model.remote, "local");
8668 var localChanges = fluid.modelPairToChanges(that.model.local, that.model.remote, "local");
8669 var changes = remoteChanges.concat(localChanges);
8670
8671 // perform model updates in a single transaction
8672 var transaction = that.applier.initiate();
8673 transaction.fireChangeRequest({path: "local", type: "DELETE"}); // clear old local model
8674 transaction.change("local", that.model.remote); // reset local model to the base for applying changes.
8675 transaction.fireChangeRequest({path: "remote", type: "DELETE"}); // clear old remote model
8676 transaction.change("remote", fetchedModel); // update remote model to fetched changes.
8677 fluid.fireChanges(transaction, changes); // apply changes from remote and local onto base model.
8678 transaction.commit(); // submit transaction
8679 };
8680
8681 fluid.remoteModelComponent.updateRemoteFromLocal = function (that) {
8682 // perform model updates in a single transaction
8683 var transaction = that.applier.initiate();
8684 transaction.fireChangeRequest({path: "remote", type: "DELETE"}); // clear old remote model
8685 transaction.change("remote", that.model.local); // update remote model to local changes.
8686 transaction.commit(); // submit transaction
8687 };
8688
8689 /*
8690 * Similar to fluid.promise.makeSequenceStrategy from FluidPromises.js; however, rather than passing along the
8691 * result from one listener in the sequence to the next, the original payload is always passed to each listener.
8692 * In this way, the synthetic events are handled like typical events, but a promise can be resolved/rejected at the
8693 * end of the sequence.
8694 */
8695 fluid.remoteModelComponent.makeSequenceStrategy = function (payload) {
8696 return {
8697 invokeNext: function (that) {
8698 var lisrec = that.sources[that.index];
8699 lisrec.listener = fluid.event.resolveListener(lisrec.listener);
8700 var value = lisrec.listener.apply(null, [payload, that.options]);
8701 return value;
8702 },
8703 resolveResult: function () {
8704 return payload;
8705 }
8706 };
8707 };
8708
8709 fluid.remoteModelComponent.makeSequence = function (listeners, payload, options) {
8710 var sequencer = fluid.promise.makeSequencer(listeners, options, fluid.remoteModelComponent.makeSequenceStrategy(payload));
8711 fluid.promise.resumeSequence(sequencer);
8712 return sequencer;
8713 };
8714
8715 fluid.remoteModelComponent.fireEventSequence = function (event, payload, options) {
8716 var listeners = fluid.makeArray(event.sortedListeners);
8717 var sequence = fluid.remoteModelComponent.makeSequence(listeners, payload, options);
8718 return sequence.promise;
8719 };
8720
8721 /**
8722 * Adds a fetch request and returns a promise.
8723 *
8724 * Only one request can be in flight (processing) at a time. If a write request is in flight, the fetch will be
8725 * queued. If a fetch request is already in queue/flight, the result of that request will be passed along to the
8726 * current fetch request. When a fetch request is in flight , it will trigger the fetchImpl invoker to perform the
8727 * actual request.
8728 *
8729 * Two synthetic events, onFetch and afterFetch, are fired during the processing of a fetch. onFetch can be used to
8730 * perform any necessary actions before running fetchImpl. afterFetch can be used to perform any necessary actions
8731 * after running fetchImpl (e.g. updating the model, unblocking the queue). If promises returned from onFetch, afterFetch, or
8732 * fetchImpl are rejected, the onFetchError event will be fired.
8733 *
8734 * @param {Object} that - The component itself.
8735 * @return {Promise} - A promise that will be resolved with the fetched value or rejected if there is an error.
8736 */
8737 fluid.remoteModelComponent.fetch = function (that) {
8738 var promise = fluid.promise();
8739 var activePromise;
8740
8741 if (that.pendingRequests.fetch) {
8742 activePromise = that.pendingRequests.fetch;
8743 fluid.promise.follow(activePromise, promise);
8744 } else {
8745 activePromise = promise;
8746 that.pendingRequests.fetch = promise;
8747 }
8748
8749 if (!that.model.requestInFlight) {
8750 var onFetchSeqPromise = fluid.remoteModelComponent.fireEventSequence(that.events.onFetch);
8751 onFetchSeqPromise.then(function () {
8752 that.applier.change("requestInFlight", true);
8753 var reqPromise = that.fetchImpl();
8754 reqPromise.then(function (data) {
8755 var afterFetchSeqPromise = fluid.remoteModelComponent.fireEventSequence(that.events.afterFetch, data);
8756 fluid.promise.follow(afterFetchSeqPromise, activePromise);
8757 }, that.events.onFetchError.fire);
8758
8759 }, that.events.onFetchError.fire);
8760 }
8761 return promise;
8762 };
8763
8764 fluid.remoteModelComponent.unblockFetchReq = function (that) {
8765 that.pendingRequests.fetch = null;
8766 that.applier.change("requestInFlight", false);
8767 };
8768
8769 /**
8770 * Adds a write request and returns a promise.
8771 *
8772 * Only one request can be in flight (processing) at a time. If a fetch or write request is in flight, the write will
8773 * be queued. If a write request is already in queue, the result of that request will be passed along to the current
8774 * write request. When a write request is in flight , it will trigger the writeImpl invoker to perform the
8775 * actual request.
8776 *
8777 * Two synthetic events, onWrite and afterWrite, are fired during the processing of a write. onWrite can be used to
8778 * perform any necessary actions before running writeImpl (e.g. performing a fetch). afterWrite can be used to perform any necessary actions
8779 * after running writeImpl (e.g. unblocking the queue, performing a fetch). If promises returned from onWrite, afterWrite, or
8780 * writeImpl are rejected, the onWriteError event will be fired.
8781 *
8782 * @param {Object} that - The component itself.
8783 * @return {Promise} - A promise that will be resolved when the value is written or rejected if there is an error.
8784 */
8785 fluid.remoteModelComponent.write = function (that) {
8786 var promise = fluid.promise();
8787 var activePromise;
8788
8789 if (that.pendingRequests.write) {
8790 activePromise = that.pendingRequests.write;
8791 fluid.promise.follow(that.pendingRequests.write, promise);
8792 } else {
8793 activePromise = promise;
8794 }
8795
8796 if (that.model.requestInFlight) {
8797 that.pendingRequests.write = activePromise;
8798 } else {
8799 var onWriteSeqPromise = fluid.remoteModelComponent.fireEventSequence(that.events.onWrite);
8800 onWriteSeqPromise.then(function () {
8801 that.applier.change("requestInFlight", true);
8802 that.pendingRequests.write = null;
8803
8804 if (fluid.model.diff(that.model.local, that.model.remote)) {
8805 var afterWriteSeqPromise = fluid.remoteModelComponent.fireEventSequence(that.events.afterWrite, that.model.local);
8806 fluid.promise.follow(afterWriteSeqPromise, activePromise);
8807 } else {
8808 var reqPromise = that.writeImpl(that.model.local);
8809 reqPromise.then(function (data) {
8810 var afterWriteSeqPromise = fluid.remoteModelComponent.fireEventSequence(that.events.afterWrite, data);
8811 fluid.promise.follow(afterWriteSeqPromise, activePromise);
8812 }, that.events.onWriteError.fire);;
8813 }
8814 }, that.events.onWriteError.fire);
8815 }
8816
8817 return promise;
8818 };
8819
8820})(jQuery, fluid_3_0_0);
8821;
8822/*
8823Copyright The Infusion copyright holders
8824See the AUTHORS.md file at the top-level directory of this distribution and at
8825https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
8826
8827Licensed under the Educational Community License (ECL), Version 2.0 or the New
8828BSD license. You may not use this file except in compliance with one these
8829Licenses.
8830
8831You may obtain a copy of the ECL 2.0 License and BSD License at
8832https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
8833*/
8834
8835var fluid_3_0_0 = fluid_3_0_0 || {};
8836
8837(function ($, fluid) {
8838 "use strict";
8839
8840 fluid.registerNamespace("fluid.model.transform");
8841
8842 /** Grade definitions for standard transformation function hierarchy **/
8843
8844 fluid.defaults("fluid.transformFunction", {
8845 gradeNames: "fluid.function"
8846 });
8847
8848 // uses standard layout and workflow involving inputPath - an undefined input value
8849 // will short-circuit the evaluation
8850 fluid.defaults("fluid.standardInputTransformFunction", {
8851 gradeNames: "fluid.transformFunction"
8852 });
8853
8854 fluid.defaults("fluid.standardOutputTransformFunction", {
8855 gradeNames: "fluid.transformFunction"
8856 });
8857
8858 // defines a set of options "inputVariables" referring to its inputs, which are converted
8859 // to functions that the transform may explicitly use to demand the input value
8860 fluid.defaults("fluid.multiInputTransformFunction", {
8861 gradeNames: "fluid.transformFunction"
8862 });
8863
8864 // uses the standard layout and workflow involving inputPath and outputPath
8865 fluid.defaults("fluid.standardTransformFunction", {
8866 gradeNames: ["fluid.standardInputTransformFunction", "fluid.standardOutputTransformFunction"]
8867 });
8868
8869 fluid.defaults("fluid.lens", {
8870 gradeNames: "fluid.transformFunction",
8871 invertConfiguration: null
8872 // this function method returns "inverted configuration" rather than actually performing inversion
8873 // TODO: harmonise with strategy used in VideoPlayer_framework.js
8874 });
8875
8876 /***********************************
8877 * Base utilities for transformers *
8878 ***********************************/
8879
8880 // unsupported, NON-API function
8881 fluid.model.transform.pathToRule = function (inputPath) {
8882 return {
8883 transform: {
8884 type: "fluid.transforms.value",
8885 inputPath: inputPath
8886 }
8887 };
8888 };
8889
8890 // unsupported, NON-API function
8891 fluid.model.transform.literalValueToRule = function (input) {
8892 return {
8893 transform: {
8894 type: "fluid.transforms.literalValue",
8895 input: input
8896 }
8897 };
8898 };
8899
8900 /* Accepts two fully escaped paths, either of which may be empty or null */
8901 fluid.model.composePaths = function (prefix, suffix) {
8902 prefix = prefix === 0 ? "0" : prefix || "";
8903 suffix = suffix === 0 ? "0" : suffix || "";
8904 return !prefix ? suffix : (!suffix ? prefix : prefix + "." + suffix);
8905 };
8906
8907 fluid.model.transform.accumulateInputPath = function (inputPath, transformer, paths) {
8908 if (inputPath !== undefined) {
8909 paths.push(fluid.model.composePaths(transformer.inputPrefix, inputPath));
8910 }
8911 };
8912
8913 fluid.model.transform.accumulateStandardInputPath = function (input, transformSpec, transformer, paths) {
8914 fluid.model.transform.getValue(undefined, transformSpec[input], transformer);
8915 fluid.model.transform.accumulateInputPath(transformSpec[input + "Path"], transformer, paths);
8916 };
8917
8918 fluid.model.transform.accumulateMultiInputPaths = function (inputVariables, transformSpec, transformer, paths) {
8919 fluid.each(inputVariables, function (v, k) {
8920 fluid.model.transform.accumulateStandardInputPath(k, transformSpec, transformer, paths);
8921 });
8922 };
8923
8924 fluid.model.transform.getValue = function (inputPath, value, transformer) {
8925 var togo;
8926 if (inputPath !== undefined) { // NB: We may one day want to reverse the crazy jQuery-like convention that "no path means root path"
8927 togo = fluid.get(transformer.source, fluid.model.composePaths(transformer.inputPrefix, inputPath), transformer.resolverGetConfig);
8928 }
8929 if (togo === undefined) {
8930 // FLUID-5867 - actually helpful behaviour here rather than the insane original default of expecting a short-form value document
8931 togo = fluid.isPrimitive(value) ? value :
8932 ("literalValue" in value ? value.literalValue :
8933 (value.transform === undefined ? value : transformer.expand(value)));
8934 }
8935 return togo;
8936 };
8937
8938 // distinguished value which indicates that a transformation rule supplied a
8939 // non-default output path, and so the user should be prevented from making use of it
8940 // in a compound transform definition
8941 fluid.model.transform.NONDEFAULT_OUTPUT_PATH_RETURN = {};
8942
8943 fluid.model.transform.setValue = function (userOutputPath, value, transformer) {
8944 // avoid crosslinking to input object - this might be controlled by a "nocopy" option in future
8945 var toset = fluid.copy(value);
8946 var outputPath = fluid.model.composePaths(transformer.outputPrefix, userOutputPath);
8947 // TODO: custom resolver config here to create non-hash output model structure
8948 if (toset !== undefined) {
8949 transformer.applier.change(outputPath, toset);
8950 }
8951 return userOutputPath ? fluid.model.transform.NONDEFAULT_OUTPUT_PATH_RETURN : toset;
8952 };
8953
8954 /* Resolves the <key> given as parameter by looking up the path <key>Path in the object
8955 * to be transformed. If not present, it resolves the <key> by using the literal value if primitive,
8956 * or expanding otherwise. <def> defines the default value if unableto resolve the key. If no
8957 * default value is given undefined is returned
8958 */
8959 fluid.model.transform.resolveParam = function (transformSpec, transformer, key, def) {
8960 var val = fluid.model.transform.getValue(transformSpec[key + "Path"], transformSpec[key], transformer);
8961 return (val !== undefined) ? val : def;
8962 };
8963
8964 // Compute a "match score" between two pieces of model material, with 0 indicating a complete mismatch, and
8965 // higher values indicating increasingly good matches
8966 fluid.model.transform.matchValue = function (expected, actual, partialMatches) {
8967 var stats = {changes: 0, unchanged: 0, changeMap: {}};
8968 fluid.model.diff(expected, actual, stats);
8969 // i) a pair with 0 matches counts for 0 in all cases
8970 // ii) without "partial match mode" (the default), we simply count matches, with any mismatch giving 0
8971 // iii) with "partial match mode", a "perfect score" in the top 24 bits is
8972 // penalised for each mismatch, with a positive score of matches store in the bottom 24 bits
8973 return stats.unchanged === 0 ? 0
8974 : (partialMatches ? 0xffffff000000 - 0x1000000 * stats.changes + stats.unchanged :
8975 (stats.changes ? 0 : 0xffffff000000 + stats.unchanged));
8976 };
8977
8978 fluid.model.transform.invertPaths = function (transformSpec, transformer) {
8979 // TODO: this will not behave correctly in the face of compound "input" which contains
8980 // further transforms
8981 var oldOutput = fluid.model.composePaths(transformer.outputPrefix, transformSpec.outputPath);
8982 transformSpec.outputPath = fluid.model.composePaths(transformer.inputPrefix, transformSpec.inputPath);
8983 transformSpec.inputPath = oldOutput;
8984 return transformSpec;
8985 };
8986
8987
8988 // TODO: prefixApplier is a transform which is currently unused and untested
8989 fluid.model.transform.prefixApplier = function (transformSpec, transformer) {
8990 if (transformSpec.inputPrefix) {
8991 transformer.inputPrefixOp.push(transformSpec.inputPrefix);
8992 }
8993 if (transformSpec.outputPrefix) {
8994 transformer.outputPrefixOp.push(transformSpec.outputPrefix);
8995 }
8996 transformer.expand(transformSpec.input);
8997 if (transformSpec.inputPrefix) {
8998 transformer.inputPrefixOp.pop();
8999 }
9000 if (transformSpec.outputPrefix) {
9001 transformer.outputPrefixOp.pop();
9002 }
9003 };
9004
9005 fluid.defaults("fluid.model.transform.prefixApplier", {
9006 gradeNames: ["fluid.transformFunction"]
9007 });
9008
9009 // unsupported, NON-API function
9010 fluid.model.makePathStack = function (transform, prefixName) {
9011 var stack = transform[prefixName + "Stack"] = [];
9012 transform[prefixName] = "";
9013 return {
9014 push: function (prefix) {
9015 var newPath = fluid.model.composePaths(transform[prefixName], prefix);
9016 stack.push(transform[prefixName]);
9017 transform[prefixName] = newPath;
9018 },
9019 pop: function () {
9020 transform[prefixName] = stack.pop();
9021 }
9022 };
9023 };
9024
9025 // unsupported, NON-API function
9026 fluid.model.transform.doTransform = function (transformSpec, transformer, transformOpts) {
9027 var expdef = transformOpts.defaults;
9028 var transformFn = fluid.getGlobalValue(transformOpts.typeName);
9029 if (typeof(transformFn) !== "function") {
9030 fluid.fail("Transformation record specifies transformation function with name " +
9031 transformSpec.type + " which is not a function - ", transformFn);
9032 }
9033 if (!fluid.hasGrade(expdef, "fluid.transformFunction")) {
9034 // If no suitable grade is set up, assume that it is intended to be used as a standardTransformFunction
9035 expdef = fluid.defaults("fluid.standardTransformFunction");
9036 }
9037 var transformArgs = [transformSpec, transformer];
9038 if (fluid.hasGrade(expdef, "fluid.multiInputTransformFunction")) {
9039 var inputs = {};
9040 fluid.each(expdef.inputVariables, function (v, k) {
9041 inputs[k] = function () {
9042 var input = fluid.model.transform.getValue(transformSpec[k + "Path"], transformSpec[k], transformer);
9043 // TODO: This is a mess, null might perfectly well be a possible default
9044 // if no match, assign default if one exists (v != null)
9045 input = (input === undefined && v !== null) ? v : input;
9046 return input;
9047 };
9048 });
9049 transformArgs.unshift(inputs);
9050 }
9051 if (fluid.hasGrade(expdef, "fluid.standardInputTransformFunction")) {
9052 if (!("input" in transformSpec) && !("inputPath" in transformSpec)) {
9053 fluid.fail("Error in transform specification. Either \"input\" or \"inputPath\" must be specified for a standardInputTransformFunction: received ", transformSpec);
9054 }
9055 var expanded = fluid.model.transform.getValue(transformSpec.inputPath, transformSpec.input, transformer);
9056
9057 transformArgs.unshift(expanded);
9058 // if the function has no input, the result is considered undefined, and this is returned
9059 if (expanded === undefined) {
9060 return undefined;
9061 }
9062 }
9063 var transformed = transformFn.apply(null, transformArgs);
9064 if (fluid.hasGrade(expdef, "fluid.standardOutputTransformFunction")) {
9065 // "doOutput" flag is currently set nowhere, but could be used in future
9066 var outputPath = transformSpec.outputPath !== undefined ? transformSpec.outputPath : (transformOpts.doOutput ? "" : undefined);
9067 if (outputPath !== undefined && transformed !== undefined) {
9068 //If outputPath is given in the expander we want to:
9069 // (1) output to the document
9070 // (2) return undefined, to ensure that expanders higher up in the hierarchy doesn't attempt to output it again
9071 fluid.model.transform.setValue(transformSpec.outputPath, transformed, transformer);
9072 transformed = undefined;
9073 }
9074 }
9075 return transformed;
9076 };
9077
9078 // OLD PATHUTIL utilities: Rescued from old DataBinding implementation to support obsolete "schema" scheme for transforms - all of this needs to be rethought
9079 var globalAccept = [];
9080
9081 fluid.registerNamespace("fluid.pathUtil");
9082
9083 /* Parses a path segment, following escaping rules, starting from character index i in the supplied path */
9084 fluid.pathUtil.getPathSegment = function (path, i) {
9085 fluid.pathUtil.getPathSegmentImpl(globalAccept, path, i);
9086 return globalAccept[0];
9087 };
9088 /* Returns just the head segment of an EL path */
9089 fluid.pathUtil.getHeadPath = function (path) {
9090 return fluid.pathUtil.getPathSegment(path, 0);
9091 };
9092
9093 /* Returns all of an EL path minus its first segment - if the path consists of just one segment, returns "" */
9094 fluid.pathUtil.getFromHeadPath = function (path) {
9095 var firstdot = fluid.pathUtil.getPathSegmentImpl(null, path, 0);
9096 return firstdot === path.length ? "" : path.substring(firstdot + 1);
9097 };
9098
9099 /** Determines whether a particular EL path matches a given path specification.
9100 * The specification consists of a path with optional wildcard segments represented by "*".
9101 * @param {String} spec - The specification to be matched
9102 * @param {String} path - The path to be tested
9103 * @param {Boolean} exact - Whether the path must exactly match the length of the specification in
9104 * terms of path segments in order to count as match. If exact is falsy, short specifications will
9105 * match all longer paths as if they were padded out with "*" segments
9106 * @return {Array|null} - An array of {String} path segments which matched the specification, or <code>null</code> if there was no match.
9107 */
9108 fluid.pathUtil.matchPath = function (spec, path, exact) {
9109 var togo = [];
9110 while (true) {
9111 if (((path === "") ^ (spec === "")) && exact) {
9112 return null;
9113 }
9114 // FLUID-4625 - symmetry on spec and path is actually undesirable, but this
9115 // quickly avoids at least missed notifications - improved (but slower)
9116 // implementation should explode composite changes
9117 if (!spec || !path) {
9118 break;
9119 }
9120 var spechead = fluid.pathUtil.getHeadPath(spec);
9121 var pathhead = fluid.pathUtil.getHeadPath(path);
9122 // if we fail to match on a specific component, fail.
9123 if (spechead !== "*" && spechead !== pathhead) {
9124 return null;
9125 }
9126 togo.push(pathhead);
9127 spec = fluid.pathUtil.getFromHeadPath(spec);
9128 path = fluid.pathUtil.getFromHeadPath(path);
9129 }
9130 return togo;
9131 };
9132
9133 // unsupported, NON-API function
9134 fluid.model.transform.expandWildcards = function (transformer, source) {
9135 fluid.each(source, function (value, key) {
9136 var q = transformer.queuedTransforms;
9137 transformer.pathOp.push(fluid.pathUtil.escapeSegment(key.toString()));
9138 for (var i = 0; i < q.length; ++i) {
9139 if (fluid.pathUtil.matchPath(q[i].matchPath, transformer.path, true)) {
9140 var esCopy = fluid.copy(q[i].transformSpec);
9141 if (esCopy.inputPath === undefined || fluid.model.transform.hasWildcard(esCopy.inputPath)) {
9142 esCopy.inputPath = "";
9143 }
9144 // TODO: allow some kind of interpolation for output path
9145 // TODO: Also, we now require outputPath to be specified in these cases for output to be produced as well.. Is that something we want to continue with?
9146 transformer.inputPrefixOp.push(transformer.path);
9147 transformer.outputPrefixOp.push(transformer.path);
9148 var transformOpts = fluid.model.transform.lookupType(esCopy.type);
9149 var result = fluid.model.transform.doTransform(esCopy, transformer, transformOpts);
9150 if (result !== undefined) {
9151 fluid.model.transform.setValue(null, result, transformer);
9152 }
9153 transformer.outputPrefixOp.pop();
9154 transformer.inputPrefixOp.pop();
9155 }
9156 }
9157 if (!fluid.isPrimitive(value)) {
9158 fluid.model.transform.expandWildcards(transformer, value);
9159 }
9160 transformer.pathOp.pop();
9161 });
9162 };
9163
9164 // unsupported, NON-API function
9165 fluid.model.transform.hasWildcard = function (path) {
9166 return typeof(path) === "string" && path.indexOf("*") !== -1;
9167 };
9168
9169 // unsupported, NON-API function
9170 fluid.model.transform.maybePushWildcard = function (transformSpec, transformer) {
9171 var hw = fluid.model.transform.hasWildcard;
9172 var matchPath;
9173 if (hw(transformSpec.inputPath)) {
9174 matchPath = fluid.model.composePaths(transformer.inputPrefix, transformSpec.inputPath);
9175 }
9176 else if (hw(transformer.outputPrefix) || hw(transformSpec.outputPath)) {
9177 matchPath = fluid.model.composePaths(transformer.outputPrefix, transformSpec.outputPath);
9178 }
9179
9180 if (matchPath) {
9181 transformer.queuedTransforms.push({transformSpec: transformSpec, outputPrefix: transformer.outputPrefix, inputPrefix: transformer.inputPrefix, matchPath: matchPath});
9182 return true;
9183 }
9184 return false;
9185 };
9186
9187 fluid.model.sortByKeyLength = function (inObject) {
9188 var keys = fluid.keys(inObject);
9189 return keys.sort(fluid.compareStringLength(true));
9190 };
9191
9192 // Three handler functions operating the (currently) three different processing modes
9193 // unsupported, NON-API function
9194 fluid.model.transform.handleTransformStrategy = function (transformSpec, transformer, transformOpts) {
9195 if (fluid.model.transform.maybePushWildcard(transformSpec, transformer)) {
9196 return;
9197 }
9198 else {
9199 return fluid.model.transform.doTransform(transformSpec, transformer, transformOpts);
9200 }
9201 };
9202 // unsupported, NON-API function
9203 fluid.model.transform.handleInvertStrategy = function (transformSpec, transformer, transformOpts) {
9204 transformSpec = fluid.copy(transformSpec);
9205 // if we have a standardTransformFunction we can switch input and output arguments:
9206 if (fluid.hasGrade(transformOpts.defaults, "fluid.standardTransformFunction")) {
9207 transformSpec = fluid.model.transform.invertPaths(transformSpec, transformer);
9208 }
9209 var invertor = transformOpts.defaults && transformOpts.defaults.invertConfiguration;
9210 if (invertor) {
9211 var inverted = fluid.invokeGlobalFunction(invertor, [transformSpec, transformer]);
9212 transformer.inverted.push(inverted);
9213 } else {
9214 transformer.inverted.push(fluid.model.transform.uninvertibleTransform);
9215 }
9216 };
9217
9218 // unsupported, NON-API function
9219 fluid.model.transform.handleCollectStrategy = function (transformSpec, transformer, transformOpts) {
9220 var defaults = transformOpts.defaults;
9221 var standardInput = fluid.hasGrade(defaults, "fluid.standardInputTransformFunction");
9222 var multiInput = fluid.hasGrade(defaults, "fluid.multiInputTransformFunction");
9223
9224 if (standardInput) {
9225 fluid.model.transform.accumulateStandardInputPath("input", transformSpec, transformer, transformer.inputPaths);
9226 }
9227 if (multiInput) {
9228 fluid.model.transform.accumulateMultiInputPaths(defaults.inputVariables, transformSpec, transformer, transformer.inputPaths);
9229 }
9230 var collector = defaults.collectInputPaths;
9231 if (collector) {
9232 var collected = fluid.makeArray(fluid.invokeGlobalFunction(collector, [transformSpec, transformer]));
9233 Array.prototype.push.apply(transformer.inputPaths, collected); // push all elements of collected onto inputPaths
9234 }
9235 };
9236
9237 fluid.model.transform.lookupType = function (typeName, transformSpec) {
9238 if (!typeName) {
9239 fluid.fail("Transformation record is missing a type name: ", transformSpec);
9240 }
9241 if (typeName.indexOf(".") === -1) {
9242 typeName = "fluid.transforms." + typeName;
9243 }
9244 var defaults = fluid.defaults(typeName);
9245 return { defaults: defaults, typeName: typeName};
9246 };
9247
9248 // unsupported, NON-API function
9249 fluid.model.transform.processRule = function (rule, transformer) {
9250 if (typeof(rule) === "string") {
9251 rule = fluid.model.transform.pathToRule(rule);
9252 }
9253 // special dispensation to allow "literalValue" to escape any value
9254 else if (rule.literalValue !== undefined) {
9255 rule = fluid.model.transform.literalValueToRule(rule.literalValue);
9256 }
9257 var togo;
9258 if (rule.transform) {
9259 var transformSpec, transformOpts;
9260 if (fluid.isArrayable(rule.transform)) {
9261 // if the transform holds an array, each transformer within that is responsible for its own output
9262 var transforms = rule.transform;
9263 togo = undefined;
9264 for (var i = 0; i < transforms.length; ++i) {
9265 transformSpec = transforms[i];
9266 transformOpts = fluid.model.transform.lookupType(transformSpec.type);
9267 transformer.transformHandler(transformSpec, transformer, transformOpts);
9268 }
9269 } else {
9270 // else we just have a normal single transform which will return 'undefined' as a flag to defeat cascading output
9271 transformSpec = rule.transform;
9272 transformOpts = fluid.model.transform.lookupType(transformSpec.type);
9273 togo = transformer.transformHandler(transformSpec, transformer, transformOpts);
9274 }
9275 }
9276 // if rule is an array, save path for later use in schema strategy on final applier (so output will be interpreted as array)
9277 if (fluid.isArrayable(rule)) {
9278 transformer.collectedFlatSchemaOpts = transformer.collectedFlatSchemaOpts || {};
9279 transformer.collectedFlatSchemaOpts[transformer.outputPrefix] = "array";
9280 }
9281 fluid.each(rule, function (value, key) {
9282 if (key !== "transform") {
9283 transformer.outputPrefixOp.push(key);
9284 var togo = transformer.expand(value, transformer);
9285 // Value expanders and arrays as rules implicitly output, unless they have nothing (undefined) to output
9286 if (togo !== undefined) {
9287 fluid.model.transform.setValue(null, togo, transformer);
9288 // ensure that expanders further up does not try to output this value as well.
9289 togo = undefined;
9290 }
9291 transformer.outputPrefixOp.pop();
9292 }
9293 });
9294 return togo;
9295 };
9296
9297 // unsupported, NON-API function
9298 // 3rd arg is disused by the framework and always defaults to fluid.model.transform.processRule
9299 fluid.model.transform.makeStrategy = function (transformer, handleFn, transformFn) {
9300 transformFn = transformFn || fluid.model.transform.processRule;
9301 transformer.expand = function (rules) {
9302 return transformFn(rules, transformer);
9303 };
9304 transformer.outputPrefixOp = fluid.model.makePathStack(transformer, "outputPrefix");
9305 transformer.inputPrefixOp = fluid.model.makePathStack(transformer, "inputPrefix");
9306 transformer.transformHandler = handleFn;
9307 };
9308
9309 /* A special, empty, transform document representing the inversion of a transformation which does not not have an inverse
9310 */
9311 fluid.model.transform.uninvertibleTransform = Object.freeze({});
9312
9313 /** Accepts a transformation document, and returns its inverse if all of its constituent transforms have inverses
9314 * defined via their individual invertConfiguration functions, or else `fluid.model.transform.uninvertibleTransform`
9315 * if any of them do not.
9316 * Note that this algorithm will give faulty results in many cases of compound transformation documents.
9317 * @param {Transform} rules - The model transformation document to be inverted
9318 * @return {Transform} The inverse transformation document if it can be computed easily, or
9319 * `fluid.model.transform.uninvertibleTransform` if it is clear that it cannot.
9320 */
9321 fluid.model.transform.invertConfiguration = function (rules) {
9322 var transformer = {
9323 inverted: []
9324 };
9325 fluid.model.transform.makeStrategy(transformer, fluid.model.transform.handleInvertStrategy);
9326 transformer.expand(rules);
9327 var invertible = transformer.inverted.indexOf(fluid.model.transform.uninvertibleTransform) === -1;
9328 return invertible ? {
9329 transform: transformer.inverted
9330 } : fluid.model.transform.uninvertibleTransform;
9331 };
9332
9333 /** Compute the paths which will be read from the input document of the supplied transformation if it were operated.
9334 *
9335 * @param {Transform} rules - The transformation for which the input paths are to be computed.
9336 * @return {Array} - An array of paths which will be read by the document.
9337 */
9338 fluid.model.transform.collectInputPaths = function (rules) {
9339 var transformer = {
9340 inputPaths: []
9341 };
9342 fluid.model.transform.makeStrategy(transformer, fluid.model.transform.handleCollectStrategy);
9343 transformer.expand(rules);
9344 // Deduplicate input paths
9345 var inputPathHash = fluid.arrayToHash(transformer.inputPaths);
9346 return Object.keys(inputPathHash);
9347 };
9348
9349 // unsupported, NON-API function
9350 fluid.model.transform.flatSchemaStrategy = function (flatSchema, getConfig) {
9351 var keys = fluid.model.sortByKeyLength(flatSchema);
9352 return function (root, segment, index, segs) {
9353 var path = getConfig.parser.compose.apply(null, segs.slice(0, index));
9354 // TODO: clearly this implementation could be much more efficient
9355 for (var i = 0; i < keys.length; ++i) {
9356 var key = keys[i];
9357 if (fluid.pathUtil.matchPath(key, path, true) !== null) {
9358 return flatSchema[key];
9359 }
9360 }
9361 };
9362 };
9363
9364 // unsupported, NON-API function
9365 fluid.model.transform.defaultSchemaValue = function (schemaValue) {
9366 var type = fluid.isPrimitive(schemaValue) ? schemaValue : schemaValue.type;
9367 return type === "array" ? [] : {};
9368 };
9369
9370 // unsupported, NON-API function
9371 fluid.model.transform.isomorphicSchemaStrategy = function (source, getConfig) {
9372 return function (root, segment, index, segs) {
9373 var existing = fluid.get(source, segs.slice(0, index), getConfig);
9374 return fluid.isArrayable(existing) ? "array" : "object";
9375 };
9376 };
9377
9378 // unsupported, NON-API function
9379 fluid.model.transform.decodeStrategy = function (source, options, getConfig) {
9380 if (options.isomorphic) {
9381 return fluid.model.transform.isomorphicSchemaStrategy(source, getConfig);
9382 }
9383 else if (options.flatSchema) {
9384 return fluid.model.transform.flatSchemaStrategy(options.flatSchema, getConfig);
9385 }
9386 };
9387
9388 // unsupported, NON-API function
9389 fluid.model.transform.schemaToCreatorStrategy = function (strategy) {
9390 return function (root, segment, index, segs) {
9391 if (root[segment] === undefined) {
9392 var schemaValue = strategy(root, segment, index, segs);
9393 root[segment] = fluid.model.transform.defaultSchemaValue(schemaValue);
9394 return root[segment];
9395 }
9396 };
9397 };
9398
9399 /* Transforms a model by a sequence of rules. Parameters as for fluid.model.transform,
9400 * only with an array accepted for "rules"
9401 */
9402 fluid.model.transform.sequence = function (source, rules, options) {
9403 for (var i = 0; i < rules.length; ++i) {
9404 source = fluid.model.transform(source, rules[i], options);
9405 }
9406 return source;
9407 };
9408
9409 fluid.model.compareByPathLength = function (changea, changeb) {
9410 var pdiff = changea.path.length - changeb.path.length;
9411 return pdiff === 0 ? changea.sequence - changeb.sequence : pdiff;
9412 };
9413
9414 /* Fires an accumulated set of change requests in increasing order of target pathlength */
9415 fluid.model.fireSortedChanges = function (changes, applier) {
9416 changes.sort(fluid.model.compareByPathLength);
9417 fluid.fireChanges(applier, changes);
9418 };
9419
9420 /**
9421 * Transforms a model based on a specified expansion rules objects.
9422 * Rules objects take the form of:
9423 * {
9424 * "target.path": "value.el.path" || {
9425 * transform: {
9426 * type: "transform.function.path",
9427 * ...
9428 * }
9429 * }
9430 * }
9431 *
9432 * @param {Object} source - the model to transform
9433 * @param {Object} rules - a rules object containing instructions on how to transform the model
9434 * @param {Object} options - a set of rules governing the transformations. At present this may contain
9435 * the values <code>isomorphic: true</code> indicating that the output model is to be governed by the
9436 * same schema found in the input model, or <code>flatSchema</code> holding a flat schema object which
9437 * consists of a hash of EL path specifications with wildcards, to the values "array"/"object" defining
9438 * the schema to be used to construct missing trunk values.
9439 * @return {Any} The transformed model.
9440 */
9441 fluid.model.transformWithRules = function (source, rules, options) {
9442 options = options || {};
9443
9444 var getConfig = fluid.model.escapedGetConfig;
9445 var setConfig = fluid.model.escapedSetConfig;
9446
9447 var schemaStrategy = fluid.model.transform.decodeStrategy(source, options, getConfig);
9448
9449 var transformer = {
9450 source: source,
9451 target: {
9452 // TODO: This should default to undefined to allow return of primitives, etc.
9453 model: schemaStrategy ? fluid.model.transform.defaultSchemaValue(schemaStrategy(null, "", 0, [""])) : {}
9454 },
9455 resolverGetConfig: getConfig,
9456 resolverSetConfig: setConfig,
9457 collectedFlatSchemaOpts: undefined, // to hold options for flat schema collected during transforms
9458 queuedChanges: [],
9459 queuedTransforms: [] // TODO: This is used only by wildcard applier - explain its operation
9460 };
9461 fluid.model.transform.makeStrategy(transformer, fluid.model.transform.handleTransformStrategy);
9462 transformer.applier = {
9463 fireChangeRequest: function (changeRequest) {
9464 changeRequest.sequence = transformer.queuedChanges.length;
9465 transformer.queuedChanges.push(changeRequest);
9466 }
9467 };
9468 fluid.bindRequestChange(transformer.applier);
9469
9470 transformer.expand(rules);
9471
9472 var rootSetConfig = fluid.copy(setConfig);
9473 // Modify schemaStrategy if we collected flat schema options for the setConfig of finalApplier
9474 if (transformer.collectedFlatSchemaOpts !== undefined) {
9475 $.extend(transformer.collectedFlatSchemaOpts, options.flatSchema);
9476 schemaStrategy = fluid.model.transform.flatSchemaStrategy(transformer.collectedFlatSchemaOpts, getConfig);
9477 }
9478 rootSetConfig.strategies = [fluid.model.defaultFetchStrategy, schemaStrategy ? fluid.model.transform.schemaToCreatorStrategy(schemaStrategy)
9479 : fluid.model.defaultCreatorStrategy];
9480 transformer.finalApplier = options.finalApplier || fluid.makeHolderChangeApplier(transformer.target, {resolverSetConfig: rootSetConfig});
9481
9482 if (transformer.queuedTransforms.length > 0) {
9483 transformer.typeStack = [];
9484 transformer.pathOp = fluid.model.makePathStack(transformer, "path");
9485 fluid.model.transform.expandWildcards(transformer, source);
9486 }
9487 fluid.model.fireSortedChanges(transformer.queuedChanges, transformer.finalApplier);
9488 return transformer.target.model;
9489 };
9490
9491 $.extend(fluid.model.transformWithRules, fluid.model.transform);
9492 fluid.model.transform = fluid.model.transformWithRules;
9493
9494 /* Utility function to produce a standard options transformation record for a single set of rules */
9495 fluid.transformOne = function (rules) {
9496 return {
9497 transformOptions: {
9498 transformer: "fluid.model.transformWithRules",
9499 config: rules
9500 }
9501 };
9502 };
9503
9504 /* Utility function to produce a standard options transformation record for multiple rules to be applied in sequence */
9505 fluid.transformMany = function (rules) {
9506 return {
9507 transformOptions: {
9508 transformer: "fluid.model.transform.sequence",
9509 config: rules
9510 }
9511 };
9512 };
9513
9514})(jQuery, fluid_3_0_0);
9515;
9516/*
9517Copyright The Infusion copyright holders
9518See the AUTHORS.md file at the top-level directory of this distribution and at
9519https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
9520
9521Licensed under the Educational Community License (ECL), Version 2.0 or the New
9522BSD license. You may not use this file except in compliance with one these
9523Licenses.
9524
9525You may obtain a copy of the ECL 2.0 License and BSD License at
9526https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
9527*/
9528
9529var fluid_3_0_0 = fluid_3_0_0 || {};
9530
9531(function ($, fluid) {
9532 "use strict";
9533
9534 fluid.registerNamespace("fluid.model.transform");
9535 fluid.registerNamespace("fluid.transforms");
9536
9537 /**********************************
9538 * Standard transformer functions *
9539 **********************************/
9540
9541 fluid.defaults("fluid.transforms.value", {
9542 gradeNames: "fluid.standardTransformFunction",
9543 invertConfiguration: "fluid.identity"
9544 });
9545
9546 fluid.transforms.value = fluid.identity;
9547
9548 // Export the use of the "value" transform under the "identity" name for FLUID-5293
9549 fluid.transforms.identity = fluid.transforms.value;
9550 fluid.defaults("fluid.transforms.identity", {
9551 gradeNames: "fluid.transforms.value"
9552 });
9553
9554 // A helpful utility function to be used when a transform's inverse is the identity
9555 fluid.transforms.invertToIdentity = function (transformSpec) {
9556 transformSpec.type = "fluid.transforms.identity";
9557 return transformSpec;
9558 };
9559
9560 fluid.defaults("fluid.transforms.literalValue", {
9561 gradeNames: "fluid.standardOutputTransformFunction"
9562 });
9563
9564 fluid.transforms.literalValue = function (transformSpec) {
9565 return transformSpec.input;
9566 };
9567
9568 fluid.defaults("fluid.transforms.stringToNumber", {
9569 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
9570 invertConfiguration: "fluid.transforms.stringToNumber.invert"
9571 });
9572
9573 fluid.transforms.stringToNumber = function (value) {
9574 var newValue = Number(value);
9575 return isNaN(newValue) ? undefined : newValue;
9576 };
9577
9578 fluid.transforms.stringToNumber.invert = function (transformSpec) {
9579 transformSpec.type = "fluid.transforms.numberToString";
9580 return transformSpec;
9581 };
9582
9583 fluid.defaults("fluid.transforms.numberToString", {
9584 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
9585 invertConfiguration: "fluid.transforms.numberToString.invert"
9586 });
9587
9588 fluid.transforms.numberToString = function (value, transformSpec) {
9589 if (typeof value === "number") {
9590 if (typeof transformSpec.scale === "number" && !isNaN(transformSpec.scale)) {
9591 var rounded = fluid.roundToDecimal(value, transformSpec.scale, transformSpec.method);
9592 return rounded.toString();
9593 } else {
9594 return value.toString();
9595 }
9596 }
9597 };
9598
9599 fluid.transforms.numberToString.invert = function (transformSpec) {
9600 transformSpec.type = "fluid.transforms.stringToNumber";
9601 return transformSpec;
9602 };
9603
9604 fluid.defaults("fluid.transforms.count", {
9605 gradeNames: "fluid.standardTransformFunction"
9606 });
9607
9608 fluid.transforms.count = function (value) {
9609 return fluid.makeArray(value).length;
9610 };
9611
9612
9613 fluid.defaults("fluid.transforms.round", {
9614 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
9615 invertConfiguration: "fluid.transforms.invertToIdentity"
9616 });
9617
9618 fluid.transforms.round = function (value, transformSpec) {
9619 // validation of scale is handled by roundToDecimal
9620 return fluid.roundToDecimal(value, transformSpec.scale, transformSpec.method);
9621 };
9622
9623 fluid.defaults("fluid.transforms.delete", {
9624 gradeNames: "fluid.transformFunction"
9625 });
9626
9627 fluid.transforms["delete"] = function (transformSpec, transformer) {
9628 var outputPath = fluid.model.composePaths(transformer.outputPrefix, transformSpec.outputPath);
9629 transformer.applier.change(outputPath, null, "DELETE");
9630 };
9631
9632
9633 fluid.defaults("fluid.transforms.firstValue", {
9634 gradeNames: "fluid.standardOutputTransformFunction"
9635 });
9636
9637 fluid.transforms.firstValue = function (transformSpec, transformer) {
9638 if (!transformSpec.values || !transformSpec.values.length) {
9639 fluid.fail("firstValue transformer requires an array of values at path named \"values\", supplied", transformSpec);
9640 }
9641 for (var i = 0; i < transformSpec.values.length; i++) {
9642 var value = transformSpec.values[i];
9643 // TODO: problem here - all of these transforms will have their side-effects (setValue) even if only one is chosen
9644 var expanded = transformer.expand(value);
9645 if (expanded !== undefined) {
9646 return expanded;
9647 }
9648 }
9649 };
9650
9651 fluid.defaults("fluid.transforms.linearScale", {
9652 gradeNames: ["fluid.multiInputTransformFunction",
9653 "fluid.standardTransformFunction",
9654 "fluid.lens" ],
9655 invertConfiguration: "fluid.transforms.linearScale.invert",
9656 inputVariables: {
9657 factor: 1,
9658 offset: 0
9659 }
9660 });
9661
9662 /* simple linear transformation */
9663 fluid.transforms.linearScale = function (input, extraInputs) {
9664 var factor = extraInputs.factor();
9665 var offset = extraInputs.offset();
9666
9667 if (typeof(input) !== "number" || typeof(factor) !== "number" || typeof(offset) !== "number") {
9668 return undefined;
9669 }
9670 return input * factor + offset;
9671 };
9672
9673 /* TODO: This inversion doesn't work if the value and factors are given as paths in the source model */
9674 fluid.transforms.linearScale.invert = function (transformSpec) {
9675 // delete the factor and offset paths if present
9676 delete transformSpec.factorPath;
9677 delete transformSpec.offsetPath;
9678
9679 if (transformSpec.factor !== undefined) {
9680 transformSpec.factor = (transformSpec.factor === 0) ? 0 : 1 / transformSpec.factor;
9681 }
9682 if (transformSpec.offset !== undefined) {
9683 transformSpec.offset = -transformSpec.offset * (transformSpec.factor !== undefined ? transformSpec.factor : 1);
9684 }
9685 return transformSpec;
9686 };
9687
9688 fluid.defaults("fluid.transforms.binaryOp", {
9689 gradeNames: [ "fluid.multiInputTransformFunction", "fluid.standardOutputTransformFunction" ],
9690 inputVariables: {
9691 left: null,
9692 right: null
9693 }
9694 });
9695
9696 fluid.transforms.binaryLookup = {
9697 "===": function (a, b) { return fluid.model.isSameValue(a, b); },
9698 "!==": function (a, b) { return !fluid.model.isSameValue(a, b); },
9699 "<=": function (a, b) { return a <= b; },
9700 "<": function (a, b) { return a < b; },
9701 ">=": function (a, b) { return a >= b; },
9702 ">": function (a, b) { return a > b; },
9703 "+": function (a, b) { return a + b; },
9704 "-": function (a, b) { return a - b; },
9705 "*": function (a, b) { return a * b; },
9706 "/": function (a, b) { return a / b; },
9707 "%": function (a, b) { return a % b; },
9708 "&&": function (a, b) { return a && b; },
9709 "||": function (a, b) { return a || b; }
9710 };
9711
9712 fluid.transforms.binaryOp = function (inputs, transformSpec, transformer) {
9713 var left = inputs.left();
9714 var right = inputs.right();
9715
9716 var operator = fluid.model.transform.getValue(undefined, transformSpec.operator, transformer);
9717
9718 var fun = fluid.transforms.binaryLookup[operator];
9719 return (fun === undefined || left === undefined || right === undefined) ?
9720 undefined : fun(left, right);
9721 };
9722
9723 fluid.defaults("fluid.transforms.condition", {
9724 gradeNames: [ "fluid.multiInputTransformFunction", "fluid.standardOutputTransformFunction" ],
9725 inputVariables: {
9726 "true": null,
9727 "false": null,
9728 "condition": null
9729 }
9730 });
9731
9732 fluid.transforms.condition = function (inputs) {
9733 var condition = inputs.condition();
9734 if (condition === null) {
9735 return undefined;
9736 }
9737
9738 return inputs[condition ? "true" : "false"]();
9739 };
9740
9741 fluid.defaults("fluid.transforms.valueMapper", {
9742 gradeNames: ["fluid.lens"],
9743 invertConfiguration: "fluid.transforms.valueMapper.invert",
9744 collectInputPaths: "fluid.transforms.valueMapper.collect"
9745 });
9746
9747 /* unsupported, NON-API function
9748 * sorts by the object's 'matchValue' property, where higher is better.
9749 * Tiebreaking is done via the `index` property, where a lower index takes priority
9750 */
9751 fluid.model.transform.compareMatches = function (speca, specb) {
9752 var matchDiff = specb.matchValue - speca.matchValue;
9753 return matchDiff === 0 ? speca.index - specb.index : matchDiff; // tiebreak using 'index'
9754 };
9755
9756 fluid.transforms.valueMapper = function (transformSpec, transformer) {
9757 if (!transformSpec.match) {
9758 fluid.fail("valueMapper requires an array or hash of matches at path named \"match\", supplied ", transformSpec);
9759 }
9760 var value = fluid.model.transform.getValue(transformSpec.defaultInputPath, transformSpec.defaultInput, transformer);
9761
9762 var matchedEntry = (fluid.isArrayable(transformSpec.match)) ? // long form with array of records?
9763 fluid.transforms.valueMapper.longFormMatch(value, transformSpec, transformer) :
9764 transformSpec.match[value];
9765
9766 if (matchedEntry === undefined) { // if no matches found, default to noMatch
9767 matchedEntry = transformSpec.noMatch;
9768 }
9769
9770 if (matchedEntry === undefined) { // if there was no noMatch directive, return undefined
9771 return;
9772 }
9773
9774 var outputPath = matchedEntry.outputPath === undefined ? transformSpec.defaultOutputPath : matchedEntry.outputPath;
9775 transformer.outputPrefixOp.push(outputPath);
9776
9777 var outputValue;
9778 if (fluid.isPrimitive(matchedEntry)) {
9779 outputValue = matchedEntry;
9780 } else if (matchedEntry.outputUndefinedValue) { // if outputUndefinedValue is set, outputValue `undefined`
9781 outputValue = undefined;
9782 } else {
9783 // get value from outputValue. If none is found set the outputValue to be that of defaultOutputValue (or undefined)
9784 outputValue = fluid.model.transform.resolveParam(matchedEntry, transformer, "outputValue", undefined);
9785 outputValue = (outputValue === undefined) ? transformSpec.defaultOutputValue : outputValue;
9786 }
9787 // output if we have a path and something to output
9788 if (typeof(outputPath) === "string" && outputValue !== undefined) {
9789 fluid.model.transform.setValue(undefined, outputValue, transformer, transformSpec.merge);
9790 outputValue = undefined; // make sure we don't also return value
9791 }
9792 transformer.outputPrefixOp.pop();
9793 return outputValue;
9794 };
9795
9796 // unsupported, NON-API function
9797 fluid.transforms.valueMapper.longFormMatch = function (valueFromDefaultPath, transformSpec, transformer) {
9798 var o = transformSpec.match;
9799 if (o.length === 0) {
9800 fluid.fail("valueMapper supplied empty list of matches: ", transformSpec);
9801 }
9802 var matchPower = [];
9803 for (var i = 0; i < o.length; ++i) {
9804 var option = o[i];
9805 var value = option.inputPath ?
9806 fluid.model.transform.getValue(option.inputPath, undefined, transformer) : valueFromDefaultPath;
9807
9808 var matchValue = fluid.model.transform.matchValue(option.inputValue, value, option.partialMatches);
9809 matchPower[i] = {index: i, matchValue: matchValue};
9810 }
9811 matchPower.sort(fluid.model.transform.compareMatches);
9812 return matchPower[0].matchValue <= 0 ? undefined : o[matchPower[0].index];
9813 };
9814
9815 fluid.transforms.valueMapper.invert = function (transformSpec, transformer) {
9816 var match = [];
9817 var togo = {
9818 type: "fluid.transforms.valueMapper",
9819 match: match
9820 };
9821 var isArray = fluid.isArrayable(transformSpec.match);
9822
9823 togo.defaultInputPath = fluid.model.composePaths(transformer.outputPrefix, transformSpec.defaultOutputPath);
9824 togo.defaultOutputPath = fluid.model.composePaths(transformer.inputPrefix, transformSpec.defaultInputPath);
9825
9826 var def = fluid.firstDefined;
9827 fluid.each(transformSpec.match, function (option, key) {
9828 if (option.outputUndefinedValue === true) {
9829 return; // don't attempt to invert undefined output value entries
9830 }
9831 var outOption = {};
9832 var origInputValue = def(isArray ? option.inputValue : key, transformSpec.defaultInputValue);
9833 if (origInputValue === undefined) {
9834 fluid.fail("Failure inverting configuration for valueMapper - inputValue could not be resolved for record " + key + ": ", transformSpec);
9835 }
9836 outOption.outputValue = origInputValue;
9837 outOption.inputValue = !isArray && fluid.isPrimitive(option) ?
9838 option : def(option.outputValue, transformSpec.defaultOutputValue);
9839
9840 if (option.outputPath) {
9841 outOption.inputPath = fluid.model.composePaths(transformer.outputPrefix, def(option.outputPath, transformSpec.outputPath));
9842 }
9843 if (option.inputPath) {
9844 outOption.outputPath = fluid.model.composePaths(transformer.inputPrefix, def(option.inputPath, transformSpec.inputPath));
9845 }
9846 match.push(outOption);
9847 });
9848 return togo;
9849 };
9850
9851 fluid.transforms.valueMapper.collect = function (transformSpec, transformer) {
9852 var togo = [];
9853 fluid.model.transform.accumulateStandardInputPath("defaultInput", transformSpec, transformer, togo);
9854 fluid.each(transformSpec.match, function (option) {
9855 fluid.model.transform.accumulateInputPath(option.inputPath, transformer, togo);
9856 });
9857 return togo;
9858 };
9859
9860 /* -------- arrayToSetMembership and setMembershipToArray ---------------- */
9861
9862 fluid.defaults("fluid.transforms.arrayToSetMembership", {
9863 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
9864 invertConfiguration: "fluid.transforms.arrayToSetMembership.invert"
9865 });
9866
9867
9868 fluid.transforms.arrayToSetMembership = function (value, transformSpec, transformer) {
9869 var output = {};
9870 var options = transformSpec.options;
9871
9872 if (!value || !fluid.isArrayable(value)) {
9873 fluid.fail("arrayToSetMembership didn't find array at inputPath nor passed as value.", transformSpec);
9874 }
9875 if (!options) {
9876 fluid.fail("arrayToSetMembership requires an options block set");
9877 }
9878
9879 if (transformSpec.presentValue === undefined) {
9880 transformSpec.presentValue = true;
9881 }
9882
9883 if (transformSpec.missingValue === undefined) {
9884 transformSpec.missingValue = false;
9885 }
9886
9887 fluid.each(options, function (outPath, key) {
9888 // write to output object the value <presentValue> or <missingValue> depending on whether key is found in user input
9889 var outVal = (value.indexOf(key) !== -1) ? transformSpec.presentValue : transformSpec.missingValue;
9890 fluid.set(output, outPath, outVal, transformer.resolverSetConfig);
9891 });
9892 return output;
9893 };
9894
9895 /*
9896 * NON-API function; Copies the entire transformSpec with the following modifications:
9897 * * A new type is set (from argument)
9898 * * each [key]=value entry in the options is swapped to be: [value]=key
9899 */
9900 fluid.transforms.arrayToSetMembership.invertWithType = function (transformSpec, transformer, newType) {
9901 transformSpec.type = newType;
9902 var newOptions = {};
9903 fluid.each(transformSpec.options, function (path, oldKey) {
9904 newOptions[path] = oldKey;
9905 });
9906 transformSpec.options = newOptions;
9907 return transformSpec;
9908 };
9909
9910 fluid.transforms.arrayToSetMembership.invert = function (transformSpec, transformer) {
9911 return fluid.transforms.arrayToSetMembership.invertWithType(transformSpec, transformer,
9912 "fluid.transforms.setMembershipToArray");
9913 };
9914
9915 fluid.defaults("fluid.transforms.setMembershipToArray", {
9916 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
9917 invertConfiguration: "fluid.transforms.setMembershipToArray.invert"
9918 });
9919
9920 fluid.transforms.setMembershipToArray = function (input, transformSpec, transformer) {
9921 var options = transformSpec.options;
9922
9923 if (!options) {
9924 fluid.fail("setMembershipToArray requires an options block specified");
9925 }
9926
9927 if (transformSpec.presentValue === undefined) {
9928 transformSpec.presentValue = true;
9929 }
9930
9931 if (transformSpec.missingValue === undefined) {
9932 transformSpec.missingValue = false;
9933 }
9934
9935 var outputArr = [];
9936 fluid.each(options, function (outputVal, key) {
9937 var value = fluid.get(input, key, transformer.resolverGetConfig);
9938 if (value === transformSpec.presentValue) {
9939 outputArr.push(outputVal);
9940 }
9941 });
9942 return outputArr;
9943 };
9944
9945 fluid.transforms.setMembershipToArray.invert = function (transformSpec, transformer) {
9946 return fluid.transforms.arrayToSetMembership.invertWithType(transformSpec, transformer,
9947 "fluid.transforms.arrayToSetMembership");
9948 };
9949
9950 /* -------- deindexIntoArrayByKey and indexArrayByKey -------------------- */
9951
9952 /*
9953 * Transforms the given array to an object.
9954 * Uses the transformSpec.options.key values from each object within the array as new keys.
9955 *
9956 * For example, with transformSpec.key = "name" and an input object like this:
9957 *
9958 * {
9959 * b: [
9960 * { name: b1, v: v1 },
9961 * { name: b2, v: v2 }
9962 * ]
9963 * }
9964 *
9965 * The output will be:
9966 * {
9967 * b: {
9968 * b1: {
9969 * v: v1
9970 * }
9971 * },
9972 * {
9973 * b2: {
9974 * v: v2
9975 * }
9976 * }
9977 * }
9978 */
9979 fluid.model.transform.applyPaths = function (operation, pathOp, paths) {
9980 for (var i = 0; i < paths.length; ++i) {
9981 if (operation === "push") {
9982 pathOp.push(paths[i]);
9983 } else {
9984 pathOp.pop();
9985 }
9986 }
9987 };
9988
9989 fluid.model.transform.expandInnerValues = function (inputPath, outputPath, transformer, innerValues) {
9990 var inputPrefixOp = transformer.inputPrefixOp;
9991 var outputPrefixOp = transformer.outputPrefixOp;
9992 var apply = fluid.model.transform.applyPaths;
9993
9994 apply("push", inputPrefixOp, inputPath);
9995 apply("push", outputPrefixOp, outputPath);
9996 var expanded = {};
9997 fluid.each(innerValues, function (innerValue) {
9998 var expandedInner = transformer.expand(innerValue);
9999 if (!fluid.isPrimitive(expandedInner)) {
10000 $.extend(true, expanded, expandedInner);
10001 } else {
10002 expanded = expandedInner;
10003 }
10004 });
10005 apply("pop", outputPrefixOp, outputPath);
10006 apply("pop", inputPrefixOp, inputPath);
10007
10008 return expanded;
10009 };
10010
10011
10012 fluid.defaults("fluid.transforms.indexArrayByKey", {
10013 gradeNames: ["fluid.standardTransformFunction", "fluid.lens" ],
10014 invertConfiguration: "fluid.transforms.indexArrayByKey.invert"
10015 });
10016
10017 /* Transforms an array of objects into an object of objects, by indexing using the option "key" which must be supplied within the transform specification.
10018 * The key of each element will be taken from the value held in each each original object's member derived from the option value in "key" - this member should
10019 * exist in each array element. The member with name agreeing with "key" and its value will be removed from each original object before inserting into the returned
10020 * object.
10021 * For example,
10022 * <code>fluid.transforms.indexArrayByKey([{k: "e1", b: 1, c: 2}, {k: "e2", b: 2: c: 3}], {key: "k"})</code> will output the object
10023 * <code>{e1: {b: 1, c: 2}, e2: {b: 2: c, 3}</code>
10024 * Note: This transform frequently arises in the context of data which arose in XML form, which often represents "morally indexed" data in repeating array-like
10025 * constructs where the indexing key is held, for example, in an attribute.
10026 */
10027 fluid.transforms.indexArrayByKey = function (arr, transformSpec, transformer) {
10028 if (transformSpec.key === undefined) {
10029 fluid.fail("indexArrayByKey requires a 'key' option.", transformSpec);
10030 }
10031 if (!fluid.isArrayable(arr)) {
10032 fluid.fail("indexArrayByKey didn't find array at inputPath.", transformSpec);
10033 }
10034 var newHash = {};
10035 var pivot = transformSpec.key;
10036
10037 fluid.each(arr, function (v, k) {
10038 // check that we have a pivot entry in the object and it's a valid type:
10039 var newKey = v[pivot];
10040 var keyType = typeof(newKey);
10041 if (keyType !== "string" && keyType !== "boolean" && keyType !== "number") {
10042 fluid.fail("indexArrayByKey encountered untransformable array due to missing or invalid key", v);
10043 }
10044 // use the value of the key element as key and use the remaining content as value
10045 var content = fluid.copy(v);
10046 delete content[pivot];
10047 // fix sub Arrays if needed:
10048 if (transformSpec.innerValue) {
10049 content = fluid.model.transform.expandInnerValues([transformer.inputPrefix, transformSpec.inputPath, k.toString()],
10050 [transformSpec.outputPath, newKey], transformer, transformSpec.innerValue);
10051 }
10052 newHash[newKey] = content;
10053 });
10054 return newHash;
10055 };
10056
10057 fluid.transforms.indexArrayByKey.invert = function (transformSpec) {
10058 transformSpec.type = "fluid.transforms.deindexIntoArrayByKey";
10059 // invert transforms from innerValue as well:
10060 // TODO: The Model Transformations framework should be capable of this, but right now the
10061 // issue is that we use a "private contract" to operate the "innerValue" slot. We need to
10062 // spend time thinking of how this should be formalised
10063 if (transformSpec.innerValue) {
10064 var innerValue = transformSpec.innerValue;
10065 for (var i = 0; i < innerValue.length; ++i) {
10066 var inverted = fluid.model.transform.invertConfiguration(innerValue[i]);
10067 if (inverted === fluid.model.transform.uninvertibleTransform) {
10068 return inverted;
10069 } else {
10070 innerValue[i] = inverted;
10071 }
10072 }
10073 }
10074 return transformSpec;
10075 };
10076
10077
10078 fluid.defaults("fluid.transforms.deindexIntoArrayByKey", {
10079 gradeNames: [ "fluid.standardTransformFunction", "fluid.lens" ],
10080 invertConfiguration: "fluid.transforms.deindexIntoArrayByKey.invert"
10081 });
10082
10083 /*
10084 * Transforms an object of objects into an array of objects, by deindexing by the option "key" which must be supplied within the transform specification.
10085 * The key of each object will become split out into a fresh value in each array element which will be given the key held in the transformSpec option "key".
10086 * For example:
10087 * <code>fluid.transforms.deindexIntoArrayByKey({e1: {b: 1, c: 2}, e2: {b: 2: c, 3}, {key: "k"})</code> will output the array
10088 * <code>[{k: "e1", b: 1, c: 2}, {k: "e2", b: 2: c: 3}]</code>
10089 *
10090 * This performs the inverse transform of fluid.transforms.indexArrayByKey.
10091 */
10092 fluid.transforms.deindexIntoArrayByKey = function (hash, transformSpec, transformer) {
10093 if (transformSpec.key === undefined) {
10094 fluid.fail("deindexIntoArrayByKey requires a \"key\" option.", transformSpec);
10095 }
10096
10097 var newArray = [];
10098 var pivot = transformSpec.key;
10099
10100 fluid.each(hash, function (v, k) {
10101 var content = {};
10102 content[pivot] = k;
10103 if (transformSpec.innerValue) {
10104 v = fluid.model.transform.expandInnerValues([transformSpec.inputPath, k], [transformSpec.outputPath, newArray.length.toString()],
10105 transformer, transformSpec.innerValue);
10106 }
10107 $.extend(true, content, v);
10108 newArray.push(content);
10109 });
10110 return newArray;
10111 };
10112
10113 fluid.transforms.deindexIntoArrayByKey.invert = function (transformSpec) {
10114 transformSpec.type = "fluid.transforms.indexArrayByKey";
10115 // invert transforms from innerValue as well:
10116 // TODO: The Model Transformations framework should be capable of this, but right now the
10117 // issue is that we use a "private contract" to operate the "innerValue" slot. We need to
10118 // spend time thinking of how this should be formalised
10119 if (transformSpec.innerValue) {
10120 var innerValue = transformSpec.innerValue;
10121 for (var i = 0; i < innerValue.length; ++i) {
10122 innerValue[i] = fluid.model.transform.invertConfiguration(innerValue[i]);
10123 }
10124 }
10125 return transformSpec;
10126 };
10127
10128 fluid.defaults("fluid.transforms.limitRange", {
10129 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
10130 invertConfiguration: "fluid.transforms.invertToIdentity"
10131 });
10132
10133 fluid.transforms.limitRange = function (value, transformSpec) {
10134 var min = transformSpec.min;
10135 if (min !== undefined) {
10136 var excludeMin = transformSpec.excludeMin || 0;
10137 min += excludeMin;
10138 if (value < min) {
10139 value = min;
10140 }
10141 }
10142 var max = transformSpec.max;
10143 if (max !== undefined) {
10144 var excludeMax = transformSpec.excludeMax || 0;
10145 max -= excludeMax;
10146 if (value > max) {
10147 value = max;
10148 }
10149 }
10150 return value;
10151 };
10152
10153 fluid.defaults("fluid.transforms.indexOf", {
10154 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
10155 invertConfiguration: "fluid.transforms.indexOf.invert"
10156 });
10157
10158 fluid.transforms.indexOf = function (value, transformSpec) {
10159 // We do not allow a positive number as 'notFound' value, as it threatens invertibility
10160 if (typeof (transformSpec.notFound) === "number" && transformSpec.notFound >= 0) {
10161 fluid.fail("A positive number is not allowed as 'notFound' value for indexOf");
10162 }
10163 var offset = fluid.transforms.parseIndexationOffset(transformSpec.offset, "indexOf");
10164 var array = fluid.makeArray(transformSpec.array);
10165 var originalIndex = array.indexOf(value);
10166 return originalIndex === -1 && transformSpec.notFound ? transformSpec.notFound : originalIndex + offset;
10167 };
10168
10169 fluid.transforms.indexOf.invert = function (transformSpec, transformer) {
10170 var togo = fluid.transforms.invertArrayIndexation(transformSpec, transformer);
10171 togo.type = "fluid.transforms.dereference";
10172 return togo;
10173 };
10174
10175 fluid.defaults("fluid.transforms.dereference", {
10176 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
10177 invertConfiguration: "fluid.transforms.dereference.invert"
10178 });
10179
10180 fluid.transforms.dereference = function (value, transformSpec) {
10181 if (typeof (value) !== "number") {
10182 return undefined;
10183 }
10184 var offset = fluid.transforms.parseIndexationOffset(transformSpec.offset, "dereference");
10185 var array = fluid.makeArray(transformSpec.array);
10186 var index = value + offset;
10187 return array[index];
10188 };
10189
10190 fluid.transforms.dereference.invert = function (transformSpec, transformer) {
10191 var togo = fluid.transforms.invertArrayIndexation(transformSpec, transformer);
10192 togo.type = "fluid.transforms.indexOf";
10193 return togo;
10194 };
10195
10196 fluid.transforms.parseIndexationOffset = function (offset, transformName) {
10197 var parsedOffset = 0;
10198 if (offset !== undefined) {
10199 parsedOffset = fluid.parseInteger(offset);
10200 if (isNaN(parsedOffset)) {
10201 fluid.fail(transformName + " requires the value of \"offset\" to be an integer or a string that can be converted to an integer. " + offset + " is invalid.");
10202 }
10203 }
10204 return parsedOffset;
10205 };
10206
10207 fluid.transforms.invertArrayIndexation = function (transformSpec) {
10208 if (!isNaN(Number(transformSpec.offset))) {
10209 transformSpec.offset = Number(transformSpec.offset) * (-1);
10210 }
10211 return transformSpec;
10212 };
10213
10214 fluid.defaults("fluid.transforms.stringTemplate", {
10215 gradeNames: "fluid.standardOutputTransformFunction"
10216 });
10217
10218 fluid.transforms.stringTemplate = function (transformSpec) {
10219 return fluid.stringTemplate(transformSpec.template, transformSpec.terms);
10220 };
10221
10222 fluid.defaults("fluid.transforms.free", {
10223 gradeNames: "fluid.transformFunction"
10224 });
10225
10226 fluid.transforms.free = function (transformSpec) {
10227 var args = fluid.makeArray(transformSpec.args);
10228 return fluid.invokeGlobalFunction(transformSpec.func, args);
10229 };
10230
10231 fluid.defaults("fluid.transforms.quantize", {
10232 gradeNames: "fluid.standardTransformFunction",
10233 collectInputPaths: "fluid.transforms.quantize.collect"
10234 });
10235
10236 /*
10237 * Quantize function maps a continuous range into discrete values. Given an input, it will
10238 * be matched into a discrete bucket and the corresponding output will be done.
10239 */
10240 fluid.transforms.quantize = function (value, transformSpec, transformer) {
10241 if (!transformSpec.ranges || !transformSpec.ranges.length) {
10242 fluid.fail("fluid.transforms.quantize should have a key called ranges containing an array defining ranges to quantize");
10243 }
10244 // TODO: error checking that upper bounds are all numbers and increasing
10245 for (var i = 0; i < transformSpec.ranges.length; i++) {
10246 var rangeSpec = transformSpec.ranges[i];
10247 if (value <= rangeSpec.upperBound || rangeSpec.upperBound === undefined && value >= Number.NEGATIVE_INFINITY) {
10248 return fluid.isPrimitive(rangeSpec.output) ? rangeSpec.output : transformer.expand(rangeSpec.output);
10249 }
10250 }
10251 };
10252
10253 fluid.transforms.quantize.collect = function (transformSpec, transformer) {
10254 transformSpec.ranges.forEach(function (rangeSpec) {
10255 if (!fluid.isPrimitive(rangeSpec.output)) {
10256 transformer.expand(rangeSpec.output);
10257 }
10258 });
10259 };
10260
10261 /**
10262 * inRange transformer checks whether a value is within a given range and returns `true` if it is,
10263 * and `false` if it's not.
10264 *
10265 * The range is defined by the two inputs: "min" and "max" (both inclusive). If one of these inputs
10266 * is not present it is treated as -Infinity and +Infinity, respectively - In other words, if no
10267 * `min` value is defined, any value below or equal to the given `max` value will result in `true`.
10268 */
10269 fluid.defaults("fluid.transforms.inRange", {
10270 gradeNames: "fluid.standardTransformFunction"
10271 });
10272
10273 fluid.transforms.inRange = function (value, transformSpec) {
10274 return (transformSpec.min === undefined || transformSpec.min <= value) &&
10275 (transformSpec.max === undefined || transformSpec.max >= value) ? true : false;
10276 };
10277
10278 /**
10279 *
10280 * Convert a string to a Boolean, for example, when working with HTML form element values.
10281 *
10282 * The following are all false: undefined, null, "", "0", "false", false, 0
10283 *
10284 * Everything else is true.
10285 *
10286 * @param {String} value - The value to be interpreted.
10287 * @return {Boolean} The interpreted value.
10288 */
10289 fluid.transforms.stringToBoolean = function (value) {
10290 if (value) {
10291 return !(value === "0" || value === "false");
10292 }
10293 else {
10294 return false;
10295 }
10296 };
10297
10298 fluid.transforms.stringToBoolean.invert = function (transformSpec) {
10299 transformSpec.type = "fluid.transforms.booleanToString";
10300 return transformSpec;
10301 };
10302
10303 fluid.defaults("fluid.transforms.stringToBoolean", {
10304 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
10305 invertConfiguration: "fluid.transforms.stringToBoolean.invert"
10306 });
10307
10308 /**
10309 *
10310 * Convert any value into a stringified boolean, i. e. either "true" or "false". Anything that evaluates to
10311 * true (1, true, "non empty string", {}, et. cetera) returns "true". Anything else (0, false, null, et. cetera)
10312 * returns "false".
10313 *
10314 * @param {Any} value - The value to be converted to a stringified Boolean.
10315 * @return {String} - A stringified boolean representation of the value.
10316 */
10317 fluid.transforms.booleanToString = function (value) {
10318 return value ? "true" : "false";
10319 };
10320
10321 fluid.transforms.booleanToString.invert = function (transformSpec) {
10322 transformSpec.type = "fluid.transforms.stringToBoolean";
10323 return transformSpec;
10324 };
10325
10326 fluid.defaults("fluid.transforms.booleanToString", {
10327 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
10328 invertConfiguration: "fluid.transforms.booleanToString.invert"
10329 });
10330
10331 /**
10332 *
10333 * Transform stringified JSON to an object using `JSON.parse`. Returns `undefined` if the JSON string is invalid.
10334 *
10335 * @param {String} value - The stringified JSON to be converted to an object.
10336 * @return {Any} - The parsed value of the string, or `undefined` if it can't be parsed.
10337 */
10338 fluid.transforms.JSONstringToObject = function (value) {
10339 try {
10340 return JSON.parse(value);
10341 }
10342 catch (e) {
10343 return undefined;
10344 }
10345 };
10346
10347 fluid.transforms.JSONstringToObject.invert = function (transformSpec) {
10348 transformSpec.type = "fluid.transforms.objectToJSONString";
10349 return transformSpec;
10350 };
10351
10352 fluid.defaults("fluid.transforms.JSONstringToObject", {
10353 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
10354 invertConfiguration: "fluid.transforms.JSONstringToObject.invert"
10355 });
10356
10357 /**
10358 *
10359 * Transform an object to a string using `JSON.stringify`. You can pass the `space` option to be used
10360 * as part of your transform, as in:
10361 *
10362 * ```
10363 * "": {
10364 * transform: {
10365 * funcName: "fluid.transforms.objectToJSONString",
10366 * inputPath: "",
10367 * space: 2
10368 * }
10369 * }
10370 * ```
10371 *
10372 * The default value for `space` is 0, which disables spacing and line breaks.
10373 *
10374 * @param {Object} value - An object to be converted to stringified JSON.
10375 * @param {Object} transformSpec - An object describing the transformation spec, see above.
10376 * @return {String} - A string representation of the object.
10377 *
10378 */
10379 fluid.transforms.objectToJSONString = function (value, transformSpec) {
10380 var space = transformSpec.space || 0;
10381 return JSON.stringify(value, null, space);
10382 };
10383
10384 fluid.transforms.objectToJSONString.invert = function (transformSpec) {
10385 transformSpec.type = "fluid.transforms.JSONstringToObject";
10386 return transformSpec;
10387 };
10388
10389 fluid.defaults("fluid.transforms.objectToJSONString", {
10390 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
10391 invertConfiguration: "fluid.transforms.objectToJSONString.invert"
10392 });
10393
10394 /**
10395 *
10396 * Transform a string to a date using the Date constructor. Accepts (among other things) the date and dateTime
10397 * values returned by HTML5 date and dateTime inputs.
10398 *
10399 * A string that cannot be parsed will be treated as `undefined`.
10400 *
10401 * Note: This function allows you to create Date objects from an ISO 8601 string such as `2017-01-23T08:51:25.891Z`.
10402 * It is intended to provide a consistent mechanism for recreating Date objects stored as strings. Although the
10403 * framework currently works as expected with Date objects stored in the model, this is very likely to change. If
10404 * you are working with Date objects in your model, your best option for ensuring your code continues to work in the
10405 * future is to handle serialisation and deserialisation yourself, for example, by using this transform and one of
10406 * its inverse transforms, `fluid.transforms.dateToString` or `fluid.transforms.dateTimeToString`. See the Infusion
10407 * documentation for details about supported model values:
10408 *
10409 * http://docs.fluidproject.org/infusion/development/FrameworkConcepts.html#model-objects
10410 *
10411 * @param {String} value - The String value to be transformed into a Date object.
10412 * @return {Date} - A date object, or `undefined`.
10413 *
10414 */
10415 fluid.transforms.stringToDate = function (value) {
10416 var date = new Date(value);
10417 return isNaN(date.getTime()) ? undefined : date;
10418 };
10419
10420 fluid.transforms.stringToDate.invert = function (transformSpec) {
10421 transformSpec.type = "fluid.transforms.dateToString";
10422 return transformSpec;
10423 };
10424
10425 fluid.defaults("fluid.transforms.stringToDate", {
10426 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
10427 invertConfiguration: "fluid.transforms.stringToDate.invert"
10428 });
10429
10430 /**
10431 *
10432 * Transform a Date object into a date string using its toISOString method. Strips the "time" portion away to
10433 * produce date strings that are suitable for use with both HTML5 "date" inputs and JSON Schema "date" format
10434 * string validation, for example: `2016-11-23`
10435 *
10436 * If you wish to preserve the time, use `fluid.transforms.dateTimeToString` instead.
10437 *
10438 * A non-date object will be treated as `undefined`.
10439 *
10440 * Note: This function allows you to seralise Date objects (not including time information) as ISO 8601 strings such
10441 * as `2017-01-23`. It is intended to provide a consistent mechanism for storing Date objects in a model. Although
10442 * the framework currently works as expected with Date objects stored in the model, this is very likely to change.
10443 * If you are working with Date objects in your model, your best option for ensuring your code continues to work in
10444 * the future is to handle serialisation and deserialisation yourself, for example, by using this transform and its
10445 * inverse, `fluid.transforms.stringToDate`. See the Infusion documentation for details about supported model
10446 * values:
10447 *
10448 * http://docs.fluidproject.org/infusion/development/FrameworkConcepts.html#model-objects
10449 *
10450 * @param {Date} value - The Date object to be transformed into an ISO 8601 string.
10451 * @return {String} - A {String} value representing the date, or `undefined` if the date is invalid.
10452 *
10453 */
10454 fluid.transforms.dateToString = function (value) {
10455 if (value instanceof Date) {
10456 var isoString = value.toISOString(); // A string like "2016-09-26T08:05:57.462Z"
10457 var dateString = isoString.substring(0, isoString.indexOf("T")); // A string like "2016-09-26"
10458 return dateString;
10459 }
10460 else {
10461 return undefined;
10462 }
10463 };
10464
10465 fluid.transforms.dateToString.invert = function (transformSpec) {
10466 transformSpec.type = "fluid.transforms.stringToDate";
10467 return transformSpec;
10468 };
10469
10470 fluid.defaults("fluid.transforms.dateToString", {
10471 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
10472 invertConfiguration: "fluid.transforms.dateToString.invert"
10473 });
10474
10475 /**
10476 *
10477 * Transform a Date object into a date/time string using its toISOString method. Results in date strings that are
10478 * suitable for use with both HTML5 "dateTime" inputs and JSON Schema "date-time" format string validation, for\
10479 * example: `2016-11-23T13:05:24.079Z`
10480 *
10481 * A non-date object will be treated as `undefined`.
10482 *
10483 * Note: This function allows you to seralise Date objects (including time information) as ISO 8601 strings such as
10484 * `2017-01-23T08:51:25.891Z`. It is intended to provide a consistent mechanism for storing Date objects in a model.
10485 * Although the framework currently works as expected with Date objects stored in the model, this is very likely to
10486 * change. If you are working with Date objects in your model, your best option for ensuring your code continues to
10487 * work in the future is to handle serialisation and deserialisation yourself, for example, by using this function
10488 * and its inverse, `fluid.transforms.stringToDate`. See the Infusion documentation for details about supported
10489 * model values:
10490 *
10491 * http://docs.fluidproject.org/infusion/development/FrameworkConcepts.html#model-objects
10492 *
10493 * @param {Date} value - The Date object to be transformed into an ISO 8601 string.
10494 * @return {String} - A {String} value representing the date and time, or `undefined` if the date/time are invalid.
10495 *
10496 */
10497 fluid.transforms.dateTimeToString = function (value) {
10498 return value instanceof Date ? value.toISOString() : undefined;
10499 };
10500
10501 fluid.defaults("fluid.transforms.dateTimeToString", {
10502 gradeNames: ["fluid.standardTransformFunction", "fluid.lens"],
10503 invertConfiguration: "fluid.transforms.dateToString.invert"
10504 });
10505})(jQuery, fluid_3_0_0);
10506;
10507/*
10508Copyright The Infusion copyright holders
10509See the AUTHORS.md file at the top-level directory of this distribution and at
10510https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
10511
10512Licensed under the Educational Community License (ECL), Version 2.0 or the New
10513BSD license. You may not use this file except in compliance with one these
10514Licenses.
10515
10516You may obtain a copy of the ECL 2.0 License and BSD License at
10517https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
10518*/
10519
10520var fluid_3_0_0 = fluid_3_0_0 || {};
10521var fluid = fluid || fluid_3_0_0;
10522
10523(function ($, fluid) {
10524 "use strict";
10525
10526 // $().fluid("selectable", args)
10527 // $().fluid("selectable".that()
10528 // $().fluid("pager.pagerBar", args)
10529 // $().fluid("reorderer", options)
10530
10531 /** Create a "bridge" from code written in the Fluid standard "that-ist" style,
10532 * to the standard JQuery UI plugin architecture specified at http://docs.jquery.com/UI/Guidelines .
10533 * Every Fluid component corresponding to the top-level standard signature (JQueryable, options)
10534 * will automatically convert idiomatically to the JQuery UI standard via this adapter.
10535 * Any return value which is a primitive or array type will become the return value
10536 * of the "bridged" function - however, where this function returns a general hash
10537 * (object) this is interpreted as forming part of the Fluid "return that" pattern,
10538 * and the function will instead be bridged to "return this" as per JQuery standard,
10539 * permitting chaining to occur. However, as a courtesy, the particular "this" returned
10540 * will be augmented with a function that() which will allow the original return
10541 * value to be retrieved if desired.
10542 * @param {String} name - The name under which the "plugin space" is to be injected into JQuery
10543 * @param {Object} peer - The root of the namespace corresponding to the peer object.
10544 * @return {Function} - A JQuery UI plugin function.
10545 */
10546
10547 fluid.thatistBridge = function (name, peer) {
10548
10549 var togo = function (funcname) {
10550 var segs = funcname.split(".");
10551 var move = peer;
10552 for (var i = 0; i < segs.length; ++i) {
10553 move = move[segs[i]];
10554 }
10555 var args = [this];
10556 if (arguments.length === 2) {
10557 args = args.concat($.makeArray(arguments[1]));
10558 }
10559 var ret = move.apply(null, args);
10560 this.that = function () {
10561 return ret;
10562 };
10563 var type = typeof(ret);
10564 return !ret || type === "string" || type === "number" || type === "boolean" ||
10565 (ret && ret.length !== undefined) ? ret : this;
10566 };
10567 $.fn[name] = togo;
10568 return togo;
10569 };
10570
10571 fluid.thatistBridge("fluid", fluid);
10572 fluid.thatistBridge("fluid_3_0_0", fluid_3_0_0);
10573
10574/*************************************************************************
10575 * Tabindex normalization - compensate for browser differences in naming
10576 * and function of "tabindex" attribute and tabbing order.
10577 */
10578
10579 // -- Private functions --
10580
10581
10582 var normalizeTabindexName = function () {
10583 return $.browser.msie ? "tabIndex" : "tabindex";
10584 };
10585
10586 var canHaveDefaultTabindex = function (elements) {
10587 if (elements.length <= 0) {
10588 return false;
10589 }
10590
10591 return $(elements[0]).is("a, input, button, select, area, textarea, object");
10592 };
10593
10594 var getValue = function (elements) {
10595 if (elements.length <= 0) {
10596 return undefined;
10597 }
10598
10599 if (!fluid.tabindex.hasAttr(elements)) {
10600 return canHaveDefaultTabindex(elements) ? Number(0) : undefined;
10601 }
10602
10603 // Get the attribute and return it as a number value.
10604 var value = elements.attr(normalizeTabindexName());
10605 return Number(value);
10606 };
10607
10608 var setValue = function (elements, toIndex) {
10609 return elements.each(function (i, item) {
10610 $(item).attr(normalizeTabindexName(), toIndex);
10611 });
10612 };
10613
10614 // -- Public API --
10615
10616 /**
10617 * Gets the value of the tabindex attribute for the first item, or sets the tabindex value of all elements
10618 * if toIndex is specified.
10619 *
10620 * @param {jQuery} target - The target element.
10621 * @param {String|Number} toIndex - (Optional) the tabIndex value to set on the target.
10622 * @return {Any} - The result of the underlying "get" or "set" operation.
10623 */
10624 fluid.tabindex = function (target, toIndex) {
10625 target = $(target);
10626 if (toIndex !== null && toIndex !== undefined) {
10627 return setValue(target, toIndex);
10628 } else {
10629 return getValue(target);
10630 }
10631 };
10632
10633 /*
10634 * Removes the tabindex attribute altogether from each element.
10635 */
10636 fluid.tabindex.remove = function (target) {
10637 target = $(target);
10638 return target.each(function (i, item) {
10639 $(item).removeAttr(normalizeTabindexName());
10640 });
10641 };
10642
10643 /*
10644 * Determines if an element actually has a tabindex attribute present.
10645 */
10646 fluid.tabindex.hasAttr = function (target) {
10647 target = $(target);
10648 if (target.length <= 0) {
10649 return false;
10650 }
10651 var togo = target.map(
10652 function () {
10653 var attributeNode = this.getAttributeNode(normalizeTabindexName());
10654 return attributeNode ? attributeNode.specified : false;
10655 }
10656 );
10657 return togo.length === 1 ? togo[0] : togo;
10658 };
10659
10660 /*
10661 * Determines if an element either has a tabindex attribute or is naturally tab-focussable.
10662 */
10663 fluid.tabindex.has = function (target) {
10664 target = $(target);
10665 return fluid.tabindex.hasAttr(target) || canHaveDefaultTabindex(target);
10666 };
10667
10668 // Keyboard navigation
10669 // Public, static constants needed by the rest of the library.
10670 fluid.a11y = $.a11y || {};
10671
10672 fluid.a11y.orientation = {
10673 HORIZONTAL: 0,
10674 VERTICAL: 1,
10675 BOTH: 2
10676 };
10677
10678 var UP_DOWN_KEYMAP = {
10679 next: $.ui.keyCode.DOWN,
10680 previous: $.ui.keyCode.UP
10681 };
10682
10683 var LEFT_RIGHT_KEYMAP = {
10684 next: $.ui.keyCode.RIGHT,
10685 previous: $.ui.keyCode.LEFT
10686 };
10687
10688 // Private functions.
10689 var unwrap = function (element) {
10690 return element.jquery ? element[0] : element; // Unwrap the element if it's a jQuery.
10691 };
10692
10693
10694 var makeElementsTabFocussable = function (elements) {
10695 // If each element doesn't have a tabindex, or has one set to a negative value, set it to 0.
10696 elements.each(function (idx, item) {
10697 item = $(item);
10698 if (!item.fluid("tabindex.has") || item.fluid("tabindex") < 0) {
10699 item.fluid("tabindex", 0);
10700 }
10701 });
10702 };
10703
10704 // Public API.
10705 /*
10706 * Makes all matched elements available in the tab order by setting their tabindices to "0".
10707 */
10708 fluid.tabbable = function (target) {
10709 target = $(target);
10710 makeElementsTabFocussable(target);
10711 };
10712
10713 /***********************************************************************
10714 * Selectable functionality - geometrising a set of nodes such that they
10715 * can be navigated (by setting focus) using a set of directional keys
10716 */
10717
10718 var CONTEXT_KEY = "selectionContext";
10719 var NO_SELECTION = -32768;
10720
10721 var cleanUpWhenLeavingContainer = function (selectionContext) {
10722 if (selectionContext.activeItemIndex !== NO_SELECTION) {
10723 if (selectionContext.options.onLeaveContainer) {
10724 selectionContext.options.onLeaveContainer(
10725 selectionContext.selectables[selectionContext.activeItemIndex]
10726 );
10727 } else if (selectionContext.options.onUnselect) {
10728 selectionContext.options.onUnselect(
10729 selectionContext.selectables[selectionContext.activeItemIndex]
10730 );
10731 }
10732 }
10733
10734 if (!selectionContext.options.rememberSelectionState) {
10735 selectionContext.activeItemIndex = NO_SELECTION;
10736 }
10737 };
10738
10739 /*
10740 * Does the work of selecting an element and delegating to the client handler.
10741 */
10742 var drawSelection = function (elementToSelect, handler) {
10743 if (handler) {
10744 handler(elementToSelect);
10745 }
10746 };
10747
10748 /*
10749 * Does does the work of unselecting an element and delegating to the client handler.
10750 */
10751 var eraseSelection = function (selectedElement, handler) {
10752 if (handler && selectedElement) {
10753 handler(selectedElement);
10754 }
10755 };
10756
10757 var unselectElement = function (selectedElement, selectionContext) {
10758 eraseSelection(selectedElement, selectionContext.options.onUnselect);
10759 };
10760
10761 var selectElement = function (elementToSelect, selectionContext) {
10762 // It's possible that we're being called programmatically, in which case we should clear any previous selection.
10763 unselectElement(selectionContext.selectedElement(), selectionContext);
10764
10765 elementToSelect = unwrap(elementToSelect);
10766 var newIndex = selectionContext.selectables.index(elementToSelect);
10767
10768 // Next check if the element is a known selectable. If not, do nothing.
10769 if (newIndex === -1) {
10770 return;
10771 }
10772
10773 // Select the new element.
10774 selectionContext.activeItemIndex = newIndex;
10775 drawSelection(elementToSelect, selectionContext.options.onSelect);
10776 };
10777
10778 var selectableFocusHandler = function (selectionContext) {
10779 return function (evt) {
10780 // FLUID-3590: newer browsers (FF 3.6, Webkit 4) have a form of "bug" in that they will go bananas
10781 // on attempting to move focus off an element which has tabindex dynamically set to -1.
10782 $(evt.target).fluid("tabindex", 0);
10783 selectElement(evt.target, selectionContext);
10784
10785 // Force focus not to bubble on some browsers.
10786 return evt.stopPropagation();
10787 };
10788 };
10789
10790 var selectableBlurHandler = function (selectionContext) {
10791 return function (evt) {
10792 $(evt.target).fluid("tabindex", selectionContext.options.selectablesTabindex);
10793 unselectElement(evt.target, selectionContext);
10794
10795 // Force blur not to bubble on some browsers.
10796 return evt.stopPropagation();
10797 };
10798 };
10799
10800 var reifyIndex = function (sc_that) {
10801 var elements = sc_that.selectables;
10802 if (sc_that.activeItemIndex >= elements.length) {
10803 sc_that.activeItemIndex = (sc_that.options.noWrap ? elements.length - 1 : 0);
10804 }
10805 if (sc_that.activeItemIndex < 0 && sc_that.activeItemIndex !== NO_SELECTION) {
10806 sc_that.activeItemIndex = (sc_that.options.noWrap ? 0 : elements.length - 1);
10807 }
10808 if (sc_that.activeItemIndex >= 0) {
10809 fluid.focus(elements[sc_that.activeItemIndex]);
10810 }
10811 };
10812
10813 var prepareShift = function (selectionContext) {
10814 // FLUID-3590: FF 3.6 and Safari 4.x won't fire blur() when programmatically moving focus.
10815 var selElm = selectionContext.selectedElement();
10816 if (selElm) {
10817 fluid.blur(selElm);
10818 }
10819
10820 unselectElement(selectionContext.selectedElement(), selectionContext);
10821 if (selectionContext.activeItemIndex === NO_SELECTION) {
10822 selectionContext.activeItemIndex = -1;
10823 }
10824 };
10825
10826 var focusNextElement = function (selectionContext) {
10827 prepareShift(selectionContext);
10828 ++selectionContext.activeItemIndex;
10829 reifyIndex(selectionContext);
10830 };
10831
10832 var focusPreviousElement = function (selectionContext) {
10833 prepareShift(selectionContext);
10834 --selectionContext.activeItemIndex;
10835 reifyIndex(selectionContext);
10836 };
10837
10838 var arrowKeyHandler = function (selectionContext, keyMap) {
10839 return function (evt) {
10840 if (evt.which === keyMap.next) {
10841 focusNextElement(selectionContext);
10842 evt.preventDefault();
10843 } else if (evt.which === keyMap.previous) {
10844 focusPreviousElement(selectionContext);
10845 evt.preventDefault();
10846 }
10847 };
10848 };
10849
10850 var getKeyMapForDirection = function (direction) {
10851 // Determine the appropriate mapping for next and previous based on the specified direction.
10852 var keyMap;
10853 if (direction === fluid.a11y.orientation.HORIZONTAL) {
10854 keyMap = LEFT_RIGHT_KEYMAP;
10855 }
10856 else if (direction === fluid.a11y.orientation.VERTICAL) {
10857 // Assume vertical in any other case.
10858 keyMap = UP_DOWN_KEYMAP;
10859 }
10860
10861 return keyMap;
10862 };
10863
10864 var tabKeyHandler = function (selectionContext) {
10865 return function (evt) {
10866 if (evt.which !== $.ui.keyCode.TAB) {
10867 return;
10868 }
10869 cleanUpWhenLeavingContainer(selectionContext);
10870
10871 // Catch Shift-Tab and note that focus is on its way out of the container.
10872 if (evt.shiftKey) {
10873 selectionContext.focusIsLeavingContainer = true;
10874 }
10875 };
10876 };
10877
10878 var containerFocusHandler = function (selectionContext) {
10879 return function (evt) {
10880 var shouldOrig = selectionContext.options.autoSelectFirstItem;
10881 var shouldSelect = typeof(shouldOrig) === "function" ? shouldOrig() : shouldOrig;
10882
10883 // Override the autoselection if we're on the way out of the container.
10884 if (selectionContext.focusIsLeavingContainer) {
10885 shouldSelect = false;
10886 }
10887
10888 // This target check works around the fact that sometimes focus bubbles, even though it shouldn't.
10889 if (shouldSelect && evt.target === selectionContext.container.get(0)) {
10890 if (selectionContext.activeItemIndex === NO_SELECTION) {
10891 selectionContext.activeItemIndex = 0;
10892 }
10893 fluid.focus(selectionContext.selectables[selectionContext.activeItemIndex]);
10894 }
10895
10896 // Force focus not to bubble on some browsers.
10897 return evt.stopPropagation();
10898 };
10899 };
10900
10901 var containerBlurHandler = function (selectionContext) {
10902 return function (evt) {
10903 selectionContext.focusIsLeavingContainer = false;
10904
10905 // Force blur not to bubble on some browsers.
10906 return evt.stopPropagation();
10907 };
10908 };
10909
10910 var makeElementsSelectable = function (container, defaults, userOptions) {
10911
10912 var options = $.extend(true, {}, defaults, userOptions);
10913
10914 var keyMap = getKeyMapForDirection(options.direction);
10915
10916 var selectableElements = options.selectableElements ? options.selectableElements :
10917 container.find(options.selectableSelector);
10918
10919 // Context stores the currently active item(undefined to start) and list of selectables.
10920 var that = {
10921 container: container,
10922 activeItemIndex: NO_SELECTION,
10923 selectables: selectableElements,
10924 focusIsLeavingContainer: false,
10925 options: options
10926 };
10927
10928 that.selectablesUpdated = function (focusedItem) {
10929 // Remove selectables from the tab order and add focus/blur handlers
10930 if (typeof(that.options.selectablesTabindex) === "number") {
10931 that.selectables.fluid("tabindex", that.options.selectablesTabindex);
10932 }
10933 that.selectables.off("focus." + CONTEXT_KEY);
10934 that.selectables.off("blur." + CONTEXT_KEY);
10935 that.selectables.on("focus." + CONTEXT_KEY, selectableFocusHandler(that));
10936 that.selectables.on("blur." + CONTEXT_KEY, selectableBlurHandler(that));
10937 if (keyMap && that.options.noBubbleListeners) {
10938 that.selectables.off("keydown." + CONTEXT_KEY);
10939 that.selectables.on("keydown." + CONTEXT_KEY, arrowKeyHandler(that, keyMap));
10940 }
10941 if (focusedItem) {
10942 selectElement(focusedItem, that);
10943 }
10944 else {
10945 reifyIndex(that);
10946 }
10947 };
10948
10949 that.refresh = function () {
10950 if (!that.options.selectableSelector) {
10951 fluid.fail("Cannot refresh selectable context which was not initialised by a selector");
10952 }
10953 that.selectables = container.find(options.selectableSelector);
10954 that.selectablesUpdated();
10955 };
10956
10957 that.selectedElement = function () {
10958 return that.activeItemIndex < 0 ? null : that.selectables[that.activeItemIndex];
10959 };
10960
10961 // Add various handlers to the container.
10962 if (keyMap && !that.options.noBubbleListeners) {
10963 container.keydown(arrowKeyHandler(that, keyMap));
10964 }
10965 container.keydown(tabKeyHandler(that));
10966 container.focus(containerFocusHandler(that));
10967 container.blur(containerBlurHandler(that));
10968
10969 that.selectablesUpdated();
10970
10971 return that;
10972 };
10973
10974 /*
10975 * Makes all matched elements selectable with the arrow keys.
10976 * Supply your own handlers object with onSelect: and onUnselect: properties for custom behaviour.
10977 * Options provide configurability, including direction: and autoSelectFirstItem:
10978 * Currently supported directions are jQuery.a11y.directions.HORIZONTAL and VERTICAL.
10979 */
10980 fluid.selectable = function (target, options) {
10981 target = $(target);
10982 var that = makeElementsSelectable(target, fluid.selectable.defaults, options);
10983 fluid.setScopedData(target, CONTEXT_KEY, that);
10984 return that;
10985 };
10986
10987 /*
10988 * Selects the specified element.
10989 */
10990 fluid.selectable.select = function (target, toSelect) {
10991 fluid.focus(toSelect);
10992 };
10993
10994 /*
10995 * Selects the next matched element.
10996 */
10997 fluid.selectable.selectNext = function (target) {
10998 target = $(target);
10999 focusNextElement(fluid.getScopedData(target, CONTEXT_KEY));
11000 };
11001
11002 /*
11003 * Selects the previous matched element.
11004 */
11005 fluid.selectable.selectPrevious = function (target) {
11006 target = $(target);
11007 focusPreviousElement(fluid.getScopedData(target, CONTEXT_KEY));
11008 };
11009
11010 /*
11011 * Returns the currently selected item wrapped as a jQuery object.
11012 */
11013 fluid.selectable.currentSelection = function (target) {
11014 target = $(target);
11015 var that = fluid.getScopedData(target, CONTEXT_KEY);
11016 return $(that.selectedElement());
11017 };
11018
11019 fluid.selectable.defaults = {
11020 direction: fluid.a11y.orientation.VERTICAL,
11021 selectablesTabindex: -1,
11022 autoSelectFirstItem: true,
11023 rememberSelectionState: true,
11024 selectableSelector: ".selectable",
11025 selectableElements: null,
11026 onSelect: null,
11027 onUnselect: null,
11028 onLeaveContainer: null,
11029 noWrap: false
11030 };
11031
11032 /********************************************************************
11033 * Activation functionality - declaratively associating actions with
11034 * a set of keyboard bindings.
11035 */
11036
11037 var checkForModifier = function (binding, evt) {
11038 // If no modifier was specified, just return true.
11039 if (!binding.modifier) {
11040 return true;
11041 }
11042
11043 var modifierKey = binding.modifier;
11044 var isCtrlKeyPresent = modifierKey && evt.ctrlKey;
11045 var isAltKeyPresent = modifierKey && evt.altKey;
11046 var isShiftKeyPresent = modifierKey && evt.shiftKey;
11047
11048 return isCtrlKeyPresent || isAltKeyPresent || isShiftKeyPresent;
11049 };
11050
11051 /* Constructs a raw "keydown"-facing handler, given a binding entry. This
11052 * checks whether the key event genuinely triggers the event and forwards it
11053 * to any "activateHandler" registered in the binding.
11054 */
11055 var makeActivationHandler = function (binding) {
11056 return function (evt) {
11057 var target = evt.target;
11058 if (!fluid.enabled(target)) {
11059 return;
11060 }
11061// The following 'if' clause works in the real world, but there's a bug in the jQuery simulation
11062// that causes keyboard simulation to fail in Safari, causing our tests to fail:
11063// http://ui.jquery.com/bugs/ticket/3229
11064// The replacement 'if' clause works around this bug.
11065// When this issue is resolved, we should revert to the original clause.
11066// if (evt.which === binding.key && binding.activateHandler && checkForModifier(binding, evt)) {
11067 var code = evt.which ? evt.which : evt.keyCode;
11068 if (code === binding.key && binding.activateHandler && checkForModifier(binding, evt)) {
11069 var event = $.Event("fluid-activate");
11070 $(target).trigger(event, [binding.activateHandler]);
11071 if (event.isDefaultPrevented()) {
11072 evt.preventDefault();
11073 }
11074 }
11075 };
11076 };
11077
11078 var makeElementsActivatable = function (elements, onActivateHandler, defaultKeys, options) {
11079 // Create bindings for each default key.
11080 var bindings = [];
11081 $(defaultKeys).each(function (index, key) {
11082 bindings.push({
11083 modifier: null,
11084 key: key,
11085 activateHandler: onActivateHandler
11086 });
11087 });
11088
11089 // Merge with any additional key bindings.
11090 if (options && options.additionalBindings) {
11091 bindings = bindings.concat(options.additionalBindings);
11092 }
11093
11094 fluid.initEnablement(elements);
11095
11096 // Add listeners for each key binding.
11097 for (var i = 0; i < bindings.length; ++i) {
11098 var binding = bindings[i];
11099 elements.keydown(makeActivationHandler(binding));
11100 }
11101 elements.on("fluid-activate", function (evt, handler) {
11102 handler = handler || onActivateHandler;
11103 return handler ? handler(evt) : null;
11104 });
11105 };
11106
11107 /*
11108 * Makes all matched elements activatable with the Space and Enter keys.
11109 * Provide your own handler function for custom behaviour.
11110 * Options allow you to provide a list of additionalActivationKeys.
11111 */
11112 fluid.activatable = function (target, fn, options) {
11113 target = $(target);
11114 makeElementsActivatable(target, fn, fluid.activatable.defaults.keys, options);
11115 };
11116
11117 /*
11118 * Activates the specified element.
11119 */
11120 fluid.activate = function (target) {
11121 $(target).trigger("fluid-activate");
11122 };
11123
11124 // Public Defaults.
11125 fluid.activatable.defaults = {
11126 keys: [$.ui.keyCode.ENTER, $.ui.keyCode.SPACE]
11127 };
11128
11129
11130})(jQuery, fluid_3_0_0);
11131;
11132/*
11133Copyright The Infusion copyright holders
11134See the AUTHORS.md file at the top-level directory of this distribution and at
11135https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
11136
11137Licensed under the Educational Community License (ECL), Version 2.0 or the New
11138BSD license. You may not use this file except in compliance with one these
11139Licenses.
11140
11141You may obtain a copy of the ECL 2.0 License and BSD License at
11142https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
11143*/
11144
11145/** This file contains functions which depend on the presence of a DOM document
11146 * and which depend on the contents of Fluid.js **/
11147
11148var fluid_3_0_0 = fluid_3_0_0 || {};
11149
11150(function ($, fluid) {
11151 "use strict";
11152
11153 fluid.defaults("fluid.viewComponent", {
11154 gradeNames: ["fluid.modelComponent"],
11155 initFunction: "fluid.initView",
11156 argumentMap: {
11157 container: 0,
11158 options: 1
11159 },
11160 members: { // Used to allow early access to DOM binder via IoC, but to also avoid triggering evaluation of selectors
11161 dom: "@expand:fluid.initDomBinder({that}, {that}.options.selectors)"
11162 }
11163 });
11164
11165 // unsupported, NON-API function
11166 fluid.dumpSelector = function (selectable) {
11167 return typeof (selectable) === "string" ? selectable :
11168 selectable.selector ? selectable.selector : "";
11169 };
11170
11171 // unsupported, NON-API function
11172 // NOTE: this function represents a temporary strategy until we have more integrated IoC debugging.
11173 // It preserves the 1.3 and previous framework behaviour for the 1.x releases, but provides a more informative
11174 // diagnostic - in fact, it is perfectly acceptable for a component's creator to return no value and
11175 // the failure is really in assumptions in fluid.initLittleComponent. Revisit this issue for 2.0
11176 fluid.diagnoseFailedView = function (componentName, that, options, args) {
11177 if (!that && fluid.hasGrade(options, "fluid.viewComponent")) {
11178 var container = fluid.wrap(args[1]);
11179 var message1 = "Instantiation of view component with type " + componentName + " failed, since ";
11180 if (!container) {
11181 fluid.fail(message1 + " container argument is empty");
11182 }
11183 else if (container.length === 0) {
11184 fluid.fail(message1 + "selector \"", fluid.dumpSelector(args[1]), "\" did not match any markup in the document");
11185 } else {
11186 fluid.fail(message1 + " component creator function did not return a value");
11187 }
11188 }
11189 };
11190
11191 fluid.checkTryCatchParameter = function () {
11192 var location = window.location || { search: "", protocol: "file:" };
11193 var GETparams = location.search.slice(1).split("&");
11194 return fluid.find(GETparams, function (param) {
11195 if (param.indexOf("notrycatch") === 0) {
11196 return true;
11197 }
11198 }) === true;
11199 };
11200
11201 fluid.notrycatch = fluid.checkTryCatchParameter();
11202
11203
11204 /**
11205 * Wraps an object in a jQuery if it isn't already one. This function is useful since
11206 * it ensures to wrap a null or otherwise falsy argument to itself, rather than the
11207 * often unhelpful jQuery default of returning the overall document node.
11208 *
11209 * @param {Object} obj - the object to wrap in a jQuery
11210 * @param {jQuery} [userJQuery] - the jQuery object to use for the wrapping, optional - use the current jQuery if absent
11211 * @return {jQuery} - The wrapped object.
11212 */
11213 fluid.wrap = function (obj, userJQuery) {
11214 userJQuery = userJQuery || $;
11215 return ((!obj || obj.jquery) ? obj : userJQuery(obj));
11216 };
11217
11218 /**
11219 * If obj is a jQuery, this function will return the first DOM element within it. Otherwise, the object will be returned unchanged.
11220 *
11221 * @param {jQuery} obj - The jQuery instance to unwrap into a pure DOM element.
11222 * @return {Object} - The unwrapped object.
11223 */
11224 fluid.unwrap = function (obj) {
11225 return obj && obj.jquery ? obj[0] : obj;
11226 };
11227
11228 /**
11229 * Fetches a single container element and returns it as a jQuery.
11230 *
11231 * @param {String|jQuery|element} containerSpec - an id string, a single-element jQuery, or a DOM element specifying a unique container
11232 * @param {Boolean} fallible - <code>true</code> if an empty container is to be reported as a valid condition
11233 * @param {jQuery} [userJQuery] - the jQuery object to use for the wrapping, optional - use the current jQuery if absent
11234 * @return {jQuery} - A single-element jQuery container.
11235 */
11236 fluid.container = function (containerSpec, fallible, userJQuery) {
11237 var selector = containerSpec.selector || containerSpec;
11238 if (userJQuery) {
11239 containerSpec = fluid.unwrap(containerSpec);
11240 }
11241 var container = fluid.wrap(containerSpec, userJQuery);
11242 if (fallible && (!container || container.length === 0)) {
11243 return null;
11244 }
11245
11246 if (!container || !container.jquery || container.length !== 1) {
11247 if (typeof (containerSpec) !== "string") {
11248 containerSpec = container.selector;
11249 }
11250 var count = container.length !== undefined ? container.length : 0;
11251 fluid.fail((count > 1 ? "More than one (" + count + ") container elements were"
11252 : "No container element was") + " found for selector " + containerSpec);
11253 }
11254 if (!fluid.isDOMNode(container[0])) {
11255 fluid.fail("fluid.container was supplied a non-jQueryable element");
11256 }
11257
11258 // To address FLUID-5966, manually adding back the selector and context properties that were removed from jQuery v3.0.
11259 // ( see: https://jquery.com/upgrade-guide/3.0/#breaking-change-deprecated-context-and-selector-properties-removed )
11260 // In most cases the "selector" property will already be restored through the DOM binder;
11261 // however, when a selector or pure jQuery element is supplied directly as a component's container, we need to add them
11262 // if it is possible to infer them. This feature is rarely used but is crucial for the prefs framework infrastructure
11263 // in Panels.js fluid.prefs.subPanel.resetDomBinder
11264 container.selector = selector;
11265 container.context = container.context || containerSpec.ownerDocument || document;
11266
11267 return container;
11268 };
11269
11270 /**
11271 * Creates a new DOM Binder instance, used to locate elements in the DOM by name.
11272 *
11273 * @param {Object} container - the root element in which to locate named elements
11274 * @param {Object} selectors - a collection of named jQuery selectors
11275 * @return {Object} - The new DOM binder.
11276 */
11277 fluid.createDomBinder = function (container, selectors) {
11278 // don't put on a typename to avoid confusing primitive visitComponentChildren
11279 var that = {
11280 id: fluid.allocateGuid(),
11281 cache: {}
11282 };
11283 var userJQuery = container.constructor;
11284
11285 function cacheKey(name, thisContainer) {
11286 return fluid.allocateSimpleId(thisContainer) + "-" + name;
11287 }
11288
11289 function record(name, thisContainer, result) {
11290 that.cache[cacheKey(name, thisContainer)] = result;
11291 }
11292
11293 that.locate = function (name, localContainer) {
11294 var selector, thisContainer, togo;
11295
11296 selector = selectors[name];
11297 if (selector === undefined) {
11298 return undefined;
11299 }
11300 thisContainer = localContainer ? $(localContainer) : container;
11301 if (!thisContainer) {
11302 fluid.fail("DOM binder invoked for selector " + name + " without container");
11303 }
11304 if (selector === "") {
11305 togo = thisContainer;
11306 }
11307 else if (!selector) {
11308 togo = userJQuery();
11309 }
11310 else {
11311 if (typeof (selector) === "function") {
11312 togo = userJQuery(selector.call(null, fluid.unwrap(thisContainer)));
11313 } else {
11314 togo = userJQuery(selector, thisContainer);
11315 }
11316 }
11317
11318 if (!togo.selector) {
11319 togo.selector = selector;
11320 togo.context = thisContainer;
11321 }
11322 togo.selectorName = name;
11323 record(name, thisContainer, togo);
11324 return togo;
11325 };
11326 that.fastLocate = function (name, localContainer) {
11327 var thisContainer = localContainer ? localContainer : container;
11328 var key = cacheKey(name, thisContainer);
11329 var togo = that.cache[key];
11330 return togo ? togo : that.locate(name, localContainer);
11331 };
11332 that.clear = function () {
11333 that.cache = {};
11334 };
11335 that.refresh = function (names, localContainer) {
11336 var thisContainer = localContainer ? localContainer : container;
11337 if (typeof names === "string") {
11338 names = [names];
11339 }
11340 if (thisContainer.length === undefined) {
11341 thisContainer = [thisContainer];
11342 }
11343 for (var i = 0; i < names.length; ++i) {
11344 for (var j = 0; j < thisContainer.length; ++j) {
11345 that.locate(names[i], thisContainer[j]);
11346 }
11347 }
11348 };
11349 that.resolvePathSegment = that.locate;
11350
11351 return that;
11352 };
11353
11354 /* Expect that jQuery selector query has resulted in a non-empty set of
11355 * results. If none are found, this function will fail with a diagnostic message,
11356 * with the supplied message prepended.
11357 */
11358 fluid.expectFilledSelector = function (result, message) {
11359 if (result && result.length === 0 && result.jquery) {
11360 fluid.fail(message + ": selector \"" + result.selector + "\" with name " + result.selectorName +
11361 " returned no results in context " + fluid.dumpEl(result.context));
11362 }
11363 };
11364
11365 /**
11366 * The central initialiation method called as the first act of every Fluid
11367 * component. This function automatically merges user options with defaults,
11368 * attaches a DOM Binder to the instance, and configures events.
11369 *
11370 * @param {String} componentName - The unique "name" of the component, which will be used
11371 * to fetch the default options from store. By recommendation, this should be the global
11372 * name of the component's creator function.
11373 * @param {jQueryable} containerSpec - A specifier for the single root "container node" in the
11374 * DOM which will house all the markup for this component.
11375 * @param {Object} userOptions - The user configuration options for this component.
11376 * @param {Object} localOptions - The local configuration options for this component. Unsupported, see comments for initLittleComponent.
11377 * @return {Object|null} - The newly created component, or `null` id the container does not exist.
11378 */
11379 fluid.initView = function (componentName, containerSpec, userOptions, localOptions) {
11380 var container = fluid.container(containerSpec, true);
11381 fluid.expectFilledSelector(container, "Error instantiating component with name \"" + componentName);
11382 if (!container) {
11383 return null;
11384 }
11385 // Need to ensure container is set early, without relying on an IoC mechanism - rethink this with asynchrony
11386 var receiver = function (that) {
11387 that.container = container;
11388 };
11389 var that = fluid.initLittleComponent(componentName, userOptions, localOptions || {gradeNames: ["fluid.viewComponent"]}, receiver);
11390
11391 if (!that.dom) {
11392 fluid.initDomBinder(that);
11393 }
11394 // TODO: cannot afford a mutable container - put this into proper workflow
11395 var userJQuery = that.options.jQuery; // Do it a second time to correct for jQuery injection
11396 // if (userJQuery) {
11397 // container = fluid.container(containerSpec, true, userJQuery);
11398 // }
11399 fluid.log("Constructing view component " + componentName + " with container " + container.constructor.expando +
11400 (userJQuery ? " user jQuery " + userJQuery.expando : "") + " env: " + $.expando);
11401
11402 return that;
11403 };
11404
11405 /**
11406 * Creates a new DOM Binder instance for the specified component and mixes it in.
11407 *
11408 * @param {Object} that - The component instance to attach the new DOM Binder to.
11409 * @param {Object} selectors - a collection of named jQuery selectors
11410 * @return {Object} - The DOM for the component.
11411 */
11412 fluid.initDomBinder = function (that, selectors) {
11413 if (!that.container) {
11414 fluid.fail("fluid.initDomBinder called for component with typeName " + that.typeName +
11415 " without an initialised container - this has probably resulted from placing \"fluid.viewComponent\" in incorrect position in grade merging order. " +
11416 " Make sure to place it to the right of any non-view grades in the gradeNames list to ensure that it overrides properly: resolved gradeNames is ", that.options.gradeNames, " for component ", that);
11417 }
11418 that.dom = fluid.createDomBinder(that.container, selectors || that.options.selectors || {});
11419 that.locate = that.dom.locate;
11420 return that.dom;
11421 };
11422
11423 // DOM Utilities.
11424
11425 /**
11426 * Finds the nearest ancestor of the element that matches a predicate
11427 * @param {Element} element - DOM element
11428 * @param {Function} test - A function (predicate) accepting a DOM element, returning a truthy value representing a match
11429 * @return {Element|undefined} - The first element parent for which the predicate returns truthy - or undefined if no parent matches
11430 */
11431 fluid.findAncestor = function (element, test) {
11432 element = fluid.unwrap(element);
11433 while (element) {
11434 if (test(element)) {
11435 return element;
11436 }
11437 element = element.parentNode;
11438 }
11439 };
11440
11441 fluid.findForm = function (node) {
11442 return fluid.findAncestor(node, function (element) {
11443 return element.nodeName.toLowerCase() === "form";
11444 });
11445 };
11446
11447 /* A utility with the same signature as jQuery.text and jQuery.html, but without the API irregularity
11448 * that treats a single argument of undefined as different to no arguments */
11449 // in jQuery 1.7.1, jQuery pulled the same dumb trick with $.text() that they did with $.val() previously,
11450 // see comment in fluid.value below
11451 fluid.each(["text", "html"], function (method) {
11452 fluid[method] = function (node, newValue) {
11453 node = $(node);
11454 return newValue === undefined ? node[method]() : node[method](newValue);
11455 };
11456 });
11457
11458 /* A generalisation of jQuery.val to correctly handle the case of acquiring and
11459 * setting the value of clustered radio button/checkbox sets, potentially, given
11460 * a node corresponding to just one element.
11461 */
11462 fluid.value = function (nodeIn, newValue) {
11463 var node = fluid.unwrap(nodeIn);
11464 var multiple = false;
11465 if (node.nodeType === undefined && node.length > 1) {
11466 node = node[0];
11467 multiple = true;
11468 }
11469 if ("input" !== node.nodeName.toLowerCase() || !/radio|checkbox/.test(node.type)) {
11470 // resist changes to contract of jQuery.val() in jQuery 1.5.1 (see FLUID-4113)
11471 return newValue === undefined ? $(node).val() : $(node).val(newValue);
11472 }
11473 var name = node.name;
11474 if (name === undefined) {
11475 fluid.fail("Cannot acquire value from node " + fluid.dumpEl(node) + " which does not have name attribute set");
11476 }
11477 var elements;
11478 if (multiple) {
11479 elements = nodeIn;
11480 } else {
11481 elements = node.ownerDocument.getElementsByName(name);
11482 var scope = fluid.findForm(node);
11483 elements = $.grep(elements, function (element) {
11484 if (element.name !== name) {
11485 return false;
11486 }
11487 return !scope || fluid.dom.isContainer(scope, element);
11488 });
11489 }
11490 if (newValue !== undefined) {
11491 if (typeof(newValue) === "boolean") {
11492 newValue = (newValue ? "true" : "false");
11493 }
11494 // jQuery gets this partially right, but when dealing with radio button array will
11495 // set all of their values to "newValue" rather than setting the checked property
11496 // of the corresponding control.
11497 $.each(elements, function () {
11498 this.checked = (newValue instanceof Array ?
11499 newValue.indexOf(this.value) !== -1 : newValue === this.value);
11500 });
11501 } else { // this part jQuery will not do - extracting value from <input> array
11502 var checked = $.map(elements, function (element) {
11503 return element.checked ? element.value : null;
11504 });
11505 return node.type === "radio" ? checked[0] : checked;
11506 }
11507 };
11508
11509
11510 fluid.BINDING_ROOT_KEY = "fluid-binding-root";
11511
11512 /* Recursively find any data stored under a given name from a node upwards
11513 * in its DOM hierarchy **/
11514
11515 fluid.findData = function (elem, name) {
11516 while (elem) {
11517 var data = $.data(elem, name);
11518 if (data) {
11519 return data;
11520 }
11521 elem = elem.parentNode;
11522 }
11523 };
11524
11525 fluid.bindFossils = function (node, data, fossils) {
11526 $.data(node, fluid.BINDING_ROOT_KEY, {data: data, fossils: fossils});
11527 };
11528
11529 fluid.boundPathForNode = function (node, fossils) {
11530 node = fluid.unwrap(node);
11531 var key = node.name || node.id;
11532 var record = fossils[key];
11533 return record ? record.EL : null;
11534 };
11535
11536 /* relevant, the changed value received at the given DOM node */
11537 fluid.applyBoundChange = function (node, newValue, applier) {
11538 node = fluid.unwrap(node);
11539 if (newValue === undefined) {
11540 newValue = fluid.value(node);
11541 }
11542 if (node.nodeType === undefined && node.length > 0) {
11543 node = node[0];
11544 } // assume here that they share name and parent
11545 var root = fluid.findData(node, fluid.BINDING_ROOT_KEY);
11546 if (!root) {
11547 fluid.fail("Bound data could not be discovered in any node above " + fluid.dumpEl(node));
11548 }
11549 var name = node.name;
11550 var fossil = root.fossils[name];
11551 if (!fossil) {
11552 fluid.fail("No fossil discovered for name " + name + " in fossil record above " + fluid.dumpEl(node));
11553 }
11554 if (typeof(fossil.oldvalue) === "boolean") { // deal with the case of an "isolated checkbox"
11555 newValue = newValue[0] ? true : false;
11556 }
11557 var EL = root.fossils[name].EL;
11558 if (applier) {
11559 applier.fireChangeRequest({path: EL, value: newValue, source: "DOM:" + node.id});
11560 } else {
11561 fluid.set(root.data, EL, newValue);
11562 }
11563 };
11564
11565
11566 /*
11567 * Returns a jQuery object given the id of a DOM node. In the case the element
11568 * is not found, will return an empty list.
11569 */
11570 fluid.jById = function (id, dokkument) {
11571 dokkument = dokkument && dokkument.nodeType === 9 ? dokkument : document;
11572 var element = fluid.byId(id, dokkument);
11573 var togo = element ? $(element) : [];
11574 togo.selector = "#" + id;
11575 togo.context = dokkument;
11576 return togo;
11577 };
11578
11579 /**
11580 * Returns an DOM element quickly, given an id
11581 *
11582 * @param {Object} id - the id of the DOM node to find
11583 * @param {Document} dokkument - the document in which it is to be found (if left empty, use the current document)
11584 * @return {Object} - The DOM element with this id, or null, if none exists in the document.
11585 */
11586 fluid.byId = function (id, dokkument) {
11587 dokkument = dokkument && dokkument.nodeType === 9 ? dokkument : document;
11588 var el = dokkument.getElementById(id);
11589 if (el) {
11590 // Use element id property here rather than attribute, to work around FLUID-3953
11591 if (el.id !== id) {
11592 fluid.fail("Problem in document structure - picked up element " +
11593 fluid.dumpEl(el) + " for id " + id +
11594 " without this id - most likely the element has a name which conflicts with this id");
11595 }
11596 return el;
11597 } else {
11598 return null;
11599 }
11600 };
11601
11602 /**
11603 * Returns the id attribute from a jQuery or pure DOM element.
11604 *
11605 * @param {jQuery|Element} element - the element to return the id attribute for.
11606 * @return {String} - The id attribute of the element.
11607 */
11608 fluid.getId = function (element) {
11609 return fluid.unwrap(element).id;
11610 };
11611
11612 /*
11613 * Allocate an id to the supplied element if it has none already, by a simple
11614 * scheme resulting in ids "fluid-id-nnnn" where nnnn is an increasing integer.
11615 */
11616 fluid.allocateSimpleId = function (element) {
11617 element = fluid.unwrap(element);
11618 if (!element || fluid.isPrimitive(element)) {
11619 return null;
11620 }
11621
11622 if (!element.id) {
11623 var simpleId = "fluid-id-" + fluid.allocateGuid();
11624 element.id = simpleId;
11625 }
11626 return element.id;
11627 };
11628
11629 /**
11630 * Returns the document to which an element belongs, or the element itself if it is already a document
11631 *
11632 * @param {jQuery|Element} element - The element to return the document for
11633 * @return {Document} - The document in which it is to be found
11634 */
11635 fluid.getDocument = function (element) {
11636 var node = fluid.unwrap(element);
11637 // DOCUMENT_NODE - guide to node types at https://developer.mozilla.org/en/docs/Web/API/Node/nodeType
11638 return node.nodeType === 9 ? node : node.ownerDocument;
11639 };
11640
11641 fluid.defaults("fluid.ariaLabeller", {
11642 gradeNames: ["fluid.viewComponent"],
11643 labelAttribute: "aria-label",
11644 liveRegionMarkup: "<div class=\"liveRegion fl-hidden-accessible\" aria-live=\"polite\"></div>",
11645 liveRegionId: "fluid-ariaLabeller-liveRegion",
11646 invokers: {
11647 generateLiveElement: {
11648 funcName: "fluid.ariaLabeller.generateLiveElement",
11649 args: "{that}"
11650 },
11651 update: {
11652 funcName: "fluid.ariaLabeller.update",
11653 args: ["{that}", "{arguments}.0"]
11654 }
11655 },
11656 listeners: {
11657 onCreate: {
11658 func: "{that}.update",
11659 args: [null]
11660 }
11661 }
11662 });
11663
11664 fluid.ariaLabeller.update = function (that, newOptions) {
11665 newOptions = newOptions || that.options;
11666 that.container.attr(that.options.labelAttribute, newOptions.text);
11667 if (newOptions.dynamicLabel) {
11668 var live = fluid.jById(that.options.liveRegionId);
11669 if (live.length === 0) {
11670 live = that.generateLiveElement();
11671 }
11672 live.text(newOptions.text);
11673 }
11674 };
11675
11676 fluid.ariaLabeller.generateLiveElement = function (that) {
11677 var liveEl = $(that.options.liveRegionMarkup);
11678 liveEl.prop("id", that.options.liveRegionId);
11679 $("body").append(liveEl);
11680 return liveEl;
11681 };
11682
11683 var LABEL_KEY = "aria-labelling";
11684
11685 fluid.getAriaLabeller = function (element) {
11686 element = $(element);
11687 var that = fluid.getScopedData(element, LABEL_KEY);
11688 return that;
11689 };
11690
11691 /* Manages an ARIA-mediated label attached to a given DOM element. An
11692 * aria-labelledby attribute and target node is fabricated in the document
11693 * if they do not exist already, and a "little component" is returned exposing a method
11694 * "update" that allows the text to be updated. */
11695
11696 fluid.updateAriaLabel = function (element, text, options) {
11697 options = $.extend({}, options || {}, {text: text});
11698 var that = fluid.getAriaLabeller(element);
11699 if (!that) {
11700 that = fluid.ariaLabeller(element, options);
11701 fluid.setScopedData(element, LABEL_KEY, that);
11702 } else {
11703 that.update(options);
11704 }
11705 return that;
11706 };
11707
11708 /* "Global Dismissal Handler" for the entire page. Attaches a click handler to the
11709 * document root that will cause dismissal of any elements (typically dialogs) which
11710 * have registered themselves. Dismissal through this route will automatically clean up
11711 * the record - however, the dismisser themselves must take care to deregister in the case
11712 * dismissal is triggered through the dialog interface itself. This component can also be
11713 * automatically configured by fluid.deadMansBlur by means of the "cancelByDefault" option */
11714
11715 var dismissList = {};
11716
11717 $(document).click(function (event) {
11718 var target = fluid.resolveEventTarget(event);
11719 while (target) {
11720 if (dismissList[target.id]) {
11721 return;
11722 }
11723 target = target.parentNode;
11724 }
11725 fluid.each(dismissList, function (dismissFunc, key) {
11726 dismissFunc(event);
11727 delete dismissList[key];
11728 });
11729 });
11730 // TODO: extend a configurable equivalent of the above dealing with "focusin" events
11731
11732 /* Accepts a free hash of nodes and an optional "dismissal function".
11733 * If dismissFunc is set, this "arms" the dismissal system, such that when a click
11734 * is received OUTSIDE any of the hierarchy covered by "nodes", the dismissal function
11735 * will be executed.
11736 */
11737 fluid.globalDismissal = function (nodes, dismissFunc) {
11738 fluid.each(nodes, function (node) {
11739 // Don't bother to use the real id if it is from a foreign document - we will never receive events
11740 // from it directly in any case - and foreign documents may be under the control of malign fiends
11741 // such as tinyMCE who allocate the same id to everything
11742 var id = fluid.unwrap(node).ownerDocument === document ? fluid.allocateSimpleId(node) : fluid.allocateGuid();
11743 if (dismissFunc) {
11744 dismissList[id] = dismissFunc;
11745 }
11746 else {
11747 delete dismissList[id];
11748 }
11749 });
11750 };
11751
11752 /* Provides an abstraction for determing the current time.
11753 * This is to provide a fix for FLUID-4762, where IE6 - IE8
11754 * do not support Date.now().
11755 */
11756 fluid.now = function () {
11757 return Date.now ? Date.now() : (new Date()).getTime();
11758 };
11759
11760
11761 /* Sets an interation on a target control, which morally manages a "blur" for
11762 * a possibly composite region.
11763 * A timed blur listener is set on the control, which waits for a short period of
11764 * time (options.delay, defaults to 150ms) to discover whether the reason for the
11765 * blur interaction is that either a focus or click is being serviced on a nominated
11766 * set of "exclusions" (options.exclusions, a free hash of elements or jQueries).
11767 * If no such event is received within the window, options.handler will be called
11768 * with the argument "control", to service whatever interaction is required of the
11769 * blur.
11770 */
11771
11772 fluid.deadMansBlur = function (control, options) {
11773 // TODO: This should be rewritten as a proper component
11774 var that = {options: $.extend(true, {}, fluid.defaults("fluid.deadMansBlur"), options)};
11775 that.blurPending = false;
11776 that.lastCancel = 0;
11777 that.canceller = function (event) {
11778 fluid.log("Cancellation through " + event.type + " on " + fluid.dumpEl(event.target));
11779 that.lastCancel = fluid.now();
11780 that.blurPending = false;
11781 };
11782 that.noteProceeded = function () {
11783 fluid.globalDismissal(that.options.exclusions);
11784 };
11785 that.reArm = function () {
11786 fluid.globalDismissal(that.options.exclusions, that.proceed);
11787 };
11788 that.addExclusion = function (exclusions) {
11789 fluid.globalDismissal(exclusions, that.proceed);
11790 };
11791 that.proceed = function (event) {
11792 fluid.log("Direct proceed through " + event.type + " on " + fluid.dumpEl(event.target));
11793 that.blurPending = false;
11794 that.options.handler(control);
11795 };
11796 fluid.each(that.options.exclusions, function (exclusion) {
11797 exclusion = $(exclusion);
11798 fluid.each(exclusion, function (excludeEl) {
11799 $(excludeEl).on("focusin", that.canceller).
11800 on("fluid-focus", that.canceller).
11801 click(that.canceller).mousedown(that.canceller);
11802 // Mousedown is added for FLUID-4212, as a result of Chrome bug 6759, 14204
11803 });
11804 });
11805 if (!that.options.cancelByDefault) {
11806 $(control).on("focusout", function (event) {
11807 fluid.log("Starting blur timer for element " + fluid.dumpEl(event.target));
11808 var now = fluid.now();
11809 fluid.log("back delay: " + (now - that.lastCancel));
11810 if (now - that.lastCancel > that.options.backDelay) {
11811 that.blurPending = true;
11812 }
11813 setTimeout(function () {
11814 if (that.blurPending) {
11815 that.options.handler(control);
11816 }
11817 }, that.options.delay);
11818 });
11819 }
11820 else {
11821 that.reArm();
11822 }
11823 return that;
11824 };
11825
11826 fluid.defaults("fluid.deadMansBlur", {
11827 gradeNames: "fluid.function",
11828 delay: 150,
11829 backDelay: 100
11830 });
11831
11832})(jQuery, fluid_3_0_0);
11833;
11834/*
11835Copyright The Infusion copyright holders
11836See the AUTHORS.md file at the top-level directory of this distribution and at
11837https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
11838
11839Licensed under the Educational Community License (ECL), Version 2.0 or the New
11840BSD license. You may not use this file except in compliance with one these
11841Licenses.
11842
11843You may obtain a copy of the ECL 2.0 License and BSD License at
11844https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
11845*/
11846
11847var fluid_3_0_0 = fluid_3_0_0 || {};
11848
11849(function ($, fluid) {
11850 "use strict";
11851
11852 /** NOTE: All contents of this file are DEPRECATED and no entry point should be considered a supported API **/
11853
11854 fluid.explodeLocalisedName = function (fileName, locale, defaultLocale) {
11855 var lastDot = fileName.lastIndexOf(".");
11856 if (lastDot === -1 || lastDot === 0) {
11857 lastDot = fileName.length;
11858 }
11859 var baseName = fileName.substring(0, lastDot);
11860 var extension = fileName.substring(lastDot);
11861
11862 var segs = locale.split("_");
11863
11864 var exploded = fluid.transform(segs, function (seg, index) {
11865 var shortSegs = segs.slice(0, index + 1);
11866 return baseName + "_" + shortSegs.join("_") + extension;
11867 });
11868 if (defaultLocale) {
11869 exploded.unshift(baseName + "_" + defaultLocale + extension);
11870 }
11871 return exploded;
11872 };
11873
11874 /** Framework-global caching state for fluid.fetchResources **/
11875
11876 var resourceCache = {};
11877
11878 var pendingClass = {};
11879
11880 /** Accepts a hash of structures with free keys, where each entry has either
11881 * href/url or nodeId set - on completion, callback will be called with the populated
11882 * structure with fetched resource text in the field "resourceText" for each
11883 * entry. Each structure may contain "options" holding raw options to be forwarded
11884 * to jQuery.ajax().
11885 */
11886
11887 fluid.fetchResources = function (resourceSpecs, callback, options) {
11888 var that = {
11889 options: fluid.copy(options || {})
11890 };
11891 that.resourceSpecs = resourceSpecs;
11892 that.callback = callback;
11893 that.operate = function () {
11894 fluid.fetchResources.fetchResourcesImpl(that);
11895 };
11896 fluid.each(resourceSpecs, function (resourceSpec, key) {
11897 resourceSpec.recurseFirer = fluid.makeEventFirer({name: "I/O completion for resource \"" + key + "\""});
11898 resourceSpec.recurseFirer.addListener(that.operate);
11899 if (resourceSpec.url && !resourceSpec.href) {
11900 resourceSpec.href = resourceSpec.url;
11901 }
11902
11903 // If options.defaultLocale is set, it will replace any
11904 // defaultLocale set on an individual resourceSpec
11905 if (that.options.defaultLocale) {
11906 resourceSpec.defaultLocale = that.options.defaultLocale;
11907 }
11908 if (!resourceSpec.locale) {
11909 resourceSpec.locale = resourceSpec.defaultLocale;
11910 }
11911
11912 });
11913 if (that.options.amalgamateClasses) {
11914 fluid.fetchResources.amalgamateClasses(resourceSpecs, that.options.amalgamateClasses, that.operate);
11915 }
11916 fluid.fetchResources.explodeForLocales(resourceSpecs);
11917 that.operate();
11918 return that;
11919 };
11920
11921 fluid.fetchResources.explodeForLocales = function (resourceSpecs) {
11922 fluid.each(resourceSpecs, function (resourceSpec, key) {
11923 if (resourceSpec.locale) {
11924 var exploded = fluid.explodeLocalisedName(resourceSpec.href, resourceSpec.locale, resourceSpec.defaultLocale);
11925 for (var i = 0; i < exploded.length; ++i) {
11926 var newKey = key + "$localised-" + i;
11927 var newRecord = $.extend(true, {}, resourceSpec, {
11928 href: exploded[i],
11929 localeExploded: true
11930 });
11931 resourceSpecs[newKey] = newRecord;
11932 }
11933 resourceSpec.localeExploded = exploded.length;
11934 }
11935 });
11936 return resourceSpecs;
11937 };
11938
11939 fluid.fetchResources.condenseOneResource = function (resourceSpecs, resourceSpec, key, localeCount) {
11940 var localeSpecs = [resourceSpec];
11941 for (var i = 0; i < localeCount; ++i) {
11942 var localKey = key + "$localised-" + i;
11943 localeSpecs.unshift(resourceSpecs[localKey]);
11944 delete resourceSpecs[localKey];
11945 }
11946 var lastNonError = fluid.find_if(localeSpecs, function (spec) {
11947 return !spec.fetchError;
11948 });
11949 if (lastNonError) {
11950 resourceSpecs[key] = lastNonError;
11951 }
11952 };
11953
11954 fluid.fetchResources.condenseForLocales = function (resourceSpecs) {
11955 fluid.each(resourceSpecs, function (resourceSpec, key) {
11956 if (typeof(resourceSpec.localeExploded) === "number") {
11957 fluid.fetchResources.condenseOneResource(resourceSpecs, resourceSpec, key, resourceSpec.localeExploded);
11958 }
11959 });
11960 };
11961
11962 fluid.fetchResources.notifyResources = function (that, resourceSpecs, callback) {
11963 fluid.fetchResources.condenseForLocales(resourceSpecs);
11964 callback(resourceSpecs);
11965 };
11966
11967 /*
11968 * This function is unsupported: It is not really intended for use by implementors.
11969 */
11970 // Add "synthetic" elements of *this* resourceSpec list corresponding to any
11971 // still pending elements matching the PROLEPTICK CLASS SPECIFICATION supplied
11972 fluid.fetchResources.amalgamateClasses = function (specs, classes, operator) {
11973 fluid.each(classes, function (clazz) {
11974 var pending = pendingClass[clazz];
11975 fluid.each(pending, function (pendingrec, canon) {
11976 specs[clazz + "!" + canon] = pendingrec;
11977 pendingrec.recurseFirer.addListener(operator);
11978 });
11979 });
11980 };
11981
11982 /*
11983 * This function is unsupported: It is not really intended for use by implementors.
11984 */
11985 fluid.fetchResources.timeSuccessCallback = function (resourceSpec) {
11986 if (resourceSpec.timeSuccess && resourceSpec.options && resourceSpec.options.success) {
11987 var success = resourceSpec.options.success;
11988 resourceSpec.options.success = function () {
11989 var startTime = new Date();
11990 var ret = success.apply(null, arguments);
11991 fluid.log("External callback for URL " + resourceSpec.href + " completed - callback time: " +
11992 (new Date().getTime() - startTime.getTime()) + "ms");
11993 return ret;
11994 };
11995 }
11996 };
11997
11998 // TODO: Integrate punch-through from old Engage implementation
11999 function canonUrl(url) {
12000 return url;
12001 }
12002
12003 fluid.fetchResources.clearResourceCache = function (url) {
12004 if (url) {
12005 delete resourceCache[canonUrl(url)];
12006 }
12007 else {
12008 fluid.clear(resourceCache);
12009 }
12010 };
12011
12012 /*
12013 * This function is unsupported: It is not really intended for use by implementors.
12014 */
12015 fluid.fetchResources.handleCachedRequest = function (resourceSpec, response, fetchError) {
12016 var canon = canonUrl(resourceSpec.href);
12017 var cached = resourceCache[canon];
12018 if (cached.$$firer$$) {
12019 fluid.log("Handling request for " + canon + " from cache");
12020 var fetchClass = resourceSpec.fetchClass;
12021 if (fetchClass && pendingClass[fetchClass]) {
12022 fluid.log("Clearing pendingClass entry for class " + fetchClass);
12023 delete pendingClass[fetchClass][canon];
12024 }
12025 var result = {response: response, fetchError: fetchError};
12026 resourceCache[canon] = result;
12027 cached.fire(response, fetchError);
12028 }
12029 };
12030
12031 /*
12032 * This function is unsupported: It is not really intended for use by implementors.
12033 */
12034 fluid.fetchResources.completeRequest = function (thisSpec) {
12035 thisSpec.queued = false;
12036 thisSpec.completeTime = new Date();
12037 fluid.log("Request to URL " + thisSpec.href + " completed - total elapsed time: " +
12038 (thisSpec.completeTime.getTime() - thisSpec.initTime.getTime()) + "ms");
12039 thisSpec.recurseFirer.fire();
12040 };
12041
12042 /*
12043 * This function is unsupported: It is not really intended for use by implementors.
12044 */
12045 fluid.fetchResources.makeResourceCallback = function (thisSpec) {
12046 return {
12047 success: function (response) {
12048 thisSpec.resourceText = response;
12049 thisSpec.resourceKey = thisSpec.href;
12050 if (thisSpec.forceCache) {
12051 fluid.fetchResources.handleCachedRequest(thisSpec, response);
12052 }
12053 fluid.fetchResources.completeRequest(thisSpec);
12054 },
12055 error: function (response, textStatus, errorThrown) {
12056 thisSpec.fetchError = {
12057 status: response.status,
12058 textStatus: response.textStatus,
12059 errorThrown: errorThrown
12060 };
12061 if (thisSpec.forceCache) {
12062 fluid.fetchResources.handleCachedRequest(thisSpec, null, thisSpec.fetchError);
12063 }
12064 fluid.fetchResources.completeRequest(thisSpec);
12065 }
12066
12067 };
12068 };
12069
12070
12071 /*
12072 * This function is unsupported: It is not really intended for use by implementors.
12073 */
12074 fluid.fetchResources.issueCachedRequest = function (resourceSpec, options) {
12075 var canon = canonUrl(resourceSpec.href);
12076 var cached = resourceCache[canon];
12077 if (!cached) {
12078 fluid.log("First request for cached resource with url " + canon);
12079 cached = fluid.makeEventFirer({name: "cache notifier for resource URL " + canon});
12080 cached.$$firer$$ = true;
12081 resourceCache[canon] = cached;
12082 var fetchClass = resourceSpec.fetchClass;
12083 if (fetchClass) {
12084 if (!pendingClass[fetchClass]) {
12085 pendingClass[fetchClass] = {};
12086 }
12087 pendingClass[fetchClass][canon] = resourceSpec;
12088 }
12089 options.cache = false; // TODO: Getting weird "not modified" issues on Firefox
12090 $.ajax(options);
12091 }
12092 else {
12093 if (!cached.$$firer$$) {
12094 if (cached.response) {
12095 options.success(cached.response);
12096 } else {
12097 options.error(cached.fetchError);
12098 }
12099 }
12100 else {
12101 fluid.log("Request for cached resource which is in flight: url " + canon);
12102 cached.addListener(function (response, fetchError) {
12103 if (response) {
12104 options.success(response);
12105 } else {
12106 options.error(fetchError);
12107 }
12108 });
12109 }
12110 }
12111 };
12112
12113 /*
12114 * This function is unsupported: It is not really intended for use by implementors.
12115 */
12116 // Compose callbacks in such a way that the 2nd, marked "external" will be applied
12117 // first if it exists, but in all cases, the first, marked internal, will be
12118 // CALLED WITHOUT FAIL
12119 fluid.fetchResources.composeCallbacks = function (internal, external) {
12120 return external ? (internal ?
12121 function () {
12122 try {
12123 external.apply(null, arguments);
12124 }
12125 catch (e) {
12126 fluid.log("Exception applying external fetchResources callback: " + e);
12127 }
12128 internal.apply(null, arguments); // call the internal callback without fail
12129 } : external ) : internal;
12130 };
12131
12132 // unsupported, NON-API function
12133 fluid.fetchResources.composePolicy = function (target, source) {
12134 return fluid.fetchResources.composeCallbacks(target, source);
12135 };
12136
12137 fluid.defaults("fluid.fetchResources.issueRequest", {
12138 mergePolicy: {
12139 success: fluid.fetchResources.composePolicy,
12140 error: fluid.fetchResources.composePolicy,
12141 url: "reverse"
12142 }
12143 });
12144
12145 // unsupported, NON-API function
12146 fluid.fetchResources.issueRequest = function (resourceSpec, key) {
12147 var thisCallback = fluid.fetchResources.makeResourceCallback(resourceSpec);
12148 var options = {
12149 url: resourceSpec.href,
12150 success: thisCallback.success,
12151 error: thisCallback.error,
12152 dataType: resourceSpec.dataType || "text"
12153 };
12154 fluid.fetchResources.timeSuccessCallback(resourceSpec);
12155 options = fluid.merge(fluid.defaults("fluid.fetchResources.issueRequest").mergePolicy,
12156 options, resourceSpec.options);
12157 resourceSpec.queued = true;
12158 resourceSpec.initTime = new Date();
12159 fluid.log("Request with key " + key + " queued for " + resourceSpec.href);
12160
12161 if (resourceSpec.forceCache) {
12162 fluid.fetchResources.issueCachedRequest(resourceSpec, options);
12163 }
12164 else {
12165 $.ajax(options);
12166 }
12167 };
12168
12169
12170 fluid.fetchResources.fetchResourcesImpl = function (that) {
12171 var complete = true;
12172 var resourceSpecs = that.resourceSpecs;
12173 for (var key in resourceSpecs) {
12174 var resourceSpec = resourceSpecs[key];
12175 if (resourceSpec.href && !resourceSpec.completeTime) {
12176 if (!resourceSpec.queued) {
12177 fluid.fetchResources.issueRequest(resourceSpec, key);
12178 }
12179 if (resourceSpec.queued) {
12180 complete = false;
12181 }
12182 }
12183 else if (resourceSpec.nodeId && !resourceSpec.resourceText) {
12184 var node = document.getElementById(resourceSpec.nodeId);
12185 // upgrade this to somehow detect whether node is "armoured" somehow
12186 // with comment or CDATA wrapping
12187 resourceSpec.resourceText = fluid.dom.getElementText(node);
12188 resourceSpec.resourceKey = resourceSpec.nodeId;
12189 }
12190 }
12191 if (complete && that.callback && !that.callbackCalled) {
12192 that.callbackCalled = true;
12193 // Always defer notification in an anti-Zalgo scheme to ease problems like FLUID-6202
12194 // In time this will be resolved by i) latched events, ii) global async ginger world
12195 setTimeout(function () {
12196 fluid.fetchResources.notifyResources(that, resourceSpecs, that.callback);
12197 }, 1);
12198 }
12199 };
12200
12201 // TODO: This framework function is a stop-gap before the "ginger world" is capable of
12202 // asynchronous instantiation. It currently performs very poor fidelity expansion of a
12203 // component's options to discover "resources" only held in the static environment
12204 fluid.fetchResources.primeCacheFromResources = function (componentName) {
12205 var resources = fluid.defaults(componentName).resources;
12206 var expanded = (fluid.expandOptions ? fluid.expandOptions : fluid.identity)(fluid.copy(resources));
12207 fluid.fetchResources(expanded);
12208 };
12209
12210 /** Utilities invoking requests for expansion **/
12211 fluid.registerNamespace("fluid.expander");
12212
12213 /*
12214 * This function is unsupported: It is not really intended for use by implementors.
12215 */
12216 fluid.expander.makeDefaultFetchOptions = function (successdisposer, failid, options) {
12217 return $.extend(true, {dataType: "text"}, options, {
12218 success: function (response, environmentdisposer) {
12219 var json = JSON.parse(response);
12220 environmentdisposer(successdisposer(json));
12221 },
12222 error: function (response, textStatus) {
12223 fluid.log("Error fetching " + failid + ": " + textStatus);
12224 }
12225 });
12226 };
12227
12228 /*
12229 * This function is unsupported: It is not really intended for use by implementors.
12230 */
12231 fluid.expander.makeFetchExpander = function (options) {
12232 return { expander: {
12233 type: "fluid.expander.deferredFetcher",
12234 href: options.url,
12235 options: fluid.expander.makeDefaultFetchOptions(options.disposer, options.url, options.options),
12236 resourceSpecCollector: "{resourceSpecCollector}",
12237 fetchKey: options.fetchKey
12238 }};
12239 };
12240
12241 fluid.expander.deferredFetcher = function (deliverer, source, expandOptions) {
12242 var expander = source.expander;
12243 var spec = fluid.copy(expander);
12244 // fetch the "global" collector specified in the external environment to receive
12245 // this resourceSpec
12246 var collector = fluid.expand(expander.resourceSpecCollector, expandOptions);
12247 delete spec.type;
12248 delete spec.resourceSpecCollector;
12249 delete spec.fetchKey;
12250 var environmentdisposer = function (disposed) {
12251 deliverer(disposed);
12252 };
12253 // replace the callback which is there (taking 2 arguments) with one which
12254 // directly responds to the request, passing in the result and OUR "disposer" -
12255 // which once the user has processed the response (say, parsing JSON and repackaging)
12256 // finally deposits it in the place of the expander in the tree to which this reference
12257 // has been stored at the point this expander was evaluated.
12258 spec.options.success = function (response) {
12259 expander.options.success(response, environmentdisposer);
12260 };
12261 var key = expander.fetchKey || fluid.allocateGuid();
12262 collector[key] = spec;
12263 return fluid.NO_VALUE;
12264 };
12265
12266
12267})(jQuery, fluid_3_0_0);
12268;
12269/*
12270Copyright The Infusion copyright holders
12271See the AUTHORS.md file at the top-level directory of this distribution and at
12272https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
12273
12274Licensed under the Educational Community License (ECL), Version 2.0 or the New
12275BSD license. You may not use this file except in compliance with one these
12276Licenses.
12277
12278You may obtain a copy of the ECL 2.0 License and BSD License at
12279https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
12280*/
12281
12282var fluid_3_0_0 = fluid_3_0_0 || {};
12283
12284(function ($, fluid) {
12285 "use strict";
12286
12287 fluid.defaults("fluid.messageResolver", {
12288 gradeNames: ["fluid.component"],
12289 mergePolicy: {
12290 messageBase: "nomerge",
12291 parents: "nomerge"
12292 },
12293 resolveFunc: fluid.stringTemplate,
12294 parseFunc: fluid.identity,
12295 messageBase: {},
12296 members: {
12297 messageBase: "@expand:{that}.options.parseFunc({that}.options.messageBase)"
12298 },
12299 invokers: {
12300 lookup: "fluid.messageResolver.lookup({that}, {arguments}.0)", // messagecodes
12301 resolve: "fluid.messageResolver.resolve({that}, {arguments}.0, {arguments}.1)" // messagecodes, args
12302 },
12303 parents: []
12304 });
12305
12306 /**
12307 *
12308 * Look up the first matching message template, starting with the current grade and working up through its parents.
12309 * Returns both the template for the message and the function used to resolve the localised value. By default
12310 * the resolve function is `fluid.stringTemplate`, and the template returned uses its syntax.
12311 *
12312 * @param {Object} that - The component itself.
12313 * @param {Array} messagecodes - One or more message codes to look up templates for.
12314 * @return {Object} - An object that contains`template` and `resolveFunc` members (see above).
12315 *
12316 */
12317 fluid.messageResolver.lookup = function (that, messagecodes) {
12318 var resolved = fluid.messageResolver.resolveOne(that.messageBase, messagecodes);
12319 if (resolved === undefined) {
12320 return fluid.find(that.options.parents, function (parent) {
12321 return parent ? parent.lookup(messagecodes) : undefined;
12322 });
12323 } else {
12324 return {template: resolved, resolveFunc: that.options.resolveFunc};
12325 }
12326 };
12327
12328 /**
12329 *
12330 * Look up the first message that corresponds to a message code found in `messageCodes`. Then, resolve its
12331 * localised value. By default, supports variable substitutions using `fluid.stringTemplate`.
12332 *
12333 * @param {Object} that - The component itself.
12334 * @param {Array} messagecodes - A list of message codes to look for.
12335 * @param {Object} args - A map of variables that may potentially be used as part of the final output.
12336 * @return {String} - The final message, localised, with any variables found in `args`.
12337 *
12338 */
12339 fluid.messageResolver.resolve = function (that, messagecodes, args) {
12340 if (!messagecodes) {
12341 return "[No messagecodes provided]";
12342 }
12343 messagecodes = fluid.makeArray(messagecodes);
12344 var looked = that.lookup(messagecodes);
12345 return looked ? looked.resolveFunc(looked.template, args) :
12346 "[Message string for key " + messagecodes[0] + " not found]";
12347 };
12348
12349
12350 // unsupported, NON-API function
12351 fluid.messageResolver.resolveOne = function (messageBase, messagecodes) {
12352 for (var i = 0; i < messagecodes.length; ++i) {
12353 var code = messagecodes[i];
12354 var message = messageBase[code];
12355 if (message !== undefined) {
12356 return message;
12357 }
12358 }
12359 };
12360
12361 /**
12362 *
12363 * Converts a data structure consisting of a mapping of keys to message strings, into a "messageLocator" function
12364 * which maps an array of message codes, to be tried in sequence until a key is found, and an array of substitution
12365 * arguments, into a substituted message string.
12366 *
12367 * @param {Object} messageBase - A body of messages to wrap in a resolver function.
12368 * @param {Function} resolveFunc (Optional) - A "resolver" function to use instead of the default `fluid.stringTemplate`.
12369 * @return {Function} - A "messageLocator" function (see above).
12370 *
12371 */
12372 fluid.messageLocator = function (messageBase, resolveFunc) {
12373 var resolver = fluid.messageResolver({messageBase: messageBase, resolveFunc: resolveFunc});
12374 return function (messagecodes, args) {
12375 return resolver.resolve(messagecodes, args);
12376 };
12377 };
12378
12379 /**
12380 *
12381 * Resolve a "message source", which is either itself a resolver, or an object representing a bundle of messages
12382 * and the associated resolution function.
12383 *
12384 * When passing a "data" object, it is expected to have a `type` element that is set to `data`, and to have a
12385 * `messages` array and a `resolveFunc` function that can be used to resolve messages.
12386 *
12387 * A "resolver" is expected to be an object with a `type` element that is set to `resolver` that exposes a `resolve`
12388 * function.
12389 *
12390 * @param {Object} messageSource - See above.
12391 * @return {Function|String} - A resolve function or a `String` representing the final resolved output.
12392 *
12393 */
12394 fluid.resolveMessageSource = function (messageSource) {
12395 if (messageSource.type === "data") {
12396 if (messageSource.url === undefined) {
12397 return fluid.messageLocator(messageSource.messages, messageSource.resolveFunc);
12398 }
12399 else {
12400 // TODO: fetch via AJAX, and convert format if necessary
12401 }
12402 }
12403 else if (messageSource.type === "resolver") {
12404 return messageSource.resolver.resolve;
12405 }
12406 };
12407})(jQuery, fluid_3_0_0);
12408;
12409/*
12410Copyright The Infusion copyright holders
12411See the AUTHORS.md file at the top-level directory of this distribution and at
12412https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
12413
12414Licensed under the Educational Community License (ECL), Version 2.0 or the New
12415BSD license. You may not use this file except in compliance with one these
12416Licenses.
12417
12418You may obtain a copy of the ECL 2.0 License and BSD License at
12419https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
12420*/
12421
12422var fluid_3_0_0 = fluid_3_0_0 || {};
12423
12424(function ($, fluid) {
12425
12426 "use strict";
12427
12428 /**
12429 * A configurable component to allow users to load multiple resources via AJAX requests.
12430 * The resources can be localised by means of options `locale`, `defaultLocale`. Once all
12431 * resources are loaded, the event `onResourceLoaded` will be fired, which can be used
12432 * to time the creation of components dependent on the resources.
12433 *
12434 * @param {Object} options - The component options.
12435 */
12436 fluid.defaults("fluid.resourceLoader", {
12437 gradeNames: ["fluid.component"],
12438 listeners: {
12439 "onCreate.loadResources": {
12440 listener: "fluid.resourceLoader.loadResources",
12441 args: ["{that}", {expander: {func: "{that}.resolveResources"}}]
12442 }
12443 },
12444 defaultLocale: null,
12445 locale: null,
12446 terms: {}, // Must be supplied by integrators
12447 resources: {}, // Must be supplied by integrators
12448 resourceOptions: {},
12449 // Unsupported, non-API option
12450 invokers: {
12451 transformURL: {
12452 funcName: "fluid.stringTemplate",
12453 args: ["{arguments}.0", "{that}.options.terms"]
12454 },
12455 resolveResources: {
12456 funcName: "fluid.resourceLoader.resolveResources",
12457 args: "{that}"
12458 }
12459 },
12460 events: {
12461 onResourcesLoaded: null
12462 }
12463 });
12464
12465 fluid.resourceLoader.resolveResources = function (that) {
12466 var mapped = fluid.transform(that.options.resources, that.transformURL);
12467
12468 return fluid.transform(mapped, function (url) {
12469 var resourceSpec = {url: url, forceCache: true, options: that.options.resourceOptions};
12470 return $.extend(resourceSpec, fluid.filterKeys(that.options, ["defaultLocale", "locale"]));
12471 });
12472 };
12473
12474 fluid.resourceLoader.loadResources = function (that, resources) {
12475 fluid.fetchResources(resources, function () {
12476 that.resources = resources;
12477 that.events.onResourcesLoaded.fire(resources);
12478 });
12479 };
12480
12481})(jQuery, fluid_3_0_0);
12482;
12483/*
12484Copyright The Infusion copyright holders
12485See the AUTHORS.md file at the top-level directory of this distribution and at
12486https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
12487
12488Licensed under the Educational Community License (ECL), Version 2.0 or the New
12489BSD license. You may not use this file except in compliance with one these
12490Licenses.
12491
12492You may obtain a copy of the ECL 2.0 License and BSD License at
12493https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
12494*/
12495
12496/*
12497 The contents of this file were adapted from ViewComponentSupport.js and ComponentGraph.js in fluid-authoring
12498 See: https://github.com/fluid-project/fluid-authoring/blob/FLUID-4884/src/js/ViewComponentSupport.js
12499 https://github.com/fluid-project/fluid-authoring/blob/FLUID-4884/src/js/ComponentGraph.js
12500*/
12501
12502var fluid_3_0_0 = fluid_3_0_0 || {};
12503
12504(function ($, fluid) {
12505 "use strict";
12506
12507 /**
12508 * A variant of fluid.viewComponent that bypasses the wacky "initView" and variant signature
12509 * workflow, sourcing instead its "container" from an option of that name, so that this argument
12510 * can participate in standard ginger resolution. This enables useful results such as a component
12511 * which can render its own container into the DOM on startup, whilst the container remains immutable.
12512 */
12513 fluid.defaults("fluid.newViewComponent", {
12514 gradeNames: ["fluid.modelComponent"],
12515 members: {
12516 // 3rd argument is throwaway to force evaluation of container
12517 dom: "@expand:fluid.initDomBinder({that}, {that}.options.selectors, {that}.container)",
12518 container: "@expand:fluid.container({that}.options.container)"
12519 }
12520 });
12521
12522 /**
12523 * Used to add an element to a parent container. Internally it can use either of jQuery's prepend or append methods.
12524 *
12525 * @param {jQuery|DOMElement|Selector} parentContainer - any jQueryable selector representing the parent element to
12526 * inject the `elm` into.
12527 * @param {DOMElement|jQuery} elm - a DOM element or jQuery element to be added to the parent.
12528 * @param {String} method - (optional) a string representing the method to use to add the `elm` to the
12529 * `parentContainer`. The method can be "append" (default), "prepend", or "html" (will
12530 * replace the contents).
12531 */
12532 fluid.newViewComponent.addToParent = function (parentContainer, elm, method) {
12533 method = method || "append";
12534 $(parentContainer)[method](elm);
12535 };
12536
12537 /**
12538 * Similar to fluid.newViewComponent; however, it will render its own markup including its container, into a
12539 * specified parent container.
12540 */
12541 fluid.defaults("fluid.containerRenderingView", {
12542 gradeNames: ["fluid.newViewComponent"],
12543 container: "@expand:{that}.renderContainer()",
12544 // The DOM element which this component should inject its markup into on startup
12545 parentContainer: "fluid.notImplemented", // must be overridden
12546 injectionType: "append",
12547 invokers: {
12548 renderMarkup: "fluid.identity({that}.options.markup.container)",
12549 renderContainer: "fluid.containerRenderingView.renderContainer({that}, {that}.renderMarkup, {that}.addToParent)",
12550 addToParent: {
12551 funcName: "fluid.newViewComponent.addToParent",
12552 args: ["{that}.options.parentContainer", "{arguments}.0", "{that}.options.injectionType"]
12553 }
12554 }
12555 });
12556
12557 /**
12558 * Renders the components markup and inserts it into the parent container based on the addToParent method
12559 *
12560 * @param {Component} that - the component
12561 * @param {Function} renderMarkup - a function returning the components container markup to be inserted into the
12562 * parentContainer element
12563 * @param {Function} addToParent - a function that inserts the container into the DOM
12564 * @return {DOMElement} - the container
12565 */
12566 fluid.containerRenderingView.renderContainer = function (that, renderMarkup, addToParent) {
12567 fluid.log("Rendering container for " + that.id);
12568 var containerMarkup = renderMarkup();
12569 var container = $(containerMarkup);
12570 addToParent(container);
12571 return container;
12572 };
12573
12574 /**
12575 * Similar to fluid.newViewComponent; however, it will fetch a template and render it into the container.
12576 *
12577 * The template path must be supplied either via a top level `template` option or directly to the
12578 * `resources.template` option. The path may optionally include "terms" to use as tokens which will be resolved
12579 * from values specified in the `terms` option.
12580 *
12581 * The template is fetched on creation and rendered into the container after it has been fetched. After rendering
12582 * the `afterRender` event is fired.
12583 */
12584 fluid.defaults("fluid.templateRenderingView", {
12585 gradeNames: ["fluid.newViewComponent", "fluid.resourceLoader"],
12586 resources: {
12587 template: "fluid.notImplemented"
12588 },
12589 injectionType: "append",
12590 events: {
12591 afterRender: null
12592 },
12593 listeners: {
12594 "onResourcesLoaded.render": "{that}.render",
12595 "onResourcesLoaded.afterRender": {
12596 listener: "{that}.events.afterRender",
12597 args: ["{that}"],
12598 priority: "after:render"
12599 }
12600 },
12601 invokers: {
12602 render: {
12603 funcName: "fluid.newViewComponent.addToParent",
12604 args: ["{that}.container", "{that}.resources.template.resourceText", "{that}.options.injectionType"]
12605 }
12606 },
12607 distributeOptions: {
12608 "mapTemplateSource": {
12609 source: "{that}.options.template",
12610 removeSource: true,
12611 target: "{that}.options.resources.template"
12612 }
12613 }
12614 });
12615
12616})(jQuery, fluid_3_0_0);
12617;
12618/*
12619Copyright The Infusion copyright holders
12620See the AUTHORS.md file at the top-level directory of this distribution and at
12621https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
12622
12623Licensed under the Educational Community License (ECL), Version 2.0 or the New
12624BSD license. You may not use this file except in compliance with one these
12625Licenses.
12626
12627You may obtain a copy of the ECL 2.0 License and BSD License at
12628https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
12629*/
12630
12631var fluid_3_0_0 = fluid_3_0_0 || {};
12632
12633(function ($, fluid) {
12634 "use strict";
12635
12636 /*
12637 * Provides a grade for creating and interacting with a Mutation Observer to listen/respond to DOM changes.
12638 */
12639 fluid.defaults("fluid.mutationObserver", {
12640 gradeNames: ["fluid.viewComponent"],
12641 events: {
12642 onNodeAdded: null,
12643 onNodeRemoved: null,
12644 onAttributeChanged: null
12645 },
12646 listeners: {
12647 "onDestroy.disconnect": "{that}.disconnect"
12648 },
12649 members: {
12650 observer: {
12651 expander: {
12652 func: "{that}.createObserver"
12653 }
12654 }
12655 },
12656 defaultObserveConfig: {
12657 attributes: true,
12658 childList: true,
12659 subtree: true
12660 },
12661 invokers: {
12662 observe: {
12663 funcName: "fluid.mutationObserver.observe",
12664 args: ["{that}", "{arguments}.0", "{arguments}.1"]
12665 },
12666 disconnect: {
12667 "this": "{that}.observer",
12668 method: "disconnect"
12669 },
12670 takeRecords: {
12671 "this": "{that}.observer",
12672 method: "takeRecords"
12673 },
12674 createObserver: {
12675 funcName: "fluid.mutationObserver.createObserver",
12676 args: ["{that}"]
12677 }
12678 }
12679 });
12680
12681 /**
12682 * A Mutation Observer; allows for tracking changes to the DOM.
12683 * A mutation observer is created with a callback function, configured through its `observe` method.
12684 * See: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
12685 *
12686 * @typedef {Object} MutationObserver
12687 */
12688
12689 /**
12690 * Instantiates a mutation observer, defining the callback function which relays the observations to component
12691 * events. The configuration passed to the observe function will determine what mutations are observed and what
12692 * information is returned. See `fluid.mutationObserver.observe` about configuring the mutation observer.
12693 *
12694 * Event Mapping:
12695 * onNodeAdded - fired for each added node, includes the node and mutation record
12696 * onNodeRemoved - fired for each removed node, includes the node and mutation record
12697 * onAttributeChanged - fired for each attribute change, includes the node and mutation record
12698 *
12699 * @param {Component} that - an instance of `fluid.mutationObserver`
12700 *
12701 * @return {MutationObserver} - the created mutation observer`
12702 */
12703 fluid.mutationObserver.createObserver = function (that) {
12704 var observer = new MutationObserver(function (mutationRecords) {
12705 fluid.each(mutationRecords, function (mutationRecord) {
12706 // IE11 doesn't support forEach on NodeLists and NodeLists aren't real arrays so using fluid.each
12707 // will iterate over the object properties. Therefore we use a for loop to iterate over the nodes.
12708 for (var i = 0; i < mutationRecord.addedNodes.length; i++) {
12709 that.events.onNodeAdded.fire(mutationRecord.addedNodes[i], mutationRecord);
12710 }
12711 for (var j = 0; j < mutationRecord.removedNodes.length; j++) {
12712 that.events.onNodeRemoved.fire(mutationRecord.removedNodes[j], mutationRecord);
12713 }
12714 if (mutationRecord.type === "attributes") {
12715 that.events.onAttributeChanged.fire(mutationRecord.target, mutationRecord);
12716 }
12717 });
12718 });
12719
12720 return observer;
12721 };
12722
12723 /**
12724 * Starts observing the DOM changes. Optionally takes in a target and configuration for setting up the specific
12725 * observation. The observe method may be called multiple times; however, if the same observer is set on the same
12726 * node, the old one will be replaced. If an observation is disconnected, the observe method will need to be called
12727 * again to re-instate the mutation observation.
12728 * See: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver/observe
12729 *
12730 * @param {Component} that - an instance of `fluid.mutationObserver`
12731 * @param {DOMElement|jQuery} target - a DOM element or jQuery element to be observed. By default the component's
12732 * container element is used.
12733 * @param {Object} options - config options to pass to the observations. This specifies which mutations should be
12734 * reported. See: https://developer.mozilla.org/en-US/docs/Web/API/MutationObserverInit
12735 * By default the config specified at `that.options.defaultObserveConfig` is used.
12736 */
12737 fluid.mutationObserver.observe = function (that, target, options) {
12738 target = fluid.unwrap(target || that.container);
12739 that.observer.observe(target, options || that.options.defaultObserveConfig);
12740 };
12741
12742
12743})(jQuery, fluid_3_0_0);
12744;
12745/*
12746Copyright The Infusion copyright holders
12747See the AUTHORS.md file at the top-level directory of this distribution and at
12748https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
12749
12750Licensed under the Educational Community License (ECL), Version 2.0 or the New
12751BSD license. You may not use this file except in compliance with one these
12752Licenses.
12753
12754You may obtain a copy of the ECL 2.0 License and BSD License at
12755https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
12756*/
12757
12758var fluid_3_0_0 = fluid_3_0_0 || {};
12759
12760(function ($, fluid) {
12761 "use strict";
12762
12763 /*******************************************************************************
12764 * fluid.textNodeParser
12765 *
12766 * Parses out the text nodes from a DOM element and its descendants
12767 *******************************************************************************/
12768
12769 fluid.defaults("fluid.textNodeParser", {
12770 gradeNames: ["fluid.component"],
12771 events: {
12772 onParsedTextNode: null,
12773 afterParse: null
12774 },
12775 invokers: {
12776 parse: {
12777 funcName: "fluid.textNodeParser.parse",
12778 args: ["{that}", "{arguments}.0", "{arguments}.1", "{that}.events.afterParse.fire"]
12779 },
12780 hasTextToRead: "fluid.textNodeParser.hasTextToRead",
12781 isWord: "fluid.textNodeParser.isWord",
12782 getLang: "fluid.textNodeParser.getLang"
12783 }
12784 });
12785
12786 /**
12787 * Tests if a string is a word; i.e. it has a value and is not only whitespace.
12788 * inspired by https://stackoverflow.com/a/2031143
12789 *
12790 * @param {String} str - the String to test
12791 *
12792 * @return {Boolean} - `true` if a word, `false` otherwise.
12793 */
12794 fluid.textNodeParser.isWord = function (str) {
12795 return fluid.isValue(str) && /\S/.test(str);
12796 };
12797
12798 /**
12799 * Determines if there is text in an element that should be read.
12800 * Will return false in the following conditions:
12801 * - elm is falsey (undefined, null, etc.)
12802 * - elm's offsetHeight is 0 (e.g. display none set on itself or its parent)
12803 * - elm has no text or only whitespace
12804 *
12805 * NOTE: Text added by pseudo elements (e.g. :before, :after) are not considered.
12806 * NOTE: This method is not supported in IE 11 because innerText returns the text for some hidden elements
12807 * that is inconsistent with modern browsers.
12808 *
12809 * @param {jQuery|DomElement} elm - either a DOM node or a jQuery element
12810 *
12811 * @return {Boolean} - returns true if there is rendered text within the element and false otherwise.
12812 * (See rules above)
12813 */
12814 fluid.textNodeParser.hasTextToRead = function (elm) {
12815 elm = fluid.unwrap(elm);
12816
12817 return elm &&
12818 !!elm.offsetHeight &&
12819 fluid.textNodeParser.isWord(elm.innerText);
12820 };
12821
12822 /**
12823 * Similar to `fluid.textNodeParser.hasTextToRead` except adds the following condition:
12824 * - elm or its parent has `aria-hidden="true"` set.
12825 *
12826 * @param {jQuery|DomElement} elm - either a DOM node or a jQuery element
12827 *
12828 * @return {Boolean} - returns true if there is rendered text within the element and false otherwise.
12829 * (See rules above)
12830 */
12831 fluid.textNodeParser.hasVisibleText = function (elm) {
12832 return fluid.textNodeParser.hasTextToRead(elm) && !$(elm).closest("[aria-hidden=\"true\"]").length;
12833 };
12834
12835 /**
12836 * Uses jQuery's `closest` method to find the closest element with a lang attribute, and returns the value.
12837 *
12838 * @param {jQuery|DomElement} elm - either a DOM node or a jQuery element
12839 *
12840 * @return {String|Undefined} - a valid BCP 47 language code if found, otherwise undefined.
12841 */
12842 fluid.textNodeParser.getLang = function (elm) {
12843 return $(elm).closest("[lang]").attr("lang");
12844 };
12845
12846 /**
12847 * Recursively parses a DOM element and it's sub elements and fires the `onParsedTextNode` event for each text node
12848 * found. The event is fired with the text node, language and index of the text node in the list of its parent's
12849 * child nodes..
12850 *
12851 * Note: elements that return `false` to `that.hasTextToRead` are ignored.
12852 *
12853 * @param {Component} that - an instance of `fluid.textNodeParser`
12854 * @param {jQuery|DomElement} elm - the DOM node to parse
12855 * @param {String} lang - a valid BCP 47 language code.
12856 * @param {Event} afterParseEvent - the event to fire after parsing has completed.
12857 */
12858 fluid.textNodeParser.parse = function (that, elm, lang, afterParseEvent) {
12859 elm = fluid.unwrap(elm);
12860 lang = lang || that.getLang(elm);
12861
12862 if (that.hasTextToRead(elm)) {
12863 var childNodes = elm.childNodes;
12864 var elementLang = elm.getAttribute("lang") || lang;
12865
12866 $.each(childNodes, function (childIndex, childNode) {
12867 if (childNode.nodeType === Node.TEXT_NODE) {
12868 that.events.onParsedTextNode.fire(childNode, elementLang, childIndex);
12869 } else if (childNode.nodeType === Node.ELEMENT_NODE) {
12870 fluid.textNodeParser.parse(that, childNode, elementLang);
12871 }
12872 });
12873 }
12874
12875 if (afterParseEvent) {
12876 afterParseEvent(that);
12877 }
12878 };
12879
12880})(jQuery, fluid_3_0_0);
12881;
12882(function () {
12883
12884var module = {
12885 exports: null
12886};
12887/**
12888 * @constructor
12889 * @param {!{patterns: !Object, leftmin: !number, rightmin: !number}} language The language pattern file. Compatible with Hyphenator.js.
12890 */
12891function Hypher(language) {
12892 var exceptions = [],
12893 i = 0;
12894 /**
12895 * @type {!Hypher.TrieNode}
12896 */
12897 this.trie = this.createTrie(language['patterns']);
12898
12899 /**
12900 * @type {!number}
12901 * @const
12902 */
12903 this.leftMin = language['leftmin'];
12904
12905 /**
12906 * @type {!number}
12907 * @const
12908 */
12909 this.rightMin = language['rightmin'];
12910
12911 /**
12912 * @type {!Object.<string, !Array.<string>>}
12913 */
12914 this.exceptions = {};
12915
12916 if (language['exceptions']) {
12917 exceptions = language['exceptions'].split(/,\s?/g);
12918
12919 for (; i < exceptions.length; i += 1) {
12920 this.exceptions[exceptions[i].replace(/\u2027/g, '').toLowerCase()] = new RegExp('(' + exceptions[i].split('\u2027').join(')(') + ')', 'i');
12921 }
12922 }
12923}
12924
12925/**
12926 * @typedef {{_points: !Array.<number>}}
12927 */
12928Hypher.TrieNode;
12929
12930/**
12931 * Creates a trie from a language pattern.
12932 * @private
12933 * @param {!Object} patternObject An object with language patterns.
12934 * @return {!Hypher.TrieNode} An object trie.
12935 */
12936Hypher.prototype.createTrie = function (patternObject) {
12937 var size = 0,
12938 i = 0,
12939 c = 0,
12940 p = 0,
12941 chars = null,
12942 points = null,
12943 codePoint = null,
12944 t = null,
12945 tree = {
12946 _points: []
12947 },
12948 patterns;
12949
12950 for (size in patternObject) {
12951 if (patternObject.hasOwnProperty(size)) {
12952 patterns = patternObject[size].match(new RegExp('.{1,' + (+size) + '}', 'g'));
12953
12954 for (i = 0; i < patterns.length; i += 1) {
12955 chars = patterns[i].replace(/[0-9]/g, '').split('');
12956 points = patterns[i].split(/\D/);
12957 t = tree;
12958
12959 for (c = 0; c < chars.length; c += 1) {
12960 codePoint = chars[c].charCodeAt(0);
12961
12962 if (!t[codePoint]) {
12963 t[codePoint] = {};
12964 }
12965 t = t[codePoint];
12966 }
12967
12968 t._points = [];
12969
12970 for (p = 0; p < points.length; p += 1) {
12971 t._points[p] = points[p] || 0;
12972 }
12973 }
12974 }
12975 }
12976 return tree;
12977};
12978
12979/**
12980 * Hyphenates a text.
12981 *
12982 * @param {!string} str The text to hyphenate.
12983 * @return {!string} The same text with soft hyphens inserted in the right positions.
12984 */
12985Hypher.prototype.hyphenateText = function (str, minLength) {
12986 minLength = minLength || 4;
12987
12988 // Regexp("\b", "g") splits on word boundaries,
12989 // compound separators and ZWNJ so we don't need
12990 // any special cases for those characters. Unfortunately
12991 // it does not support unicode word boundaries, so
12992 // we implement it manually.
12993 var words = str.split(/([a-zA-Z0-9_\u0027\u00DF-\u00EA\u00EC-\u00EF\u00F1-\u00F6\u00F8-\u00FD\u0101\u0103\u0105\u0107\u0109\u010D\u010F\u0111\u0113\u0117\u0119\u011B\u011D\u011F\u0123\u0125\u012B\u012F\u0131\u0135\u0137\u013C\u013E\u0142\u0144\u0146\u0148\u0151\u0153\u0155\u0159\u015B\u015D\u015F\u0161\u0165\u016B\u016D\u016F\u0171\u0173\u017A\u017C\u017E\u017F\u0219\u021B\u02BC\u0390\u03AC-\u03CE\u03F2\u0401\u0410-\u044F\u0451\u0454\u0456\u0457\u045E\u0491\u0531-\u0556\u0561-\u0587\u0902\u0903\u0905-\u090B\u090E-\u0910\u0912\u0914-\u0928\u092A-\u0939\u093E-\u0943\u0946-\u0948\u094A-\u094D\u0982\u0983\u0985-\u098B\u098F\u0990\u0994-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BE-\u09C3\u09C7\u09C8\u09CB-\u09CD\u09D7\u0A02\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A14-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A82\u0A83\u0A85-\u0A8B\u0A8F\u0A90\u0A94-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABE-\u0AC3\u0AC7\u0AC8\u0ACB-\u0ACD\u0B02\u0B03\u0B05-\u0B0B\u0B0F\u0B10\u0B14-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3E-\u0B43\u0B47\u0B48\u0B4B-\u0B4D\u0B57\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB5\u0BB7-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD7\u0C02\u0C03\u0C05-\u0C0B\u0C0E-\u0C10\u0C12\u0C14-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3E-\u0C43\u0C46-\u0C48\u0C4A-\u0C4D\u0C82\u0C83\u0C85-\u0C8B\u0C8E-\u0C90\u0C92\u0C94-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBE-\u0CC3\u0CC6-\u0CC8\u0CCA-\u0CCD\u0D02\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D28\u0D2A-\u0D39\u0D3E-\u0D43\u0D46-\u0D48\u0D4A-\u0D4D\u0D57\u0D60\u0D61\u0D7A-\u0D7F\u1F00-\u1F07\u1F10-\u1F15\u1F20-\u1F27\u1F30-\u1F37\u1F40-\u1F45\u1F50-\u1F57\u1F60-\u1F67\u1F70-\u1F7D\u1F80-\u1F87\u1F90-\u1F97\u1FA0-\u1FA7\u1FB2-\u1FB4\u1FB6\u1FB7\u1FBD\u1FBF\u1FC2-\u1FC4\u1FC6\u1FC7\u1FD2\u1FD3\u1FD6\u1FD7\u1FE2-\u1FE7\u1FF2-\u1FF4\u1FF6\u1FF7\u200D\u2019]+)/g);
12994
12995 for (var i = 0; i < words.length; i += 1) {
12996 if (words[i].indexOf('/') !== -1) {
12997 // Don't insert a zero width space if the slash is at the beginning or end
12998 // of the text, or right after or before a space.
12999 if (i !== 0 && i !== words.length - 1 && !(/\s+\/|\/\s+/.test(words[i]))) {
13000 words[i] += '\u200B';
13001 }
13002 } else if (words[i].length > minLength) {
13003 words[i] = this.hyphenate(words[i]).join('\u00AD');
13004 }
13005 }
13006 return words.join('');
13007};
13008
13009/**
13010 * Hyphenates a word.
13011 *
13012 * @param {!string} word The word to hyphenate
13013 * @return {!Array.<!string>} An array of word fragments indicating valid hyphenation points.
13014 */
13015Hypher.prototype.hyphenate = function (word) {
13016 var characters,
13017 characterPoints = [],
13018 originalCharacters,
13019 i,
13020 j,
13021 k,
13022 node,
13023 points = [],
13024 wordLength,
13025 lowerCaseWord = word.toLowerCase(),
13026 nodePoints,
13027 nodePointsLength,
13028 m = Math.max,
13029 trie = this.trie,
13030 result = [''];
13031
13032 if (this.exceptions.hasOwnProperty(lowerCaseWord)) {
13033 return word.match(this.exceptions[lowerCaseWord]).slice(1);
13034 }
13035
13036 if (word.indexOf('\u00AD') !== -1) {
13037 return [word];
13038 }
13039
13040 word = '_' + word + '_';
13041
13042 characters = word.toLowerCase().split('');
13043 originalCharacters = word.split('');
13044 wordLength = characters.length;
13045
13046 for (i = 0; i < wordLength; i += 1) {
13047 points[i] = 0;
13048 characterPoints[i] = characters[i].charCodeAt(0);
13049 }
13050
13051 for (i = 0; i < wordLength; i += 1) {
13052 node = trie;
13053 for (j = i; j < wordLength; j += 1) {
13054 node = node[characterPoints[j]];
13055
13056 if (node) {
13057 nodePoints = node._points;
13058 if (nodePoints) {
13059 for (k = 0, nodePointsLength = nodePoints.length; k < nodePointsLength; k += 1) {
13060 points[i + k] = m(points[i + k], nodePoints[k]);
13061 }
13062 }
13063 } else {
13064 break;
13065 }
13066 }
13067 }
13068
13069 for (i = 1; i < wordLength - 1; i += 1) {
13070 if (i > this.leftMin && i < (wordLength - this.rightMin) && points[i] % 2) {
13071 result.push(originalCharacters[i]);
13072 } else {
13073 result[result.length - 1] += originalCharacters[i];
13074 }
13075 }
13076
13077 return result;
13078};
13079
13080module.exports = Hypher;
13081window['Hypher'] = module.exports;
13082
13083window['Hypher']['languages'] = {};
13084}());(function ($) {
13085 $.fn.hyphenate = function (language) {
13086 if (window['Hypher']['languages'][language]) {
13087 return this.each(function () {
13088 var i = 0, len = this.childNodes.length;
13089 for (; i < len; i += 1) {
13090 if (this.childNodes[i].nodeType === 3) {
13091 this.childNodes[i].nodeValue = window['Hypher']['languages'][language].hyphenateText(this.childNodes[i].nodeValue);
13092 }
13093 }
13094 });
13095 }
13096 };
13097}(jQuery));;
13098(function(global) {
13099 /**
13100 * Polyfill URLSearchParams
13101 *
13102 * Inspired from : https://github.com/WebReflection/url-search-params/blob/master/src/url-search-params.js
13103 */
13104
13105 var checkIfIteratorIsSupported = function() {
13106 try {
13107 return !!Symbol.iterator;
13108 } catch (error) {
13109 return false;
13110 }
13111 };
13112
13113
13114 var iteratorSupported = checkIfIteratorIsSupported();
13115
13116 var createIterator = function(items) {
13117 var iterator = {
13118 next: function() {
13119 var value = items.shift();
13120 return { done: value === void 0, value: value };
13121 }
13122 };
13123
13124 if (iteratorSupported) {
13125 iterator[Symbol.iterator] = function() {
13126 return iterator;
13127 };
13128 }
13129
13130 return iterator;
13131 };
13132
13133 /**
13134 * Search param name and values should be encoded according to https://url.spec.whatwg.org/#urlencoded-serializing
13135 * encodeURIComponent() produces the same result except encoding spaces as `%20` instead of `+`.
13136 */
13137 var serializeParam = function(value) {
13138 return encodeURIComponent(value).replace(/%20/g, '+');
13139 };
13140
13141 var deserializeParam = function(value) {
13142 return decodeURIComponent(value).replace(/\+/g, ' ');
13143 };
13144
13145 var polyfillURLSearchParams = function() {
13146
13147 var URLSearchParams = function(searchString) {
13148 Object.defineProperty(this, '_entries', { writable: true, value: {} });
13149 var typeofSearchString = typeof searchString;
13150
13151 if (typeofSearchString === 'undefined') {
13152 // do nothing
13153 } else if (typeofSearchString === 'string') {
13154 if (searchString !== '') {
13155 this._fromString(searchString);
13156 }
13157 } else if (searchString instanceof URLSearchParams) {
13158 var _this = this;
13159 searchString.forEach(function(value, name) {
13160 _this.append(name, value);
13161 });
13162 } else if ((searchString !== null) && (typeofSearchString === 'object')) {
13163 if (Object.prototype.toString.call(searchString) === '[object Array]') {
13164 for (var i = 0; i < searchString.length; i++) {
13165 var entry = searchString[i];
13166 if ((Object.prototype.toString.call(entry) === '[object Array]') || (entry.length !== 2)) {
13167 this.append(entry[0], entry[1]);
13168 } else {
13169 throw new TypeError('Expected [string, any] as entry at index ' + i + ' of URLSearchParams\'s input');
13170 }
13171 }
13172 } else {
13173 for (var key in searchString) {
13174 if (searchString.hasOwnProperty(key)) {
13175 this.append(key, searchString[key]);
13176 }
13177 }
13178 }
13179 } else {
13180 throw new TypeError('Unsupported input\'s type for URLSearchParams');
13181 }
13182 };
13183
13184 var proto = URLSearchParams.prototype;
13185
13186 proto.append = function(name, value) {
13187 if (name in this._entries) {
13188 this._entries[name].push(String(value));
13189 } else {
13190 this._entries[name] = [String(value)];
13191 }
13192 };
13193
13194 proto.delete = function(name) {
13195 delete this._entries[name];
13196 };
13197
13198 proto.get = function(name) {
13199 return (name in this._entries) ? this._entries[name][0] : null;
13200 };
13201
13202 proto.getAll = function(name) {
13203 return (name in this._entries) ? this._entries[name].slice(0) : [];
13204 };
13205
13206 proto.has = function(name) {
13207 return (name in this._entries);
13208 };
13209
13210 proto.set = function(name, value) {
13211 this._entries[name] = [String(value)];
13212 };
13213
13214 proto.forEach = function(callback, thisArg) {
13215 var entries;
13216 for (var name in this._entries) {
13217 if (this._entries.hasOwnProperty(name)) {
13218 entries = this._entries[name];
13219 for (var i = 0; i < entries.length; i++) {
13220 callback.call(thisArg, entries[i], name, this);
13221 }
13222 }
13223 }
13224 };
13225
13226 proto.keys = function() {
13227 var items = [];
13228 this.forEach(function(value, name) {
13229 items.push(name);
13230 });
13231 return createIterator(items);
13232 };
13233
13234 proto.values = function() {
13235 var items = [];
13236 this.forEach(function(value) {
13237 items.push(value);
13238 });
13239 return createIterator(items);
13240 };
13241
13242 proto.entries = function() {
13243 var items = [];
13244 this.forEach(function(value, name) {
13245 items.push([name, value]);
13246 });
13247 return createIterator(items);
13248 };
13249
13250 if (iteratorSupported) {
13251 proto[Symbol.iterator] = proto.entries;
13252 }
13253
13254 proto.toString = function() {
13255 var searchArray = [];
13256 this.forEach(function(value, name) {
13257 searchArray.push(serializeParam(name) + '=' + serializeParam(value));
13258 });
13259 return searchArray.join('&');
13260 };
13261
13262
13263 global.URLSearchParams = URLSearchParams;
13264 };
13265
13266 if (!('URLSearchParams' in global) || (new URLSearchParams('?a=1').toString() !== 'a=1')) {
13267 polyfillURLSearchParams();
13268 }
13269
13270 var proto = URLSearchParams.prototype;
13271
13272 if (typeof proto.sort !== 'function') {
13273 proto.sort = function() {
13274 var _this = this;
13275 var items = [];
13276 this.forEach(function(value, name) {
13277 items.push([name, value]);
13278 if (!_this._entries) {
13279 _this.delete(name);
13280 }
13281 });
13282 items.sort(function(a, b) {
13283 if (a[0] < b[0]) {
13284 return -1;
13285 } else if (a[0] > b[0]) {
13286 return +1;
13287 } else {
13288 return 0;
13289 }
13290 });
13291 if (_this._entries) { // force reset because IE keeps keys index
13292 _this._entries = {};
13293 }
13294 for (var i = 0; i < items.length; i++) {
13295 this.append(items[i][0], items[i][1]);
13296 }
13297 };
13298 }
13299
13300 if (typeof proto._fromString !== 'function') {
13301 Object.defineProperty(proto, '_fromString', {
13302 enumerable: false,
13303 configurable: false,
13304 writable: false,
13305 value: function(searchString) {
13306 if (this._entries) {
13307 this._entries = {};
13308 } else {
13309 var keys = [];
13310 this.forEach(function(value, name) {
13311 keys.push(name);
13312 });
13313 for (var i = 0; i < keys.length; i++) {
13314 this.delete(keys[i]);
13315 }
13316 }
13317
13318 searchString = searchString.replace(/^\?/, '');
13319 var attributes = searchString.split('&');
13320 var attribute;
13321 for (var i = 0; i < attributes.length; i++) {
13322 attribute = attributes[i].split('=');
13323 this.append(
13324 deserializeParam(attribute[0]),
13325 (attribute.length > 1) ? deserializeParam(attribute[1]) : ''
13326 );
13327 }
13328 }
13329 });
13330 }
13331
13332 // HTMLAnchorElement
13333
13334})(
13335 (typeof global !== 'undefined') ? global
13336 : ((typeof window !== 'undefined') ? window
13337 : ((typeof self !== 'undefined') ? self : this))
13338);
13339
13340(function(global) {
13341 /**
13342 * Polyfill URL
13343 *
13344 * Inspired from : https://github.com/arv/DOM-URL-Polyfill/blob/master/src/url.js
13345 */
13346
13347 var checkIfURLIsSupported = function() {
13348 try {
13349 var u = new URL('b', 'http://a');
13350 u.pathname = 'c%20d';
13351 return (u.href === 'http://a/c%20d') && u.searchParams;
13352 } catch (e) {
13353 return false;
13354 }
13355 };
13356
13357
13358 var polyfillURL = function() {
13359 var _URL = global.URL;
13360
13361 var URL = function(url, base) {
13362 if (typeof url !== 'string') url = String(url);
13363
13364 // Only create another document if the base is different from current location.
13365 var doc = document, baseElement;
13366 if (base && (global.location === void 0 || base !== global.location.href)) {
13367 doc = document.implementation.createHTMLDocument('');
13368 baseElement = doc.createElement('base');
13369 baseElement.href = base;
13370 doc.head.appendChild(baseElement);
13371 try {
13372 if (baseElement.href.indexOf(base) !== 0) throw new Error(baseElement.href);
13373 } catch (err) {
13374 throw new Error('URL unable to set base ' + base + ' due to ' + err);
13375 }
13376 }
13377
13378 var anchorElement = doc.createElement('a');
13379 anchorElement.href = url;
13380 if (baseElement) {
13381 doc.body.appendChild(anchorElement);
13382 anchorElement.href = anchorElement.href; // force href to refresh
13383 }
13384
13385 if (anchorElement.protocol === ':' || !/:/.test(anchorElement.href)) {
13386 throw new TypeError('Invalid URL');
13387 }
13388
13389 Object.defineProperty(this, '_anchorElement', {
13390 value: anchorElement
13391 });
13392
13393
13394 // create a linked searchParams which reflect its changes on URL
13395 var searchParams = new URLSearchParams(this.search);
13396 var enableSearchUpdate = true;
13397 var enableSearchParamsUpdate = true;
13398 var _this = this;
13399 ['append', 'delete', 'set'].forEach(function(methodName) {
13400 var method = searchParams[methodName];
13401 searchParams[methodName] = function() {
13402 method.apply(searchParams, arguments);
13403 if (enableSearchUpdate) {
13404 enableSearchParamsUpdate = false;
13405 _this.search = searchParams.toString();
13406 enableSearchParamsUpdate = true;
13407 }
13408 };
13409 });
13410
13411 Object.defineProperty(this, 'searchParams', {
13412 value: searchParams,
13413 enumerable: true
13414 });
13415
13416 var search = void 0;
13417 Object.defineProperty(this, '_updateSearchParams', {
13418 enumerable: false,
13419 configurable: false,
13420 writable: false,
13421 value: function() {
13422 if (this.search !== search) {
13423 search = this.search;
13424 if (enableSearchParamsUpdate) {
13425 enableSearchUpdate = false;
13426 this.searchParams._fromString(this.search);
13427 enableSearchUpdate = true;
13428 }
13429 }
13430 }
13431 });
13432 };
13433
13434 var proto = URL.prototype;
13435
13436 var linkURLWithAnchorAttribute = function(attributeName) {
13437 Object.defineProperty(proto, attributeName, {
13438 get: function() {
13439 return this._anchorElement[attributeName];
13440 },
13441 set: function(value) {
13442 this._anchorElement[attributeName] = value;
13443 },
13444 enumerable: true
13445 });
13446 };
13447
13448 ['hash', 'host', 'hostname', 'port', 'protocol']
13449 .forEach(function(attributeName) {
13450 linkURLWithAnchorAttribute(attributeName);
13451 });
13452
13453 Object.defineProperty(proto, 'search', {
13454 get: function() {
13455 return this._anchorElement['search'];
13456 },
13457 set: function(value) {
13458 this._anchorElement['search'] = value;
13459 this._updateSearchParams();
13460 },
13461 enumerable: true
13462 });
13463
13464 Object.defineProperties(proto, {
13465
13466 'toString': {
13467 get: function() {
13468 var _this = this;
13469 return function() {
13470 return _this.href;
13471 };
13472 }
13473 },
13474
13475 'href': {
13476 get: function() {
13477 return this._anchorElement.href.replace(/\?$/, '');
13478 },
13479 set: function(value) {
13480 this._anchorElement.href = value;
13481 this._updateSearchParams();
13482 },
13483 enumerable: true
13484 },
13485
13486 'pathname': {
13487 get: function() {
13488 return this._anchorElement.pathname.replace(/(^\/?)/, '/');
13489 },
13490 set: function(value) {
13491 this._anchorElement.pathname = value;
13492 },
13493 enumerable: true
13494 },
13495
13496 'origin': {
13497 get: function() {
13498 // get expected port from protocol
13499 var expectedPort = { 'http:': 80, 'https:': 443, 'ftp:': 21 }[this._anchorElement.protocol];
13500 // add port to origin if, expected port is different than actual port
13501 // and it is not empty f.e http://foo:8080
13502 // 8080 != 80 && 8080 != ''
13503 var addPortToOrigin = this._anchorElement.port != expectedPort &&
13504 this._anchorElement.port !== '';
13505
13506 return this._anchorElement.protocol +
13507 '//' +
13508 this._anchorElement.hostname +
13509 (addPortToOrigin ? (':' + this._anchorElement.port) : '');
13510 },
13511 enumerable: true
13512 },
13513
13514 'password': { // TODO
13515 get: function() {
13516 return '';
13517 },
13518 set: function(value) {
13519 },
13520 enumerable: true
13521 },
13522
13523 'username': { // TODO
13524 get: function() {
13525 return '';
13526 },
13527 set: function(value) {
13528 },
13529 enumerable: true
13530 },
13531 });
13532
13533 URL.createObjectURL = function(blob) {
13534 return _URL.createObjectURL.apply(_URL, arguments);
13535 };
13536
13537 URL.revokeObjectURL = function(url) {
13538 return _URL.revokeObjectURL.apply(_URL, arguments);
13539 };
13540
13541 global.URL = URL;
13542
13543 };
13544
13545 if (!checkIfURLIsSupported()) {
13546 polyfillURL();
13547 }
13548
13549 if ((global.location !== void 0) && !('origin' in global.location)) {
13550 var getOrigin = function() {
13551 return global.location.protocol + '//' + global.location.hostname + (global.location.port ? (':' + global.location.port) : '');
13552 };
13553
13554 try {
13555 Object.defineProperty(global.location, 'origin', {
13556 get: getOrigin,
13557 enumerable: true
13558 });
13559 } catch (e) {
13560 setInterval(function() {
13561 global.location.origin = getOrigin();
13562 }, 100);
13563 }
13564 }
13565
13566})(
13567 (typeof global !== 'undefined') ? global
13568 : ((typeof window !== 'undefined') ? window
13569 : ((typeof self !== 'undefined') ? self : this))
13570);
13571;
13572/*
13573Copyright The Infusion copyright holders
13574See the AUTHORS.md file at the top-level directory of this distribution and at
13575https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
13576
13577Licensed under the Educational Community License (ECL), Version 2.0 or the New
13578BSD license. You may not use this file except in compliance with one these
13579Licenses.
13580
13581You may obtain a copy of the ECL 2.0 License and BSD License at
13582https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
13583*/
13584
13585var fluid_3_0_0 = fluid_3_0_0 || {};
13586
13587(function ($, fluid) {
13588 "use strict";
13589
13590 fluid.registerNamespace("fluid.contextAware");
13591
13592 fluid.defaults("fluid.contextAware.marker", {
13593 gradeNames: ["fluid.component"]
13594 });
13595
13596
13597 // unsupported, NON-API function
13598 fluid.contextAware.makeCheckMarkers = function (checks, path, instantiator) {
13599 fluid.each(checks, function (value, markerTypeName) {
13600 fluid.constructSingle(path, {
13601 type: markerTypeName,
13602 gradeNames: "fluid.contextAware.marker",
13603 value: value
13604 }, instantiator);
13605 });
13606
13607 };
13608 /** Peforms the computation for `fluid.contextAware.makeChecks` and returns a structure suitable for being sent to `fluid.contextAware.makeCheckMarkers` -
13609 *
13610 * @return A hash of marker type names to grade names - this can be sent to fluid.contextAware.makeCheckMarkers
13611 */
13612 // unsupported, NON-API function
13613 fluid.contextAware.performChecks = function (checkHash) {
13614 return fluid.transform(checkHash, function (checkRecord) {
13615 if (typeof(checkRecord) === "function") {
13616 checkRecord = {func: checkRecord};
13617 } else if (typeof(checkRecord) === "string") {
13618 checkRecord = {funcName: checkRecord};
13619 }
13620 if (fluid.isPrimitive(checkRecord)) {
13621 return checkRecord;
13622 } else if ("value" in checkRecord) {
13623 return checkRecord.value;
13624 } else if ("func" in checkRecord) {
13625 return checkRecord.func();
13626 } else if ("funcName" in checkRecord) {
13627 return fluid.invokeGlobalFunction(checkRecord.funcName);
13628 } else {
13629 fluid.fail("Error in contextAwareness check record ", checkRecord, " - must contain an entry with name value, func, or funcName");
13630 }
13631 });
13632 };
13633
13634 /**
13635 * Takes an object whose keys are check context names and whose values are check records, designating a collection of context markers which might be registered at a location
13636 * in the component tree.
13637 *
13638 * @param {Object} checkHash - The keys in this structure are the context names to be supplied if the check passes, and the values are check records.
13639 * A check record contains:
13640 * ONE OF:
13641 * value {Any} [optional] A literal value name to be attached to the context
13642 * func {Function} [optional] A zero-arg function to be called to compute the value
13643 * funcName {String} [optional] The name of a zero-arg global function which will compute the value
13644 * If the check record consists of a Number or Boolean, it is assumed to be the value given to "value".
13645 * @param {String|Array} [path] - [optional] The path in the component tree at which the check markers are to be registered. If omitted, "" is assumed
13646 * @param {Instantiator} [instantiator] - [optional] The instantiator holding the component tree which will receive the markers. If omitted, use `fluid.globalInstantiator`.
13647 */
13648 fluid.contextAware.makeChecks = function (checkHash, path, instantiator) {
13649 var checkOptions = fluid.contextAware.performChecks(checkHash);
13650 fluid.contextAware.makeCheckMarkers(checkOptions, path, instantiator);
13651 };
13652
13653 /**
13654 * Forgets a check made at a particular level of the component tree.
13655 *
13656 * @param {String[]} markerNames - The marker typeNames whose check values are to be forgotten.
13657 * @param {String|String[]} [path] - [optional] The path in the component tree at which the check markers are to be removed. If omitted, "" is assumed
13658 * @param {Instantiator} [instantiator] - [optional] The instantiator holding the component tree the markers are to be removed from. If omitted, use `fluid.globalInstantiator`.
13659 */
13660 fluid.contextAware.forgetChecks = function (markerNames, path, instantiator) {
13661 instantiator = instantiator || fluid.globalInstantiator;
13662 path = path || [];
13663 var markerArray = fluid.makeArray(markerNames);
13664 fluid.each(markerArray, function (markerName) {
13665 var memberName = fluid.typeNameToMemberName(markerName);
13666 var segs = fluid.model.parseToSegments(path, instantiator.parseEL, true);
13667 segs.push(memberName);
13668 fluid.destroy(segs, instantiator);
13669 });
13670 };
13671
13672 /** A grade to be given to a component which requires context-aware adaptation.
13673 * This grade consumes configuration held in the block named "contextAwareness", which is an object whose keys are check namespaces and whose values hold
13674 * sequences of "checks" to be made in the component tree above the component. The value searched by
13675 * each check is encoded as the element named `contextValue` - this either represents an IoC reference to a component
13676 * or a particular value held at the component. If this reference has no path component, the path ".options.value" will be assumed.
13677 * These checks seek contexts which
13678 * have been previously registered using fluid.contextAware.makeChecks. The first context which matches
13679 * with a value of `true` terminates the search, and returns by applying the grade names held in `gradeNames` to the current component.
13680 * If no check matches, the grades held in `defaultGradeNames` will be applied.
13681 */
13682 fluid.defaults("fluid.contextAware", {
13683 gradeNames: ["{that}.check"],
13684 mergePolicy: {
13685 contextAwareness: "noexpand"
13686 },
13687 contextAwareness: {
13688 // Hash of names (check namespaces) to records: {
13689 // checks: {}, // Hash of check namespace to: {
13690 // contextValue: IoCExpression testing value in environment,
13691 // gradeNames: gradeNames which will be output,
13692 // priority: String/Number for priority of check [optional]
13693 // equals: Value to be compared to contextValue [optional - default is `true`]
13694 // defaultGradeNames: // String or String[] holding default gradeNames which will be output if no check matches [optional]
13695 // priority: // Number or String encoding priority relative to other records (same format as with event listeners) [optional]
13696 // }
13697 },
13698 invokers: {
13699 check: {
13700 funcName: "fluid.contextAware.check",
13701 args: ["{that}", "{that}.options.contextAwareness"]
13702 }
13703 }
13704 });
13705
13706 fluid.contextAware.getCheckValue = function (that, reference) {
13707 // cf. core of distributeOptions!
13708 var targetRef = fluid.parseContextReference(reference);
13709 var targetComponent = fluid.resolveContext(targetRef.context, that);
13710 var path = targetRef.path || ["options", "value"];
13711 var value = fluid.getForComponent(targetComponent, path);
13712 return value;
13713 };
13714
13715 // unsupported, NON-API function
13716 fluid.contextAware.checkOne = function (that, contextAwareRecord) {
13717 if (contextAwareRecord.checks && contextAwareRecord.checks.contextValue) {
13718 fluid.fail("Nesting error in contextAwareness record ", contextAwareRecord, " - the \"checks\" entry must contain a hash and not a contextValue/gradeNames record at top level");
13719 }
13720 var checkList = fluid.parsePriorityRecords(contextAwareRecord.checks, "contextAwareness checkRecord");
13721 return fluid.find(checkList, function (check) {
13722 if (!check.contextValue) {
13723 fluid.fail("Cannot perform check for contextAwareness record ", check, " without a valid field named \"contextValue\"");
13724 }
13725 var value = fluid.contextAware.getCheckValue(that, check.contextValue);
13726 if (check.equals === undefined ? value : value === check.equals) {
13727 return check.gradeNames;
13728 }
13729 }, contextAwareRecord.defaultGradeNames);
13730 };
13731
13732 // unsupported, NON-API function
13733 fluid.contextAware.check = function (that, contextAwarenessOptions) {
13734 var gradeNames = [];
13735 var contextAwareList = fluid.parsePriorityRecords(contextAwarenessOptions, "contextAwareness adaptationRecord");
13736 fluid.each(contextAwareList, function (record) {
13737 var matched = fluid.contextAware.checkOne(that, record);
13738 gradeNames = gradeNames.concat(fluid.makeArray(matched));
13739 });
13740 return gradeNames;
13741 };
13742
13743 /** Given a set of options, broadcast an adaptation to all instances of a particular component in a particular context. ("new demands blocks").
13744 * This has the effect of fabricating a grade with a particular name with an options distribution to `{/ typeName}` for the required component,
13745 * and then constructing a single well-known instance of it.
13746 * Options layout:
13747 * distributionName {String} A grade name - the name to be given to the fabricated grade
13748 * targetName {String} A grade name - the name of the grade to receive the adaptation
13749 * adaptationName {String} the name of the contextAwareness record to receive the record - this will be a simple string
13750 * checkName {String} the name of the check within the contextAwareness record to receive the record - this will be a simple string
13751 * record {Object} the record to be broadcast into contextAwareness - should contain entries
13752 * contextValue {IoC expression} the context value to be checked to activate the adaptation
13753 * gradeNames {String/String[]} the grade names to be supplied to the adapting target (matching advisedName)
13754 *
13755 * @param {Object} options - The options to use when making an adaptation. See above for supported sub-options.
13756 */
13757 fluid.contextAware.makeAdaptation = function (options) {
13758 fluid.expect("fluid.contextAware.makeAdaptation", options, ["distributionName", "targetName", "adaptationName", "checkName", "record"]);
13759 fluid.defaults(options.distributionName, {
13760 gradeNames: ["fluid.component"],
13761 distributeOptions: {
13762 target: "{/ " + options.targetName + "}.options.contextAwareness." + options.adaptationName + ".checks." + options.checkName,
13763 record: options.record
13764 }
13765 });
13766 fluid.constructSingle([], options.distributionName);
13767 };
13768
13769 // Context awareness for the browser environment
13770
13771 fluid.contextAware.isBrowser = function () {
13772 return typeof(window) !== "undefined" && !!window.document;
13773 };
13774
13775 fluid.contextAware.makeChecks({
13776 "fluid.browser": {
13777 funcName: "fluid.contextAware.isBrowser"
13778 }
13779 });
13780
13781 // Context awareness for the reported browser platform name (operating system)
13782
13783 fluid.registerNamespace("fluid.contextAware.browser");
13784
13785 fluid.contextAware.browser.getPlatformName = function () {
13786 return typeof(navigator) !== "undefined" && navigator.platform ? navigator.platform : undefined;
13787 };
13788
13789 // Context awareness for the reported user agent name
13790
13791 fluid.contextAware.browser.getUserAgent = function () {
13792 return typeof(navigator) !== "undefined" && navigator.userAgent ? navigator.userAgent : undefined;
13793 };
13794
13795 fluid.contextAware.makeChecks({
13796 "fluid.browser.platformName": {
13797 funcName: "fluid.contextAware.browser.getPlatformName"
13798 },
13799 "fluid.browser.userAgent": {
13800 funcName: "fluid.contextAware.browser.getUserAgent"
13801 }
13802 });
13803
13804})(jQuery, fluid_3_0_0);
13805;
13806/*
13807Copyright The Infusion copyright holders
13808See the AUTHORS.md file at the top-level directory of this distribution and at
13809https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
13810
13811Licensed under the Educational Community License (ECL), Version 2.0 or the New
13812BSD license. You may not use this file except in compliance with one these
13813Licenses.
13814
13815You may obtain a copy of the ECL 2.0 License and BSD License at
13816https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
13817*/
13818
13819var fluid_3_0_0 = fluid_3_0_0 || {};
13820
13821(function ($, fluid) {
13822 "use strict";
13823
13824 fluid.registerNamespace("fluid.enhance");
13825
13826 /**********************************************************
13827 * This code runs immediately upon inclusion of this file *
13828 **********************************************************/
13829
13830 // Use JavaScript to hide any markup that is specifically in place for cases when JavaScript is off.
13831 // Note: the use of fl-ProgEnhance-basic is deprecated, and replaced by fl-progEnhance-basic.
13832 // It is included here for backward compatibility only.
13833 // Distinguish the standalone jQuery from the real one so that this can be included in IoC standalone tests
13834 if (fluid.contextAware.isBrowser() && $.fn) {
13835 $("head").append("<style type='text/css'>.fl-progEnhance-basic, .fl-ProgEnhance-basic { display: none; } .fl-progEnhance-enhanced, .fl-ProgEnhance-enhanced { display: block; }</style>");
13836 }
13837
13838})(jQuery, fluid_3_0_0);
13839;
13840// =========================================================================
13841//
13842// tinyxmlsax.js - an XML SAX parser in JavaScript compressed for downloading
13843//
13844// version 3.1
13845//
13846// =========================================================================
13847//
13848// Copyright (C) 2000 - 2002, 2003 Michael Houghton (mike@idle.org), Raymond Irving and David Joham (djoham@yahoo.com)
13849//
13850// This library is free software; you can redistribute it and/or
13851// modify it under the terms of the GNU Lesser General Public
13852// License as published by the Free Software Foundation; either
13853// version 2.1 of the License, or (at your option) any later version.
13854
13855// This library is distributed in the hope that it will be useful,
13856// but WITHOUT ANY WARRANTY; without even the implied warranty of
13857// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13858// Lesser General Public License for more details.
13859
13860// You should have received a copy of the GNU Lesser General Public
13861// License along with this library; if not, write to the Free Software
13862// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
13863//
13864// Visit the XML for <SCRIPT> home page at http://xmljs.sourceforge.net
13865//
13866
13867/*
13868The zlib/libpng License
13869
13870Copyright (c) 2000 - 2002, 2003 Michael Houghton (mike@idle.org), Raymond Irving and David Joham (djoham@yahoo.com)
13871
13872This software is provided 'as-is', without any express or implied
13873warranty. In no event will the authors be held liable for any damages
13874arising from the use of this software.
13875
13876Permission is granted to anyone to use this software for any purpose,
13877including commercial applications, and to alter it and redistribute it
13878freely, subject to the following restrictions:
13879
13880 1. The origin of this software must not be misrepresented; you must not
13881 claim that you wrote the original software. If you use this software
13882 in a product, an acknowledgment in the product documentation would be
13883 appreciated but is not required.
13884
13885 2. Altered source versions must be plainly marked as such, and must not be
13886 misrepresented as being the original software.
13887
13888 3. This notice may not be removed or altered from any source
13889 distribution.
13890 */
13891
13892var fluid_3_0_0 = fluid_3_0_0 || {};
13893
13894(function ($, fluid) {
13895 "use strict";
13896
13897 fluid.XMLP = function(strXML) {
13898 return fluid.XMLP.XMLPImpl(strXML);
13899 };
13900
13901
13902 // List of closed HTML tags, taken from JQuery 1.2.3
13903 fluid.XMLP.closedTags = {
13904 abbr: true,
13905 br: true,
13906 col: true,
13907 img: true,
13908 input: true,
13909 link: true,
13910 meta: true,
13911 param: true,
13912 hr: true,
13913 area: true,
13914 embed:true
13915 };
13916
13917 fluid.XMLP._NONE = 0;
13918 fluid.XMLP._ELM_B = 1;
13919 fluid.XMLP._ELM_E = 2;
13920 fluid.XMLP._ELM_EMP = 3;
13921 fluid.XMLP._ATT = 4;
13922 fluid.XMLP._TEXT = 5;
13923 fluid.XMLP._ENTITY = 6;
13924 fluid.XMLP._PI = 7;
13925 fluid.XMLP._CDATA = 8;
13926 fluid.XMLP._COMMENT = 9;
13927 fluid.XMLP._DTD = 10;
13928 fluid.XMLP._ERROR = 11;
13929
13930 fluid.XMLP._CONT_XML = 0;
13931 fluid.XMLP._CONT_ALT = 1;
13932 fluid.XMLP._ATT_NAME = 0;
13933 fluid.XMLP._ATT_VAL = 1;
13934
13935 fluid.XMLP._STATE_PROLOG = 1;
13936 fluid.XMLP._STATE_DOCUMENT = 2;
13937 fluid.XMLP._STATE_MISC = 3;
13938
13939 fluid.XMLP._errs = [];
13940 fluid.XMLP._errs[fluid.XMLP.ERR_CLOSE_PI = 0 ] = "PI: missing closing sequence";
13941 fluid.XMLP._errs[fluid.XMLP.ERR_CLOSE_DTD = 1 ] = "DTD: missing closing sequence";
13942 fluid.XMLP._errs[fluid.XMLP.ERR_CLOSE_COMMENT = 2 ] = "Comment: missing closing sequence";
13943 fluid.XMLP._errs[fluid.XMLP.ERR_CLOSE_CDATA = 3 ] = "CDATA: missing closing sequence";
13944 fluid.XMLP._errs[fluid.XMLP.ERR_CLOSE_ELM = 4 ] = "Element: missing closing sequence";
13945 fluid.XMLP._errs[fluid.XMLP.ERR_CLOSE_ENTITY = 5 ] = "Entity: missing closing sequence";
13946 fluid.XMLP._errs[fluid.XMLP.ERR_PI_TARGET = 6 ] = "PI: target is required";
13947 fluid.XMLP._errs[fluid.XMLP.ERR_ELM_EMPTY = 7 ] = "Element: cannot be both empty and closing";
13948 fluid.XMLP._errs[fluid.XMLP.ERR_ELM_NAME = 8 ] = "Element: name must immediately follow \"<\"";
13949 fluid.XMLP._errs[fluid.XMLP.ERR_ELM_LT_NAME = 9 ] = "Element: \"<\" not allowed in element names";
13950 fluid.XMLP._errs[fluid.XMLP.ERR_ATT_VALUES = 10] = "Attribute: values are required and must be in quotes";
13951 fluid.XMLP._errs[fluid.XMLP.ERR_ATT_LT_NAME = 11] = "Element: \"<\" not allowed in attribute names";
13952 fluid.XMLP._errs[fluid.XMLP.ERR_ATT_LT_VALUE = 12] = "Attribute: \"<\" not allowed in attribute values";
13953 fluid.XMLP._errs[fluid.XMLP.ERR_ATT_DUP = 13] = "Attribute: duplicate attributes not allowed";
13954 fluid.XMLP._errs[fluid.XMLP.ERR_ENTITY_UNKNOWN = 14] = "Entity: unknown entity";
13955 fluid.XMLP._errs[fluid.XMLP.ERR_INFINITELOOP = 15] = "Infinite loop";
13956 fluid.XMLP._errs[fluid.XMLP.ERR_DOC_STRUCTURE = 16] = "Document: only comments, processing instructions, or whitespace allowed outside of document element";
13957 fluid.XMLP._errs[fluid.XMLP.ERR_ELM_NESTING = 17] = "Element: must be nested correctly";
13958
13959
13960 fluid.XMLP._checkStructure = function(that, iEvent) {
13961 var stack = that.m_stack;
13962 if (fluid.XMLP._STATE_PROLOG == that.m_iState) {
13963 // disabled original check for text node in prologue
13964 that.m_iState = fluid.XMLP._STATE_DOCUMENT;
13965 }
13966
13967 if (fluid.XMLP._STATE_DOCUMENT === that.m_iState) {
13968 if ((fluid.XMLP._ELM_B == iEvent) || (fluid.XMLP._ELM_EMP == iEvent)) {
13969 that.m_stack[stack.length] = that.getName();
13970 }
13971 if ((fluid.XMLP._ELM_E == iEvent) || (fluid.XMLP._ELM_EMP == iEvent)) {
13972 if (stack.length === 0) {
13973 //return fluid.XMLP._setErr(XMLP.ERR_DOC_STRUCTURE);
13974 return fluid.XMLP._NONE;
13975 }
13976 var strTop = stack[stack.length - 1];
13977 that.m_stack.length--;
13978 if (strTop === null || strTop !== that.getName()) {
13979 return fluid.XMLP._setErr(that, fluid.XMLP.ERR_ELM_NESTING);
13980 }
13981 }
13982
13983 // disabled original check for text node in epilogue - "MISC" state is disused
13984 }
13985 return iEvent;
13986 };
13987
13988
13989 fluid.XMLP._parseCDATA = function(that, iB) {
13990 var iE = that.m_xml.indexOf("]]>", iB);
13991 if (iE == -1) { return fluid.XMLP._setErr(that, fluid.XMLP.ERR_CLOSE_CDATA);}
13992 fluid.XMLP._setContent(that, fluid.XMLP._CONT_XML, iB, iE);
13993 that.m_iP = iE + 3;
13994 return fluid.XMLP._CDATA;
13995 };
13996
13997
13998 fluid.XMLP._parseComment = function(that, iB) {
13999 var iE = that.m_xml.indexOf("-" + "->", iB);
14000 if (iE == -1) {
14001 return fluid.XMLP._setErr(that, fluid.XMLP.ERR_CLOSE_COMMENT);
14002 }
14003 fluid.XMLP._setContent(that, fluid.XMLP._CONT_XML, iB - 4, iE + 3);
14004 that.m_iP = iE + 3;
14005 return fluid.XMLP._COMMENT;
14006 };
14007
14008 fluid.XMLP._parseDTD = function(that, iB) {
14009 var iE, strClose, iInt, iLast;
14010 iE = that.m_xml.indexOf(">", iB);
14011 if (iE == -1) {
14012 return fluid.XMLP._setErr(that, fluid.XMLP.ERR_CLOSE_DTD);
14013 }
14014 iInt = that.m_xml.indexOf("[", iB);
14015 strClose = ((iInt != -1) && (iInt < iE)) ? "]>" : ">";
14016 while (true) {
14017 if (iE == iLast) {
14018 return fluid.XMLP._setErr(that, fluid.XMLP.ERR_INFINITELOOP);
14019 }
14020 iLast = iE;
14021 iE = that.m_xml.indexOf(strClose, iB);
14022 if(iE == -1) {
14023 return fluid.XMLP._setErr(that, fluid.XMLP.ERR_CLOSE_DTD);
14024 }
14025 if (that.m_xml.substring(iE - 1, iE + 2) != "]]>") { break;}
14026 }
14027 that.m_iP = iE + strClose.length;
14028 return fluid.XMLP._DTD;
14029 };
14030
14031 fluid.XMLP._parsePI = function(that, iB) {
14032 var iE, iTB, iTE, iCB, iCE;
14033 iE = that.m_xml.indexOf("?>", iB);
14034 if (iE == -1) { return fluid.XMLP._setErr(that, fluid.XMLP.ERR_CLOSE_PI);}
14035 iTB = fluid.SAXStrings.indexOfNonWhitespace(that.m_xml, iB, iE);
14036 if (iTB == -1) { return fluid.XMLP._setErr(that, fluid.XMLP.ERR_PI_TARGET);}
14037 iTE = fluid.SAXStrings.indexOfWhitespace(that.m_xml, iTB, iE);
14038 if (iTE == -1) { iTE = iE;}
14039 iCB = fluid.SAXStrings.indexOfNonWhitespace(that.m_xml, iTE, iE);
14040 if (iCB == -1) { iCB = iE;}
14041 iCE = fluid.SAXStrings.lastIndexOfNonWhitespace(that.m_xml, iCB, iE);
14042 if (iCE == -1) { iCE = iE - 1;}
14043 that.m_name = that.m_xml.substring(iTB, iTE);
14044 fluid.XMLP._setContent(that, fluid.XMLP._CONT_XML, iCB, iCE + 1);
14045 that.m_iP = iE + 2;
14046 return fluid.XMLP._PI;
14047 };
14048
14049 fluid.XMLP._parseText = function(that, iB) {
14050 var iE = that.m_xml.indexOf("<", iB);
14051 if (iE == -1) { iE = that.m_xml.length;}
14052 fluid.XMLP._setContent(that, fluid.XMLP._CONT_XML, iB, iE);
14053 that.m_iP = iE;
14054 return fluid.XMLP._TEXT;
14055 };
14056
14057 fluid.XMLP._setContent = function(that, iSrc) {
14058 var args = arguments;
14059 if (fluid.XMLP._CONT_XML == iSrc) {
14060 that.m_cAlt = null;
14061 that.m_cB = args[2];
14062 that.m_cE = args[3];
14063 }
14064 else {
14065 that.m_cAlt = args[2];
14066 that.m_cB = 0;
14067 that.m_cE = args[2].length;
14068 }
14069
14070 that.m_cSrc = iSrc;
14071 };
14072
14073 fluid.XMLP._setErr = function(that, iErr) {
14074 var strErr = fluid.XMLP._errs[iErr];
14075 that.m_cAlt = strErr;
14076 that.m_cB = 0;
14077 that.m_cE = strErr.length;
14078 that.m_cSrc = fluid.XMLP._CONT_ALT;
14079 return fluid.XMLP._ERROR;
14080 };
14081
14082
14083 fluid.XMLP._parseElement = function(that, iB) {
14084 var iE, iDE, iRet;
14085 var iType, strN, iLast;
14086 iDE = iE = that.m_xml.indexOf(">", iB);
14087 if (iE == -1) {
14088 return fluid.XMLP._setErr(that, fluid.XMLP.ERR_CLOSE_ELM);
14089 }
14090 if (that.m_xml.charAt(iB) == "/") {
14091 iType = fluid.XMLP._ELM_E;
14092 iB++;
14093 }
14094 else {
14095 iType = fluid.XMLP._ELM_B;
14096 }
14097 if (that.m_xml.charAt(iE - 1) == "/") {
14098 if (iType == fluid.XMLP._ELM_E) {
14099 return fluid.XMLP._setErr(that, fluid.XMLP.ERR_ELM_EMPTY);
14100 }
14101 iType = fluid.XMLP._ELM_EMP; iDE--;
14102 }
14103
14104 that.nameRegex.lastIndex = iB;
14105 var nameMatch = that.nameRegex.exec(that.m_xml);
14106 if (!nameMatch) {
14107 return fluid.XMLP._setErr(that, fluid.XMLP.ERR_ELM_NAME);
14108 }
14109 strN = nameMatch[1].toLowerCase();
14110 // This branch is specially necessary for broken markup in IE. If we see an li
14111 // tag apparently directly nested in another, first emit a synthetic close tag
14112 // for the earlier one without advancing the pointer, and set a flag to ensure
14113 // doing this just once.
14114 if ("li" === strN && iType !== fluid.XMLP._ELM_E && that.m_stack.length > 0 &&
14115 that.m_stack[that.m_stack.length - 1] === "li" && !that.m_emitSynthetic) {
14116 that.m_name = "li";
14117 that.m_emitSynthetic = true;
14118 return fluid.XMLP._ELM_E;
14119 }
14120 // We have acquired the tag name, now set about parsing any attribute list
14121 that.m_attributes = {};
14122 that.m_cAlt = "";
14123
14124 if (that.nameRegex.lastIndex < iDE) {
14125 that.m_iP = that.nameRegex.lastIndex;
14126 while (that.m_iP < iDE) {
14127 that.attrStartRegex.lastIndex = that.m_iP;
14128 var attrMatch = that.attrStartRegex.exec(that.m_xml);
14129 if (!attrMatch) {
14130 return fluid.XMLP._setErr(that, fluid.XMLP.ERR_ATT_VALUES);
14131 }
14132 var attrname = attrMatch[1].toLowerCase();
14133 var attrval;
14134 if (that.m_xml.charCodeAt(that.attrStartRegex.lastIndex) === 61) { // =
14135 var valRegex = that.m_xml.charCodeAt(that.attrStartRegex.lastIndex + 1) === 34? that.attrValRegex : that.attrValIERegex; // "
14136 valRegex.lastIndex = that.attrStartRegex.lastIndex + 1;
14137 attrMatch = valRegex.exec(that.m_xml);
14138 if (!attrMatch) {
14139 return fluid.XMLP._setErr(that, fluid.XMLP.ERR_ATT_VALUES);
14140 }
14141 attrval = attrMatch[1];
14142 }
14143 else { // accommodate insanity on unvalued IE attributes
14144 attrval = attrname;
14145 valRegex = that.attrStartRegex;
14146 }
14147 if (!that.m_attributes[attrname] || that.m_attributes[attrname] === attrval) {
14148 // last branch required because of fresh duplicate attribute bug introduced in IE10 and above - FLUID-5204
14149 that.m_attributes[attrname] = attrval;
14150 }
14151 else {
14152 return fluid.XMLP._setErr(that, fluid.XMLP.ERR_ATT_DUP);
14153 }
14154 that.m_iP = valRegex.lastIndex;
14155
14156 }
14157 }
14158 if (strN.indexOf("<") != -1) {
14159 return fluid.XMLP._setErr(that, fluid.XMLP.ERR_ELM_LT_NAME);
14160 }
14161
14162 that.m_name = strN;
14163 that.m_iP = iE + 1;
14164 // Check for corrupted "closed tags" from innerHTML
14165 if (fluid.XMLP.closedTags[strN]) {
14166 that.closeRegex.lastIndex = iE + 1;
14167 var closeMatch = that.closeRegex.exec;
14168 if (closeMatch) {
14169 var matchclose = that.m_xml.indexOf(strN, closeMatch.lastIndex);
14170 if (matchclose === closeMatch.lastIndex) {
14171 return iType; // bail out, a valid close tag is separated only by whitespace
14172 }
14173 else {
14174 return fluid.XMLP._ELM_EMP;
14175 }
14176 }
14177 }
14178 that.m_emitSynthetic = false;
14179 return iType;
14180 };
14181
14182 fluid.XMLP._parse = function(that) {
14183 var iP = that.m_iP;
14184 var xml = that.m_xml;
14185 if (iP === xml.length) { return fluid.XMLP._NONE;}
14186 var c = xml.charAt(iP);
14187 if (c === '<') {
14188 var c2 = xml.charAt(iP + 1);
14189 if (c2 === '?') {
14190 return fluid.XMLP._parsePI(that, iP + 2);
14191 }
14192 else if (c2 === '!') {
14193 if (iP === xml.indexOf("<!DOCTYPE", iP)) {
14194 return fluid.XMLP._parseDTD(that, iP + 9);
14195 }
14196 else if (iP === xml.indexOf("<!--", iP)) {
14197 return fluid.XMLP._parseComment(that, iP + 4);
14198 }
14199 else if (iP === xml.indexOf("<![CDATA[", iP)) {
14200 return fluid.XMLP._parseCDATA(that, iP + 9);
14201 }
14202 }
14203 else {
14204 return fluid.XMLP._parseElement(that, iP + 1);
14205 }
14206 }
14207 else {
14208 return fluid.XMLP._parseText(that, iP);
14209 }
14210 };
14211
14212
14213 fluid.XMLP.XMLPImpl = function(strXML) {
14214 var that = {};
14215 that.m_xml = strXML;
14216 that.m_iP = 0;
14217 that.m_iState = fluid.XMLP._STATE_PROLOG;
14218 that.m_stack = [];
14219 that.m_attributes = {};
14220 that.m_emitSynthetic = false; // state used for emitting synthetic tags used to correct broken markup (IE)
14221
14222 that.getColumnNumber = function() {
14223 return fluid.SAXStrings.getColumnNumber(that.m_xml, that.m_iP);
14224 };
14225
14226 that.getContent = function() {
14227 return (that.m_cSrc == fluid.XMLP._CONT_XML) ? that.m_xml : that.m_cAlt;
14228 };
14229
14230 that.getContentBegin = function() { return that.m_cB;};
14231 that.getContentEnd = function() { return that.m_cE;};
14232
14233 that.getLineNumber = function() {
14234 return fluid.SAXStrings.getLineNumber(that.m_xml, that.m_iP);
14235 };
14236
14237 that.getName = function() {
14238 return that.m_name;
14239 };
14240
14241 that.next = function() {
14242 return fluid.XMLP._checkStructure(that, fluid.XMLP._parse(that));
14243 };
14244
14245 that.nameRegex = /([^\s\/>]+)/g;
14246 that.attrStartRegex = /\s*([\w:_][\w:_\-\.]*)/gm;
14247 that.attrValRegex = /\"([^\"]*)\"\s*/gm; // "normal" XHTML attribute values
14248 that.attrValIERegex = /([^\>\s]+)\s*/gm; // "stupid" unquoted IE attribute values (sometimes)
14249 that.closeRegex = /\s*<\//g;
14250
14251 return that;
14252 };
14253
14254
14255 fluid.SAXStrings = {};
14256
14257 fluid.SAXStrings.WHITESPACE = " \t\n\r";
14258 fluid.SAXStrings.QUOTES = "\"'";
14259 fluid.SAXStrings.getColumnNumber = function (strD, iP) {
14260 if (!strD) { return -1;}
14261 iP = iP || strD.length;
14262 var arrD = strD.substring(0, iP).split("\n");
14263 arrD.length--;
14264 var iLinePos = arrD.join("\n").length;
14265 return iP - iLinePos;
14266 };
14267
14268 fluid.SAXStrings.getLineNumber = function (strD, iP) {
14269 if (!strD) { return -1;}
14270 iP = iP || strD.length;
14271 return strD.substring(0, iP).split("\n").length;
14272 };
14273
14274 fluid.SAXStrings.indexOfNonWhitespace = function (strD, iB, iE) {
14275 if (!strD) return -1;
14276 iB = iB || 0;
14277 iE = iE || strD.length;
14278
14279 for (var i = iB; i < iE; ++ i) {
14280 var c = strD.charAt(i);
14281 if (c !== ' ' && c !== '\t' && c !== '\n' && c !== '\r') return i;
14282 }
14283 return -1;
14284 };
14285
14286
14287 fluid.SAXStrings.indexOfWhitespace = function (strD, iB, iE) {
14288 if (!strD) { return -1;}
14289 iB = iB || 0;
14290 iE = iE || strD.length;
14291 for (var i = iB; i < iE; i++) {
14292 if (fluid.SAXStrings.WHITESPACE.indexOf(strD.charAt(i)) != -1) { return i;}
14293 }
14294 return -1;
14295 };
14296
14297
14298 fluid.SAXStrings.lastIndexOfNonWhitespace = function (strD, iB, iE) {
14299 if (!strD) { return -1;}
14300 iB = iB || 0; iE = iE || strD.length;
14301 for (var i = iE - 1; i >= iB; i--) {
14302 if (fluid.SAXStrings.WHITESPACE.indexOf(strD.charAt(i)) == -1) {
14303 return i;
14304 }
14305 }
14306 return -1;
14307 };
14308
14309 fluid.SAXStrings.replace = function(strD, iB, iE, strF, strR) {
14310 if (!strD) { return "";}
14311 iB = iB || 0;
14312 iE = iE || strD.length;
14313 return strD.substring(iB, iE).split(strF).join(strR);
14314 };
14315
14316})(jQuery, fluid_3_0_0);
14317;
14318/*
14319Copyright The Infusion copyright holders
14320See the AUTHORS.md file at the top-level directory of this distribution and at
14321https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
14322
14323Licensed under the Educational Community License (ECL), Version 2.0 or the New
14324BSD license. You may not use this file except in compliance with one these
14325Licenses.
14326
14327You may obtain a copy of the ECL 2.0 License and BSD License at
14328https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
14329*/
14330
14331fluid_3_0_0 = fluid_3_0_0 || {};
14332
14333(function ($, fluid) {
14334 "use strict";
14335
14336 // unsupported, non-API function
14337 fluid.parseTemplate = function (template, baseURL, scanStart, cutpoints_in, opts) {
14338 opts = opts || {};
14339
14340 if (!template) {
14341 fluid.fail("empty template supplied to fluid.parseTemplate");
14342 }
14343
14344 var t;
14345 var parser;
14346 var tagstack;
14347 var lumpindex = 0;
14348 var nestingdepth = 0;
14349 var justended = false;
14350
14351 var defstart = -1;
14352 var defend = -1;
14353
14354 var debugMode = false;
14355
14356 var cutpoints = []; // list of selector, tree, id
14357 var simpleClassCutpoints = {};
14358
14359 var cutstatus = [];
14360
14361 var XMLLump = function (lumpindex, nestingdepth) {
14362 return {
14363 //rsfID: "",
14364 //text: "",
14365 //downmap: {},
14366 //attributemap: {},
14367 //finallump: {},
14368 nestingdepth: nestingdepth,
14369 lumpindex: lumpindex,
14370 parent: t
14371 };
14372 };
14373
14374 function isSimpleClassCutpoint(tree) {
14375 return tree.length === 1 && tree[0].predList.length === 1 && tree[0].predList[0].clazz;
14376 }
14377
14378 function init(baseURLin, debugModeIn, cutpointsIn) {
14379 t.rootlump = XMLLump(0, -1); // eslint-disable-line new-cap
14380 tagstack = [t.rootlump];
14381 lumpindex = 0;
14382 nestingdepth = 0;
14383 justended = false;
14384 defstart = -1;
14385 defend = -1;
14386 baseURL = baseURLin;
14387 debugMode = debugModeIn;
14388 if (cutpointsIn) {
14389 for (var i = 0; i < cutpointsIn.length; ++i) {
14390 var tree = fluid.parseSelector(cutpointsIn[i].selector, fluid.simpleCSSMatcher);
14391 var clazz = isSimpleClassCutpoint(tree);
14392 if (clazz) {
14393 simpleClassCutpoints[clazz] = cutpointsIn[i].id;
14394 }
14395 else {
14396 cutstatus.push([]);
14397 cutpoints.push($.extend({}, cutpointsIn[i], {tree: tree}));
14398 }
14399 }
14400 }
14401 }
14402
14403 function findTopContainer() {
14404 for (var i = tagstack.length - 1; i >= 0; --i) {
14405 var lump = tagstack[i];
14406 if (lump.rsfID !== undefined) {
14407 return lump;
14408 }
14409 }
14410 return t.rootlump;
14411 }
14412
14413 function newLump() {
14414 var togo = XMLLump(lumpindex, nestingdepth); // eslint-disable-line new-cap
14415 if (debugMode) {
14416 togo.line = parser.getLineNumber();
14417 togo.column = parser.getColumnNumber();
14418 }
14419 //togo.parent = t;
14420 t.lumps[lumpindex] = togo;
14421 ++lumpindex;
14422 return togo;
14423 }
14424
14425 function addLump(mmap, ID, lump) {
14426 var list = mmap[ID];
14427 if (!list) {
14428 list = [];
14429 mmap[ID] = list;
14430 }
14431 list[list.length] = lump;
14432 }
14433
14434 function checkContribute(ID, lump) {
14435 if (ID.indexOf("scr=contribute-") !== -1) {
14436 var scr = ID.substring("scr=contribute-".length);
14437 addLump(t.collectmap, scr, lump);
14438 }
14439 }
14440
14441 function debugLump(lump) {
14442 // TODO expand this to agree with the Firebug "self-selector" idiom
14443 return "<" + lump.tagname + ">";
14444 }
14445
14446 function hasCssClass(clazz, totest) {
14447 if (!totest) {
14448 return false;
14449 }
14450 // algorithm from jQuery
14451 return (" " + totest + " ").indexOf(" " + clazz + " ") !== -1;
14452 }
14453
14454 function matchNode(term, headlump, headclazz) {
14455 if (term.predList) {
14456 for (var i = 0; i < term.predList.length; ++i) {
14457 var pred = term.predList[i];
14458 if (pred.id && headlump.attributemap.id !== pred.id) {return false;}
14459 if (pred.clazz && !hasCssClass(pred.clazz, headclazz)) {return false;}
14460 if (pred.tag && headlump.tagname !== pred.tag) {return false;}
14461 }
14462 return true;
14463 }
14464 }
14465
14466 function tagStartCut(headlump) {
14467 var togo;
14468 var headclazz = headlump.attributemap["class"];
14469 var i;
14470 if (headclazz) {
14471 var split = headclazz.split(" ");
14472 for (i = 0; i < split.length; ++i) {
14473 var simpleCut = simpleClassCutpoints[$.trim(split[i])];
14474 if (simpleCut) {
14475 return simpleCut;
14476 }
14477 }
14478 }
14479 for (i = 0; i < cutpoints.length; ++i) {
14480 var cut = cutpoints[i];
14481 var cutstat = cutstatus[i];
14482 var nextterm = cutstat.length; // the next term for this node
14483 if (nextterm < cut.tree.length) {
14484 var term = cut.tree[nextterm];
14485 if (nextterm > 0) {
14486 if (cut.tree[nextterm - 1].child &&
14487 cutstat[nextterm - 1] !== headlump.nestingdepth - 1) {
14488 continue; // it is a failure to match if not at correct nesting depth
14489 }
14490 }
14491 var isMatch = matchNode(term, headlump, headclazz);
14492 if (isMatch) {
14493 cutstat[cutstat.length] = headlump.nestingdepth;
14494 if (cutstat.length === cut.tree.length) {
14495 if (togo !== undefined) {
14496 fluid.fail("Cutpoint specification error - node " +
14497 debugLump(headlump) +
14498 " has already matched with rsf:id of " + togo);
14499 }
14500 if (cut.id === undefined || cut.id === null) {
14501 fluid.fail("Error in cutpoints list - entry at position " + i + " does not have an id set");
14502 }
14503 togo = cut.id;
14504 }
14505 }
14506 }
14507 }
14508 return togo;
14509 }
14510
14511 function tagEndCut() {
14512 if (cutpoints) {
14513 for (var i = 0; i < cutpoints.length; ++i) {
14514 var cutstat = cutstatus[i];
14515 if (cutstat.length > 0 && cutstat[cutstat.length - 1] === nestingdepth) {
14516 cutstat.length--;
14517 }
14518 }
14519 }
14520 }
14521
14522 function processTagEnd() {
14523 tagEndCut();
14524 var endlump = newLump();
14525 --nestingdepth;
14526 endlump.text = "</" + parser.getName() + ">";
14527 var oldtop = tagstack[tagstack.length - 1];
14528 oldtop.close_tag = t.lumps[lumpindex - 1];
14529 tagstack.length--;
14530 justended = true;
14531 }
14532
14533 function processTagStart(isempty) {
14534 ++nestingdepth;
14535 if (justended) {
14536 justended = false;
14537 var backlump = newLump();
14538 backlump.nestingdepth--;
14539 }
14540 if (t.firstdocumentindex === -1) {
14541 t.firstdocumentindex = lumpindex;
14542 }
14543 var headlump = newLump();
14544 var stacktop = tagstack[tagstack.length - 1];
14545 headlump.uplump = stacktop;
14546 var tagname = parser.getName();
14547 headlump.tagname = tagname;
14548 // NB - attribute names and values are now NOT DECODED!!
14549 var attrs = headlump.attributemap = parser.m_attributes;
14550 var ID = attrs[fluid.ID_ATTRIBUTE];
14551 if (ID === undefined) {
14552 ID = tagStartCut(headlump);
14553 }
14554 for (var attrname in attrs) {
14555 if (ID === undefined) {
14556 if (/href|src|codebase|action/.test(attrname)) {
14557 ID = "scr=rewrite-url";
14558 }
14559 // port of TPI effect of IDRelationRewriter
14560 else if (ID === undefined && /for|headers/.test(attrname)) {
14561 ID = "scr=null";
14562 }
14563 }
14564 }
14565
14566 if (ID) {
14567 // TODO: ensure this logic is correct on RSF Server
14568 if (ID.charCodeAt(0) === 126) { // "~"
14569 ID = ID.substring(1);
14570 headlump.elide = true;
14571 }
14572 checkContribute(ID, headlump);
14573 headlump.rsfID = ID;
14574 var downreg = findTopContainer();
14575 if (!downreg.downmap) {
14576 downreg.downmap = {};
14577 }
14578 while (downreg) { // TODO: unusual fix for locating branches in parent contexts (applies to repetitive leaves)
14579 if (downreg.downmap) {
14580 addLump(downreg.downmap, ID, headlump);
14581 }
14582 downreg = downreg.uplump;
14583 }
14584 addLump(t.globalmap, ID, headlump);
14585 var colpos = ID.indexOf(":");
14586 if (colpos !== -1) {
14587 var prefix = ID.substring(0, colpos);
14588 if (!stacktop.finallump) {
14589 stacktop.finallump = {};
14590 }
14591 stacktop.finallump[prefix] = headlump;
14592 }
14593 }
14594
14595 // TODO: accelerate this by grabbing original template text (requires parser
14596 // adjustment) as well as dealing with empty tags
14597 headlump.text = "<" + tagname + fluid.dumpAttributes(attrs) + (isempty && !ID ? "/>" : ">");
14598 tagstack[tagstack.length] = headlump;
14599 if (isempty) {
14600 if (ID) {
14601 processTagEnd();
14602 }
14603 else {
14604 --nestingdepth;
14605 tagstack.length--;
14606 }
14607 }
14608 }
14609
14610
14611
14612 function processDefaultTag() {
14613 if (defstart !== -1) {
14614 if (t.firstdocumentindex === -1) {
14615 t.firstdocumentindex = lumpindex;
14616 }
14617 var text = parser.getContent().substr(defstart, defend - defstart);
14618 justended = false;
14619 var newlump = newLump();
14620 newlump.text = text;
14621 defstart = -1;
14622 }
14623 }
14624
14625 /** ACTUAL BODY of fluid.parseTemplate begins here **/
14626
14627 t = fluid.XMLViewTemplate();
14628
14629 init(baseURL, opts.debugMode, cutpoints_in);
14630
14631 var idpos = template.indexOf(fluid.ID_ATTRIBUTE);
14632 if (scanStart) {
14633 var brackpos = template.indexOf(">", idpos);
14634 parser = fluid.XMLP(template.substring(brackpos + 1));
14635 }
14636 else {
14637 parser = fluid.XMLP(template);
14638 }
14639
14640parseloop: // eslint-disable-line indent
14641 while (true) {
14642 var iEvent = parser.next();
14643 switch (iEvent) {
14644 case fluid.XMLP._ELM_B:
14645 processDefaultTag();
14646 //var text = parser.getContent().substr(parser.getContentBegin(), parser.getContentEnd() - parser.getContentBegin());
14647 processTagStart(false, "");
14648 break;
14649 case fluid.XMLP._ELM_E:
14650 processDefaultTag();
14651 processTagEnd();
14652 break;
14653 case fluid.XMLP._ELM_EMP:
14654 processDefaultTag();
14655 //var text = parser.getContent().substr(parser.getContentBegin(), parser.getContentEnd() - parser.getContentBegin());
14656 processTagStart(true, "");
14657 break;
14658 case fluid.XMLP._PI:
14659 case fluid.XMLP._DTD:
14660 defstart = -1;
14661 continue; // not interested in reproducing these
14662 case fluid.XMLP._TEXT:
14663 case fluid.XMLP._ENTITY:
14664 case fluid.XMLP._CDATA:
14665 case fluid.XMLP._COMMENT:
14666 if (defstart === -1) {
14667 defstart = parser.m_cB;
14668 }
14669 defend = parser.m_cE;
14670 break;
14671 case fluid.XMLP._ERROR:
14672 fluid.setLogging(true);
14673 var message = "Error parsing template: " + parser.m_cAlt + " at line " + parser.getLineNumber();
14674 fluid.log(message);
14675 fluid.log("Just read: " + parser.m_xml.substring(parser.m_iP - 30, parser.m_iP));
14676 fluid.log("Still to read: " + parser.m_xml.substring(parser.m_iP, parser.m_iP + 30));
14677 fluid.fail(message);
14678 break parseloop;
14679 case fluid.XMLP._NONE:
14680 break parseloop;
14681 }
14682 }
14683 processDefaultTag();
14684 var excess = tagstack.length - 1;
14685 if (excess) {
14686 fluid.fail("Error parsing template - unclosed tag(s) of depth " + (excess) +
14687 ": " + fluid.transform(tagstack.splice(1, excess), function (lump) {return debugLump(lump);}).join(", "));
14688 }
14689 return t;
14690 };
14691
14692 // unsupported, non-API function
14693 fluid.debugLump = function (lump) {
14694 var togo = lump.text;
14695 togo += " at ";
14696 togo += "lump line " + lump.line + " column " + lump.column + " index " + lump.lumpindex;
14697 togo += lump.parent.href === null ? "" : " in file " + lump.parent.href;
14698 return togo;
14699 };
14700
14701 // Public definitions begin here
14702
14703 fluid.ID_ATTRIBUTE = "rsf:id";
14704
14705 // unsupported, non-API function
14706 fluid.getPrefix = function (id) {
14707 var colpos = id.indexOf(":");
14708 return colpos === -1 ? id : id.substring(0, colpos);
14709 };
14710
14711 // unsupported, non-API function
14712 fluid.SplitID = function (id) {
14713 var that = {};
14714 var colpos = id.indexOf(":");
14715 if (colpos === -1) {
14716 that.prefix = id;
14717 }
14718 else {
14719 that.prefix = id.substring(0, colpos);
14720 that.suffix = id.substring(colpos + 1);
14721 }
14722 return that;
14723 };
14724
14725 // unsupported, non-API function
14726 fluid.XMLViewTemplate = function () {
14727 return {
14728 globalmap: {},
14729 collectmap: {},
14730 lumps: [],
14731 firstdocumentindex: -1
14732 };
14733 };
14734
14735 // TODO: find faster encoder
14736 fluid.XMLEncode = function (text) {
14737 return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/\"/g, "&quot;");
14738 };
14739
14740 // unsupported, non-API function
14741 fluid.dumpAttributes = function (attrcopy) {
14742 var togo = "";
14743 for (var attrname in attrcopy) {
14744 var attrvalue = attrcopy[attrname];
14745 if (attrvalue !== null && attrvalue !== undefined) {
14746 togo += " " + attrname + "=\"" + attrvalue + "\"";
14747 }
14748 }
14749 return togo;
14750 };
14751
14752 // unsupported, non-API function
14753 fluid.aggregateMMap = function (target, source) {
14754 for (var key in source) {
14755 var targhas = target[key];
14756 if (!targhas) {
14757 target[key] = [];
14758 }
14759 target[key] = target[key].concat(source[key]);
14760 }
14761 };
14762
14763 /* Returns a "template structure", with globalmap in the root, and a list
14764 * of entries {href, template, cutpoints} for each parsed template.
14765 */
14766 fluid.parseTemplates = function (resourceSpec, templateList, opts) {
14767 var togo = [];
14768 opts = opts || {};
14769 togo.globalmap = {};
14770 for (var i = 0; i < templateList.length; ++i) {
14771 var resource = resourceSpec[templateList[i]];
14772 var lastslash = resource.href.lastIndexOf("/");
14773 var baseURL = lastslash === -1 ? "" : resource.href.substring(0, lastslash + 1);
14774
14775 var template = fluid.parseTemplate(resource.resourceText, baseURL,
14776 opts.scanStart && i === 0, resource.cutpoints, opts);
14777 if (i === 0) {
14778 fluid.aggregateMMap(togo.globalmap, template.globalmap);
14779 }
14780 template.href = resource.href;
14781 template.baseURL = baseURL;
14782 template.resourceKey = resource.resourceKey;
14783
14784 togo[i] = template;
14785 fluid.aggregateMMap(togo.globalmap, template.rootlump.downmap);
14786 }
14787 return togo;
14788 };
14789
14790})(jQuery, fluid_3_0_0);
14791;
14792/*
14793Copyright The Infusion copyright holders
14794See the AUTHORS.md file at the top-level directory of this distribution and at
14795https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
14796
14797Licensed under the Educational Community License (ECL), Version 2.0 or the New
14798BSD license. You may not use this file except in compliance with one these
14799Licenses.
14800
14801You may obtain a copy of the ECL 2.0 License and BSD License at
14802https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
14803*/
14804
14805fluid_3_0_0 = fluid_3_0_0 || {};
14806
14807(function ($, fluid) {
14808 "use strict";
14809
14810
14811
14812 function debugPosition(component) {
14813 return "as child of " + (component.parent.fullID ? "component with full ID " + component.parent.fullID : "root");
14814 }
14815
14816 function computeFullID(component) {
14817 var togo = "";
14818 var move = component;
14819 if (component.children === undefined) { // not a container
14820 // unusual case on the client-side, since a repetitive leaf may have localID blasted onto it.
14821 togo = component.ID + (component.localID !== undefined ? component.localID : "");
14822 move = component.parent;
14823 }
14824
14825 while (move.parent) {
14826 var parent = move.parent;
14827 if (move.fullID !== undefined) {
14828 togo = move.fullID + togo;
14829 return togo;
14830 }
14831 if (move.noID === undefined) {
14832 var ID = move.ID;
14833 if (ID === undefined) {
14834 fluid.fail("Error in component tree - component found with no ID " +
14835 debugPosition(parent) + ": please check structure");
14836 }
14837 var colpos = ID.indexOf(":");
14838 var prefix = colpos === -1 ? ID : ID.substring(0, colpos);
14839 togo = prefix + ":" + (move.localID === undefined ? "" : move.localID) + ":" + togo;
14840 }
14841 move = parent;
14842 }
14843
14844 return togo;
14845 }
14846
14847 var renderer = {};
14848
14849 renderer.isBoundPrimitive = function (value) {
14850 return fluid.isPrimitive(value) || fluid.isArrayable(value) &&
14851 (value.length === 0 || typeof (value[0]) === "string");
14852 };
14853
14854 var unzipComponent;
14855
14856 function processChild(value, key) {
14857 if (renderer.isBoundPrimitive(value)) {
14858 return {componentType: "UIBound", value: value, ID: key};
14859 }
14860 else {
14861 var unzip = unzipComponent(value);
14862 if (unzip.ID) {
14863 return {ID: key, componentType: "UIContainer", children: [unzip]};
14864 } else {
14865 unzip.ID = key;
14866 return unzip;
14867 }
14868 }
14869 }
14870
14871 function fixChildren(children) {
14872 if (!fluid.isArrayable(children)) {
14873 var togo = [];
14874 for (var key in children) {
14875 var value = children[key];
14876 if (fluid.isArrayable(value)) {
14877 for (var i = 0; i < value.length; ++i) {
14878 var processed = processChild(value[i], key);
14879 // if (processed.componentType === "UIContainer" &&
14880 // processed.localID === undefined) {
14881 // processed.localID = i;
14882 // }
14883 togo[togo.length] = processed;
14884 }
14885 } else {
14886 togo[togo.length] = processChild(value, key);
14887 }
14888 }
14889 return togo;
14890 } else {return children; }
14891 }
14892
14893 function fixupValue(uibound, model, resolverGetConfig) {
14894 if (uibound.value === undefined && uibound.valuebinding !== undefined) {
14895 uibound.value = fluid.get(model, uibound.valuebinding, resolverGetConfig);
14896 }
14897 }
14898
14899 function upgradeBound(holder, property, model, resolverGetConfig) {
14900 if (holder[property] !== undefined) {
14901 if (renderer.isBoundPrimitive(holder[property])) {
14902 holder[property] = {value: holder[property]};
14903 }
14904 else if (holder[property].messagekey) {
14905 holder[property].componentType = "UIMessage";
14906 }
14907 }
14908 else {
14909 holder[property] = {value: null};
14910 }
14911 fixupValue(holder[property], model, resolverGetConfig);
14912 }
14913
14914 renderer.duckMap = {children: "UIContainer",
14915 value: "UIBound", valuebinding: "UIBound", messagekey: "UIMessage",
14916 markup: "UIVerbatim", selection: "UISelect", target: "UILink",
14917 choiceindex: "UISelectChoice", functionname: "UIInitBlock"};
14918
14919 var boundMap = {
14920 UISelect: ["selection", "optionlist", "optionnames"],
14921 UILink: ["target", "linktext"],
14922 UIVerbatim: ["markup"],
14923 UIMessage: ["messagekey"]
14924 };
14925
14926 renderer.boundMap = fluid.transform(boundMap, fluid.arrayToHash);
14927
14928 renderer.inferComponentType = function (component) {
14929 for (var key in renderer.duckMap) {
14930 if (component[key] !== undefined) {
14931 return renderer.duckMap[key];
14932 }
14933 }
14934 };
14935
14936 renderer.applyComponentType = function (component) {
14937 component.componentType = renderer.inferComponentType(component);
14938 if (component.componentType === undefined && component.ID !== undefined) {
14939 component.componentType = "UIBound";
14940 }
14941 };
14942
14943 unzipComponent = function (component, model, resolverGetConfig) {
14944 if (component) {
14945 renderer.applyComponentType(component);
14946 }
14947 if (!component || component.componentType === undefined) {
14948 var decorators = component.decorators;
14949 if (decorators) {delete component.decorators;}
14950 component = {componentType: "UIContainer", children: component};
14951 component.decorators = decorators;
14952 }
14953 var cType = component.componentType;
14954 if (cType === "UIContainer") {
14955 component.children = fixChildren(component.children);
14956 }
14957 else {
14958 var map = renderer.boundMap[cType];
14959 if (map) {
14960 fluid.each(map, function (value, key) {
14961 upgradeBound(component, key, model, resolverGetConfig);
14962 });
14963 }
14964 }
14965
14966 return component;
14967 };
14968
14969 function fixupTree(tree, model, resolverGetConfig) {
14970 if (tree.componentType === undefined) {
14971 tree = unzipComponent(tree, model, resolverGetConfig);
14972 }
14973 if (tree.componentType !== "UIContainer" && !tree.parent) {
14974 tree = {children: [tree]};
14975 }
14976
14977 if (tree.children) {
14978 tree.childmap = {};
14979 for (var i = 0; i < tree.children.length; ++i) {
14980 var child = tree.children[i];
14981 if (child.componentType === undefined) {
14982 child = unzipComponent(child, model, resolverGetConfig);
14983 tree.children[i] = child;
14984 }
14985 child.parent = tree;
14986 if (child.ID === undefined) {
14987 fluid.fail("Error in component tree: component found with no ID " + debugPosition(child));
14988 }
14989 tree.childmap[child.ID] = child;
14990 var colpos = child.ID.indexOf(":");
14991 if (colpos === -1) {
14992 // tree.childmap[child.ID] = child; // moved out of branch to allow
14993 // "relative id expressions" to be easily parsed
14994 }
14995 else {
14996 var prefix = child.ID.substring(0, colpos);
14997 var childlist = tree.childmap[prefix];
14998 if (!childlist) {
14999 childlist = [];
15000 tree.childmap[prefix] = childlist;
15001 }
15002 if (child.localID === undefined && childlist.length !== 0) {
15003 child.localID = childlist.length;
15004 }
15005 childlist[childlist.length] = child;
15006 }
15007 child.fullID = computeFullID(child);
15008
15009 var componentType = child.componentType;
15010 if (componentType === "UISelect") {
15011 child.selection.fullID = child.fullID;
15012 }
15013 else if (componentType === "UIInitBlock") {
15014 var call = child.functionname + "(";
15015 var childArgs = child.arguments;
15016 for (var j = 0; j < childArgs.length; ++j) {
15017 if (childArgs[j] instanceof fluid.ComponentReference) {
15018 // TODO: support more forms of id reference
15019 childArgs[j] = child.parent.fullID + childArgs[j].reference;
15020 }
15021 call += JSON.stringify(childArgs[j]);
15022 if (j < childArgs.length - 1) {
15023 call += ", ";
15024 }
15025 }
15026 child.markup = {value: call + ")\n"};
15027 child.componentType = "UIVerbatim";
15028 }
15029 else if (componentType === "UIBound") {
15030 fixupValue(child, model, resolverGetConfig);
15031 }
15032 fixupTree(child, model, resolverGetConfig);
15033 }
15034 }
15035 return tree;
15036 }
15037
15038 fluid.NULL_STRING = "\u25a9null\u25a9";
15039
15040 var LINK_ATTRIBUTES = {
15041 a: "href",
15042 link: "href",
15043 img: "src",
15044 frame: "src",
15045 script: "src",
15046 style: "src",
15047 input: "src",
15048 embed: "src",
15049 form: "action",
15050 applet: "codebase",
15051 object: "codebase"
15052 };
15053
15054 renderer.decoratorComponentPrefix = "**-renderer-";
15055
15056 renderer.IDtoComponentName = function (ID, num) {
15057 return renderer.decoratorComponentPrefix + ID.replace(/\./g, "") + "-" + num;
15058 };
15059
15060 renderer.invokeFluidDecorator = function (func, args, ID, num, options) {
15061 var that;
15062 if (options.parentComponent) {
15063 var parent = options.parentComponent;
15064 var name = renderer.IDtoComponentName(ID, num);
15065 fluid.set(parent, ["options", "components", name], {
15066 type: func,
15067 container: args[0],
15068 options: args[1]
15069 });
15070 that = fluid.initDependent(options.parentComponent, name);
15071 }
15072 else {
15073 that = fluid.invokeGlobalFunction(func, args);
15074 }
15075 return that;
15076 };
15077
15078 fluid.renderer = function (templates, tree, options, fossilsIn) {
15079
15080 options = options || {};
15081 tree = tree || {};
15082 var debugMode = options.debugMode;
15083 if (!options.messageLocator && options.messageSource) {
15084 options.messageLocator = fluid.resolveMessageSource(options.messageSource);
15085 }
15086 options.document = options.document || document;
15087 options.jQuery = options.jQuery || $;
15088 options.fossils = options.fossils || fossilsIn || {}; // map of submittingname to {EL, submittingname, oldvalue}
15089
15090 var globalmap = {};
15091 var branchmap = {};
15092 var rewritemap = {}; // map of rewritekey (for original id in template) to full ID
15093 var seenset = {};
15094 var collected = {};
15095 var out = "";
15096 var renderOptions = options;
15097 var decoratorQueue = [];
15098
15099 var renderedbindings = {}; // map of fullID to true for UISelects which have already had bindings written
15100 var usedIDs = {};
15101
15102 var that = {options: options};
15103
15104 function getRewriteKey(template, parent, id) {
15105 return template.resourceKey + parent.fullID + id;
15106 }
15107 // returns: lump
15108 function resolveInScope(searchID, defprefix, scope) {
15109 var deflump;
15110 var scopelook = scope ? scope[searchID] : null;
15111 if (scopelook) {
15112 for (var i = 0; i < scopelook.length; ++i) {
15113 var scopelump = scopelook[i];
15114 if (!deflump && scopelump.rsfID === defprefix) {
15115 deflump = scopelump;
15116 }
15117 if (scopelump.rsfID === searchID) {
15118 return scopelump;
15119 }
15120 }
15121 }
15122 return deflump;
15123 }
15124 // returns: lump
15125 function resolveCall(sourcescope, child) {
15126 var searchID = child.jointID ? child.jointID : child.ID;
15127 var split = fluid.SplitID(searchID);
15128 var defprefix = split.prefix + ":";
15129 var match = resolveInScope(searchID, defprefix, sourcescope.downmap, child);
15130 if (match) {return match;}
15131 if (child.children) {
15132 match = resolveInScope(searchID, defprefix, globalmap, child);
15133 if (match) {return match;}
15134 }
15135 return null;
15136 }
15137
15138 function noteCollected(template) {
15139 if (!seenset[template.href]) {
15140 fluid.aggregateMMap(collected, template.collectmap);
15141 seenset[template.href] = true;
15142 }
15143 }
15144
15145 var fetchComponent;
15146
15147 function resolveRecurse(basecontainer, parentlump) {
15148 var i;
15149 var id;
15150 var resolved;
15151 for (i = 0; i < basecontainer.children.length; ++i) {
15152 var branch = basecontainer.children[i];
15153 if (branch.children) { // it is a branch
15154 resolved = resolveCall(parentlump, branch);
15155 if (resolved) {
15156 branchmap[branch.fullID] = resolved;
15157 id = resolved.attributemap.id;
15158 if (id !== undefined) {
15159 rewritemap[getRewriteKey(parentlump.parent, basecontainer, id)] = branch.fullID;
15160 }
15161 // on server-side this is done separately
15162 noteCollected(resolved.parent);
15163 resolveRecurse(branch, resolved);
15164 }
15165 }
15166 }
15167 // collect any rewritten ids for the purpose of later rewriting
15168 if (parentlump.downmap) {
15169 for (id in parentlump.downmap) {
15170 //if (id.indexOf(":") === -1) {
15171 var lumps = parentlump.downmap[id];
15172 for (i = 0; i < lumps.length; ++i) {
15173 var lump = lumps[i];
15174 var lumpid = lump.attributemap.id;
15175 if (lumpid !== undefined && lump.rsfID !== undefined) {
15176 resolved = fetchComponent(basecontainer, lump.rsfID);
15177 if (resolved !== null) {
15178 var resolveID = resolved.fullID;
15179 rewritemap[getRewriteKey(parentlump.parent, basecontainer,
15180 lumpid)] = resolveID;
15181 }
15182 }
15183 }
15184 // }
15185 }
15186 }
15187
15188 }
15189
15190 function resolveBranches(globalmapp, basecontainer, parentlump) {
15191 branchmap = {};
15192 rewritemap = {};
15193 seenset = {};
15194 collected = {};
15195 globalmap = globalmapp;
15196 branchmap[basecontainer.fullID] = parentlump;
15197 resolveRecurse(basecontainer, parentlump);
15198 }
15199
15200 function dumpTillLump(lumps, start, limit) {
15201 for (; start < limit; ++start) {
15202 var text = lumps[start].text;
15203 if (text) { // guard against "undefined" lumps from "justended"
15204 out += lumps[start].text;
15205 }
15206 }
15207 }
15208
15209 function dumpScan(lumps, renderindex, basedepth, closeparent, insideleaf) {
15210 var start = renderindex;
15211 while (true) {
15212 if (renderindex === lumps.length) {
15213 break;
15214 }
15215 var lump = lumps[renderindex];
15216 if (lump.nestingdepth < basedepth) {
15217 break;
15218 }
15219 if (lump.rsfID !== undefined) {
15220 if (!insideleaf) {break;}
15221 if (insideleaf && lump.nestingdepth > basedepth + (closeparent ? 0 : 1)) {
15222 fluid.log("Error in component tree - leaf component found to contain further components - at " +
15223 lump.toString());
15224 }
15225 else {break;}
15226 }
15227 // target.print(lump.text);
15228 ++renderindex;
15229 }
15230 // ASSUMPTIONS: close tags are ONE LUMP
15231 if (!closeparent && (renderindex === lumps.length || !lumps[renderindex].rsfID)) {
15232 --renderindex;
15233 }
15234
15235 dumpTillLump(lumps, start, renderindex);
15236 //target.write(buffer, start, limit - start);
15237 return renderindex;
15238 }
15239
15240
15241 function isPlaceholder() {
15242 // TODO: equivalent of server-side "placeholder" system
15243 return false;
15244 }
15245
15246 function isValue(value) {
15247 return value !== null && value !== undefined && !isPlaceholder(value);
15248 }
15249
15250 // In RSF Client, this is a "flyweight" "global" object that is reused for every tag,
15251 // to avoid generating garbage. In RSF Server, it is an argument to the following rendering
15252 // methods of type "TagRenderContext".
15253
15254 var trc = {};
15255
15256 /*** TRC METHODS ***/
15257
15258 function openTag() {
15259 if (!trc.iselide) {
15260 out += "<" + trc.uselump.tagname;
15261 }
15262 }
15263
15264 function closeTag() {
15265 if (!trc.iselide) {
15266 out += "</" + trc.uselump.tagname + ">";
15267 }
15268 }
15269
15270 function renderUnchanged() {
15271 // TODO needs work since we don't keep attributes in text
15272 dumpTillLump(trc.uselump.parent.lumps, trc.uselump.lumpindex + 1,
15273 trc.close.lumpindex + (trc.iselide ? 0 : 1));
15274 }
15275
15276 function isSelfClose() {
15277 return trc.endopen.lumpindex === trc.close.lumpindex && fluid.XMLP.closedTags[trc.uselump.tagname];
15278 }
15279
15280 function dumpTemplateBody() {
15281 if (isSelfClose()) {
15282 if (!trc.iselide) {
15283 out += "/>";
15284 }
15285 }
15286 else {
15287 if (!trc.iselide) {
15288 out += ">";
15289 }
15290 dumpTillLump(trc.uselump.parent.lumps, trc.endopen.lumpindex,
15291 trc.close.lumpindex + (trc.iselide ? 0 : 1));
15292 }
15293 }
15294
15295 function replaceAttributes() {
15296 if (!trc.iselide) {
15297 out += fluid.dumpAttributes(trc.attrcopy);
15298 }
15299 dumpTemplateBody();
15300 }
15301
15302 function replaceAttributesOpen() {
15303 if (trc.iselide) {
15304 replaceAttributes();
15305 }
15306 else {
15307 out += fluid.dumpAttributes(trc.attrcopy);
15308 var selfClose = isSelfClose();
15309 // TODO: the parser does not ever produce empty tags
15310 out += selfClose ? "/>" : ">";
15311
15312 trc.nextpos = selfClose ? trc.close.lumpindex + 1 : trc.endopen.lumpindex;
15313 }
15314 }
15315
15316 function replaceBody(value) {
15317 out += fluid.dumpAttributes(trc.attrcopy);
15318 if (!trc.iselide) {
15319 out += ">";
15320 }
15321 out += fluid.XMLEncode(value.toString());
15322 closeTag();
15323 }
15324
15325 function rewriteLeaf(value) {
15326 if (isValue(value)) {
15327 replaceBody(value);
15328 }
15329 else {
15330 replaceAttributes();
15331 }
15332 }
15333
15334 function rewriteLeafOpen(value) {
15335 if (trc.iselide) {
15336 rewriteLeaf(trc.value);
15337 }
15338 else {
15339 if (isValue(value)) {
15340 replaceBody(value);
15341 }
15342 else {
15343 replaceAttributesOpen();
15344 }
15345 }
15346 }
15347
15348
15349 /*** END TRC METHODS**/
15350
15351 function rewriteUrl(template, url) {
15352 if (renderOptions.urlRewriter) {
15353 var rewritten = renderOptions.urlRewriter(url);
15354 if (rewritten) {
15355 return rewritten;
15356 }
15357 }
15358 if (!renderOptions.rebaseURLs) {
15359 return url;
15360 }
15361 var protpos = url.indexOf(":/");
15362 if (url.charAt(0) === "/" || protpos !== -1 && protpos < 7) {
15363 return url;
15364 }
15365 else {
15366 return renderOptions.baseURL + url;
15367 }
15368 }
15369
15370 function dumpHiddenField(/** UIParameter **/ todump) {
15371 out += "<input type=\"hidden\" ";
15372 var isvirtual = todump.virtual;
15373 var outattrs = {};
15374 outattrs[isvirtual ? "id" : "name"] = todump.name;
15375 outattrs.value = todump.value;
15376 out += fluid.dumpAttributes(outattrs);
15377 out += " />\n";
15378 }
15379
15380 var outDecoratorsImpl;
15381
15382 function applyAutoBind(torender, finalID) {
15383 if (!finalID) {
15384 // if no id is assigned so far, this is a signal that this is a "virtual" component such as
15385 // a non-HTML UISelect which will not have physical markup.
15386 return;
15387 }
15388 var tagname = trc.uselump.tagname;
15389 var applier = renderOptions.applier;
15390 function applyFunc() {
15391 fluid.applyBoundChange(fluid.byId(finalID, renderOptions.document), undefined, applier);
15392 }
15393 if (renderOptions.autoBind && /input|select|textarea/.test(tagname) && !renderedbindings[finalID]) {
15394 var decorators = [{jQuery: ["change", applyFunc]}];
15395 // Work around bug 193: http://webbugtrack.blogspot.com/2007/11/bug-193-onchange-does-not-fire-properly.html
15396 if ($.browser.msie && tagname === "input" && /radio|checkbox/.test(trc.attrcopy.type)) {
15397 decorators.push({jQuery: ["click", applyFunc]});
15398 }
15399 if ($.browser.safari && tagname === "input" && trc.attrcopy.type === "radio") {
15400 decorators.push({jQuery: ["keyup", applyFunc]});
15401 }
15402 outDecoratorsImpl(torender, decorators, trc.attrcopy, finalID);
15403 }
15404 }
15405
15406 function dumpBoundFields(/** UIBound**/ torender, parent) {
15407 if (torender) {
15408 var holder = parent ? parent : torender;
15409 if (renderOptions.fossils && holder.valuebinding !== undefined) {
15410 var fossilKey = holder.submittingname || torender.finalID;
15411 // TODO: this will store multiple times for each member of a UISelect
15412 renderOptions.fossils[fossilKey] = {
15413 name: fossilKey,
15414 EL: holder.valuebinding,
15415 oldvalue: holder.value
15416 };
15417 // But this has to happen multiple times
15418 applyAutoBind(torender, torender.finalID);
15419 }
15420 if (torender.fossilizedbinding) {
15421 dumpHiddenField(torender.fossilizedbinding);
15422 }
15423 if (torender.fossilizedshaper) {
15424 dumpHiddenField(torender.fossilizedshaper);
15425 }
15426 }
15427 }
15428
15429 function dumpSelectionBindings(uiselect) {
15430 if (!renderedbindings[uiselect.selection.fullID]) {
15431 renderedbindings[uiselect.selection.fullID] = true; // set this true early so that selection does not autobind twice
15432 dumpBoundFields(uiselect.selection);
15433 dumpBoundFields(uiselect.optionlist);
15434 dumpBoundFields(uiselect.optionnames);
15435 }
15436 }
15437
15438 function isSelectedValue(torender, value) {
15439 var selection = torender.selection;
15440 return fluid.isArrayable(selection.value) ? selection.value.indexOf(value) !== -1 : selection.value === value;
15441 }
15442
15443 function getRelativeComponent(component, relativeID) {
15444 component = component.parent;
15445 while (relativeID.indexOf("..::") === 0) {
15446 relativeID = relativeID.substring(4);
15447 component = component.parent;
15448 }
15449 return component.childmap[relativeID];
15450 }
15451
15452 // TODO: This mechanism inefficiently handles the rare case of a target document
15453 // id collision requiring a rewrite for FLUID-5048. In case it needs improving, we
15454 // could hold an inverted index - however, these cases will become even rarer with FLUID-5047
15455 function rewriteRewriteMap(from, to) {
15456 fluid.each(rewritemap, function (value, key) {
15457 if (value === from) {
15458 rewritemap[key] = to;
15459 }
15460 });
15461 }
15462
15463 function adjustForID(attrcopy, component, late, forceID) {
15464 if (!late) {
15465 delete attrcopy["rsf:id"];
15466 }
15467 if (component.finalID !== undefined) {
15468 attrcopy.id = component.finalID;
15469 }
15470 else if (forceID !== undefined) {
15471 attrcopy.id = forceID;
15472 }
15473 else {
15474 if (attrcopy.id || late) {
15475 attrcopy.id = component.fullID;
15476 }
15477 }
15478
15479 var count = 1;
15480 var baseid = attrcopy.id;
15481 while (renderOptions.document.getElementById(attrcopy.id) || usedIDs[attrcopy.id]) {
15482 attrcopy.id = baseid + "-" + (count++);
15483 }
15484 if (count !== 1) {
15485 rewriteRewriteMap(baseid, attrcopy.id);
15486 }
15487 component.finalID = attrcopy.id;
15488 return attrcopy.id;
15489 }
15490
15491 function assignSubmittingName(attrcopy, component, parent) {
15492 var submitting = parent || component;
15493 // if a submittingName is required, we must already go out to the document to
15494 // uniquify the id that it will be derived from
15495 adjustForID(attrcopy, component, true, component.fullID);
15496 if (submitting.submittingname === undefined && submitting.willinput !== false) {
15497 submitting.submittingname = submitting.finalID || submitting.fullID;
15498 }
15499 return submitting.submittingname;
15500 }
15501
15502 function explodeDecorators(decorators) {
15503 var togo = [];
15504 if (decorators.type) {
15505 togo[0] = decorators;
15506 }
15507 else {
15508 for (var key in decorators) {
15509 if (key === "$") {key = "jQuery";}
15510 var value = decorators[key];
15511 var decorator = {
15512 type: key
15513 };
15514 if (key === "jQuery") {
15515 decorator.func = value[0];
15516 decorator.args = value.slice(1);
15517 }
15518 else if (key === "addClass" || key === "removeClass") {
15519 decorator.classes = value;
15520 }
15521 else if (key === "attrs") {
15522 decorator.attributes = value;
15523 }
15524 else if (key === "identify") {
15525 decorator.key = value;
15526 }
15527 togo[togo.length] = decorator;
15528 }
15529 }
15530 return togo;
15531 }
15532
15533 outDecoratorsImpl = function (torender, decorators, attrcopy, finalID) {
15534 var id;
15535 var sanitizeAttrs = function (value, key) {
15536 if (value === null || value === undefined) {
15537 delete attrcopy[key];
15538 }
15539 else {
15540 attrcopy[key] = fluid.XMLEncode(value);
15541 }
15542 };
15543 renderOptions.idMap = renderOptions.idMap || {};
15544 for (var i = 0; i < decorators.length; ++i) {
15545 var decorator = decorators[i];
15546 var type = decorator.type;
15547 if (!type) {
15548 var explodedDecorators = explodeDecorators(decorator);
15549 outDecoratorsImpl(torender, explodedDecorators, attrcopy, finalID);
15550 continue;
15551 }
15552 if (type === "$") {type = decorator.type = "jQuery";}
15553 if (type === "jQuery" || type === "event" || type === "fluid") {
15554 id = adjustForID(attrcopy, torender, true, finalID);
15555 if (decorator.ids === undefined) {
15556 decorator.ids = [];
15557 decoratorQueue[decoratorQueue.length] = decorator;
15558 }
15559 decorator.ids.push(id);
15560 }
15561 // honour these remaining types immediately
15562 else if (type === "attrs") {
15563 fluid.each(decorator.attributes, sanitizeAttrs);
15564 }
15565 else if (type === "addClass" || type === "removeClass") {
15566 // Using an unattached DOM node because jQuery will use the
15567 // node's setAttribute method to add the class.
15568 var fakeNode = $("<div>", {class: attrcopy["class"]})[0];
15569 renderOptions.jQuery(fakeNode)[type](decorator.classes);
15570 attrcopy["class"] = fakeNode.className;
15571 }
15572 else if (type === "identify") {
15573 id = adjustForID(attrcopy, torender, true, finalID);
15574 renderOptions.idMap[decorator.key] = id;
15575 }
15576 else if (type !== "null") {
15577 fluid.log("Unrecognised decorator of type " + type + " found at component of ID " + finalID);
15578 }
15579 }
15580 };
15581
15582 function outDecorators(torender, attrcopy) {
15583 if (!torender.decorators) {return;}
15584 if (torender.decorators.length === undefined) {
15585 torender.decorators = explodeDecorators(torender.decorators);
15586 }
15587 outDecoratorsImpl(torender, torender.decorators, attrcopy);
15588 }
15589
15590 function dumpBranchHead(branch, targetlump) {
15591 if (targetlump.elide) {
15592 return;
15593 }
15594 var attrcopy = {};
15595 $.extend(true, attrcopy, targetlump.attributemap);
15596 adjustForID(attrcopy, branch);
15597 outDecorators(branch, attrcopy);
15598 out += "<" + targetlump.tagname + " ";
15599 out += fluid.dumpAttributes(attrcopy);
15600 out += ">";
15601 }
15602
15603 function resolveArgs(args) {
15604 if (!args) {return args;}
15605 args = fluid.copy(args); // FLUID-4737: Avoid corrupting material which may have been fetched from the model
15606 return fluid.transform(args, function (arg, index) {
15607 upgradeBound(args, index, renderOptions.model, renderOptions.resolverGetConfig);
15608 return args[index].value;
15609 });
15610 }
15611
15612 function degradeMessage(torender) {
15613 if (torender.componentType === "UIMessage") {
15614 // degrade UIMessage to UIBound by resolving the message
15615 torender.componentType = "UIBound";
15616 if (!renderOptions.messageLocator) {
15617 torender.value = "[No messageLocator is configured in options - please consult documentation on options.messageSource]";
15618 }
15619 else {
15620 upgradeBound(torender, "messagekey", renderOptions.model, renderOptions.resolverGetConfig);
15621 var resArgs = resolveArgs(torender.args);
15622 torender.value = renderOptions.messageLocator(torender.messagekey.value, resArgs);
15623 }
15624 }
15625 }
15626
15627
15628 function renderComponent(torender) {
15629 var value;
15630 var attrcopy = trc.attrcopy;
15631
15632 degradeMessage(torender);
15633 var componentType = torender.componentType;
15634 var tagname = trc.uselump.tagname;
15635
15636 outDecorators(torender, attrcopy);
15637
15638 function makeFail(torender, end) {
15639 fluid.fail("Error in component tree - UISelectChoice with id " + torender.fullID + end);
15640 }
15641
15642 if (componentType === "UIBound" || componentType === "UISelectChoice") {
15643 var parent;
15644 if (torender.choiceindex !== undefined) {
15645 if (torender.parentRelativeID !== undefined) {
15646 parent = getRelativeComponent(torender, torender.parentRelativeID);
15647 if (!parent) {
15648 makeFail(torender, " has parentRelativeID of " + torender.parentRelativeID + " which cannot be resolved");
15649 }
15650 }
15651 else {
15652 makeFail(torender, " does not have parentRelativeID set");
15653 }
15654 assignSubmittingName(attrcopy, torender, parent.selection);
15655 dumpSelectionBindings(parent);
15656 }
15657
15658 var submittingname = parent ? parent.selection.submittingname : torender.submittingname;
15659 if (!parent && torender.valuebinding) {
15660 // Do this for all bound fields even if non submitting so that finalID is set in order to track fossils (FLUID-3387)
15661 submittingname = assignSubmittingName(attrcopy, torender);
15662 }
15663 if (tagname === "input" || tagname === "textarea") {
15664 if (submittingname !== undefined) {
15665 attrcopy.name = submittingname;
15666 }
15667 }
15668 // this needs to happen early on the client, since it may cause the allocation of the
15669 // id in the case of a "deferred decorator". However, for server-side bindings, this
15670 // will be an inappropriate time, unless we shift the timing of emitting the opening tag.
15671 dumpBoundFields(torender, parent ? parent.selection : null);
15672
15673 if (typeof(torender.value) === "boolean" || attrcopy.type === "radio" || attrcopy.type === "checkbox") {
15674 var underlyingValue;
15675 var directValue = torender.value;
15676
15677 if (torender.choiceindex !== undefined) {
15678 if (!parent.optionlist.value) {
15679 fluid.fail("Error in component tree - selection control with full ID " + parent.fullID + " has no values");
15680 }
15681 underlyingValue = parent.optionlist.value[torender.choiceindex];
15682 directValue = isSelectedValue(parent, underlyingValue);
15683 }
15684 if (isValue(directValue)) {
15685 if (directValue) {
15686 attrcopy.checked = "checked";
15687 }
15688 else {
15689 delete attrcopy.checked;
15690 }
15691 }
15692 attrcopy.value = fluid.XMLEncode(underlyingValue ? underlyingValue : "true");
15693 rewriteLeaf(null);
15694 }
15695 else if (fluid.isArrayable(torender.value)) {
15696 // Cannot be rendered directly, must be fake
15697 renderUnchanged();
15698 }
15699 else { // String value
15700 value = parent ?
15701 parent[tagname === "textarea" || tagname === "input" ? "optionlist" : "optionnames"].value[torender.choiceindex] :
15702 torender.value;
15703 if (tagname === "textarea") {
15704 if (isPlaceholder(value) && torender.willinput) {
15705 // FORCE a blank value for input components if nothing from
15706 // model, if input was intended.
15707 value = "";
15708 }
15709 rewriteLeaf(value);
15710 }
15711 else if (tagname === "input") {
15712 if (torender.willinput || isValue(value)) {
15713 attrcopy.value = fluid.XMLEncode(String(value));
15714 }
15715 rewriteLeaf(null);
15716 }
15717 else {
15718 delete attrcopy.name;
15719 rewriteLeafOpen(value);
15720 }
15721 }
15722 }
15723 else if (componentType === "UISelect") {
15724
15725 var ishtmlselect = tagname === "select";
15726 var ismultiple = false; // eslint-disable-line no-unused-vars
15727
15728 if (fluid.isArrayable(torender.selection.value)) {
15729 ismultiple = true;
15730 if (ishtmlselect) {
15731 attrcopy.multiple = "multiple";
15732 }
15733 }
15734 // assignSubmittingName is now the definitive trigger point for uniquifying output IDs
15735 // However, if id is already assigned it is probably through attempt to decorate root select.
15736 // in this case restore it.
15737 assignSubmittingName(attrcopy, torender.selection);
15738
15739 if (ishtmlselect) {
15740 // The HTML submitted value from a <select> actually corresponds
15741 // with the selection member, not the top-level component.
15742 if (torender.selection.willinput !== false) {
15743 attrcopy.name = torender.selection.submittingname;
15744 }
15745 applyAutoBind(torender, attrcopy.id);
15746 }
15747
15748 out += fluid.dumpAttributes(attrcopy);
15749 if (ishtmlselect) {
15750 out += ">";
15751 var values = torender.optionlist.value;
15752 var names = torender.optionnames === null || torender.optionnames === undefined || !torender.optionnames.value ? values : torender.optionnames.value;
15753 if (!names || !names.length) {
15754 fluid.fail("Error in component tree - UISelect component with fullID " +
15755 torender.fullID + " does not have optionnames set");
15756 }
15757 for (var i = 0; i < names.length; ++i) {
15758 out += "<option value=\"";
15759 value = values[i];
15760 if (value === null) {
15761 value = fluid.NULL_STRING;
15762 }
15763 out += fluid.XMLEncode(value);
15764 if (isSelectedValue(torender, value)) {
15765 out += "\" selected=\"selected";
15766 }
15767 out += "\">";
15768 out += fluid.XMLEncode(names[i]);
15769 out += "</option>\n";
15770 }
15771 closeTag();
15772 }
15773 else {
15774 dumpTemplateBody();
15775 }
15776 dumpSelectionBindings(torender);
15777 }
15778 else if (componentType === "UILink") {
15779 var attrname = LINK_ATTRIBUTES[tagname];
15780 if (attrname) {
15781 degradeMessage(torender.target);
15782 var target = torender.target.value;
15783 if (!isValue(target)) {
15784 target = attrcopy[attrname];
15785 }
15786 target = rewriteUrl(trc.uselump.parent, target);
15787 // Note that all real browsers succeed in recovering the URL here even if it is presented in violation of XML
15788 // seemingly due to the purest accident, the text &amp; cannot occur in a properly encoded URL :P
15789 attrcopy[attrname] = fluid.XMLEncode(target);
15790 }
15791 value = undefined;
15792 if (torender.linktext) {
15793 degradeMessage(torender.linktext);
15794 value = torender.linktext.value;
15795 }
15796 if (!isValue(value)) {
15797 replaceAttributesOpen();
15798 }
15799 else {
15800 rewriteLeaf(value);
15801 }
15802 }
15803
15804 else if (torender.markup !== undefined) { // detect UIVerbatim
15805 degradeMessage(torender.markup);
15806 var rendered = torender.markup.value;
15807 if (rendered === null) {
15808 // TODO, doesn't quite work due to attr folding cf Java code
15809 out += fluid.dumpAttributes(attrcopy);
15810 out += ">";
15811 renderUnchanged();
15812 }
15813 else {
15814 if (!trc.iselide) {
15815 out += fluid.dumpAttributes(attrcopy);
15816 out += ">";
15817 }
15818 out += rendered;
15819 closeTag();
15820 }
15821 }
15822 if (attrcopy.id !== undefined) {
15823 usedIDs[attrcopy.id] = true;
15824 }
15825 }
15826
15827 function rewriteIDRelation(context) {
15828 var attrname;
15829 var attrval = trc.attrcopy["for"];
15830 if (attrval !== undefined) {
15831 attrname = "for";
15832 }
15833 else {
15834 attrval = trc.attrcopy.headers;
15835 if (attrval !== undefined) {
15836 attrname = "headers";
15837 }
15838 }
15839 if (!attrname) {return;}
15840 var tagname = trc.uselump.tagname;
15841 if (attrname === "for" && tagname !== "label") {return;}
15842 if (attrname === "headers" && tagname !== "td" && tagname !== "th") {return;}
15843 var rewritten = rewritemap[getRewriteKey(trc.uselump.parent, context, attrval)];
15844 if (rewritten !== undefined) {
15845 trc.attrcopy[attrname] = rewritten;
15846 }
15847 }
15848
15849 function renderComment(message) {
15850 out += ("<!-- " + fluid.XMLEncode(message) + "-->");
15851 }
15852
15853 function renderDebugMessage(message) {
15854 out += "<span style=\"background-color:#FF466B;color:white;padding:1px;\">";
15855 out += message;
15856 out += "</span><br/>";
15857 }
15858
15859 function reportPath(/*UIComponent*/ branch) {
15860 var path = branch.fullID;
15861 return !path ? "component tree root" : "full path " + path;
15862 }
15863
15864 function renderComponentSystem(context, torendero, lump) {
15865 var lumpindex = lump.lumpindex;
15866 var lumps = lump.parent.lumps;
15867 var nextpos = -1;
15868 var outerendopen = lumps[lumpindex + 1];
15869 var outerclose = lump.close_tag;
15870
15871 nextpos = outerclose.lumpindex + 1;
15872
15873 var payloadlist = lump.downmap ? lump.downmap["payload-component"] : null;
15874 var payload = payloadlist ? payloadlist[0] : null;
15875
15876 var iselide = lump.rsfID.charCodeAt(0) === 126; // "~"
15877
15878 var endopen = outerendopen;
15879 var close = outerclose;
15880 var uselump = lump;
15881 var attrcopy = {};
15882 $.extend(true, attrcopy, (payload === null ? lump : payload).attributemap);
15883
15884 trc.attrcopy = attrcopy;
15885 trc.uselump = uselump;
15886 trc.endopen = endopen;
15887 trc.close = close;
15888 trc.nextpos = nextpos;
15889 trc.iselide = iselide;
15890
15891 rewriteIDRelation(context);
15892
15893 if (torendero === null) {
15894 if (lump.rsfID.indexOf("scr=") === (iselide ? 1 : 0)) {
15895 var scrname = lump.rsfID.substring(4 + (iselide ? 1 : 0));
15896 if (scrname === "ignore") {
15897 nextpos = trc.close.lumpindex + 1;
15898 }
15899 else if (scrname === "rewrite-url") {
15900 torendero = {componentType: "UILink", target: {}};
15901 }
15902 else {
15903 openTag();
15904 replaceAttributesOpen();
15905 nextpos = trc.endopen.lumpindex;
15906 }
15907 }
15908 }
15909 if (torendero !== null) {
15910 // else there IS a component and we are going to render it. First make
15911 // sure we render any preamble.
15912
15913 if (payload) {
15914 trc.endopen = lumps[payload.lumpindex + 1];
15915 trc.close = payload.close_tag;
15916 trc.uselump = payload;
15917 dumpTillLump(lumps, lumpindex, payload.lumpindex);
15918 lumpindex = payload.lumpindex;
15919 }
15920
15921 adjustForID(attrcopy, torendero);
15922 //decoratormanager.decorate(torendero.decorators, uselump.getTag(), attrcopy);
15923
15924 // ALWAYS dump the tag name, this can never be rewritten. (probably?!)
15925 openTag();
15926
15927 renderComponent(torendero);
15928 // if there is a payload, dump the postamble.
15929 if (payload !== null) {
15930 // the default case is initialised to tag close
15931 if (trc.nextpos === nextpos) {
15932 dumpTillLump(lumps, trc.close.lumpindex + 1, outerclose.lumpindex + 1);
15933 }
15934 }
15935 nextpos = trc.nextpos;
15936 }
15937 return nextpos;
15938 }
15939 var renderRecurse;
15940
15941 function renderContainer(child, targetlump) {
15942 var t2 = targetlump.parent;
15943 var firstchild = t2.lumps[targetlump.lumpindex + 1];
15944 if (child.children !== undefined) {
15945 dumpBranchHead(child, targetlump);
15946 }
15947 else {
15948 renderComponentSystem(child.parent, child, targetlump);
15949 }
15950 renderRecurse(child, targetlump, firstchild);
15951 }
15952
15953 fetchComponent = function (basecontainer, id) {
15954 if (id.indexOf("msg=") === 0) {
15955 var key = id.substring(4);
15956 return {componentType: "UIMessage", messagekey: key};
15957 }
15958 while (basecontainer) {
15959 var togo = basecontainer.childmap[id];
15960 if (togo) {
15961 return togo;
15962 }
15963 basecontainer = basecontainer.parent;
15964 }
15965 return null;
15966 };
15967
15968 function fetchComponents(basecontainer, id) {
15969 var togo;
15970 while (basecontainer) {
15971 togo = basecontainer.childmap[id];
15972 if (togo) {
15973 break;
15974 }
15975 basecontainer = basecontainer.parent;
15976 }
15977 return togo;
15978 }
15979
15980 function findChild(sourcescope, child) {
15981 var split = fluid.SplitID(child.ID);
15982 var headlumps = sourcescope.downmap[child.ID];
15983 if (!headlumps) {
15984 headlumps = sourcescope.downmap[split.prefix + ":"];
15985 }
15986 return headlumps ? headlumps[0] : null;
15987 }
15988
15989 renderRecurse = function (basecontainer, parentlump, baselump) {
15990 var children;
15991 var targetlump;
15992 var child;
15993 var renderindex = baselump.lumpindex;
15994 var basedepth = parentlump.nestingdepth;
15995 var t1 = parentlump.parent;
15996 var rendered;
15997 if (debugMode) {
15998 rendered = {};
15999 }
16000 while (true) {
16001 renderindex = dumpScan(t1.lumps, renderindex, basedepth, !parentlump.elide, false);
16002 if (renderindex === t1.lumps.length) {
16003 break;
16004 }
16005 var lump = t1.lumps[renderindex];
16006 var id = lump.rsfID;
16007 // new stopping rule - we may have been inside an elided tag
16008 if (lump.nestingdepth < basedepth || id === undefined) {
16009 break;
16010 }
16011
16012 if (id.charCodeAt(0) === 126) { // "~"
16013 id = id.substring(1);
16014 }
16015
16016 //var ismessagefor = id.indexOf("message-for:") === 0;
16017
16018 if (id.indexOf(":") !== -1) {
16019 var prefix = fluid.getPrefix(id);
16020 children = fetchComponents(basecontainer, prefix);
16021
16022 var finallump = lump.uplump.finallump[prefix];
16023 var closefinal = finallump.close_tag;
16024
16025 if (children) {
16026 for (var i = 0; i < children.length; ++i) {
16027 child = children[i];
16028 if (child.children) { // it is a branch
16029 if (debugMode) {
16030 rendered[child.fullID] = true;
16031 }
16032 targetlump = branchmap[child.fullID];
16033 if (targetlump) {
16034 if (debugMode) {
16035 renderComment("Branching for " + child.fullID + " from " +
16036 fluid.debugLump(lump) + " to " + fluid.debugLump(targetlump));
16037 }
16038
16039 renderContainer(child, targetlump);
16040
16041 if (debugMode) {
16042 renderComment("Branch returned for " + child.fullID +
16043 fluid.debugLump(lump) + " to " + fluid.debugLump(targetlump));
16044 }
16045 }
16046 else if (debugMode) {
16047 renderDebugMessage(
16048 "No matching template branch found for branch container with full ID " +
16049 child.fullID +
16050 " rendering from parent template branch " +
16051 fluid.debugLump(baselump));
16052 }
16053 }
16054 else { // repetitive leaf
16055 targetlump = findChild(parentlump, child);
16056 if (!targetlump) {
16057 if (debugMode) {
16058 renderDebugMessage("Repetitive leaf with full ID " +
16059 child.fullID +
16060 " could not be rendered from parent template branch " +
16061 fluid.debugLump(baselump));
16062 }
16063 continue;
16064 }
16065 var renderend = renderComponentSystem(basecontainer, child, targetlump);
16066 var wasopentag = renderend < t1.lumps.lengtn && t1.lumps[renderend].nestingdepth >= targetlump.nestingdepth;
16067 var newbase = child.children ? child : basecontainer;
16068 if (wasopentag) {
16069 renderRecurse(newbase, targetlump, t1.lumps[renderend]);
16070 renderend = targetlump.close_tag.lumpindex + 1;
16071 }
16072 if (i !== children.length - 1) {
16073 // TODO - fix this bug in RSF Server!
16074 if (renderend < closefinal.lumpindex) {
16075 dumpScan(t1.lumps, renderend, targetlump.nestingdepth - 1, false, false);
16076 }
16077 }
16078 else {
16079 dumpScan(t1.lumps, renderend, targetlump.nestingdepth, true, false);
16080 }
16081 }
16082 } // end for each repetitive child
16083 }
16084 else {
16085 if (debugMode) {
16086 renderDebugMessage("No branch container with prefix " +
16087 prefix + ": found in container " +
16088 reportPath(basecontainer) +
16089 " rendering at template position " +
16090 fluid.debugLump(baselump) +
16091 ", skipping");
16092 }
16093 }
16094
16095 renderindex = closefinal.lumpindex + 1;
16096 if (debugMode) {
16097 renderComment("Stack returned from branch for ID " + id + " to " +
16098 fluid.debugLump(baselump) + ": skipping from " + fluid.debugLump(lump) +
16099 " to " + fluid.debugLump(closefinal));
16100 }
16101 }
16102 else {
16103 var component;
16104 if (id) {
16105 component = fetchComponent(basecontainer, id, lump);
16106 if (debugMode && component) {
16107 rendered[component.fullID] = true;
16108 }
16109 }
16110 if (component && component.children !== undefined) {
16111 renderContainer(component);
16112 renderindex = lump.close_tag.lumpindex + 1;
16113 }
16114 else {
16115 renderindex = renderComponentSystem(basecontainer, component, lump);
16116 }
16117 }
16118 if (renderindex === t1.lumps.length) {
16119 break;
16120 }
16121 }
16122 if (debugMode) {
16123 children = basecontainer.children;
16124 for (var key = 0; key < children.length; ++key) {
16125 child = children[key];
16126 if (!rendered[child.fullID]) {
16127 renderDebugMessage("Component " +
16128 child.componentType + " with full ID " +
16129 child.fullID + " could not be found within template " +
16130 fluid.debugLump(baselump));
16131 }
16132 }
16133 }
16134
16135 };
16136
16137 function renderCollect(collump) {
16138 dumpTillLump(collump.parent.lumps, collump.lumpindex, collump.close_tag.lumpindex + 1);
16139 }
16140
16141 // Let us pray
16142 function renderCollects() {
16143 for (var key in collected) {
16144 var collist = collected[key];
16145 for (var i = 0; i < collist.length; ++i) {
16146 renderCollect(collist[i]);
16147 }
16148 }
16149 }
16150
16151 function processDecoratorQueue() {
16152 for (var i = 0; i < decoratorQueue.length; ++i) {
16153 var decorator = decoratorQueue[i];
16154 for (var j = 0; j < decorator.ids.length; ++j) {
16155 var id = decorator.ids[j];
16156 var node = fluid.byId(id, renderOptions.document);
16157 if (!node) {
16158 fluid.fail("Error during rendering - component with id " + id +
16159 " which has a queued decorator was not found in the output markup");
16160 }
16161 if (decorator.type === "jQuery") {
16162 var jnode = renderOptions.jQuery(node);
16163 jnode[decorator.func].apply(jnode, fluid.makeArray(decorator.args));
16164 }
16165 else if (decorator.type === "fluid") {
16166 var args = decorator.args;
16167 if (!args) {
16168 var thisContainer = renderOptions.jQuery(node);
16169 if (!decorator.container) {
16170 decorator.container = thisContainer;
16171 }
16172 else {
16173 decorator.container.push(node);
16174 }
16175 args = [thisContainer, decorator.options];
16176 }
16177 var that = renderer.invokeFluidDecorator(decorator.func, args, id, i, options);
16178 decorator.that = that;
16179 }
16180 else if (decorator.type === "event") {
16181 node[decorator.event] = decorator.handler;
16182 }
16183 }
16184 }
16185 }
16186
16187 that.renderTemplates = function () {
16188 tree = fixupTree(tree, options.model, options.resolverGetConfig);
16189 var template = templates[0];
16190 resolveBranches(templates.globalmap, tree, template.rootlump);
16191 renderedbindings = {};
16192 renderCollects();
16193 renderRecurse(tree, template.rootlump, template.lumps[template.firstdocumentindex]);
16194 return out;
16195 };
16196
16197 that.processDecoratorQueue = function () {
16198 processDecoratorQueue();
16199 };
16200 return that;
16201
16202 };
16203
16204 jQuery.extend(true, fluid.renderer, renderer);
16205
16206 /*
16207 * This function is unsupported: It is not really intended for use by implementors.
16208 */
16209 fluid.ComponentReference = function (reference) {
16210 this.reference = reference;
16211 };
16212
16213 // Explodes a raw "hash" into a list of UIOutput/UIBound entries
16214 fluid.explode = function (hash, basepath) {
16215 var togo = [];
16216 for (var key in hash) {
16217 var binding = basepath === undefined ? key : basepath + "." + key;
16218 togo[togo.length] = {ID: key, value: hash[key], valuebinding: binding};
16219 }
16220 return togo;
16221 };
16222
16223
16224 /**
16225 * A common utility function to make a simple view of rows, where each row has a selection control and a label
16226 * @param {Object} optionlist - An array of the values of the options in the select
16227 * @param {Object} opts - An object with this structure: {
16228 * selectID: "",
16229 * rowID: "",
16230 * inputID: "",
16231 * labelID: ""
16232 * }
16233 * @return {Object} - The results of transforming optionlist.
16234 */
16235 fluid.explodeSelectionToInputs = function (optionlist, opts) {
16236 return fluid.transform(optionlist, function (option, index) {
16237 return {
16238 ID: opts.rowID,
16239 children: [
16240 {ID: opts.inputID, parentRelativeID: "..::" + opts.selectID, choiceindex: index},
16241 {ID: opts.labelID, parentRelativeID: "..::" + opts.selectID, choiceindex: index}
16242 ]
16243 };
16244 });
16245 };
16246
16247 fluid.renderTemplates = function (templates, tree, options, fossilsIn) {
16248 var renderer = fluid.renderer(templates, tree, options, fossilsIn);
16249 var rendered = renderer.renderTemplates();
16250 return rendered;
16251 };
16252 /** A driver to render and bind an already parsed set of templates onto
16253 * a node. See documentation for fluid.selfRender.
16254 * @param templates A parsed template set, as returned from fluid.selfRender or
16255 * fluid.parseTemplates.
16256 */
16257
16258 fluid.reRender = function (templates, node, tree, options) {
16259 options = options || {};
16260 var renderer = fluid.renderer(templates, tree, options, options.fossils);
16261 options = renderer.options;
16262 // Empty the node first, to head off any potential id collisions when rendering
16263 node = fluid.unwrap(node);
16264 var lastFocusedElement = fluid.getLastFocusedElement ? fluid.getLastFocusedElement() : null;
16265 var lastId;
16266 if (lastFocusedElement && fluid.dom.isContainer(node, lastFocusedElement)) {
16267 lastId = lastFocusedElement.id;
16268 }
16269 if ($.browser.msie) {
16270 options.jQuery(node).empty(); //- this operation is very slow.
16271 }
16272 else {
16273 node.innerHTML = "";
16274 }
16275
16276 var rendered = renderer.renderTemplates();
16277 if (options.renderRaw) {
16278 rendered = fluid.XMLEncode(rendered);
16279 rendered = rendered.replace(/\n/g, "<br/>");
16280 }
16281 if (options.model) {
16282 fluid.bindFossils(node, options.model, options.fossils);
16283 }
16284 if ($.browser.msie) {
16285 options.jQuery(node).html(rendered);
16286 }
16287 else {
16288 node.innerHTML = rendered;
16289 }
16290 renderer.processDecoratorQueue();
16291 if (lastId) {
16292 var element = fluid.byId(lastId, options.document);
16293 if (element) {
16294 options.jQuery(element).focus();
16295 }
16296 }
16297
16298 return templates;
16299 };
16300
16301 function findNodeValue(rootNode) {
16302 var node = fluid.dom.iterateDom(rootNode, function (node) {
16303 // NB, in Firefox at least, comment and cdata nodes cannot be distinguished!
16304 return node.nodeType === 8 || node.nodeType === 4 ? "stop" : null;
16305 }, true);
16306 var value = node.nodeValue;
16307 if (value.indexOf("[CDATA[") === 0) {
16308 return value.substring(6, value.length - 2);
16309 }
16310 else {
16311 return value;
16312 }
16313 }
16314
16315 fluid.extractTemplate = function (node, armouring) {
16316 if (!armouring) {
16317 return node.innerHTML;
16318 }
16319 else {
16320 return findNodeValue(node);
16321 }
16322 };
16323 /** A slightly generalised version of fluid.selfRender that does not assume that the
16324 * markup used to source the template is within the target node.
16325 * @param {Object | String} source - Either a structure {node: node, armouring: armourstyle} or a string
16326 * holding a literal template
16327 * @param {Object} target - The node to receive the rendered markup
16328 * @param {Object} tree - The component tree to be rendered.
16329 * @param {Object} options - An options structure to configure the rendering and binding process.
16330 * @return {Object} - A templates structure, suitable for a further call to fluid.reRender or fluid.renderTemplates.
16331 */
16332 fluid.render = function (source, target, tree, options) {
16333 options = options || {};
16334 var template = source;
16335 if (typeof(source) === "object") {
16336 template = fluid.extractTemplate(fluid.unwrap(source.node), source.armouring);
16337 }
16338 target = fluid.unwrap(target);
16339 var resourceSpec = {base: {resourceText: template,
16340 href: ".", resourceKey: ".", cutpoints: options.cutpoints}
16341 };
16342 var templates = fluid.parseTemplates(resourceSpec, ["base"], options);
16343 return fluid.reRender(templates, target, tree, options);
16344 };
16345
16346 /** A simple driver for single node self-templating. Treats the markup for a
16347 * node as a template, parses it into a template structure, renders it using
16348 * the supplied component tree and options, then replaces the markup in the
16349 * node with the rendered markup, and finally performs any required data
16350 * binding. The parsed template is returned for use with a further call to
16351 * reRender.
16352 * @param {Object} node - The node both holding the template, and whose markup is to be
16353 * replaced with the rendered result.
16354 * @param {Object} tree - The component tree to be rendered.
16355 * @param {Object} options - An options structure to configure the rendering and binding process.
16356 * @return {Object} - A templates structure, suitable for a further call to fluid.reRender or fluid.renderTemplates.
16357 */
16358 fluid.selfRender = function (node, tree, options) {
16359 options = options || {};
16360 return fluid.render({node: node, armouring: options.armouring}, node, tree, options);
16361 };
16362
16363})(jQuery, fluid_3_0_0);
16364;
16365/*
16366Copyright The Infusion copyright holders
16367See the AUTHORS.md file at the top-level directory of this distribution and at
16368https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
16369
16370Licensed under the Educational Community License (ECL), Version 2.0 or the New
16371BSD license. You may not use this file except in compliance with one these
16372Licenses.
16373
16374You may obtain a copy of the ECL 2.0 License and BSD License at
16375https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
16376*/
16377
16378fluid_3_0_0 = fluid_3_0_0 || {};
16379
16380(function ($, fluid) {
16381 "use strict";
16382
16383 if (!fluid.renderer) {
16384 fluid.fail("fluidRenderer.js is a necessary dependency of RendererUtilities");
16385 }
16386
16387 // TODO: API status of these 3 functions is uncertain. So far, they have never
16388 // appeared in documentation.
16389 fluid.renderer.visitDecorators = function (that, visitor) {
16390 fluid.visitComponentChildren(that, function (component, name) {
16391 if (name.indexOf(fluid.renderer.decoratorComponentPrefix) === 0) {
16392 visitor(component, name);
16393 }
16394 }, {flat: true}, []);
16395 };
16396
16397 fluid.renderer.clearDecorators = function (that) {
16398 var instantiator = fluid.getInstantiator(that);
16399 fluid.renderer.visitDecorators(that, function (component, name) {
16400 instantiator.clearComponent(that, name);
16401 });
16402 };
16403
16404 fluid.renderer.getDecoratorComponents = function (that) {
16405 var togo = {};
16406 fluid.renderer.visitDecorators(that, function (component, name) {
16407 togo[name] = component;
16408 });
16409 return togo;
16410 };
16411
16412 // Utilities for coordinating options in renderer components - this code is all pretty
16413 // dreadful and needs to be organised as a suitable set of defaults and policies
16414 fluid.renderer.modeliseOptions = function (options, defaults, baseOptions) {
16415 return $.extend({}, defaults, fluid.filterKeys(baseOptions, ["model", "applier"]), options);
16416 };
16417 fluid.renderer.reverseMerge = function (target, source, names) {
16418 names = fluid.makeArray(names);
16419 fluid.each(names, function (name) {
16420 if (target[name] === undefined && source[name] !== undefined) {
16421 target[name] = source[name];
16422 }
16423 });
16424 };
16425
16426 /** "Renderer component" infrastructure **/
16427 // TODO: fix this up with IoC and improved handling of templateSource as well as better
16428 // options layout (model appears in both rOpts and eOpts)
16429 // "options" here is the original "rendererFnOptions"
16430 fluid.renderer.createRendererSubcomponent = function (container, selectors, options, parentThat, fossils) {
16431 options = options || {};
16432 var source = options.templateSource ? options.templateSource : {node: $(container)};
16433 var nativeModel = options.rendererOptions.model === undefined;
16434 var rendererOptions = fluid.renderer.modeliseOptions(options.rendererOptions, null, parentThat);
16435 rendererOptions.fossils = fossils || {};
16436 rendererOptions.parentComponent = parentThat;
16437 if (container.jquery) {
16438 var cascadeOptions = {
16439 document: container[0].ownerDocument,
16440 jQuery: container.constructor
16441 };
16442 fluid.renderer.reverseMerge(rendererOptions, cascadeOptions, fluid.keys(cascadeOptions));
16443 }
16444
16445 var that = {};
16446
16447 var templates = null;
16448 that.render = function (tree) {
16449 var cutpointFn = options.cutpointGenerator || "fluid.renderer.selectorsToCutpoints";
16450 rendererOptions.cutpoints = rendererOptions.cutpoints || fluid.invokeGlobalFunction(cutpointFn, [selectors, options]);
16451 if (nativeModel) { // check necessary since the component insanely supports the possibility the model is not the component's model!
16452 // and the pagedTable uses this.
16453 rendererOptions.model = parentThat.model; // fix FLUID-5664
16454 }
16455 var renderTarget = $(options.renderTarget ? options.renderTarget : container);
16456
16457 if (templates) {
16458 fluid.clear(rendererOptions.fossils);
16459 fluid.reRender(templates, renderTarget, tree, rendererOptions);
16460 }
16461 else {
16462 if (typeof(source) === "function") { // TODO: make a better attempt than this at asynchrony
16463 source = source();
16464 }
16465 templates = fluid.render(source, renderTarget, tree, rendererOptions);
16466 }
16467 };
16468 return that;
16469 };
16470
16471 fluid.defaults("fluid.rendererComponent", {
16472 gradeNames: ["fluid.viewComponent"],
16473 initFunction: "fluid.initRendererComponent",
16474 mergePolicy: {
16475 "rendererOptions.idMap": "nomerge",
16476 protoTree: "noexpand, replace",
16477 parentBundle: "nomerge",
16478 "changeApplierOptions.resolverSetConfig": "resolverSetConfig"
16479 },
16480 invokers: {
16481 refreshView: {
16482 funcName: "fluid.rendererComponent.refreshView",
16483 args: "{that}"
16484 },
16485 produceTree: {
16486 funcName: "fluid.rendererComponent.produceTree",
16487 args: "{that}"
16488 }
16489 },
16490 rendererOptions: {
16491 autoBind: true
16492 },
16493 events: {
16494 onResourcesFetched: null,
16495 prepareModelForRender: null,
16496 onRenderTree: null,
16497 afterRender: null
16498 },
16499 listeners: {
16500 onCreate: {
16501 funcName: "fluid.rendererComponent.renderOnInit",
16502 args: ["{that}.options.renderOnInit", "{that}"],
16503 priority: "last"
16504 }
16505 }
16506 });
16507
16508 fluid.rendererComponent.renderOnInit = function (renderOnInit, that) {
16509 if (renderOnInit || that.renderOnInit) {
16510 that.refreshView();
16511 }
16512 };
16513
16514 fluid.protoExpanderForComponent = function (parentThat, options) {
16515 var expanderOptions = fluid.renderer.modeliseOptions(options.expanderOptions, {ELstyle: "${}"}, parentThat);
16516 fluid.renderer.reverseMerge(expanderOptions, options, ["resolverGetConfig", "resolverSetConfig"]);
16517 var expander = fluid.renderer.makeProtoExpander(expanderOptions, parentThat);
16518 return expander;
16519 };
16520
16521 fluid.rendererComponent.refreshView = function (that) {
16522 if (!that.renderer) {
16523 // Terrible stopgap fix for FLUID-5279 - all of this implementation will be swept away
16524 // model relay may cause this to be called during init, and we have no proper definition for "that.renderer" since it is
16525 // constructed in a terrible way
16526 that.renderOnInit = true;
16527 return;
16528 } else {
16529 fluid.renderer.clearDecorators(that);
16530 that.events.prepareModelForRender.fire(that.model, that.applier, that);
16531 var tree = that.produceTree(that);
16532 var rendererFnOptions = that.renderer.rendererFnOptions;
16533 // Terrible stopgap fix for FLUID-5821 - given that model reference may be rebound, generate the expander from scratch on every render
16534 if (!rendererFnOptions.noexpand) {
16535 var expander = fluid.protoExpanderForComponent(that, rendererFnOptions);
16536 tree = expander(tree);
16537 }
16538 that.events.onRenderTree.fire(that, tree);
16539 that.renderer.render(tree);
16540 that.events.afterRender.fire(that);
16541 }
16542 };
16543
16544 fluid.rendererComponent.produceTree = function (that) {
16545 var produceTreeOption = that.options.produceTree;
16546 return produceTreeOption ?
16547 (typeof(produceTreeOption) === "string" ? fluid.getGlobalValue(produceTreeOption) : produceTreeOption) (that) :
16548 that.options.protoTree;
16549 };
16550
16551 fluid.initRendererComponent = function (componentName, container, options) {
16552 var that = fluid.initView(componentName, container, options, {gradeNames: ["fluid.rendererComponent"]});
16553 fluid.getForComponent(that, "model"); // Force resolution of these due to our terrible workflow
16554 fluid.getForComponent(that, "applier");
16555 fluid.diagnoseFailedView(componentName, that, fluid.defaults(componentName), arguments);
16556
16557 fluid.fetchResources(that.options.resources, that.events.onResourcesFetched.fire); // TODO: deal with asynchrony
16558
16559 var rendererOptions = fluid.renderer.modeliseOptions(that.options.rendererOptions, null, that);
16560
16561 var messageResolver;
16562 if (!rendererOptions.messageSource && that.options.strings) {
16563 messageResolver = fluid.messageResolver({
16564 messageBase: that.options.strings,
16565 resolveFunc: that.options.messageResolverFunction,
16566 parents: fluid.makeArray(that.options.parentBundle)
16567 });
16568 rendererOptions.messageSource = {type: "resolver", resolver: messageResolver};
16569 }
16570 fluid.renderer.reverseMerge(rendererOptions, that.options, ["resolverGetConfig", "resolverSetConfig"]);
16571 that.rendererOptions = rendererOptions;
16572
16573 var rendererFnOptions = $.extend({}, that.options.rendererFnOptions, {
16574 rendererOptions: rendererOptions,
16575 repeatingSelectors: that.options.repeatingSelectors,
16576 selectorsToIgnore: that.options.selectorsToIgnore,
16577 expanderOptions: {
16578 envAdd: {styles: that.options.styles}
16579 }
16580 });
16581
16582 if (that.options.resources && that.options.resources.template) {
16583 rendererFnOptions.templateSource = function () { // TODO: don't obliterate, multitemplates, etc.
16584 return that.options.resources.template.resourceText;
16585 };
16586 }
16587
16588 fluid.renderer.reverseMerge(rendererFnOptions, that.options, ["resolverGetConfig", "resolverSetConfig"]);
16589 if (rendererFnOptions.rendererTargetSelector) {
16590 container = function () {return that.dom.locate(rendererFnOptions.rendererTargetSelector); };
16591 }
16592 var renderer = {
16593 fossils: {},
16594 rendererFnOptions: rendererFnOptions,
16595 boundPathForNode: function (node) {
16596 return fluid.boundPathForNode(node, renderer.fossils);
16597 }
16598 };
16599
16600 var rendererSub = fluid.renderer.createRendererSubcomponent(container, that.options.selectors, rendererFnOptions, that, renderer.fossils);
16601 that.renderer = $.extend(renderer, rendererSub);
16602
16603 if (messageResolver) {
16604 that.messageResolver = messageResolver;
16605 }
16606 renderer.refreshView = fluid.getForComponent(that, "refreshView"); // Stopgap implementation for FLUID-4334
16607
16608 return that;
16609 };
16610
16611 var removeSelectors = function (selectors, selectorsToIgnore) {
16612 fluid.each(fluid.makeArray(selectorsToIgnore), function (selectorToIgnore) {
16613 delete selectors[selectorToIgnore];
16614 });
16615 return selectors;
16616 };
16617
16618 var markRepeated = function (selectorKey, repeatingSelectors) {
16619 if (repeatingSelectors) {
16620 fluid.each(repeatingSelectors, function (repeatingSelector) {
16621 if (selectorKey === repeatingSelector) {
16622 selectorKey = selectorKey + ":";
16623 }
16624 });
16625 }
16626 return selectorKey;
16627 };
16628
16629 fluid.renderer.selectorsToCutpoints = function (selectors, options) {
16630 var togo = [];
16631 options = options || {};
16632 selectors = fluid.copy(selectors); // Make a copy before potentially destructively changing someone's selectors.
16633
16634 if (options.selectorsToIgnore) {
16635 selectors = removeSelectors(selectors, options.selectorsToIgnore);
16636 }
16637
16638 for (var selectorKey in selectors) {
16639 togo.push({
16640 id: markRepeated(selectorKey, options.repeatingSelectors),
16641 selector: selectors[selectorKey]
16642 });
16643 }
16644
16645 return togo;
16646 };
16647
16648 /** END of "Renderer Components" infrastructure **/
16649
16650 fluid.renderer.NO_COMPONENT = {};
16651
16652 /* A special "shallow copy" operation suitable for nondestructively
16653 * merging trees of components. jQuery.extend in shallow mode will
16654 * neglect null valued properties.
16655 * This function is unsupported: It is not really intended for use by implementors.
16656 */
16657 fluid.renderer.mergeComponents = function (target, source) {
16658 for (var key in source) {
16659 target[key] = source[key];
16660 }
16661 return target;
16662 };
16663
16664 fluid.registerNamespace("fluid.renderer.selection");
16665
16666 /** Definition of expanders - firstly, "heavy" expanders **/
16667
16668 fluid.renderer.selection.inputs = function (options, container, key, config) {
16669 fluid.expect("Selection to inputs expander", options, ["selectID", "inputID", "labelID", "rowID"]);
16670 var selection = config.expander(options.tree);
16671 // Remove the tree from option expansion as this is handled above, and
16672 // the tree may have strings with similar syntax to IoC references.
16673 var optsToExpand = fluid.censorKeys(options, ["tree"]);
16674 var expandedOpts = config.expandLight(optsToExpand);
16675 var rows = fluid.transform(selection.optionlist.value, function (option, index) {
16676 var togo = {};
16677 var element = {parentRelativeID: "..::" + expandedOpts.selectID, choiceindex: index};
16678 togo[expandedOpts.inputID] = element;
16679 togo[expandedOpts.labelID] = fluid.copy(element);
16680 return togo;
16681 });
16682 var togo = {}; // TODO: JICO needs to support "quoted literal key initialisers" :P
16683 togo[expandedOpts.selectID] = selection;
16684 togo[expandedOpts.rowID] = {children: rows};
16685 togo = config.expander(togo);
16686 return togo;
16687 };
16688
16689 fluid.renderer.repeat = function (options, container, key, config) {
16690 fluid.expect("Repetition expander", options, ["controlledBy", "tree"]);
16691 var env = config.threadLocal();
16692 var path = fluid.extractContextualPath(options.controlledBy, {ELstyle: "ALL"}, env);
16693 var list = fluid.get(config.model, path, config.resolverGetConfig);
16694
16695 var togo = {};
16696 if (!list || list.length === 0) {
16697 return options.ifEmpty ? config.expander(options.ifEmpty) : togo;
16698 }
16699 var expanded = [];
16700 fluid.each(list, function (element, i) {
16701 var EL = fluid.model.composePath(path, i);
16702 var envAdd = {};
16703 if (options.pathAs) {
16704 envAdd[options.pathAs] = "${" + EL + "}";
16705 }
16706 if (options.valueAs) {
16707 envAdd[options.valueAs] = fluid.get(config.model, EL, config.resolverGetConfig);
16708 }
16709 var expandrow = fluid.withEnvironment(envAdd, function () {
16710 return config.expander(options.tree);
16711 }, env);
16712 if (fluid.isArrayable(expandrow)) {
16713 if (expandrow.length > 0) {
16714 expanded.push({children: expandrow});
16715 }
16716 }
16717 else if (expandrow !== fluid.renderer.NO_COMPONENT) {
16718 expanded.push(expandrow);
16719 }
16720 });
16721 var repeatID = options.repeatID;
16722 if (repeatID.indexOf(":") === -1) {
16723 repeatID = repeatID + ":";
16724 }
16725 fluid.each(expanded, function (entry) {entry.ID = repeatID; });
16726 return expanded;
16727 };
16728
16729 fluid.renderer.condition = function (options, container, key, config) {
16730 fluid.expect("Selection to condition expander", options, ["condition"]);
16731 var condition;
16732 if (options.condition.funcName) {
16733 var args = config.expandLight(options.condition.args);
16734 condition = fluid.invokeGlobalFunction(options.condition.funcName, args);
16735 } else if (options.condition.expander) {
16736 condition = config.expander(options.condition);
16737 } else {
16738 condition = config.expandLight(options.condition);
16739 }
16740 var tree = (condition ? options.trueTree : options.falseTree);
16741 if (!tree) {
16742 tree = fluid.renderer.NO_COMPONENT;
16743 }
16744 return config.expander(tree);
16745 };
16746
16747
16748 /* An EL extraction utility suitable for context expressions which occur in
16749 * expanding component trees. It dispatches context expressions to fluid.transformContextPath
16750 * in order to resolve them against EL references stored in the direct environment, and hence
16751 * to the "true (direct) model" - however, if there is no entry in the direct environment, it will resort to the "externalFetcher".
16752 * It satisfies a similar contract as fluid.extractEL, in that it will either return
16753 * an EL path, or undefined if the string value supplied cannot be interpreted
16754 * as an EL path with respect to the supplied options - it may also return {value: value}
16755 * in the case the context can be resolved by the supplied "externalFetcher" (required for FLUID-4986)
16756 */
16757 // unsupported, non-API function
16758 fluid.extractContextualPath = function (string, options, env, externalFetcher) {
16759 var parsed = fluid.extractELWithContext(string, options);
16760 if (parsed) {
16761 if (parsed.context) {
16762 return env[parsed.context] ? fluid.transformContextPath(parsed, env).path : {value: externalFetcher(parsed)};
16763 }
16764 else {
16765 return parsed.path;
16766 }
16767 }
16768 };
16769
16770 // unsupported, non-API function
16771 fluid.transformContextPath = function (parsed, env) {
16772 if (parsed.context) {
16773 var fetched = env[parsed.context];
16774 var EL;
16775 if (typeof(fetched) === "string") {
16776 EL = fluid.extractEL(fetched, {ELstyle: "${}"});
16777 }
16778 if (EL) {
16779 return {
16780 noDereference: parsed.path === "",
16781 path: fluid.model.composePath(EL, parsed.path)
16782 };
16783 }
16784 }
16785 return parsed;
16786 };
16787
16788 // A forgiving variation of "makeStackFetcher" that returns nothing on failing to resolve an IoC reference,
16789 // in keeping with current protoComponent semantics. Note to self: abolish protoComponents
16790 fluid.renderer.makeExternalFetcher = function (contextThat) {
16791 return function (parsed) {
16792 var foundComponent = fluid.resolveContext(parsed.context, contextThat);
16793 return foundComponent ? fluid.getForComponent(foundComponent, parsed.path) : undefined;
16794 };
16795 };
16796
16797 /** Create a "protoComponent expander" with the supplied set of options.
16798 * The returned value will be a function which accepts a "protoComponent tree"
16799 * as argument, and returns a "fully expanded" tree suitable for supplying
16800 * directly to the renderer.
16801 * A "protoComponent tree" is similar to the "dehydrated form" accepted by
16802 * the historical renderer - only
16803 * i) The input format is unambiguous - this expander will NOT accept hydrated
16804 * components in the {ID: "myId, myfield: "myvalue"} form - but ONLY in
16805 * the dehydrated {myID: {myfield: myvalue}} form.
16806 * ii) This expander has considerably greater power to expand condensed trees.
16807 * In particular, an "EL style" option can be supplied which will expand bare
16808 * strings found as values in the tree into UIBound components by a configurable
16809 * strategy. Supported values for "ELstyle" are a) "ALL" - every string will be
16810 * interpreted as an EL reference and assigned to the "valuebinding" member of
16811 * the UIBound, or b) any single character, which if it appears as the first
16812 * character of the string, will mark it out as an EL reference - otherwise it
16813 * will be considered a literal value, or c) the value "${}" which will be
16814 * recognised bracketing any other EL expression.
16815 */
16816
16817 fluid.renderer.makeProtoExpander = function (expandOptions, parentThat) {
16818 // shallow copy of options - cheaply avoid destroying model, and all others are primitive
16819 var options = $.extend({
16820 ELstyle: "${}"
16821 }, expandOptions); // shallow copy of options
16822 if (parentThat) {
16823 options.externalFetcher = fluid.renderer.makeExternalFetcher(parentThat);
16824 }
16825 var threadLocal; // rebound on every expansion at entry point
16826
16827 function fetchEL(string) {
16828 var env = threadLocal();
16829 return fluid.extractContextualPath(string, options, env, options.externalFetcher);
16830 }
16831
16832 var IDescape = options.IDescape || "\\";
16833
16834 var expandLight = function (source) {
16835 return fluid.expand(source, options);
16836 };
16837
16838 var expandBound = function (value, concrete) {
16839 if (value.messagekey !== undefined) {
16840 return {
16841 componentType: "UIMessage",
16842 messagekey: expandBound(value.messagekey),
16843 args: expandLight(value.args)
16844 };
16845 }
16846 var proto;
16847 if (!fluid.isPrimitive(value) && !fluid.isArrayable(value)) {
16848 proto = $.extend({}, value);
16849 if (proto.decorators) {
16850 proto.decorators = expandLight(proto.decorators);
16851 }
16852 value = proto.value;
16853 delete proto.value;
16854 } else {
16855 proto = {};
16856 }
16857 var EL;
16858 if (typeof (value) === "string") {
16859 var fetched = fetchEL(value);
16860 EL = typeof (fetched) === "string" ? fetched : null;
16861 value = fluid.get(fetched, "value") || value;
16862 }
16863 if (EL) {
16864 proto.valuebinding = EL;
16865 } else if (value !== undefined) {
16866 proto.value = value;
16867 }
16868 if (options.model && proto.valuebinding && proto.value === undefined) {
16869 proto.value = fluid.get(options.model, proto.valuebinding, options.resolverGetConfig);
16870 }
16871 if (concrete) {
16872 proto.componentType = "UIBound";
16873 }
16874 return proto;
16875 };
16876
16877 options.filter = fluid.expander.lightFilter;
16878
16879 var expandCond;
16880 var expandLeafOrCond;
16881
16882 var expandEntry = function (entry) {
16883 var comp = [];
16884 expandCond(entry, comp);
16885 return {children: comp};
16886 };
16887
16888 var expandExternal = function (entry) {
16889 if (entry === fluid.renderer.NO_COMPONENT) {
16890 return entry;
16891 }
16892 var singleTarget;
16893 var target = [];
16894 var pusher = function (comp) {
16895 singleTarget = comp;
16896 };
16897 expandLeafOrCond(entry, target, pusher);
16898 return singleTarget || target;
16899 };
16900
16901 var expandConfig = {
16902 model: options.model,
16903 resolverGetConfig: options.resolverGetConfig,
16904 resolverSetConfig: options.resolverSetConfig,
16905 expander: expandExternal,
16906 expandLight: expandLight
16907 //threadLocal: threadLocal
16908 };
16909
16910 var expandLeaf = function (leaf, componentType) {
16911 var togo = {componentType: componentType};
16912 var map = fluid.renderer.boundMap[componentType] || {};
16913 for (var key in leaf) {
16914 if (/decorators|args/.test(key)) {
16915 togo[key] = expandLight(leaf[key]);
16916 continue;
16917 } else if (map[key]) {
16918 togo[key] = expandBound(leaf[key]);
16919 } else {
16920 togo[key] = leaf[key];
16921 }
16922 }
16923 return togo;
16924 };
16925
16926 // A child entry may be a cond, a leaf, or another "thing with children".
16927 // Unlike the case with a cond's contents, these must be homogeneous - at least
16928 // they may either be ALL leaves, or else ALL cond/childed etc.
16929 // In all of these cases, the key will be THE PARENT'S KEY
16930 var expandChildren = function (entry, pusher) {
16931 var children = entry.children;
16932 for (var i = 0; i < children.length; ++i) {
16933 // each child in this list will lead to a WHOLE FORKED set of children.
16934 var target = [];
16935 var comp = { children: target};
16936
16937 var child = children[i];
16938 // This use of function creation within a loop is acceptable since
16939 // the function does not attempt to close directly over the loop counter
16940 var childPusher = function (comp) { // eslint-disable-line no-loop-func
16941 target[target.length] = comp;
16942 };
16943
16944 expandLeafOrCond(child, target, childPusher);
16945 // Rescue the case of an expanded leaf into single component - TODO: check what sense this makes of the grammar
16946 if (comp.children.length === 1 && !comp.children[0].ID) {
16947 comp = comp.children[0];
16948 }
16949 pusher(comp);
16950 }
16951 };
16952
16953 function detectBareBound(entry) {
16954 return fluid.find(entry, function (value, key) {
16955 return key === "decorators";
16956 }) !== false;
16957 }
16958
16959 // We have reached something which is either a leaf or Cond - either inside
16960 // a Cond or as an entry in children.
16961 expandLeafOrCond = function (entry, target, pusher) {
16962 var componentType = fluid.renderer.inferComponentType(entry);
16963 if (!componentType && (fluid.isPrimitive(entry) || detectBareBound(entry))) {
16964 componentType = "UIBound";
16965 }
16966 if (componentType) {
16967 pusher(componentType === "UIBound" ? expandBound(entry, true) : expandLeaf(entry, componentType));
16968 } else {
16969 // we couldn't recognise it as a leaf, so it must be a cond
16970 // this may be illegal if we are already in a cond.
16971 if (!target) {
16972 fluid.fail("Illegal cond->cond transition");
16973 }
16974 expandCond(entry, target);
16975 }
16976 };
16977
16978 // cond entry may be a leaf, "thing with children" or a "direct bound".
16979 // a Cond can ONLY occur as a direct member of "children". Each "cond" entry may
16980 // give rise to one or many elements with the SAME key - if "expandSingle" discovers
16981 // "thing with children" they will all share the same key found in proto.
16982 expandCond = function (proto, target) {
16983 var key;
16984 var expandToTarget = function (expander) {
16985 var expanded = fluid.invokeGlobalFunction(expander.type, [expander, proto, key, expandConfig]);
16986 if (expanded !== fluid.renderer.NO_COMPONENT) {
16987 fluid.each(expanded, function (el) {target[target.length] = el; });
16988 }
16989 };
16990 var condPusher = function (comp) {
16991 comp.ID = key;
16992 target[target.length] = comp;
16993 };
16994 for (key in proto) {
16995 var entry = proto[key];
16996 if (key.charAt(0) === IDescape) {
16997 key = key.substring(1);
16998 }
16999 if (key === "expander") {
17000 var expanders = fluid.makeArray(entry);
17001 fluid.each(expanders, expandToTarget);
17002 } else if (entry) {
17003 if (entry.children) {
17004 if (key.indexOf(":") === -1) {
17005 key = key + ":";
17006 }
17007 expandChildren(entry, condPusher);
17008 } else if (fluid.renderer.isBoundPrimitive(entry)) {
17009 condPusher(expandBound(entry, true));
17010 } else {
17011 expandLeafOrCond(entry, null, condPusher);
17012 }
17013 }
17014 }
17015
17016 };
17017
17018 return function (entry) {
17019 threadLocal = fluid.threadLocal(function () {
17020 return $.extend({}, options.envAdd);
17021 });
17022 options.fetcher = fluid.makeEnvironmentFetcher(options.model, fluid.transformContextPath, threadLocal, options.externalFetcher);
17023 expandConfig.threadLocal = threadLocal;
17024 return expandEntry(entry);
17025 };
17026 };
17027
17028})(jQuery, fluid_3_0_0);
17029;
17030/*
17031Copyright The Infusion copyright holders
17032See the AUTHORS.md file at the top-level directory of this distribution and at
17033https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
17034
17035Licensed under the Educational Community License (ECL), Version 2.0 or the New
17036BSD license. You may not use this file except in compliance with one these
17037Licenses.
17038
17039You may obtain a copy of the ECL 2.0 License and BSD License at
17040https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
17041*/
17042
17043var fluid_3_0_0 = fluid_3_0_0 || {};
17044
17045(function ($, fluid) {
17046 "use strict";
17047 /**********************
17048 * Sliding Panel *
17049 *********************/
17050
17051 fluid.defaults("fluid.slidingPanel", {
17052 gradeNames: ["fluid.viewComponent"],
17053 selectors: {
17054 panel: ".flc-slidingPanel-panel",
17055 toggleButton: ".flc-slidingPanel-toggleButton",
17056 toggleButtonLabel: ".flc-slidingPanel-toggleButton"
17057 },
17058 strings: {
17059 showText: "show",
17060 hideText: "hide",
17061 panelLabel: "panel"
17062 },
17063 events: {
17064 onPanelHide: null,
17065 onPanelShow: null,
17066 afterPanelHide: null,
17067 afterPanelShow: null
17068 },
17069 listeners: {
17070 "onCreate.bindClick": {
17071 "this": "{that}.dom.toggleButton",
17072 "method": "click",
17073 "args": ["{that}.togglePanel"]
17074 },
17075 "onCreate.bindModelChange": {
17076 listener: "{that}.applier.modelChanged.addListener",
17077 args: ["isShowing", "{that}.refreshView"]
17078 },
17079 "onCreate.setAriaProps": "{that}.setAriaProps",
17080 "onCreate.setInitialState": {
17081 listener: "{that}.refreshView"
17082 },
17083 "onPanelHide.setText": {
17084 "this": "{that}.dom.toggleButtonLabel",
17085 "method": "text",
17086 "args": ["{that}.options.strings.showText"],
17087 "priority": "first"
17088 },
17089 "onPanelHide.setAriaLabel": {
17090 "this": "{that}.dom.toggleButtonLabel",
17091 "method": "attr",
17092 "args": ["aria-label", "{that}.options.strings.showTextAriaLabel"]
17093 },
17094 "onPanelShow.setText": {
17095 "this": "{that}.dom.toggleButtonLabel",
17096 "method": "text",
17097 "args": ["{that}.options.strings.hideText"],
17098 "priority": "first"
17099 },
17100 "onPanelShow.setAriaLabel": {
17101 "this": "{that}.dom.toggleButtonLabel",
17102 "method": "attr",
17103 "args": ["aria-label", "{that}.options.strings.hideTextAriaLabel"]
17104 },
17105 "onPanelHide.operate": {
17106 listener: "{that}.operateHide"
17107 },
17108 "onPanelShow.operate": {
17109 listener: "{that}.operateShow"
17110 },
17111 "onCreate.setAriaStates": "{that}.setAriaStates"
17112 },
17113 members: {
17114 panelId: {
17115 expander: {
17116 // create an id for panel
17117 // and set that.panelId to the id value
17118 funcName: "fluid.allocateSimpleId",
17119 args: "{that}.dom.panel"
17120 }
17121 }
17122 },
17123 model: {
17124 isShowing: false
17125 },
17126 modelListeners: {
17127 "isShowing": {
17128 funcName: "{that}.setAriaStates",
17129 excludeSource: "init"
17130 }
17131 },
17132 invokers: {
17133 operateHide: {
17134 "this": "{that}.dom.panel",
17135 "method": "slideUp",
17136 "args": ["{that}.options.animationDurations.hide", "{that}.events.afterPanelHide.fire"]
17137 },
17138 operateShow: {
17139 "this": "{that}.dom.panel",
17140 "method": "slideDown",
17141 "args": ["{that}.options.animationDurations.show", "{that}.events.afterPanelShow.fire"]
17142 },
17143 hidePanel: {
17144 func: "{that}.applier.change",
17145 args: ["isShowing", false]
17146 },
17147 showPanel: {
17148 func: "{that}.applier.change",
17149 args: ["isShowing", true]
17150 },
17151 setAriaStates: {
17152 funcName: "fluid.slidingPanel.setAriaStates",
17153 args: ["{that}", "{that}.model.isShowing"]
17154 },
17155 setAriaProps: {
17156 funcName: "fluid.slidingPanel.setAriaProperties",
17157 args: ["{that}", "{that}.panelId"]
17158 },
17159 togglePanel: {
17160 funcName: "fluid.slidingPanel.togglePanel",
17161 args: ["{that}"]
17162 },
17163 refreshView: {
17164 funcName: "fluid.slidingPanel.refreshView",
17165 args: ["{that}"]
17166 }
17167 },
17168 animationDurations: {
17169 hide: 400,
17170 show: 400
17171 }
17172 });
17173
17174 fluid.slidingPanel.togglePanel = function (that) {
17175 that.applier.change("isShowing", !that.model.isShowing);
17176 };
17177
17178 fluid.slidingPanel.refreshView = function (that) {
17179 that.events[that.model.isShowing ? "onPanelShow" : "onPanelHide"].fire();
17180 };
17181
17182 // panelId is passed in to ensure that it is evaluated before this
17183 // function is called.
17184 fluid.slidingPanel.setAriaProperties = function (that, panelId) {
17185 that.locate("toggleButton").attr({
17186 "role": "button",
17187 "aria-controls": panelId
17188 });
17189 that.locate("panel").attr({
17190 "aria-label": that.options.strings.panelLabel,
17191 "role": "group"
17192 });
17193 };
17194
17195 fluid.slidingPanel.setAriaStates = function (that, isShowing) {
17196 that.locate("toggleButton").attr({
17197 "aria-pressed": isShowing,
17198 "aria-expanded": isShowing
17199 });
17200 };
17201
17202})(jQuery, fluid_3_0_0);
17203;
17204/*
17205Copyright The Infusion copyright holders
17206See the AUTHORS.md file at the top-level directory of this distribution and at
17207https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
17208
17209Licensed under the Educational Community License (ECL), Version 2.0 or the New
17210BSD license. You may not use this file except in compliance with one these
17211Licenses.
17212
17213You may obtain a copy of the ECL 2.0 License and BSD License at
17214https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
17215*/
17216
17217var fluid_3_0_0 = fluid_3_0_0 || {};
17218
17219(function ($, fluid) {
17220 "use strict";
17221
17222 /*************
17223 * Textfield *
17224 *************/
17225
17226 /*
17227 * A component for controlling a textfield and handling data binding.
17228 * Typically this will be used in conjunction with a UI control widget such as
17229 * button steppers or slider.
17230 */
17231 fluid.defaults("fluid.textfield", {
17232 gradeNames: ["fluid.viewComponent"],
17233 attrs: {
17234 // Specified by implementor
17235 // ID of an external label to refer to with aria-labelledby
17236 // attribute
17237 // "aria-labelledby": "",
17238 // Should specify either "aria-label" or "aria-labelledby"
17239 // aria-label: "{that}.options.strings.label",
17240 // ID of an element that is controlled by the textfield.
17241 // "aria-controls": ""
17242 },
17243 strings: {
17244 // Specified by implementor
17245 // text of label to apply to both textfield and slider input
17246 // via aria-label attribute
17247 // "label": ""
17248 },
17249 modelListeners: {
17250 value: {
17251 "this": "{that}.container",
17252 "method": "val",
17253 args: ["{change}.value"]
17254 }
17255 },
17256 listeners: {
17257 "onCreate.bindChangeEvt": {
17258 "this": "{that}.container",
17259 "method": "change",
17260 "args": ["{that}.setModel"]
17261 },
17262 "onCreate.initTextfieldAttributes": {
17263 "this": "{that}.container",
17264 method: "attr",
17265 args: ["{that}.options.attrs"]
17266 }
17267 },
17268 invokers: {
17269 setModel: {
17270 changePath: "value",
17271 value: "{arguments}.0.target.value"
17272 }
17273 }
17274 });
17275
17276 /**
17277 * Sets the model value only if the new value is a valid number, and will reset the textfield to the current model
17278 * value otherwise.
17279 *
17280 * @param {Object} that - The component.
17281 * @param {Number} value - The new numerical entry.
17282 * @param {String} path - The path into the model for which the value should be set.
17283 */
17284 fluid.textfield.setModelRestrictToNumbers = function (that, value, path) {
17285 var isNumber = !isNaN(Number(value));
17286 if (isNumber) {
17287 that.applier.change(path, value);
17288 }
17289
17290 // Set the textfield to the latest valid entry.
17291 // This handles both the cases where an invalid entry was provided, as well as cases where a valid number is
17292 // rounded. In the case of rounded numbers this ensures that entering a number that rounds to the current
17293 // set value, doesn't leave the textfield with the unrounded number present.
17294 that.container.val(that.model.value);
17295 };
17296
17297 /******************************
17298 * TextField Range Controller *
17299 ******************************/
17300
17301 /*
17302 * Range Controller is intended to be used as a grade on a fluid.textfield component. It will limit the input
17303 * to be constrained within a given numerical range. This should be paired with configuring the textfield.setModel
17304 * invoker to use fluid.textfield.setModelRestrictToNumbers.
17305 * The Range Controller is useful when combining the textfield with a UI control element such as stepper buttons
17306 * or a slider to enter numerical values.
17307 */
17308 fluid.defaults("fluid.textfield.rangeController", {
17309 gradeNames: ["fluid.textfield"],
17310 components: {
17311 controller: {
17312 type: "fluid.modelComponent",
17313 options: {
17314 model: {
17315 value: null
17316 },
17317 modelRelay: [{
17318 source: "value",
17319 target: "{fluid.textfield}.model.value",
17320 singleTransform: {
17321 type: "fluid.transforms.numberToString",
17322 // The scale option sets the number of decimal places to round
17323 // the number to. If no scale is specified, the number will not be rounded.
17324 // Scaling is useful to avoid long decimal places due to floating point imprecision.
17325 scale: "{that}.options.scale"
17326 }
17327 }, {
17328 target: "value",
17329 singleTransform: {
17330 type: "fluid.transforms.limitRange",
17331 input: "{that}.model.value",
17332 min: "{that}.model.range.min",
17333 max: "{that}.model.range.max"
17334 }
17335 }]
17336 }
17337 }
17338 },
17339 invokers: {
17340 setModel: {
17341 funcName: "fluid.textfield.setModelRestrictToNumbers",
17342 args: ["{that}", "{arguments}.0.target.value", "value"]
17343 }
17344 }
17345 });
17346
17347})(jQuery, fluid_3_0_0);
17348;
17349/*
17350Copyright The Infusion copyright holders
17351See the AUTHORS.md file at the top-level directory of this distribution and at
17352https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
17353
17354Licensed under the Educational Community License (ECL), Version 2.0 or the New
17355BSD license. You may not use this file except in compliance with one these
17356Licenses.
17357
17358You may obtain a copy of the ECL 2.0 License and BSD License at
17359https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
17360*/
17361
17362var fluid_3_0_0 = fluid_3_0_0 || {};
17363
17364(function ($, fluid) {
17365 "use strict";
17366
17367 /********************
17368 * Textfield Slider *
17369 ********************/
17370
17371 fluid.defaults("fluid.textfieldSlider", {
17372 gradeNames: ["fluid.viewComponent"],
17373 components: {
17374 textfield: {
17375 type: "fluid.textfield.rangeController",
17376 container: "{that}.dom.textfield",
17377 options: {
17378 components: {
17379 controller: {
17380 options: {
17381 model: "{textfieldSlider}.model"
17382 }
17383 }
17384 },
17385 attrs: "{textfieldSlider}.options.attrs",
17386 strings: "{textfieldSlider}.options.strings"
17387 }
17388 },
17389 slider: {
17390 type: "fluid.slider",
17391 container: "{textfieldSlider}.dom.slider",
17392 options: {
17393 model: "{textfieldSlider}.model",
17394 attrs: "{textfieldSlider}.options.attrs",
17395 strings: "{textfieldSlider}.options.strings"
17396 }
17397 }
17398 },
17399 selectors: {
17400 textfield: ".flc-textfieldSlider-field",
17401 slider: ".flc-textfieldSlider-slider"
17402 },
17403 styles: {
17404 container: "fl-textfieldSlider fl-focus"
17405 },
17406 model: {
17407 value: null,
17408 step: 1.0,
17409 range: {
17410 min: 0,
17411 max: 100
17412 }
17413 },
17414 modelRelay: {
17415 target: "value",
17416 singleTransform: {
17417 type: "fluid.transforms.limitRange",
17418 input: "{that}.model.value",
17419 min: "{that}.options.range.min",
17420 max: "{that}.options.range.max"
17421 }
17422 },
17423
17424 attrs: {
17425 // Specified by implementor
17426 // ID of an external label to refer to with aria-labelledby
17427 // attribute
17428 // "aria-labelledby": "",
17429 // Should specify either "aria-label" or "aria-labelledby"
17430 // aria-label: "{that}.options.strings.label",
17431 // ID of an element that is controlled by the textfield.
17432 // "aria-controls": ""
17433 },
17434 strings: {
17435 // Specified by implementor
17436 // text of label to apply to both textfield and slider input
17437 // via aria-label attribute
17438 // "label": ""
17439 },
17440 listeners: {
17441 "onCreate.addContainerStyle": {
17442 "this": "{that}.container",
17443 method: "addClass",
17444 args: ["{that}.options.styles.container"]
17445 }
17446 },
17447 distributeOptions: [{
17448 // The scale option sets the number of decimal places to round
17449 // the number to. If no scale is specified, the number will not be rounded.
17450 // Scaling is useful to avoid long decimal places due to floating point imprecision.
17451 source: "{that}.options.scale",
17452 target: "{that > fluid.textfield > controller}.options.scale"
17453 }]
17454 });
17455
17456 fluid.defaults("fluid.slider", {
17457 gradeNames: ["fluid.viewComponent"],
17458 modelRelay: {
17459 target: "value",
17460 singleTransform: {
17461 type: "fluid.transforms.stringToNumber",
17462 input: "{that}.model.stringValue"
17463 }
17464 },
17465 invokers: {
17466 setModel: {
17467 changePath: "stringValue",
17468 value: {
17469 expander: {
17470 "this": "{that}.container",
17471 "method": "val"
17472 }
17473 }
17474 },
17475 updateSliderAttributes: {
17476 "this": "{that}.container",
17477 method: "attr",
17478 args: [{
17479 "min": "{that}.model.range.min",
17480 "max": "{that}.model.range.max",
17481 "step": "{that}.model.step",
17482 "type": "range",
17483 "value": "{that}.model.value",
17484 "aria-labelledby": "{that}.options.attrs.aria-labelledby",
17485 "aria-label": "{that}.options.attrs.aria-label"
17486 }]
17487 }
17488 },
17489 listeners: {
17490 "onCreate.initSliderAttributes": "{that}.updateSliderAttributes",
17491 "onCreate.bindSlideEvt": {
17492 "this": "{that}.container",
17493 "method": "on",
17494 "args": ["input", "{that}.setModel"]
17495 },
17496 "onCreate.bindRangeChangeEvt": {
17497 "this": "{that}.container",
17498 "method": "on",
17499 "args": ["change", "{that}.setModel"]
17500 }
17501 },
17502 modelListeners: {
17503 // If we don't exclude init, the value can get
17504 // set before onCreate.initSliderAttributes
17505 // sets min / max / step, which messes up the
17506 // initial slider rendering
17507 "value": [{
17508 "this": "{that}.container",
17509 "method": "val",
17510 args: ["{change}.value"],
17511 excludeSource: "init"
17512 }],
17513 "range": {
17514 listener: "{that}.updateSliderAttributes",
17515 excludeSource: "init"
17516 },
17517 "step": {
17518 listener: "{that}.updateSliderAttributes",
17519 excludeSource: "init"
17520 }
17521 }
17522 });
17523
17524})(jQuery, fluid_3_0_0);
17525;
17526/*
17527Copyright The Infusion copyright holders
17528See the AUTHORS.md file at the top-level directory of this distribution and at
17529https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
17530
17531Licensed under the Educational Community License (ECL), Version 2.0 or the New
17532BSD license. You may not use this file except in compliance with one these
17533Licenses.
17534
17535You may obtain a copy of the ECL 2.0 License and BSD License at
17536https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
17537*/
17538
17539var fluid_3_0_0 = fluid_3_0_0 || {};
17540
17541(function ($, fluid) {
17542 "use strict";
17543
17544 /*********************
17545 * Textfield Stepper *
17546 *********************/
17547
17548 fluid.defaults("fluid.textfieldStepper", {
17549 gradeNames: ["fluid.viewComponent"],
17550 strings: {
17551 // Specified by implementor
17552 // text of label to apply to both textfield and control
17553 // via aria-label attribute
17554 // "aria-label": "",
17555 increaseLabel: "increment",
17556 decreaseLabel: "decrement"
17557 },
17558 selectors: {
17559 textfield: ".flc-textfieldStepper-field",
17560 focusContainer: ".flc-textfieldStepper-focusContainer",
17561 increaseButton: ".flc-textfieldStepper-increase",
17562 decreaseButton: ".flc-textfieldStepper-decrease"
17563 },
17564 styles: {
17565 container: "fl-textfieldStepper",
17566 focus: "fl-textfieldStepper-focus"
17567 },
17568 components: {
17569 textfield: {
17570 type: "fluid.textfield.rangeController",
17571 container: "{that}.dom.textfield",
17572 options: {
17573 components: {
17574 controller: {
17575 options: {
17576 model: "{textfieldStepper}.model",
17577 modelListeners: {
17578 "range.min": {
17579 "this": "{textfield}.container",
17580 method: "attr",
17581 args: ["aria-valuemin", "{change}.value"]
17582 },
17583 "range.max": {
17584 "this": "{textfield}.container",
17585 method: "attr",
17586 args: ["aria-valuemax", "{change}.value"]
17587 }
17588 }
17589 }
17590 }
17591 },
17592 attrs: "{textfieldStepper}.options.attrs",
17593 strings: "{textfieldStepper}.options.strings",
17594 listeners: {
17595 "onCreate.bindUpArrow": {
17596 listener: "fluid.textfieldStepper.bindKeyEvent",
17597 // up arrow === 38
17598 args: ["{that}.container", "keydown", 38, "{textfieldStepper}.increase"]
17599 },
17600 "onCreate.bindDownArrow": {
17601 listener: "fluid.textfieldStepper.bindKeyEvent",
17602 // down arrow === 40
17603 args: ["{that}.container", "keydown", 40, "{textfieldStepper}.decrease"]
17604 },
17605 "onCreate.addRole": {
17606 "this": "{that}.container",
17607 method: "attr",
17608 args: ["role", "spinbutton"]
17609 }
17610 },
17611 modelListeners: {
17612 "value": {
17613 "this": "{that}.container",
17614 method: "attr",
17615 args: ["aria-valuenow", "{change}.value"]
17616 }
17617 }
17618 }
17619 },
17620 increaseButton: {
17621 type: "fluid.textfieldStepper.button",
17622 container: "{textfieldStepper}.dom.increaseButton",
17623 options: {
17624 strings: {
17625 label: "{textfieldStepper}.options.strings.increaseLabel"
17626 },
17627 listeners: {
17628 "onClick.increase": "{textfieldStepper}.increase"
17629 },
17630 modelRelay: {
17631 target: "disabled",
17632 singleTransform: {
17633 type: "fluid.transforms.binaryOp",
17634 left: "{textfieldStepper}.model.value",
17635 right: "{textfieldStepper}.model.range.max",
17636 operator: ">="
17637 }
17638 }
17639 }
17640 },
17641 decreaseButton: {
17642 type: "fluid.textfieldStepper.button",
17643 container: "{textfieldStepper}.dom.decreaseButton",
17644 options: {
17645 strings: {
17646 label: "{textfieldStepper}.options.strings.decreaseLabel"
17647 },
17648 listeners: {
17649 "onClick.decrease": "{textfieldStepper}.decrease"
17650 },
17651 modelRelay: {
17652 target: "disabled",
17653 singleTransform: {
17654 type: "fluid.transforms.binaryOp",
17655 left: "{textfieldStepper}.model.value",
17656 right: "{textfieldStepper}.model.range.min",
17657 operator: "<="
17658 }
17659 }
17660 }
17661 }
17662 },
17663 invokers: {
17664 increase: {
17665 funcName: "fluid.textfieldStepper.step",
17666 args: ["{that}"]
17667 },
17668 decrease: {
17669 funcName: "fluid.textfieldStepper.step",
17670 args: ["{that}", -1]
17671 },
17672 addFocus: {
17673 "this": "{that}.dom.focusContainer",
17674 method: "addClass",
17675 args: ["{that}.options.styles.focus"]
17676 },
17677 removeFocus: {
17678 "this": "{that}.dom.focusContainer",
17679 method: "removeClass",
17680 args: ["{that}.options.styles.focus"]
17681 }
17682 },
17683 listeners: {
17684 "onCreate.addContainerStyle": {
17685 "this": "{that}.container",
17686 method: "addClass",
17687 args: ["{that}.options.styles.container"]
17688 },
17689 "onCreate.bindFocusin": {
17690 "this": "{that}.container",
17691 method: "on",
17692 args: ["focusin", "{that}.addFocus"]
17693 },
17694 "onCreate.bindFocusout": {
17695 "this": "{that}.container",
17696 method: "on",
17697 args: ["focusout", "{that}.removeFocus"]
17698 }
17699 },
17700 model: {
17701 value: null,
17702 step: 1,
17703 range: {
17704 min: 0,
17705 max: 100
17706 }
17707 },
17708 attrs: {
17709 // Specified by implementor
17710 // ID of an element to use as a label for the stepper
17711 // attribute
17712 // "aria-labelledby": ""
17713 // Should specify either "aria-label" or "aria-labelledby"
17714 // aria-label: "{that}.options.strings.label",
17715 // ID of an element that is controlled by the textfield.
17716 // "aria-controls": ""
17717 },
17718 distributeOptions: [{
17719 // The scale option sets the number of decimal places to round
17720 // the number to. If no scale is specified, the number will not be rounded.
17721 // Scaling is useful to avoid long decimal places due to floating point imprecision.
17722 source: "{that}.options.scale",
17723 target: "{that > fluid.textfield > controller}.options.scale"
17724 }]
17725 });
17726
17727 fluid.textfieldStepper.step = function (that, coefficient) {
17728 coefficient = coefficient || 1;
17729 var newValue = that.model.value + (coefficient * that.model.step);
17730 that.applier.change("value", newValue);
17731 };
17732
17733 fluid.textfieldStepper.bindKeyEvent = function (elm, keyEvent, keyCode, fn) {
17734 $(elm).on(keyEvent, function (event) {
17735 if (event.which === keyCode) {
17736 fn();
17737 event.preventDefault();
17738 }
17739 });
17740 };
17741
17742 fluid.defaults("fluid.textfieldStepper.button", {
17743 gradeNames: ["fluid.viewComponent"],
17744 strings: {
17745 // to be specified by an implementor.
17746 // to provide a label for the button.
17747 // label: ""
17748 },
17749 styles: {
17750 container: "fl-textfieldStepper-button"
17751 },
17752 model: {
17753 disabled: false
17754 },
17755 events: {
17756 onClick: null
17757 },
17758 listeners: {
17759 "onCreate.bindClick": {
17760 "this": "{that}.container",
17761 "method": "click",
17762 "args": "{that}.events.onClick.fire"
17763 },
17764 "onCreate.addLabel": {
17765 "this": "{that}.container",
17766 method: "attr",
17767 args: ["aria-label", "{that}.options.strings.label"]
17768 },
17769 "onCreate.addContainerStyle": {
17770 "this": "{that}.container",
17771 method: "addClass",
17772 args: ["{that}.options.styles.container"]
17773 },
17774 // removing from tab order as keyboard users will
17775 // increment and decrement the stepper using the up/down arrow keys.
17776 "onCreate.removeFromTabOrder": {
17777 "this": "{that}.container",
17778 method: "attr",
17779 args: ["tabindex", "-1"]
17780 }
17781 },
17782 modelListeners: {
17783 disabled: {
17784 "this": "{that}.container",
17785 method: "prop",
17786 args: ["disabled", "{change}.value"]
17787 }
17788 }
17789 });
17790
17791})(jQuery, fluid_3_0_0);
17792;
17793/*
17794Copyright The Infusion copyright holders
17795See the AUTHORS.md file at the top-level directory of this distribution and at
17796https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
17797
17798Licensed under the Educational Community License (ECL), Version 2.0 or the New
17799BSD license. You may not use this file except in compliance with one these
17800Licenses.
17801
17802You may obtain a copy of the ECL 2.0 License and BSD License at
17803https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
17804*/
17805
17806var fluid_3_0_0 = fluid_3_0_0 || {};
17807
17808(function ($, fluid) {
17809 "use strict";
17810
17811 /**********
17812 * Switch *
17813 **********/
17814
17815 fluid.defaults("fluid.switchUI", {
17816 gradeNames: ["fluid.viewComponent"],
17817 selectors: {
17818 on: ".flc-switchUI-on",
17819 off: ".flc-switchUI-off",
17820 control: ".flc-switchUI-control"
17821 },
17822 strings: {
17823 // Specified by implementor
17824 // text of label to apply the switch, must add to "aria-label" in the attrs block
17825 label: "",
17826 on: "on",
17827 off: "off"
17828 },
17829 attrs: {
17830 // Specified by implementor
17831 // ID of an element to use as a label for the switch
17832 // "aria-labelledby": "",
17833 // Should specify either "aria-label" or "aria-labelledby"
17834 // "aria-label": "{that}.options.strings.label",
17835 // ID of an element that is controlled by the switch.
17836 // "aria-controls": ""
17837 role: "switch",
17838 tabindex: 0
17839 },
17840 model: {
17841 enabled: false
17842 },
17843 modelListeners: {
17844 enabled: {
17845 "this": "{that}.dom.control",
17846 method: "attr",
17847 args: ["aria-checked", "{change}.value"]
17848 }
17849 },
17850 listeners: {
17851 "onCreate.addAttrs": {
17852 "this": "{that}.dom.control",
17853 method: "attr",
17854 args: ["{that}.options.attrs"]
17855 },
17856 "onCreate.addOnText": {
17857 "this": "{that}.dom.on",
17858 method: "text",
17859 args: ["{that}.options.strings.on"]
17860 },
17861 "onCreate.addOffText": {
17862 "this": "{that}.dom.off",
17863 method: "text",
17864 args: ["{that}.options.strings.off"]
17865 },
17866 "onCreate.activateable": {
17867 listener: "fluid.activatable",
17868 args: ["{that}.dom.control", "{that}.activateHandler"]
17869 },
17870 "onCreate.bindClick": {
17871 "this": "{that}.dom.control",
17872 method: "on",
17873 args: ["click", "{that}.toggleModel"]
17874 }
17875 },
17876 invokers: {
17877 toggleModel: {
17878 funcName: "fluid.switchUI.toggleModel",
17879 args: ["{that}"]
17880 },
17881 activateHandler: {
17882 funcName: "fluid.switchUI.activateHandler",
17883 args: ["{arguments}.0", "{that}.toggleModel"]
17884 }
17885 }
17886 });
17887
17888 fluid.switchUI.toggleModel = function (that) {
17889 that.applier.change("enabled", !that.model.enabled);
17890 };
17891
17892 fluid.switchUI.activateHandler = function (event, fn) {
17893 event.preventDefault();
17894 fn();
17895 };
17896
17897})(jQuery, fluid_3_0_0);
17898;
17899/*
17900Copyright The Infusion copyright holders
17901See the AUTHORS.md file at the top-level directory of this distribution and at
17902https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
17903
17904Licensed under the Educational Community License (ECL), Version 2.0 or the New
17905BSD license. You may not use this file except in compliance with one these
17906Licenses.
17907
17908You may obtain a copy of the ECL 2.0 License and BSD License at
17909https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
17910*/
17911
17912var fluid_3_0_0 = fluid_3_0_0 || {};
17913
17914(function ($, fluid) {
17915 "use strict";
17916
17917 /******
17918 * ToC *
17919 *******/
17920 fluid.registerNamespace("fluid.tableOfContents");
17921
17922 fluid.tableOfContents.headingTextToAnchorInfo = function (heading) {
17923 var id = fluid.allocateSimpleId(heading);
17924
17925 var anchorInfo = {
17926 id: id,
17927 url: "#" + id
17928 };
17929
17930 return anchorInfo;
17931 };
17932
17933 fluid.tableOfContents.locateHeadings = function (that) {
17934 var headings = that.locate("headings");
17935
17936 fluid.each(that.options.ignoreForToC, function (sel) {
17937 headings = headings.not(sel).not(sel + " :header");
17938 });
17939
17940 return headings;
17941 };
17942
17943 fluid.tableOfContents.refreshView = function (that) {
17944 var headings = that.locateHeadings();
17945
17946 that.anchorInfo = fluid.transform(headings, function (heading) {
17947 return that.headingTextToAnchorInfo(heading);
17948 });
17949
17950 var headingsModel = that.modelBuilder.assembleModel(headings, that.anchorInfo);
17951 that.applier.change("", headingsModel);
17952
17953 that.events.onRefresh.fire();
17954 };
17955
17956 fluid.defaults("fluid.tableOfContents", {
17957 gradeNames: ["fluid.viewComponent"],
17958 components: {
17959 levels: {
17960 type: "fluid.tableOfContents.levels",
17961 createOnEvent: "onCreate",
17962 container: "{tableOfContents}.dom.tocContainer",
17963 options: {
17964 model: {
17965 headings: "{tableOfContents}.model"
17966 },
17967 events: {
17968 afterRender: "{tableOfContents}.events.afterRender"
17969 },
17970 listeners: {
17971 "{tableOfContents}.events.onRefresh": "{that}.refreshView"
17972 },
17973 strings: "{tableOfContents}.options.strings"
17974 }
17975 },
17976 modelBuilder: {
17977 type: "fluid.tableOfContents.modelBuilder"
17978 }
17979 },
17980 model: [],
17981 invokers: {
17982 headingTextToAnchorInfo: "fluid.tableOfContents.headingTextToAnchorInfo",
17983 locateHeadings: {
17984 funcName: "fluid.tableOfContents.locateHeadings",
17985 args: ["{that}"]
17986 },
17987 refreshView: {
17988 funcName: "fluid.tableOfContents.refreshView",
17989 args: ["{that}"]
17990 },
17991 // TODO: is it weird to have hide and show on a component?
17992 hide: {
17993 "this": "{that}.dom.tocContainer",
17994 "method": "hide"
17995 },
17996 show: {
17997 "this": "{that}.dom.tocContainer",
17998 "method": "show"
17999 }
18000 },
18001 strings: {
18002 tocHeader: "Table of Contents"
18003 },
18004 selectors: {
18005 headings: ":header:visible",
18006 tocContainer: ".flc-toc-tocContainer"
18007 },
18008 ignoreForToC: {
18009 tocContainer: "{that}.options.selectors.tocContainer"
18010 },
18011 events: {
18012 onRefresh: null,
18013 afterRender: null,
18014 onReady: {
18015 events: {
18016 "onCreate": "onCreate",
18017 "afterRender": "afterRender"
18018 },
18019 args: ["{that}"]
18020 }
18021 },
18022 listeners: {
18023 "onCreate.refreshView": "{that}.refreshView"
18024 }
18025 });
18026
18027
18028 /*******************
18029 * ToC ModelBuilder *
18030 ********************/
18031 fluid.registerNamespace("fluid.tableOfContents.modelBuilder");
18032
18033 fluid.tableOfContents.modelBuilder.toModel = function (headingInfo, modelLevelFn) {
18034 var headings = fluid.copy(headingInfo);
18035 var buildModelLevel = function (headings, level) {
18036 var modelLevel = [];
18037 while (headings.length > 0) {
18038 var heading = headings[0];
18039 if (heading.level < level) {
18040 break;
18041 }
18042 if (heading.level > level) {
18043 var subHeadings = buildModelLevel(headings, level + 1);
18044 if (modelLevel.length > 0) {
18045 modelLevel[modelLevel.length - 1].headings = subHeadings;
18046 } else {
18047 modelLevel = modelLevelFn(modelLevel, subHeadings);
18048 }
18049 }
18050 if (heading.level === level) {
18051 modelLevel.push(heading);
18052 headings.shift();
18053 }
18054 }
18055 return modelLevel;
18056 };
18057 return buildModelLevel(headings, 1);
18058 };
18059
18060 fluid.tableOfContents.modelBuilder.gradualModelLevelFn = function (modelLevel, subHeadings) {
18061 // Clone the subHeadings because we don't want to modify the reference of the subHeadings.
18062 // the reference will affect the equality condition in generateTree(), resulting an unwanted tree.
18063 var subHeadingsClone = fluid.copy(subHeadings);
18064 subHeadingsClone[0].level--;
18065 return subHeadingsClone;
18066 };
18067
18068 fluid.tableOfContents.modelBuilder.skippedModelLevelFn = function (modelLevel, subHeadings) {
18069 modelLevel.push({headings: subHeadings});
18070 return modelLevel;
18071 };
18072
18073 fluid.tableOfContents.modelBuilder.convertToHeadingObjects = function (that, headings, anchorInfo) {
18074 headings = $(headings);
18075 return fluid.transform(headings, function (heading, index) {
18076 return {
18077 level: that.headingCalculator.getHeadingLevel(heading),
18078 text: $(heading).text(),
18079 url: anchorInfo[index].url
18080 };
18081 });
18082 };
18083
18084 fluid.tableOfContents.modelBuilder.assembleModel = function (that, headings, anchorInfo) {
18085 var headingInfo = that.convertToHeadingObjects(headings, anchorInfo);
18086 return that.toModel(headingInfo);
18087 };
18088
18089 fluid.defaults("fluid.tableOfContents.modelBuilder", {
18090 gradeNames: ["fluid.component"],
18091 components: {
18092 headingCalculator: {
18093 type: "fluid.tableOfContents.modelBuilder.headingCalculator"
18094 }
18095 },
18096 invokers: {
18097 toModel: {
18098 funcName: "fluid.tableOfContents.modelBuilder.toModel",
18099 args: ["{arguments}.0", "{modelBuilder}.modelLevelFn"]
18100 },
18101 modelLevelFn: "fluid.tableOfContents.modelBuilder.gradualModelLevelFn",
18102 convertToHeadingObjects: "fluid.tableOfContents.modelBuilder.convertToHeadingObjects({that}, {arguments}.0, {arguments}.1)", // headings, anchorInfo
18103 assembleModel: "fluid.tableOfContents.modelBuilder.assembleModel({that}, {arguments}.0, {arguments}.1)" // headings, anchorInfo
18104 }
18105 });
18106
18107 /*************************************
18108 * ToC ModelBuilder headingCalculator *
18109 **************************************/
18110 fluid.registerNamespace("fluid.tableOfContents.modelBuilder.headingCalculator");
18111
18112 fluid.tableOfContents.modelBuilder.headingCalculator.getHeadingLevel = function (that, heading) {
18113 return that.options.levels.indexOf(heading.tagName) + 1;
18114 };
18115
18116 fluid.defaults("fluid.tableOfContents.modelBuilder.headingCalculator", {
18117 gradeNames: ["fluid.component"],
18118 invokers: {
18119 getHeadingLevel: "fluid.tableOfContents.modelBuilder.headingCalculator.getHeadingLevel({that}, {arguments}.0)" // heading
18120 },
18121 levels: ["H1", "H2", "H3", "H4", "H5", "H6"]
18122 });
18123
18124 /*************
18125 * ToC Levels *
18126 **************/
18127 fluid.registerNamespace("fluid.tableOfContents.levels");
18128
18129 /**
18130 * Create an object model based on the type and ID. The object should contain an
18131 * ID that maps the selectors (ie. level1:), and the object should contain a children
18132 * @param {String} type - Accepted values are: level, items
18133 * @param {Integer} ID - The current level which is used here as the ID.
18134 * @return {Object} - An object that models the level based on the type and ID.
18135 */
18136 fluid.tableOfContents.levels.objModel = function (type, ID) {
18137 var objModel = {
18138 ID: type + ID + ":",
18139 children: []
18140 };
18141 return objModel;
18142 };
18143
18144 /*
18145 * Configure item object when item object has no text, uri, level in it.
18146 * defaults to add a decorator to hide the bullets.
18147 */
18148 fluid.tableOfContents.levels.handleEmptyItemObj = function (itemObj) {
18149 itemObj.decorators = [{
18150 type: "addClass",
18151 classes: "fl-tableOfContents-hide-bullet"
18152 }];
18153 };
18154
18155 /**
18156 * @param {Object} headingsModel - that.model, the model with all the headings, it should be in the format of {headings: [...]}
18157 * @param {Integer} currentLevel - the current level we want to generate the tree for. default to 1 if not defined.
18158 * @return {Object} - A tree that looks like {children: [{ID: x, subTree:[...]}, ...]}
18159 */
18160 fluid.tableOfContents.levels.generateTree = function (headingsModel, currentLevel) {
18161 currentLevel = currentLevel || 0;
18162 var levelObj = fluid.tableOfContents.levels.objModel("level", currentLevel);
18163
18164 // FLUID-4352, run generateTree if there are headings in the model.
18165 if (headingsModel.headings.length === 0) {
18166 return currentLevel ? [] : {children: []};
18167 }
18168
18169 // base case: level is 0, returns {children:[generateTree(nextLevel)]}
18170 // purpose is to wrap the first level with a children object.
18171 if (currentLevel === 0) {
18172 var tree = {
18173 children: [
18174 fluid.tableOfContents.levels.generateTree(headingsModel, currentLevel + 1)
18175 ]
18176 };
18177 return tree;
18178 }
18179
18180 // Loop through the heading array, which can have multiple headings on the same level
18181 $.each(headingsModel.headings, function (index, model) {
18182 var itemObj = fluid.tableOfContents.levels.objModel("items", currentLevel);
18183 var linkObj = {
18184 ID: "link" + currentLevel,
18185 target: model.url,
18186 linktext: model.text
18187 };
18188
18189 // If level is undefined, then add decorator to it, otherwise add the links to it.
18190 if (!model.level) {
18191 fluid.tableOfContents.levels.handleEmptyItemObj(itemObj);
18192 } else {
18193 itemObj.children.push(linkObj);
18194 }
18195 // If there are sub-headings, go into the next level recursively
18196 if (model.headings) {
18197 itemObj.children.push(fluid.tableOfContents.levels.generateTree(model, currentLevel + 1));
18198 }
18199 // At this point, the itemObj should be in a tree format with sub-headings children
18200 levelObj.children.push(itemObj);
18201 });
18202 return levelObj;
18203 };
18204
18205 /**
18206 * @param {Object} that - The component itself.
18207 * @return {Object} - Returned produceTree must be in {headings: [trees]}
18208 */
18209 fluid.tableOfContents.levels.produceTree = function (that) {
18210 var tree = fluid.tableOfContents.levels.generateTree(that.model);
18211 // Add the header to the tree
18212 tree.children.push({
18213 ID: "tocHeader",
18214 messagekey: "tocHeader"
18215 });
18216 return tree;
18217 };
18218
18219 fluid.tableOfContents.levels.fetchResources = function (that) {
18220 fluid.fetchResources(that.options.resources, function () {
18221 that.container.append(that.options.resources.template.resourceText);
18222 that.refreshView();
18223 });
18224 };
18225
18226
18227 fluid.defaults("fluid.tableOfContents.levels", {
18228 gradeNames: ["fluid.rendererComponent"],
18229 produceTree: "fluid.tableOfContents.levels.produceTree",
18230 strings: {
18231 tocHeader: "Table of Contents"
18232 },
18233 selectors: {
18234 tocHeader: ".flc-toc-header",
18235 level1: ".flc-toc-levels-level1",
18236 level2: ".flc-toc-levels-level2",
18237 level3: ".flc-toc-levels-level3",
18238 level4: ".flc-toc-levels-level4",
18239 level5: ".flc-toc-levels-level5",
18240 level6: ".flc-toc-levels-level6",
18241 items1: ".flc-toc-levels-items1",
18242 items2: ".flc-toc-levels-items2",
18243 items3: ".flc-toc-levels-items3",
18244 items4: ".flc-toc-levels-items4",
18245 items5: ".flc-toc-levels-items5",
18246 items6: ".flc-toc-levels-items6",
18247 link1: ".flc-toc-levels-link1",
18248 link2: ".flc-toc-levels-link2",
18249 link3: ".flc-toc-levels-link3",
18250 link4: ".flc-toc-levels-link4",
18251 link5: ".flc-toc-levels-link5",
18252 link6: ".flc-toc-levels-link6"
18253 },
18254 repeatingSelectors: ["level1", "level2", "level3", "level4", "level5", "level6", "items1", "items2", "items3", "items4", "items5", "items6"],
18255 model: {
18256 headings: [] // [text: heading, url: linkURL, headings: [ an array of subheadings in the same format]
18257 },
18258 listeners: {
18259 "onCreate.fetchResources": "fluid.tableOfContents.levels.fetchResources"
18260 },
18261 resources: {
18262 template: {
18263 forceCache: true,
18264 url: "../html/TableOfContents.html"
18265 }
18266 },
18267 rendererFnOptions: {
18268 noexpand: true
18269 },
18270 rendererOptions: {
18271 debugMode: false
18272 }
18273
18274 });
18275
18276})(jQuery, fluid_3_0_0);
18277;
18278/*
18279Copyright The Infusion copyright holders
18280See the AUTHORS.md file at the top-level directory of this distribution and at
18281https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
18282
18283Licensed under the Educational Community License (ECL), Version 2.0 or the New
18284BSD license. You may not use this file except in compliance with one these
18285Licenses.
18286
18287You may obtain a copy of the ECL 2.0 License and BSD License at
18288https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
18289
18290*/
18291
18292/* global speechSynthesis, SpeechSynthesisUtterance*/
18293
18294var fluid_3_0_0 = fluid_3_0_0 || {};
18295
18296(function ($, fluid) {
18297 "use strict";
18298
18299 /*********************************************************************************************
18300 * fluid.window is a singleton component to be used for registering event bindings to *
18301 * events fired by the window object *
18302 *********************************************************************************************/
18303
18304 fluid.defaults("fluid.window", {
18305 gradeNames: ["fluid.component", "fluid.resolveRootSingle"],
18306 singleRootType: "fluid.window",
18307 members: {
18308 window: window
18309 },
18310 listeners: {
18311 "onCreate.bindEvents": {
18312 funcName: "fluid.window.bindEvents",
18313 args: ["{that}"]
18314 }
18315 }
18316 });
18317
18318 /**
18319 * Adds a lister to a window event for each event defined on the component.
18320 * The name must match a valid window event.
18321 *
18322 * @param {Component} that - the component itself
18323 */
18324 fluid.window.bindEvents = function (that) {
18325 fluid.each(that.options.events, function (type, eventName) {
18326 window.addEventListener(eventName, that.events[eventName].fire);
18327 });
18328 };
18329
18330 /*********************************************************************************************
18331 * fluid.textToSpeech provides a wrapper around the SpeechSynthesis Interface *
18332 * from the Web Speech API ( https://w3c.github.io/speech-api/speechapi.html#tts-section ) *
18333 *********************************************************************************************/
18334
18335 fluid.registerNamespace("fluid.textToSpeech");
18336
18337 fluid.textToSpeech.isSupported = function () {
18338 return !!(window && window.speechSynthesis);
18339 };
18340
18341 /**
18342 * Ensures that TTS is supported in the browser, including cases where the
18343 * feature is detected, but where the underlying audio engine is missing.
18344 * For example in VMs on SauceLabs, the behaviour for browsers which report that the speechSynthesis
18345 * API is implemented is for the `onstart` event of an utterance to never fire. If we don't receive this
18346 * event within a timeout, this API's behaviour is to return a promise which rejects.
18347 *
18348 * @param {Number} delay - A time in milliseconds to wait for the speechSynthesis to fire its onStart event
18349 * by default it is 5000ms (5s). This is crux of the test, as it needs time to attempt to run the speechSynthesis.
18350 * @return {fluid.promise} - A promise which will resolve if the TTS is supported (the onstart event is fired within the delay period)
18351 * or be rejected otherwise.
18352 */
18353 fluid.textToSpeech.checkTTSSupport = function (delay) {
18354 var promise = fluid.promise();
18355 if (fluid.textToSpeech.isSupported()) {
18356 // MS Edge speech synthesizer won't speak if the text string is blank,
18357 // so this must contain actual text
18358 var toSpeak = new SpeechSynthesisUtterance("short"); // short text to attempt to speak
18359 toSpeak.volume = 0; // mutes the Speech Synthesizer
18360 // Same timeout as the timeout in the IoC testing framework
18361 var timeout = setTimeout(function () {
18362 fluid.textToSpeech.invokeSpeechSynthesisFunc("cancel");
18363 promise.reject();
18364 }, delay || 5000);
18365 toSpeak.onend = function () {
18366 clearTimeout(timeout);
18367 fluid.textToSpeech.invokeSpeechSynthesisFunc("cancel");
18368 promise.resolve();
18369 };
18370 fluid.textToSpeech.invokeSpeechSynthesisFunc("speak", toSpeak);
18371 } else {
18372 fluid.invokeLater(promise.reject);
18373 }
18374 return promise;
18375 };
18376
18377 /*********************************************************************************************
18378 * fluid.textToSpeech component
18379 *********************************************************************************************/
18380
18381 fluid.defaults("fluid.textToSpeech", {
18382 gradeNames: ["fluid.modelComponent", "fluid.resolveRootSingle"],
18383 singleRootType: "fluid.textToSpeech",
18384 events: {
18385 onStart: null,
18386 onStop: null,
18387 onError: null,
18388 onSpeechQueued: null,
18389 utteranceOnBoundary: null,
18390 utteranceOnEnd: null,
18391 utteranceOnError: null,
18392 utteranceOnMark: null,
18393 utteranceOnPause: null,
18394 utteranceOnResume: null,
18395 utteranceOnStart: null
18396 },
18397 members: {
18398 queue: []
18399 },
18400 components: {
18401 wndw: {
18402 type: "fluid.window",
18403 options: {
18404 events: {
18405 beforeunload: null
18406 }
18407 }
18408 }
18409 },
18410 dynamicComponents: {
18411 utterance: {
18412 type: "fluid.textToSpeech.utterance",
18413 createOnEvent: "onSpeechQueued",
18414 options: {
18415 listeners: {
18416 "onBoundary.relay": "{textToSpeech}.events.utteranceOnBoundary.fire",
18417 "onEnd.relay": "{textToSpeech}.events.utteranceOnEnd.fire",
18418 "onError.relay": "{textToSpeech}.events.utteranceOnError.fire",
18419 "onMark.relay": "{textToSpeech}.events.utteranceOnMark.fire",
18420 "onPause.relay": "{textToSpeech}.events.utteranceOnPause.fire",
18421 "onResume.relay": "{textToSpeech}.events.utteranceOnResume.fire",
18422 "onStart.relay": "{textToSpeech}.events.utteranceOnStart.fire",
18423 "onCreate.queue": {
18424 "this": "{fluid.textToSpeech}.queue",
18425 method: "push",
18426 args: ["{that}"]
18427 },
18428 "onEnd.destroy": {
18429 func: "{that}.destroy",
18430 priority: "last"
18431 }
18432 },
18433 utterance: "{arguments}.0"
18434 }
18435 }
18436 },
18437 // Model paths: speaking, pending, paused, utteranceOpts, pauseRequested, resumeRequested
18438 model: {
18439 // Changes to the utteranceOpts will only affect text that is queued after the change.
18440 // All of these options can be overridden in the queueSpeech method by passing in
18441 // options directly there. It is useful in cases where a single instance needs to be
18442 // spoken with different options (e.g. single text in a different language.)
18443 utteranceOpts: {
18444 // text: "", // text to synthesize. Avoid using, it will be overwritten by text passed in directly to a queueSpeech
18445 // lang: "", // the language of the synthesized text
18446 // voice: {} // a WebSpeechSynthesis object; if not set, will use the default one provided by the browser
18447 // volume: 1, // a Floating point number between 0 and 1
18448 // rate: 1, // a Floating point number from 0.1 to 10 although different synthesizers may have a smaller range
18449 // pitch: 1, // a Floating point number from 0 to 2
18450 }
18451 },
18452 modelListeners: {
18453 "speaking": {
18454 listener: "fluid.textToSpeech.toggleSpeak",
18455 args: ["{that}", "{change}.value"]
18456 },
18457 "pauseRequested": {
18458 listener: "fluid.textToSpeech.requestControl",
18459 args: ["{that}", "pause", "{change}"]
18460 },
18461 "resumeRequested": {
18462 listener: "fluid.textToSpeech.requestControl",
18463 args: ["{that}", "resume", "{change}"]
18464 }
18465 },
18466 invokers: {
18467 queueSpeech: {
18468 funcName: "fluid.textToSpeech.queueSpeech",
18469 args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"]
18470 },
18471 cancel: {
18472 funcName: "fluid.textToSpeech.cancel",
18473 args: ["{that}"]
18474 },
18475 pause: {
18476 changePath: "pauseRequested",
18477 value: true,
18478 source: "pause"
18479 },
18480 resume: {
18481 changePath: "resumeRequested",
18482 value: true,
18483 source: "resume"
18484 },
18485 getVoices: {
18486 func: "{that}.invokeSpeechSynthesisFunc",
18487 args: ["getVoices"]
18488 },
18489 speak: {
18490 func: "{that}.invokeSpeechSynthesisFunc",
18491 args: ["speak", "{that}.queue.0.utterance"]
18492 },
18493 invokeSpeechSynthesisFunc: "fluid.textToSpeech.invokeSpeechSynthesisFunc"
18494 },
18495 listeners: {
18496 "onSpeechQueued.speak": {
18497 func: "{that}.speak",
18498 priority: "last"
18499 },
18500 "utteranceOnStart.speaking": {
18501 changePath: "speaking",
18502 value: true,
18503 source: "utteranceOnStart"
18504 },
18505 "utteranceOnEnd.stop": {
18506 funcName: "fluid.textToSpeech.handleEnd",
18507 args: ["{that}"]
18508 },
18509 "utteranceOnError.forward": "{that}.events.onError",
18510 "utteranceOnPause.pause": {
18511 changePath: "paused",
18512 value: true,
18513 source: "utteranceOnPause"
18514 },
18515 "utteranceOnResume.resume": {
18516 changePath: "paused",
18517 value: false,
18518 source: "utteranceOnResume"
18519 },
18520 "onDestroy.cleanup": {
18521 func: "{that}.invokeSpeechSynthesisFunc",
18522 args: ["cancel"]
18523 },
18524 "{wndw}.events.beforeunload": {
18525 funcName: "{that}.invokeSpeechSynthesisFunc",
18526 args: ["cancel"],
18527 namespace: "cancelSpeechSynthesisOnUnload"
18528 }
18529 }
18530 });
18531
18532 /**
18533 * Wraps the SpeechSynthesis API
18534 *
18535 * @param {String} method - a SpeechSynthesis method name
18536 * @param {Array} args - arguments to call the method with. If args isn't an array, it will be added as the first
18537 * element of one.
18538 */
18539 fluid.textToSpeech.invokeSpeechSynthesisFunc = function (method, args) {
18540 args = fluid.makeArray(args);
18541 speechSynthesis[method].apply(speechSynthesis, args);
18542 };
18543
18544 fluid.textToSpeech.toggleSpeak = function (that, speaking) {
18545 that.events[speaking ? "onStart" : "onStop"].fire();
18546 };
18547
18548 fluid.textToSpeech.requestControl = function (that, control, change) {
18549 // If there's a control request (value change to true), clear and
18550 // execute it
18551 if (change.value) {
18552 that.applier.change(change.path, false, "ADD", "requestControl");
18553 that.invokeSpeechSynthesisFunc(control);
18554 }
18555 };
18556
18557 /*
18558 * After an utterance has finished, the utterance is removed from the queue and the model is updated as needed.
18559 */
18560 fluid.textToSpeech.handleEnd = function (that) {
18561 that.queue.shift();
18562
18563 var resetValues = {
18564 speaking: false,
18565 pending: false,
18566 paused: false
18567 };
18568
18569 if (that.queue.length) {
18570 that.applier.change("pending", true, "ADD", "handleEnd.pending");
18571 } else if (!that.queue.length) {
18572 var newModel = $.extend({}, that.model, resetValues);
18573 that.applier.change("", newModel, "ADD", "handleEnd.reset");
18574 }
18575 };
18576
18577 /**
18578 * Options to configure the SpeechSynthesis Utterance with.
18579 * See: https://w3c.github.io/speech-api/speechapi.html#utterance-attributes
18580 *
18581 * @typedef {Object} UtteranceOpts
18582 * @property {String} text - The text to Synthesize
18583 * @property {String} lang - The BCP 47 language code for the synthesized text
18584 * @property {WebSpeechSynthesis} voice - If not set, will use the default one provided by the browser
18585 * @property {Float} volume - A Floating point number between 0 and 1
18586 * @property {Float} rate - A Floating point number from 0.1 to 10 although different synthesizers may have a smaller range
18587 * @property {Float} pitch - A Floating point number from 0 to 2
18588 */
18589
18590 /**
18591 * Assembles the utterance options and fires onSpeechQueued which will kick off the creation of an utterance
18592 * component. If "interrupt" is true, this utterance will replace any existing ones.
18593 *
18594 * @param {Component} that - the component
18595 * @param {String} text - the text to be synthesized
18596 * @param {Boolean} interrupt - used to indicate if this text should be queued or replace existing utterances
18597 * @param {UtteranceOpts} options - options to configure the SpeechSynthesis utterance with. It is merged on top of the
18598 * utteranceOpts from the component's model.
18599 *
18600 * @return {Promise} - returns a promise that is resolved after the onSpeechQueued event has fired.
18601 */
18602 fluid.textToSpeech.queueSpeech = function (that, text, interrupt, options) {
18603 var promise = fluid.promise();
18604 if (interrupt) {
18605 that.cancel();
18606 }
18607
18608 var utteranceOpts = $.extend({}, that.model.utteranceOpts, options, {text: text});
18609
18610 // The setTimeout is needed for Safari to fully cancel out the previous speech.
18611 // Without this the synthesizer gets confused and may play multiple utterances at once.
18612 setTimeout(function () {
18613 that.events.onSpeechQueued.fire(utteranceOpts, interrupt);
18614 promise.resolve(text);
18615 }, 100);
18616 return promise;
18617 };
18618
18619 fluid.textToSpeech.cancel = function (that) {
18620 // Safari does not fire the onend event from an utterance when the speech synthesis is cancelled.
18621 // Manually triggering the onEnd event for each utterance as we empty the queue, before calling cancel.
18622 while (that.queue.length) {
18623 var utterance = that.queue.shift();
18624 utterance.events.onEnd.fire();
18625 }
18626
18627 that.invokeSpeechSynthesisFunc("cancel");
18628 // clear any paused state.
18629 that.invokeSpeechSynthesisFunc("resume");
18630 };
18631
18632 /*********************************************************************************************
18633 * fluid.textToSpeech.utterance component
18634 *********************************************************************************************/
18635
18636 fluid.defaults("fluid.textToSpeech.utterance", {
18637 gradeNames: ["fluid.modelComponent"],
18638 members: {
18639 utterance: {
18640 expander: {
18641 funcName: "fluid.textToSpeech.utterance.construct",
18642 args: ["{that}", "{that}.options.utteranceEventMap", "{that}.options.utterance"]
18643 }
18644 }
18645 },
18646 model: {
18647 boundary: 0
18648 },
18649 utterance: {
18650 // text: "", // text to synthesize. avoid as it will override any other text passed in
18651 // lang: "", // the language of the synthesized text
18652 // voice: {} // a WebSpeechSynthesis object; if not set, will use the default one provided by the browser
18653 // volume: 1, // a Floating point number between 0 and 1
18654 // rate: 1, // a Floating point number from 0.1 to 10 although different synthesizers may have a smaller range
18655 // pitch: 1, // a Floating point number from 0 to 2
18656 },
18657 utteranceEventMap: {
18658 onboundary: "onBoundary",
18659 onend: "onEnd",
18660 onerror: "onError",
18661 onmark: "onMark",
18662 onpause: "onPause",
18663 onresume: "onResume",
18664 onstart: "onStart"
18665 },
18666 events: {
18667 onBoundary: null,
18668 onEnd: null,
18669 onError: null,
18670 onMark: null,
18671 onPause: null,
18672 onResume: null,
18673 onStart: null
18674 },
18675 listeners: {
18676 "onBoundary.updateModel": {
18677 changePath: "boundary",
18678 value: "{arguments}.0.charIndex"
18679 }
18680 }
18681 });
18682
18683 /**
18684 * Creates a SpeechSynthesisUtterance instance and configures it with the utteranceOpts and utteranceMap. For any
18685 * event provided in the utteranceEventMap, any corresponding event binding passed in directly through the
18686 * utteranceOpts will be rebound as component event listeners with the "external" namespace.
18687 *
18688 * @param {Component} that - the component
18689 * @param {Object} utteranceEventMap - a mapping from SpeechSynthesisUtterance events to component events.
18690 * @param {UtteranceOpts} utteranceOpts - options to configure the SpeechSynthesis utterance with.
18691 *
18692 * @return {SpeechSynthesisUtterance} - returns the created SpeechSynthesisUtterance object
18693 */
18694 fluid.textToSpeech.utterance.construct = function (that, utteranceEventMap, utteranceOpts) {
18695 var utterance = new SpeechSynthesisUtterance();
18696 $.extend(utterance, utteranceOpts);
18697
18698 fluid.each(utteranceEventMap, function (compEventName, utteranceEvent) {
18699 var compEvent = that.events[compEventName];
18700 var origHandler = utteranceOpts[utteranceEvent];
18701
18702 utterance[utteranceEvent] = compEvent.fire;
18703
18704 if (origHandler) {
18705 compEvent.addListener(origHandler, "external");
18706 }
18707 });
18708
18709 return utterance;
18710 };
18711
18712
18713})(jQuery, fluid_3_0_0);
18714;
18715/*
18716Copyright The Infusion copyright holders
18717See the AUTHORS.md file at the top-level directory of this distribution and at
18718https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
18719
18720Licensed under the Educational Community License (ECL), Version 2.0 or the New
18721BSD license. You may not use this file except in compliance with one these
18722Licenses.
18723
18724You may obtain a copy of the ECL 2.0 License and BSD License at
18725https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
18726*/
18727
18728var fluid_3_0_0 = fluid_3_0_0 || {};
18729
18730(function ($, fluid) {
18731 "use strict";
18732
18733 /**********************************************
18734 * fluid.orator
18735 *
18736 * A component for self voicing a web page
18737 **********************************************/
18738
18739 fluid.defaults("fluid.orator", {
18740 gradeNames: ["fluid.viewComponent"],
18741 selectors: {
18742 controller: ".flc-orator-controller",
18743 content: ".flc-orator-content"
18744 },
18745 model: {
18746 enabled: true,
18747 play: false
18748 },
18749 components: {
18750 tts: {
18751 type: "fluid.textToSpeech"
18752 },
18753 controller: {
18754 type: "fluid.orator.controller",
18755 options: {
18756 parentContainer: "{orator}.container",
18757 model: {
18758 playing: "{orator}.model.play",
18759 enabled: "{orator}.model.enabled"
18760 }
18761 }
18762 },
18763 selectionReader: {
18764 type: "fluid.orator.selectionReader",
18765 container: "{that}.container",
18766 options: {
18767 model: {
18768 enabled: "{orator}.model.enabled"
18769 }
18770 }
18771 },
18772 domReader: {
18773 type: "fluid.orator.domReader",
18774 container: "{that}.dom.content",
18775 options: {
18776 model: {
18777 tts: {
18778 enabled: "{orator}.model.enabled"
18779 }
18780 },
18781 listeners: {
18782 "utteranceOnEnd.domReaderStop": {
18783 changePath: "{orator}.model.play",
18784 value: false,
18785 source: "domReader.utteranceOnEnd",
18786 priority: "after:removeHighlight"
18787 }
18788 },
18789 modelListeners: {
18790 "{orator}.model.play": {
18791 funcName: "fluid.orator.handlePlayToggle",
18792 args: ["{that}", "{change}.value"],
18793 namespace: "domReader.handlePlayToggle"
18794 }
18795 }
18796 }
18797 }
18798 },
18799 modelListeners: {
18800 "enabled": {
18801 listener: "fluid.orator.cancelWhenDisabled",
18802 args: ["{tts}.cancel", "{change}.value"],
18803 namespace: "orator.clearSpeech"
18804 }
18805 },
18806 distributeOptions: [{
18807 source: "{that}.options.tts",
18808 target: "{that tts}.options",
18809 removeSource: true,
18810 namespace: "ttsOpts"
18811 }, {
18812 source: "{that}.options.controller",
18813 target: "{that controller}.options",
18814 removeSource: true,
18815 namespace: "controllerOpts"
18816 }, {
18817 source: "{that}.options.domReader",
18818 target: "{that domReader}.options",
18819 removeSource: true,
18820 namespace: "domReaderOpts"
18821 }, {
18822 source: "{that}.options.selectionReader",
18823 target: "{that selectionReader}.options",
18824 removeSource: true,
18825 namespace: "selectionReaderOpts"
18826 }]
18827 });
18828
18829 // TODO: When https://issues.fluidproject.org/browse/FLUID-6393 has been addressed, it will be possible to remove
18830 // this function and directly configure the modelListener to only trigger when a false value is passed.
18831 fluid.orator.cancelWhenDisabled = function (cancelFn, state) {
18832 if (!state) {
18833 cancelFn();
18834 }
18835 };
18836
18837 fluid.orator.handlePlayToggle = function (that, state) {
18838 if (state) {
18839 that.play();
18840 } else {
18841 that.pause();
18842 }
18843 };
18844
18845 /**********************************************
18846 * fluid.orator.controller
18847 *
18848 * Provides a UI Widget to control the Orator
18849 **********************************************/
18850
18851 fluid.defaults("fluid.orator.controller", {
18852 gradeNames: ["fluid.containerRenderingView"],
18853 selectors: {
18854 playToggle: ".flc-orator-controller-playToggle"
18855 },
18856 styles: {
18857 play: "fl-orator-controller-play"
18858 },
18859 strings: {
18860 play: "play",
18861 pause: "pause"
18862 },
18863 model: {
18864 playing: false,
18865 enabled: true
18866 },
18867 injectionType: "prepend",
18868 markup: {
18869 container: "<div class=\"flc-orator-controller fl-orator-controller\">" +
18870 "<div class=\"fl-icon-orator\" aria-hidden=\"true\"></div>" +
18871 "<button class=\"flc-orator-controller-playToggle\">" +
18872 "<span class=\"fl-orator-controller-playToggle fl-icon-orator-playToggle\" aria-hidden=\"true\"></span>" +
18873 "</button></div>"
18874 },
18875 invokers: {
18876 play: {
18877 changePath: "playing",
18878 value: true,
18879 source: "play"
18880 },
18881 pause: {
18882 changePath: "playing",
18883 value: false,
18884 source: "pause"
18885 },
18886 toggle: {
18887 funcName: "fluid.orator.controller.toggleState",
18888 args: ["{that}", "{arguments}.0", "{arguments}.1"]
18889 }
18890 },
18891 listeners: {
18892 "onCreate.bindClick": {
18893 listener: "fluid.orator.controller.bindClick",
18894 args: ["{that}"]
18895 }
18896 },
18897 modelListeners: {
18898 "playing": {
18899 listener: "fluid.orator.controller.setToggleView",
18900 args: ["{that}", "{change}.value"]
18901 },
18902 "enabled": {
18903 "this": "{that}.container",
18904 method: "toggle",
18905 args: ["{change}.value"],
18906 namespace: "toggleView"
18907 }
18908 }
18909 });
18910
18911 /**
18912 * Binds the click event for the "playToggle" element to trigger the `that.toggle` method.
18913 * This is not bound declaratively to ensure that the correct arguments are passed along to the `that.toggle`
18914 * method.
18915 *
18916 * @param {Component} that - an instance of `fluid.orator.controller`
18917 */
18918 fluid.orator.controller.bindClick = function (that) {
18919 that.locate("playToggle").click(function () {
18920 that.toggle("playing");
18921 });
18922 };
18923
18924 /**
18925 * Used to toggle the state of a model value at a specified path. The new state will be the inverse of the current
18926 * boolean value at the specified model path, or can be set explicitly by passing in a 'state' value. It's likely
18927 * that this method will be used in conjunction with a click handler. In that case it's most likely that the state
18928 * will be toggling the existing model value.
18929 *
18930 * @param {Component} that - an instance of `fluid.orator.controller`
18931 * @param {String|Array} path - the path, into the model, for the value to toggle
18932 * @param {Boolean} state - (optional) explicit state to set the model value to
18933 */
18934 fluid.orator.controller.toggleState = function (that, path, state) {
18935 var newState = fluid.isValue(state) ? state : !fluid.get(that.model, path);
18936 // the !! ensures that the newState is a boolean value.
18937 that.applier.change(path, !!newState, "ADD", "toggleState");
18938 };
18939
18940 /**
18941 * Sets the view state of the toggle controller.
18942 * True - play style added
18943 * - aria-label set to the `pause` string
18944 * False - play style removed
18945 * - aria-label set to the `play` string
18946 *
18947 * @param {Component} that - an instance of `fluid.orator.controller`
18948 * @param {Boolean} state - the state to set the controller to
18949 */
18950 fluid.orator.controller.setToggleView = function (that, state) {
18951 var playToggle = that.locate("playToggle");
18952 playToggle.toggleClass(that.options.styles.play, state);
18953 playToggle.attr({
18954 "aria-label": that.options.strings[state ? "pause" : "play"]
18955 });
18956 };
18957
18958
18959 /*******************************************************************************
18960 * fluid.orator.domReader
18961 *
18962 * Reads in text from a DOM element and voices it
18963 *******************************************************************************/
18964
18965 fluid.defaults("fluid.orator.domReader", {
18966 gradeNames: ["fluid.viewComponent"],
18967 selectors: {
18968 highlight: ".flc-orator-highlight"
18969 },
18970 markup: {
18971 highlight: "<mark class=\"flc-orator-highlight fl-orator-highlight\"></mark>"
18972 },
18973 events: {
18974 onQueueSpeech: null,
18975 onReadFromDOM: null,
18976 utteranceOnEnd: null,
18977 utteranceOnBoundary: null,
18978 utteranceOnError: null,
18979 utteranceOnMark: null,
18980 utteranceOnPause: null,
18981 utteranceOnResume: null,
18982 utteranceOnStart: null
18983 },
18984 utteranceEventMap: {
18985 onboundary: "utteranceOnBoundary",
18986 onend: "utteranceOnEnd",
18987 onerror: "utteranceOnError",
18988 onmark:"utteranceOnMark",
18989 onpause: "utteranceOnPause",
18990 onresume: "utteranceOnResume",
18991 onstart: "utteranceOnStart"
18992 },
18993 model: {
18994 tts: {
18995 paused: false,
18996 speaking: false,
18997 enabled: true
18998 },
18999 parseQueueLength: 0,
19000 parseIndex: null,
19001 ttsBoundary: null
19002 },
19003 modelRelay: [{
19004 target: "parseIndex",
19005 backward: "never",
19006 namespace: "getClosestIndex",
19007 singleTransform: {
19008 type: "fluid.transforms.free",
19009 func: "fluid.orator.domReader.getClosestIndex",
19010 args: ["{that}", "{that}.model.ttsBoundary"]
19011 }
19012 }],
19013 members: {
19014 parseQueue: [],
19015 range: {
19016 expander: {
19017 this: "document",
19018 method: "createRange"
19019 }
19020 }
19021 },
19022 components: {
19023 parser: {
19024 type: "fluid.textNodeParser",
19025 options: {
19026 invokers: {
19027 hasTextToRead: "fluid.textNodeParser.hasVisibleText"
19028 },
19029 listeners: {
19030 "onParsedTextNode.addToParseQueue": "{domReader}.addToParseQueue"
19031 }
19032 }
19033 }
19034 },
19035 invokers: {
19036 parsedToString: "fluid.orator.domReader.parsedToString",
19037 readFromDOM: {
19038 funcName: "fluid.orator.domReader.readFromDOM",
19039 args: ["{that}", "{that}.container"]
19040 },
19041 removeHighlight: {
19042 funcName: "fluid.orator.domReader.unWrap",
19043 args: ["{that}.dom.highlight"]
19044 },
19045 addToParseQueue: {
19046 funcName: "fluid.orator.domReader.addToParseQueue",
19047 args: ["{that}", "{arguments}.0", "{arguments}.1", "{arguments}.2"]
19048 },
19049 resetParseQueue: {
19050 funcName: "fluid.orator.domReader.resetParseQueue",
19051 args: ["{that}"]
19052 },
19053 highlight: {
19054 funcName: "fluid.orator.domReader.highlight",
19055 args: ["{that}", "{arguments}.0"]
19056 },
19057 play: {
19058 funcName: "fluid.orator.domReader.play",
19059 args: ["{that}", "{fluid.textToSpeech}.resume"]
19060 },
19061 pause: {
19062 funcName: "fluid.orator.domReader.pause",
19063 args: ["{that}", "{fluid.textToSpeech}.pause"]
19064 },
19065 queueSpeech: {
19066 funcName: "fluid.orator.domReader.queueSpeech",
19067 args: ["{that}", "{arguments}.0", true, "{arguments}.1"]
19068 },
19069 isWord: "fluid.textNodeParser.isWord"
19070 },
19071 modelListeners: {
19072 "highlight": {
19073 listener: "{that}.highlight",
19074 path: ["parseIndex", "parseQueueLength"]
19075 }
19076 },
19077 listeners: {
19078 "onQueueSpeech.removeExtraWhiteSpace": "fluid.orator.domReader.removeExtraWhiteSpace",
19079 "onQueueSpeech.queueSpeech": {
19080 func: "{fluid.textToSpeech}.queueSpeech",
19081 args: ["{arguments}.0", "{arguments}.1.interrupt", "{arguments}.1"],
19082 priority: "after:removeExtraWhiteSpace"
19083 },
19084 "utteranceOnEnd.resetParseQueue": {
19085 listener: "{that}.resetParseQueue"
19086 },
19087 "utteranceOnEnd.removeHighlight": {
19088 listener: "{that}.removeHighlight",
19089 priority: "after:resetParseQueue"
19090 },
19091 "utteranceOnEnd.updateTTSModel": {
19092 changePath: "tts",
19093 value: {
19094 speaking: false,
19095 paused: false
19096 }
19097 },
19098 "utteranceOnStart.updateTTSModel": {
19099 changePath: "tts",
19100 value: {
19101 speaking: true,
19102 paused: false
19103 }
19104 },
19105 "utteranceOnPause.updateTTSModel": {
19106 changePath: "tts",
19107 value: {
19108 speaking: true,
19109 paused: true
19110 }
19111 },
19112 "utteranceOnResume.updateTTSModel": {
19113 changePath: "tts",
19114 value: {
19115 speaking: true,
19116 paused: false
19117 }
19118 },
19119 "utteranceOnBoundary.setCurrentBoundary": {
19120 changePath: "ttsBoundary",
19121 value: "{arguments}.0.charIndex",
19122 source: "utteranceOnBoundary"
19123 },
19124 "onDestroy.detachRange": {
19125 "this": "{that}.range",
19126 method: "detach"
19127 }
19128 }
19129 });
19130
19131 fluid.orator.domReader.play = function (that, resumeFn) {
19132 if (that.model.tts.enabled) {
19133 if (that.model.tts.paused) {
19134 resumeFn();
19135 } else if (!that.model.tts.speaking) {
19136 that.readFromDOM();
19137 }
19138 }
19139 };
19140
19141 fluid.orator.domReader.pause = function (that, pauseFn) {
19142 if (that.model.tts.speaking && !that.model.tts.paused) {
19143 pauseFn();
19144 }
19145 };
19146
19147 fluid.orator.domReader.mapUtteranceEvents = function (that, utterance, utteranceEventMap) {
19148 fluid.each(utteranceEventMap, function (compEventName, utteranceEvent) {
19149 var compEvent = that.events[compEventName];
19150 utterance[utteranceEvent] = compEvent.fire;
19151 });
19152 };
19153
19154 fluid.orator.domReader.removeExtraWhiteSpace = function (text) {
19155 var promise = fluid.promise();
19156 // force a string value
19157 var str = text.toString();
19158 // trim whitespace
19159 str = str.trim();
19160
19161 if (str) {
19162 promise.resolve(str);
19163 } else {
19164 promise.reject("The text is empty");
19165 }
19166
19167 return promise;
19168 };
19169
19170 /**
19171 * Operates the core "transforming promise workflow" for queuing an utterance. The initial listener is provided the
19172 * initial text; which then proceeds through the transform chain to arrive at the final text.
19173 * To change the speech function (e.g for testing) the onQueueSpeech.queueSpeech listener can be overridden.
19174 *
19175 * @param {Component} that - an instance of `fluid.orator.domReader`
19176 * @param {String} text - The text to be synthesized
19177 * @param {Boolean} interrupt - Used to indicate if this text should be queued or replace existing utterances.
19178 * This will be passed along to the listeners in the options; `options.interrupt`.
19179 * @param {Object} options - (optional) options to configure the utterance with. This will also be interpolated with
19180 * the interrupt parameter and event mappings. See: fluid.textToSpeech.queueSpeech in
19181 * TextToSpeech.js for an example of utterance options for that speech function.
19182 *
19183 * @return {Promise} - A promise for the final resolved text
19184 */
19185 fluid.orator.domReader.queueSpeech = function (that, text, interrupt, options) {
19186 options = options || {};
19187 options.interrupt = interrupt || options.interrupt;
19188 // map events
19189 fluid.orator.domReader.mapUtteranceEvents(that, options, that.options.utteranceEventMap);
19190
19191 return fluid.promise.fireTransformEvent(that.events.onQueueSpeech, text, options);
19192 };
19193
19194 /**
19195 * Unwraps the contents of the element by removing the tag surrounding the content and placing the content
19196 * as a node within the element's parent. The parent is also normalized to combine any adjacent textnodes.
19197 *
19198 * @param {String|jQuery|DomElement} elm - element to unwrap
19199 */
19200 fluid.orator.domReader.unWrap = function (elm) {
19201 elm = $(elm);
19202
19203 if (elm.length) {
19204 var parent = elm.parent();
19205 // Remove the element, but place its contents within the parent.
19206 elm.contents().unwrap();
19207 // Normalize the parent to cleanup textnodes
19208 parent[0].normalize();
19209 }
19210 };
19211
19212 /**
19213 * Positional information about a word parsed from the text in a {DomElement}. This can be used for mappings between
19214 * a synthesizer's speech boundary and the word's location within the DOM.
19215 *
19216 * @typedef {Object} DomWordMap
19217 * @property {Integer} blockIndex - The index into the entire block of text being parsed from the DOM
19218 * @property {Integer} startOffset - The start offset of the current `word` relative to the closest
19219 * enclosing DOM element
19220 * @property {Integer} endOffset - The end offset of the current `word` relative to the closest
19221 * enclosing DOM element
19222 * @property {DomNode} node - The current child node being parsed
19223 * @property {Integer} childIndex - The index of the child node being parsed relative to its parent
19224 * @property {DomElement} parentNode - The parent DOM node
19225 * @property {String} word - The text, `word`, parsed from the node. It may contain only whitespace.
19226 */
19227
19228 /**
19229 * Takes in a textnode and separates the contained words into DomWordMaps that are added to the parseQueue.
19230 * Typically this handles parsed data passed along by a Parser's (`fluid.textNodeParser`) `onParsedTextNode` event.
19231 * Empty nodes are skipped and the subsequent text is analyzed to determine if it should be appended to the
19232 * previous DomWordMap in the parseQueue. For example: when the syllabification separator is tag is inserted
19233 * between words.
19234 *
19235 * @param {Component} that - an instance of `fluid.orator.domReader`
19236 * @param {DomNode} textNode - the text node being parsed
19237 * @param {String} lang - a valid BCP 47 language code.
19238 * @param {Integer} childIndex - the index of the text node within its parent's set of child nodes
19239 */
19240 fluid.orator.domReader.addToParseQueue = function (that, textNode, lang, childIndex) {
19241 var lastParsed = that.parseQueue[that.parseQueue.length - 1] || {};
19242 var words = textNode.textContent.split(/(\s+)/); // split on whitespace, and capture whitespace
19243 var parsed = {
19244 blockIndex: (lastParsed.blockIndex || 0) + (fluid.get(lastParsed, ["word", "length"]) || 0),
19245 startOffset: 0,
19246 node: textNode,
19247 childIndex: childIndex,
19248 parentNode: textNode.parentNode,
19249 lang: lang
19250 };
19251
19252 fluid.each(words, function (word) {
19253 var lastIsWord = that.isWord(lastParsed.word);
19254 var currentIsWord = that.isWord(word);
19255
19256 // If the last parsed item is a word and the current item is a word, combine into the the last parsed block.
19257 // Otherwise, if the new item is a word or non-empty string create a new parsed block.
19258 if (lastIsWord && currentIsWord) {
19259 lastParsed.word += word;
19260 lastParsed.endOffset += word.length;
19261 parsed.blockIndex += word.length;
19262 parsed.startOffset += word.length;
19263 } else {
19264 parsed.word = word;
19265 parsed.endOffset = parsed.startOffset + word.length;
19266 if (currentIsWord || word && lastIsWord) {
19267 lastParsed = fluid.copy(parsed);
19268 that.parseQueue.push(lastParsed);
19269 that.applier.change("parseQueueLength", that.parseQueue.length, "ADD", "addToParseQueue");
19270 parsed.blockIndex += word.length;
19271 }
19272 parsed.startOffset = parsed.endOffset;
19273 }
19274 });
19275 };
19276
19277 /**
19278 * Empty the parseQueue and related model values
19279 * @param {Component} that - an instance of `fluid.orator.domReader`
19280 */
19281 fluid.orator.domReader.resetParseQueue = function (that) {
19282 that.parseQueue = [];
19283 that.applier.change("", {
19284 parseQueueLength: 0,
19285 parseIndex: null,
19286 ttsBoundary: null
19287 }, "ADD", "resetParseQueue");
19288 };
19289
19290 /**
19291 * Combines the parsed text into a String.
19292 *
19293 * @param {DomWordMap[]} parsed - An array of {DomWordMap} objects containing the position mappings from a parsed
19294 * {DomElement}.
19295 *
19296 * @return {String} - The parsed text combined into a String.
19297 */
19298 fluid.orator.domReader.parsedToString = function (parsed) {
19299 var words = fluid.transform(parsed, function (block) {
19300 return block.word;
19301 });
19302
19303 return words.join("");
19304 };
19305
19306 /**
19307 * Parses the DOM element into data points to use for highlighting the text, and queues the text into the self
19308 * voicing engine. The parsed data points are added to the component's `parseQueue`
19309 *
19310 * @param {Component} that - an instance of `fluid.orator.domReader`
19311 * @param {String|jQuery|DomElement} elm - The DOM node to read
19312 */
19313 fluid.orator.domReader.readFromDOM = function (that, elm) {
19314 elm = $(elm);
19315
19316 // only execute if there are nodes to read from
19317 if (elm.length) {
19318 that.resetParseQueue();
19319 that.parser.parse(elm[0]);
19320 that.queueSpeech(that.parsedToString(that.parseQueue));
19321 }
19322 };
19323
19324 /**
19325 * Returns the index of the closest data point from the parseQueue based on the boundary provided.
19326 *
19327 * @param {Component} that - an instance of `fluid.orator.domReader`
19328 * @param {Integer} boundary - The boundary value used to compare against the blockIndex of the parsed data points.
19329 * If the boundary is undefined or out of bounds, `undefined` will be returned.
19330 *
19331 * @return {Integer|undefined} - Will return the index of the closest data point in the parseQueue. If the boundary
19332 * cannot be located within the parseQueue, `undefined` is returned.
19333 */
19334 fluid.orator.domReader.getClosestIndex = function (that, boundary) {
19335 var parseQueue = that.parseQueue;
19336
19337 if (!parseQueue.length || !fluid.isValue(boundary)) {
19338 return undefined;
19339 };
19340
19341 var maxIndex = Math.max(parseQueue.length - 1, 0);
19342 var index = Math.max(Math.min(that.model.parseIndex || 0, maxIndex), 0);
19343 var maxBoundary = parseQueue[maxIndex].blockIndex + parseQueue[maxIndex].word.length;
19344
19345 if (boundary > maxBoundary || boundary < 0) {
19346 return undefined;
19347 }
19348
19349 while (index >= 0) {
19350 var nextIndex = index + 1;
19351 var prevIndex = index - 1;
19352 var currentBlockIndex = parseQueue[index].blockIndex;
19353 var nextBlockIndex = index < maxIndex ? parseQueue[nextIndex].blockIndex : (maxBoundary + 1);
19354
19355 // Break if the boundary lies within the current block
19356 if (boundary >= currentBlockIndex && boundary < nextBlockIndex) {
19357 break;
19358 }
19359
19360 if (currentBlockIndex > boundary) {
19361 index = prevIndex;
19362 } else {
19363 index = nextIndex;
19364 }
19365 }
19366
19367 return index;
19368
19369 };
19370
19371 /**
19372 * Searches down, starting from the provided node, returning the first text node found.
19373 *
19374 * @param {DomNode} node - the DOM Node to start searching from.
19375 * @return {DomNode|Undefined} - Returns the first text node found, or `undefined` if none located.
19376 */
19377 fluid.orator.domReader.findTextNode = function (node) {
19378 if (!node) {
19379 return;
19380 }
19381
19382 if (node.nodeType === Node.TEXT_NODE) {
19383 return node;
19384 }
19385
19386 var children = node.childNodes;
19387 for (var i = 0; i < children.length; i++) {
19388 var textNode = fluid.orator.domReader.findTextNode(children[i]);
19389 if (textNode !== undefined) {
19390 return textNode;
19391 }
19392 }
19393 };
19394
19395 fluid.orator.domReader.getTextNodeFromSibling = function (node) {
19396 while (node.nextSibling) {
19397 node = node.nextSibling;
19398 var textNode = fluid.orator.domReader.findTextNode(node);
19399 if (textNode) {
19400 return textNode;
19401 }
19402 }
19403 };
19404
19405 fluid.orator.domReader.getNextTextNode = function (node) {
19406 var nextTextNode = fluid.orator.domReader.getTextNodeFromSibling(node);
19407
19408 if (nextTextNode) {
19409 return nextTextNode;
19410 }
19411
19412 var parent = node.parentNode;
19413
19414 if (parent) {
19415 return fluid.orator.domReader.getNextTextNode(parent);
19416 }
19417 };
19418
19419 fluid.orator.domReader.setRangeEnd = function (range, node, end) {
19420 if (end <= node.length) {
19421 range.setEnd(node, end);
19422 } else {
19423 var nextTextNode = fluid.orator.domReader.getNextTextNode(node);
19424 fluid.orator.domReader.setRangeEnd(range, nextTextNode, end - node.length);
19425 }
19426 };
19427
19428 /**
19429 * Highlights text from the parseQueue. Highlights are performed by wrapping the appropriate text in the markup
19430 * specified by `that.options.markup.highlight`.
19431 *
19432 * @param {Component} that - an instance of `fluid.orator.domReader`
19433 */
19434 fluid.orator.domReader.highlight = function (that) {
19435 that.removeHighlight();
19436
19437 if (that.model.parseQueueLength && fluid.isValue(that.model.parseIndex)) {
19438 var data = that.parseQueue[that.model.parseIndex];
19439 var rangeNode = data.parentNode.childNodes[data.childIndex];
19440
19441 that.range.selectNode(rangeNode);
19442 that.range.setStart(rangeNode, data.startOffset);
19443 fluid.orator.domReader.setRangeEnd (that.range, rangeNode, data.endOffset);
19444 that.range.surroundContents($(that.options.markup.highlight)[0]);
19445 }
19446 };
19447
19448 /*******************************************************************************
19449 * fluid.orator.selectionReader
19450 *
19451 * Reads in text from a selection and voices it
19452 *******************************************************************************/
19453
19454 fluid.defaults("fluid.orator.selectionReader", {
19455 gradeNames: ["fluid.viewComponent"],
19456 selectors: {
19457 control: ".flc-orator-selectionReader-control",
19458 controlLabel: ".flc-orator-selectionReader-controlLabel"
19459 },
19460 strings: {
19461 play: "play",
19462 stop: "stop"
19463 },
19464 styles: {
19465 above: "fl-orator-selectionReader-above",
19466 below: "fl-orator-selectionReader-below",
19467 control: "fl-orator-selectionReader-control"
19468 },
19469 markup: {
19470 control: "<button class=\"flc-orator-selectionReader-control\"><span class=\"fl-icon-orator\"></span><span class=\"flc-orator-selectionReader-controlLabel\"></span></button>"
19471 },
19472 model: {
19473 enabled: true,
19474 showUI: false,
19475 play: false,
19476 text: ""
19477 },
19478 // similar to em values as it will be multiplied by the container's font-size
19479 offsetScale: {
19480 edge: 3,
19481 pointer: 3
19482 },
19483 events: {
19484 onSelectionChanged: null,
19485 utteranceOnEnd: null,
19486 onToggleControl: null
19487 },
19488 listeners: {
19489 "onCreate.bindEvents": {
19490 funcName: "fluid.orator.selectionReader.bindSelectionEvents",
19491 args: ["{that}"]
19492 },
19493 "onSelectionChanged.updateText": "{that}.getSelectedText",
19494 "utteranceOnEnd.stop": {
19495 changePath: "play",
19496 value: false,
19497 source: "stopMethod"
19498 },
19499 "onToggleControl.togglePlay": "{that}.toggle"
19500 },
19501 modelListeners: {
19502 "showUI": {
19503 funcName: "fluid.orator.selectionReader.renderControl",
19504 args: ["{that}", "{change}.value"],
19505 namespace: "render"
19506 },
19507 "text": {
19508 func: "{that}.stop",
19509 namespace: "stopPlayingWhenTextChanges"
19510 },
19511 "play": [{
19512 func: "fluid.orator.selectionReader.queueSpeech",
19513 args: ["{that}", "{change}.value", "{fluid.textToSpeech}.queueSpeech"],
19514 namespace: "queueSpeech"
19515 }, {
19516 func: "fluid.orator.selectionReader.renderControlState",
19517 args: ["{that}", "{that}.dom.control", "{arguments}.0"],
19518 namespace: "renderControlState"
19519 }],
19520 "enabled": {
19521 funcName: "fluid.orator.selectionReader.updateText",
19522 args: ["{that}", "{change}.value"],
19523 namespace: "updateText"
19524 }
19525 },
19526 modelRelay: [{
19527 source: "text",
19528 target: "showUI",
19529 backward: "never",
19530 namespace: "showUIControl",
19531 singleTransform: {
19532 type: "fluid.transforms.stringToBoolean"
19533 }
19534 }],
19535 invokers: {
19536 getSelectedText: {
19537 changePath: "text",
19538 value: {
19539 expander: {
19540 funcName: "fluid.orator.selectionReader.getSelectedText"
19541 }
19542 },
19543 source: "getSelectedText"
19544 },
19545 play: {
19546 changePath: "play",
19547 value: true,
19548 source: "playMethod"
19549 },
19550 stop: {
19551 funcName: "fluid.orator.selectionReader.stopSpeech",
19552 args: ["{that}.model.play", "{fluid.textToSpeech}.cancel"]
19553 },
19554 toggle: {
19555 funcName: "fluid.orator.selectionReader.togglePlay",
19556 args: ["{that}", "{arguments}.0"]
19557 }
19558 }
19559 });
19560
19561 fluid.orator.selectionReader.stopSpeech = function (state, cancelFn) {
19562 if (state) {
19563 cancelFn();
19564 }
19565 };
19566
19567 fluid.orator.selectionReader.queueSpeech = function (that, state, speechFn) {
19568 if (state) {
19569 speechFn(that.model.text, true, {onend: that.events.utteranceOnEnd.fire});
19570 }
19571 };
19572
19573 fluid.orator.selectionReader.bindSelectionEvents = function (that) {
19574 $(document).on("selectionchange", function (e) {
19575 if (that.model.enabled) {
19576 that.events.onSelectionChanged.fire(e);
19577 }
19578 });
19579 };
19580
19581 fluid.orator.selectionReader.updateText = function (that, state) {
19582 if (state) {
19583 that.getSelectedText();
19584 } else {
19585 that.applier.change("text", "", "ADD", "updateText");
19586 }
19587 };
19588
19589 /**
19590 * Retrieves the text from the current selection
19591 *
19592 * @return {String} - the text from the current selection
19593 */
19594 fluid.orator.selectionReader.getSelectedText = function () {
19595 return window.getSelection().toString();
19596 };
19597
19598 fluid.orator.selectionReader.location = {
19599 TOP: 0,
19600 RIGHT: 1,
19601 BOTTOM: 2,
19602 LEFT: 3
19603 };
19604
19605 /**
19606 * An object containing specified offset scales: "edge" and "pointer" offsets to account for the room needed for a
19607 * control element to be correctly positioned.
19608 *
19609 * @typedef {Object} OffsetScale
19610 * @property {Float} edge - The minimum distance between the button and the viewport's edges
19611 * @property {Float} pointer - The distance between the button and the coordinates the DOMRect refers too. This
19612 * provides space for an arrow to point from the button.
19613 */
19614
19615 /**
19616 * An object containing the sizes of the top and left margins.
19617 *
19618 * @typedef {Object} MarginInfo
19619 * @property {Float} top - The size of margin-top
19620 * @property {Float} left - The size of margin-left
19621 */
19622
19623 /**
19624 * Coordinates for absolutely positioning a DOM Element.
19625 *
19626 * @typedef {Object} ControlPosition
19627 * @property {Float} top - The `top` pixel coordinate relative to the top/left corner
19628 * @property {Float} left - The `left` pixel coordinate relative to the top/left corner
19629 * @property {Integer} location - For location constants see: fluid.orator.selectionReader.location
19630 */
19631
19632 /**
19633 * Returns a position object containing coordinates for absolutely positioning the play button
19634 * relative to a passed in rect. By default it will be placed above the rect unless there is a collision with the
19635 * top of the window. In which case it will be placed below. This will be captured in the "location" propertied,
19636 * and is specified by a constant (See: fluid.orator.selectionReader.location).
19637 *
19638 * In addition to collision detection with the top of the window, collision detection for the left and right edges
19639 * of the window are also taken into account. However, the position will not be flipped, but will be translated
19640 * slightly to ensure that the item being placed is displayed on screen. These calculations are facilitated through
19641 * an offsetScale object passed in.
19642 *
19643 * @param {DOMRect} rect - A DOMRect object, used to calculate placement against. Specifically, the "top", "bottom",
19644 * and "left" properties may be used for positioning.
19645 * @param {MarginInfo} margin - Margin sizes
19646 * @param {Float} fontSize - The base font to multiple the offset against
19647 * @param {OffsetScale} offsetScale - (Optional) an object containing specified offsets: "edge" and "pointer".
19648 * Offsets all default to 1 and are multiplied with the fontSize for determining
19649 * the final offset value.
19650 *
19651 * @return {ControlPosition} - An object containing the coordinates for positioning the play button.
19652 * It takes the form {top: Float, left: Float, location: Integer}
19653 * For location constants see: fluid.orator.selectionReader.location
19654 */
19655 fluid.orator.selectionReader.calculatePosition = function (rect, margin, fontSize, offsetScale) {
19656 var edgeOffset = fontSize * (fluid.get(offsetScale, "edge") || 1);
19657 var pointerOffset = fontSize * (fluid.get(offsetScale, "pointer") || 1);
19658
19659 var scrollTop = document.documentElement.scrollTop || document.body.scrollTop;
19660 var scrollLeft = document.documentElement.scrollLeft || document.body.scrollLeft;
19661
19662 var position = {
19663 top: scrollTop - margin.top,
19664 left: Math.min(
19665 Math.max(rect.left + scrollLeft - margin.left, edgeOffset + scrollLeft),
19666 (document.documentElement.clientWidth + scrollLeft - margin.left - edgeOffset)
19667 )
19668 };
19669
19670 if (rect.top < edgeOffset) {
19671 position.top = position.top + rect.bottom;
19672 position.location = fluid.orator.selectionReader.location.BOTTOM;
19673 } else {
19674 position.top = position.top + rect.top - pointerOffset;
19675 position.location = fluid.orator.selectionReader.location.TOP;
19676 }
19677
19678 return position;
19679 };
19680
19681 fluid.orator.selectionReader.renderControlState = function (that, control) {
19682 var text = that.options.strings[that.model.play ? "stop" : "play"];
19683 control.find(that.options.selectors.controlLabel).text(text);
19684 };
19685
19686 fluid.orator.selectionReader.renderControl = function (that, state) {
19687 if (state) {
19688 var selectionRange = window.getSelection().getRangeAt(0);
19689 var rect = selectionRange.getClientRects()[0];
19690 var fontSize = parseFloat(that.container.css("font-size"));
19691 var margin = {
19692 top: parseFloat(that.container.css("margin-top")),
19693 left: parseFloat(that.container.css("margin-left"))
19694 };
19695
19696 var position = fluid.orator.selectionReader.calculatePosition(rect, margin, fontSize, that.options.offsetScale);
19697 var control = $(that.options.markup.control);
19698 control.addClass(that.options.styles.control);
19699 fluid.orator.selectionReader.renderControlState(that, control);
19700
19701 control.css({
19702 top: position.top,
19703 left: position.left
19704 });
19705
19706 var positionClass = that.options.styles[position.location === fluid.orator.selectionReader.location.TOP ? "above" : "below"];
19707 control.addClass(positionClass);
19708 control.click(function () {
19709 // wrapped in an empty function so as not to pass along the jQuery event object
19710 that.events.onToggleControl.fire();
19711 });
19712 control.appendTo(that.container);
19713
19714 // cleanup range
19715 selectionRange.detach();
19716
19717 } else {
19718 that.locate("control").remove();
19719 }
19720 };
19721
19722 fluid.orator.selectionReader.togglePlay = function (that, state) {
19723 var newState = state || !that.model.play;
19724 that[newState ? "play" : "stop"]();
19725 };
19726
19727})(jQuery, fluid_3_0_0);
19728;
19729/*
19730Copyright The Infusion copyright holders
19731See the AUTHORS.md file at the top-level directory of this distribution and at
19732https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
19733
19734Licensed under the Educational Community License (ECL), Version 2.0 or the New
19735BSD license. You may not use this file except in compliance with one these
19736Licenses.
19737
19738You may obtain a copy of the ECL 2.0 License and BSD License at
19739https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
19740*/
19741
19742var fluid_3_0_0 = fluid_3_0_0 || {};
19743
19744(function ($, fluid) {
19745 "use strict";
19746
19747 /** URL utilities salvaged from kettle - these should go into core framework **/
19748
19749 fluid.registerNamespace("fluid.url");
19750
19751 fluid.url.generateDepth = function (depth) {
19752 return fluid.generate(depth, "../").join("");
19753 };
19754
19755 fluid.url.parsePathInfo = function (pathInfo) {
19756 var togo = {};
19757 var segs = pathInfo.split("/");
19758 if (segs.length > 0) {
19759 var top = segs.length - 1;
19760 var dotpos = segs[top].indexOf(".");
19761 if (dotpos !== -1) {
19762 togo.extension = segs[top].substring(dotpos + 1);
19763 segs[top] = segs[top].substring(0, dotpos);
19764 }
19765 }
19766 togo.pathInfo = segs;
19767 return togo;
19768 };
19769
19770 fluid.url.parsePathInfoTrim = function (pathInfo) {
19771 var togo = fluid.url.parsePathInfo(pathInfo);
19772 if (togo.pathInfo[togo.pathInfo.length - 1] === "") {
19773 togo.pathInfo.length--;
19774 }
19775 return togo;
19776 };
19777
19778 /* Collapse the array of segments into a URL path, starting at the specified
19779 * segment index - this will not terminate with a slash, unless the final segment
19780 * is the empty string
19781 */
19782 fluid.url.collapseSegs = function (segs, from, to) {
19783 var togo = "";
19784 if (from === undefined) {
19785 from = 0;
19786 }
19787 if (to === undefined) {
19788 to = segs.length;
19789 }
19790 for (var i = from; i < to - 1; ++i) {
19791 togo += segs[i] + "/";
19792 }
19793 if (to > from) { // TODO: bug in Kettle version
19794 togo += segs[to - 1];
19795 }
19796 return togo;
19797 };
19798
19799 fluid.url.makeRelPath = function (parsed, index) {
19800 var togo = fluid.kettle.collapseSegs(parsed.pathInfo, index);
19801 if (parsed.extension) {
19802 togo += "." + parsed.extension;
19803 }
19804 return togo;
19805 };
19806
19807 /* Canonicalise IN PLACE the supplied segment array derived from parsing a
19808 * pathInfo structure. Warning, this destructively modifies the argument.
19809 */
19810 fluid.url.cononocolosePath = function (pathInfo) {
19811 var consume = 0;
19812 for (var i = 0; i < pathInfo.length; ++i) {
19813 if (pathInfo[i] === "..") {
19814 ++consume;
19815 }
19816 else if (consume !== 0) {
19817 pathInfo.splice(i - consume * 2, consume * 2);
19818 i -= consume * 2;
19819 consume = 0;
19820 }
19821 }
19822 return pathInfo;
19823 };
19824
19825 // parseUri 1.2.2
19826 // (c) Steven Levithan <stevenlevithan.com>
19827 // MIT License
19828
19829 fluid.url.parseUri = function (str) {
19830 var o = fluid.url.parseUri.options,
19831 m = o.parser[o.strictMode ? "strict" : "loose"].exec(str),
19832 uri = {},
19833 i = 14;
19834
19835 while (i--) { uri[o.key[i]] = m[i] || ""; }
19836
19837 uri[o.q.name] = {};
19838 uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
19839 if ($1) { uri[o.q.name][$1] = $2; }
19840 });
19841
19842 return uri;
19843 };
19844
19845 fluid.url.parseUri.options = {
19846 strictMode: true,
19847 key: ["source","protocol","authority","userInfo","user","password","host","port","relative","path","directory","file","query","anchor"],
19848 q: {
19849 name: "queryKey",
19850 parser: /(?:^|&)([^&=]*)=?([^&]*)/g
19851 },
19852 parser: {
19853 strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
19854 loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
19855 }
19856 };
19857
19858 fluid.url.parseSegs = function (url) {
19859 var parsed = fluid.url.parseUri(url);
19860 var parsedSegs = fluid.url.parsePathInfoTrim(parsed.directory);
19861 return parsedSegs.pathInfo;
19862 };
19863
19864 fluid.url.isAbsoluteUrl = function (url) {
19865 var parseRel = fluid.url.parseUri(url);
19866 return (parseRel.host || parseRel.protocol || parseRel.directory.charAt(0) === "/");
19867 };
19868
19869 fluid.url.computeRelativePrefix = function (outerLocation, iframeLocation, relPath) {
19870 if (fluid.url.isAbsoluteUrl(relPath)) {
19871 return relPath;
19872 }
19873 var relSegs = fluid.url.parsePathInfo(relPath).pathInfo;
19874 var parsedOuter = fluid.url.parseSegs(outerLocation);
19875 var parsedRel = parsedOuter.concat(relSegs);
19876 fluid.url.cononocolosePath(parsedRel);
19877 var parsedInner = fluid.url.parseSegs(iframeLocation);
19878 var seg = 0;
19879 for (; seg < parsedRel.length; ++seg) {
19880 if (parsedRel[seg] !== parsedInner[seg]) { break; }
19881 }
19882 var excess = parsedInner.length - seg;
19883 var back = fluid.url.generateDepth(excess);
19884 var front = fluid.url.collapseSegs(parsedRel, seg);
19885 return back + front;
19886 };
19887
19888})(jQuery, fluid_3_0_0);
19889;
19890/*
19891Copyright The Infusion copyright holders
19892See the AUTHORS.md file at the top-level directory of this distribution and at
19893https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
19894
19895Licensed under the Educational Community License (ECL), Version 2.0 or the New
19896BSD license. You may not use this file except in compliance with one these
19897Licenses.
19898
19899You may obtain a copy of the ECL 2.0 License and BSD License at
19900https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
19901*/
19902
19903var fluid_3_0_0 = fluid_3_0_0 || {};
19904
19905(function ($, fluid) {
19906 "use strict";
19907
19908 fluid.defaults("fluid.prefs.store", {
19909 gradeNames: ["fluid.dataSource", "fluid.contextAware"],
19910 contextAwareness: {
19911 strategy: {
19912 defaultGradeNames: "fluid.prefs.cookieStore"
19913 }
19914 }
19915 });
19916
19917 fluid.prefs.store.decodeURIComponent = function (payload) {
19918 if (typeof payload === "string") {
19919 return decodeURIComponent(payload);
19920 }
19921 };
19922
19923 fluid.prefs.store.encodeURIComponent = function (payload) {
19924 if (typeof payload === "string") {
19925 return encodeURIComponent(payload);
19926 }
19927 };
19928
19929 /****************
19930 * Cookie Store *
19931 ****************/
19932
19933 /**
19934 * SettingsStore Subcomponent that uses a cookie for persistence.
19935 * @param options {Object}
19936 */
19937 fluid.defaults("fluid.prefs.cookieStore", {
19938 gradeNames: ["fluid.dataSource"],
19939 cookie: {
19940 name: "fluid-ui-settings",
19941 path: "/",
19942 expires: ""
19943 },
19944 listeners: {
19945 "onRead.impl": {
19946 listener: "fluid.prefs.cookieStore.getCookie",
19947 args: ["{arguments}.1"]
19948 },
19949 "onRead.decodeURI": {
19950 listener: "fluid.prefs.store.decodeURIComponent",
19951 priority: "before:encoding"
19952 }
19953 },
19954 invokers: {
19955 get: {
19956 args: ["{that}", "{arguments}.0", "{that}.options.cookie"] // directModel, options/callback
19957 }
19958 }
19959 });
19960
19961 fluid.defaults("fluid.prefs.cookieStore.writable", {
19962 gradeNames: ["fluid.dataSource.writable"],
19963 listeners: {
19964 "onWrite.encodeURI": {
19965 func: "fluid.prefs.store.encodeURIComponent",
19966 priority: "before:impl"
19967 },
19968 "onWrite.impl": {
19969 listener: "fluid.prefs.cookieStore.writeCookie"
19970 },
19971 "onWriteResponse.decodeURI": {
19972 listener: "fluid.prefs.store.decodeURIComponent",
19973 priority: "before:encoding"
19974 }
19975 },
19976 invokers: {
19977 set: {
19978 args: ["{that}", "{arguments}.0", "{arguments}.1", "{that}.options.cookie"] // directModel, model, options/callback
19979 }
19980 }
19981 });
19982
19983 fluid.makeGradeLinkage("fluid.prefs.cookieStore.linkage", ["fluid.dataSource.writable", "fluid.prefs.cookieStore"], "fluid.prefs.cookieStore.writable");
19984
19985 /*
19986 * Retrieve and return the value of the cookie
19987 */
19988 fluid.prefs.cookieStore.getCookie = function (options) {
19989 var cookieName = fluid.get(options, ["directModel", "cookieName"]) || options.name;
19990 var cookie = document.cookie;
19991 if (cookie.length <= 0) {
19992 return;
19993 }
19994
19995 var cookiePrefix = cookieName + "=";
19996 var startIndex = cookie.indexOf(cookiePrefix);
19997 if (startIndex < 0) {
19998 return;
19999 }
20000
20001 startIndex = startIndex + cookiePrefix.length;
20002 var endIndex = cookie.indexOf(";", startIndex);
20003 if (endIndex < startIndex) {
20004 endIndex = cookie.length;
20005 }
20006 return cookie.substring(startIndex, endIndex);
20007 };
20008
20009 /**
20010 * Assembles the cookie string
20011 * @param {String} cookieName - name of the cookie
20012 * @param {String} data - the serialized data to be stored in the cookie
20013 * @param {Object} options - settings
20014 * @return {String} - A string representing the assembled cookie.
20015 */
20016 fluid.prefs.cookieStore.assembleCookie = function (cookieName, data, options) {
20017 options = options || {};
20018 var cookieStr = cookieName + "=" + data;
20019
20020 if (options.expires) {
20021 cookieStr += "; expires=" + options.expires;
20022 }
20023
20024 if (options.path) {
20025 cookieStr += "; path=" + options.path;
20026 }
20027
20028 return cookieStr;
20029 };
20030
20031 /**
20032 * Saves the settings into a cookie
20033 * @param {Object} payload - the serialized data to write to the cookie
20034 * @param {Object} options - settings
20035 * @return {Object} - The original payload.
20036 */
20037 fluid.prefs.cookieStore.writeCookie = function (payload, options) {
20038 var cookieName = fluid.get(options, ["directModel", "cookieName"]) || options.name;
20039 var cookieStr = fluid.prefs.cookieStore.assembleCookie(cookieName, payload, options);
20040
20041 document.cookie = cookieStr;
20042 return payload;
20043 };
20044
20045
20046 /**************
20047 * Temp Store *
20048 **************/
20049
20050 fluid.defaults("fluid.dataSource.encoding.model", {
20051 gradeNames: "fluid.component",
20052 invokers: {
20053 parse: "fluid.identity",
20054 render: "fluid.identity"
20055 },
20056 contentType: "application/json"
20057 });
20058
20059 /**
20060 * SettingsStore mock that doesn't do persistence.
20061 * @param options {Object}
20062 */
20063 fluid.defaults("fluid.prefs.tempStore", {
20064 gradeNames: ["fluid.dataSource", "fluid.modelComponent"],
20065 components: {
20066 encoding: {
20067 type: "fluid.dataSource.encoding.model"
20068 }
20069 },
20070 listeners: {
20071 "onRead.impl": {
20072 listener: "fluid.identity",
20073 args: ["{that}.model"]
20074 }
20075 }
20076 });
20077
20078 fluid.defaults("fluid.prefs.tempStore.writable", {
20079 gradeNames: ["fluid.dataSource.writable", "fluid.modelComponent"],
20080 components: {
20081 encoding: {
20082 type: "fluid.dataSource.encoding.model"
20083 }
20084 },
20085 listeners: {
20086 "onWrite.impl": {
20087 listener: "fluid.prefs.tempStore.write",
20088 args: ["{that}", "{arguments}.0", "{arguments}.1"]
20089 }
20090 }
20091 });
20092
20093 fluid.prefs.tempStore.write = function (that, settings) {
20094 var transaction = that.applier.initiate();
20095 transaction.fireChangeRequest({path: "", type: "DELETE"});
20096 transaction.change("", settings);
20097 transaction.commit();
20098 return that.model;
20099 };
20100
20101 fluid.makeGradeLinkage("fluid.prefs.tempStore.linkage", ["fluid.dataSource.writable", "fluid.prefs.tempStore"], "fluid.prefs.tempStore.writable");
20102
20103 fluid.defaults("fluid.prefs.globalSettingsStore", {
20104 gradeNames: ["fluid.component"],
20105 components: {
20106 settingsStore: {
20107 type: "fluid.prefs.store",
20108 options: {
20109 gradeNames: ["fluid.resolveRootSingle", "fluid.dataSource.writable"],
20110 singleRootType: "fluid.prefs.store"
20111 }
20112 }
20113 }
20114 });
20115
20116})(jQuery, fluid_3_0_0);
20117;
20118/*
20119Copyright The Infusion copyright holders
20120See the AUTHORS.md file at the top-level directory of this distribution and at
20121https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
20122
20123Licensed under the Educational Community License (ECL), Version 2.0 or the New
20124BSD license. You may not use this file except in compliance with one these
20125Licenses.
20126
20127You may obtain a copy of the ECL 2.0 License and BSD License at
20128https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
20129*/
20130
20131var fluid_3_0_0 = fluid_3_0_0 || {};
20132
20133(function ($, fluid) {
20134 "use strict";
20135
20136 /*******************************************************************************
20137 * Root Model *
20138 * *
20139 * Holds the default values for enactors and panel model values *
20140 *******************************************************************************/
20141
20142 fluid.defaults("fluid.prefs.initialModel", {
20143 gradeNames: ["fluid.component"],
20144 members: {
20145 // TODO: This information is supposed to be generated from the JSON
20146 // schema describing various preferences. For now it's kept in top
20147 // level prefsEditor to avoid further duplication.
20148 initialModel: {
20149 preferences: {} // To keep initial preferences
20150 }
20151 }
20152 });
20153
20154 /***********************************************
20155 * UI Enhancer *
20156 * *
20157 * Transforms the page based on user settings. *
20158 ***********************************************/
20159
20160 fluid.defaults("fluid.uiEnhancer", {
20161 gradeNames: ["fluid.viewComponent"],
20162 defaultLocale: "en",
20163 invokers: {
20164 updateModel: {
20165 func: "{that}.applier.change",
20166 args: ["", "{arguments}.0"]
20167 }
20168 },
20169 userGrades: "@expand:fluid.prefs.filterEnhancerGrades({that}.options.gradeNames)",
20170
20171 distributeOptions: {
20172 "uiEnhancer.messageLoader.defaultLocale": {
20173 source: "{that}.options.defaultLocale",
20174 target: "{that messageLoader}.options.defaultLocale"
20175 },
20176 // TODO: This needs to be improved as it is static and should be dynamic. Unfortunately the resource loader
20177 // accepts the locale as an option instead of a model value.
20178 "uiEnhancer.messageLoader.locale": {
20179 source: "{that}.options.locale",
20180 target: "{that messageLoader}.model.locale"
20181 }
20182 }
20183 });
20184
20185 // Make this a standalone grade since options merging can't see 2 levels deep into merging
20186 // trees and will currently trash "gradeNames" for 2nd level nested components!
20187 fluid.defaults("fluid.uiEnhancer.root", {
20188 gradeNames: ["fluid.uiEnhancer", "fluid.resolveRootSingle"],
20189 singleRootType: "fluid.uiEnhancer"
20190 });
20191
20192 fluid.uiEnhancer.ignorableGrades = ["fluid.uiEnhancer", "fluid.uiEnhancer.root", "fluid.resolveRoot", "fluid.resolveRootSingle"];
20193
20194 // These function is necessary so that we can "clone" a UIEnhancer (e.g. one in an iframe) from another.
20195 // This reflects a long-standing mistake in UIEnhancer design - we should separate the logic in an enhancer
20196 // from a particular binding onto a container.
20197 fluid.prefs.filterEnhancerGrades = function (gradeNames) {
20198 return fluid.remove_if(fluid.makeArray(gradeNames), function (gradeName) {
20199 return fluid.frameworkGrades.indexOf(gradeName) !== -1 || fluid.uiEnhancer.ignorableGrades.indexOf(gradeName) !== -1;
20200 });
20201 };
20202
20203 // This just the options that we are clear safely represent user options - naturally this all has
20204 // to go when we refactor UIEnhancer
20205 fluid.prefs.filterEnhancerOptions = function (options) {
20206 return fluid.filterKeys(options, ["classnameMap", "fontSizeMap", "tocTemplate", "tocMessage", "components"]);
20207 };
20208
20209 /********************************************************************************
20210 * PageEnhancer *
20211 * *
20212 * A UIEnhancer wrapper that concerns itself with the entire page. *
20213 * *
20214 * "originalEnhancerOptions" is a grade component to keep track of the original *
20215 * uiEnhancer user options *
20216 ********************************************************************************/
20217
20218 // TODO: Both the pageEnhancer and the uiEnhancer need to be available separately - some
20219 // references to "{uiEnhancer}" are present in prefsEditorConnections, whilst other
20220 // sites refer to "{pageEnhancer}". The fact that uiEnhancer requires "body" prevents it
20221 // being top-level until we have the options flattening revolution. Also one day we want
20222 // to make good of advertising an unmerged instance of the "originalEnhancerOptions"
20223 fluid.defaults("fluid.pageEnhancer", {
20224 gradeNames: ["fluid.component", "fluid.originalEnhancerOptions",
20225 "fluid.prefs.initialModel", "fluid.prefs.settingsGetter",
20226 "fluid.resolveRootSingle"],
20227 distributeOptions: {
20228 "pageEnhancer.uiEnhancer": {
20229 source: "{that}.options.uiEnhancer",
20230 target: "{that > uiEnhancer}.options"
20231 }
20232 },
20233 singleRootType: "fluid.pageEnhancer",
20234 components: {
20235 uiEnhancer: {
20236 type: "fluid.uiEnhancer.root",
20237 container: "body"
20238 }
20239 },
20240 originalUserOptions: "@expand:fluid.prefs.filterEnhancerOptions({uiEnhancer}.options)",
20241 listeners: {
20242 "onCreate.initModel": "fluid.pageEnhancer.init"
20243 }
20244 });
20245
20246 fluid.pageEnhancer.init = function (that) {
20247 var fetchPromise = that.getSettings();
20248 fetchPromise.then(function (fetchedSettings) {
20249 that.uiEnhancer.updateModel(fluid.get(fetchedSettings, "preferences"));
20250 });
20251 };
20252
20253})(jQuery, fluid_3_0_0);
20254;
20255/*
20256Copyright The Infusion copyright holders
20257See the AUTHORS.md file at the top-level directory of this distribution and at
20258https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
20259
20260Licensed under the Educational Community License (ECL), Version 2.0 or the New
20261BSD license. You may not use this file except in compliance with one these
20262Licenses.
20263
20264You may obtain a copy of the ECL 2.0 License and BSD License at
20265https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
20266*/
20267
20268var fluid_3_0_0 = fluid_3_0_0 || {};
20269
20270(function ($, fluid) {
20271 "use strict";
20272
20273 /*****************************
20274 * Preferences Editor Loader *
20275 *****************************/
20276
20277 /**
20278 * An Preferences Editor top-level component that reflects the collaboration between prefsEditor, templateLoader and messageLoader.
20279 * This component is the only Preferences Editor component that is intended to be called by the outside world.
20280 *
20281 * @param options {Object}
20282 */
20283 fluid.defaults("fluid.prefs.prefsEditorLoader", {
20284 gradeNames: ["fluid.prefs.settingsGetter", "fluid.prefs.initialModel", "fluid.viewComponent"],
20285 defaultLocale: "en",
20286 components: {
20287 prefsEditor: {
20288 priority: "last",
20289 type: "fluid.prefs.prefsEditor",
20290 createOnEvent: "onCreatePrefsEditorReady",
20291 options: {
20292 members: {
20293 initialModel: "{prefsEditorLoader}.initialModel"
20294 },
20295 invokers: {
20296 getSettings: "{prefsEditorLoader}.getSettings"
20297 },
20298 listeners: {
20299 "onReady.boil": {
20300 listener: "{prefsEditorLoader}.events.onReady",
20301 args: ["{prefsEditorLoader}"]
20302 }
20303 }
20304 }
20305 },
20306 templateLoader: {
20307 type: "fluid.resourceLoader",
20308 options: {
20309 events: {
20310 onResourcesLoaded: "{prefsEditorLoader}.events.onPrefsEditorTemplatesLoaded"
20311 }
20312 }
20313 },
20314 messageLoader: {
20315 type: "fluid.resourceLoader",
20316 createOnEvent: "afterInitialSettingsFetched",
20317 options: {
20318 defaultLocale: "{prefsEditorLoader}.options.defaultLocale",
20319 locale: "{prefsEditorLoader}.settings.preferences.locale",
20320 resourceOptions: {
20321 dataType: "json"
20322 },
20323 events: {
20324 onResourcesLoaded: "{prefsEditorLoader}.events.onPrefsEditorMessagesLoaded"
20325 }
20326 }
20327 }
20328 },
20329 listeners: {
20330 "onCreate.getInitialSettings": {
20331 listener: "fluid.prefs.prefsEditorLoader.getInitialSettings",
20332 args: ["{that}"]
20333 }
20334 },
20335 events: {
20336 afterInitialSettingsFetched: null,
20337 onPrefsEditorTemplatesLoaded: null,
20338 onPrefsEditorMessagesLoaded: null,
20339 onCreatePrefsEditorReady: {
20340 events: {
20341 templateLoaded: "onPrefsEditorTemplatesLoaded",
20342 prefsEditorMessagesLoaded: "onPrefsEditorMessagesLoaded"
20343 }
20344 },
20345 onReady: null
20346 },
20347 distributeOptions: {
20348 "prefsEditorLoader.templateLoader": {
20349 source: "{that}.options.templateLoader",
20350 removeSource: true,
20351 target: "{that > templateLoader}.options"
20352 },
20353 "prefsEditorLoader.templateLoader.terms": {
20354 source: "{that}.options.terms",
20355 target: "{that > templateLoader}.options.terms"
20356 },
20357 "prefsEditorLoader.messageLoader": {
20358 source: "{that}.options.messageLoader",
20359 removeSource: true,
20360 target: "{that > messageLoader}.options"
20361 },
20362 "prefsEditorLoader.messageLoader.terms": {
20363 source: "{that}.options.terms",
20364 target: "{that > messageLoader}.options.terms"
20365 },
20366 "prefsEditorLoader.prefsEditor": {
20367 source: "{that}.options.prefsEditor",
20368 removeSource: true,
20369 target: "{that > prefsEditor}.options"
20370 }
20371 }
20372 });
20373
20374 fluid.prefs.prefsEditorLoader.getInitialSettings = function (that) {
20375 var promise = fluid.promise();
20376 var fetchPromise = that.getSettings();
20377 fetchPromise.then(function (savedSettings) {
20378 that.settings = $.extend(true, {}, that.initialModel, savedSettings);
20379 that.events.afterInitialSettingsFetched.fire(that.settings);
20380 }, function (error) {
20381 fluid.log(fluid.logLevel.WARN, error);
20382 that.settings = that.initialModel;
20383 that.events.afterInitialSettingsFetched.fire(that.settings);
20384 });
20385 fluid.promise.follow(fetchPromise, promise);
20386 return promise;
20387 };
20388
20389 // TODO: This mixin grade appears to be supplied manually by various test cases but no longer appears in
20390 // the main configuration. We should remove the need for users to supply this - also the use of "defaultPanels" in fact
20391 // refers to "starter panels"
20392 fluid.defaults("fluid.prefs.transformDefaultPanelsOptions", {
20393 // Do not supply "fluid.prefs.inline" here, since when this is used as a mixin for separatedPanel, it ends up displacing the
20394 // more refined type of the prefsEditorLoader
20395 gradeNames: ["fluid.viewComponent"],
20396 distributeOptions: {
20397 "transformDefaultPanelsOptions.textSize": {
20398 source: "{that}.options.textSize",
20399 removeSource: true,
20400 target: "{that textSize}.options"
20401 },
20402 "transformDefaultPanelsOptions.lineSpace": {
20403 source: "{that}.options.lineSpace",
20404 removeSource: true,
20405 target: "{that lineSpace}.options"
20406 },
20407 "transformDefaultPanelsOptions.textFont": {
20408 source: "{that}.options.textFont",
20409 removeSource: true,
20410 target: "{that textFont}.options"
20411 },
20412 "transformDefaultPanelsOptions.contrast": {
20413 source: "{that}.options.contrast",
20414 removeSource: true,
20415 target: "{that contrast}.options"
20416 },
20417 "transformDefaultPanelsOptions.layoutControls": {
20418 source: "{that}.options.layoutControls",
20419 removeSource: true,
20420 target: "{that layoutControls}.options"
20421 },
20422 "transformDefaultPanelsOptions.enhanceInputs": {
20423 source: "{that}.options.enhanceInputs",
20424 removeSource: true,
20425 target: "{that enhanceInputs}.options"
20426 }
20427 }
20428 });
20429
20430 /**********************
20431 * Preferences Editor *
20432 **********************/
20433
20434 fluid.defaults("fluid.prefs.settingsGetter", {
20435 gradeNames: ["fluid.component"],
20436 members: {
20437 getSettings: "{fluid.prefs.store}.get"
20438 }
20439 });
20440
20441 fluid.defaults("fluid.prefs.settingsSetter", {
20442 gradeNames: ["fluid.component"],
20443 invokers: {
20444 setSettings: {
20445 funcName: "fluid.prefs.settingsSetter.setSettings",
20446 args: ["{arguments}.0", "{arguments}.1", "{fluid.prefs.store}.set"]
20447 }
20448 }
20449 });
20450
20451 fluid.prefs.settingsSetter.setSettings = function (model, directModel, set) {
20452 var userSettings = fluid.copy(model);
20453 return set(directModel, userSettings);
20454 };
20455
20456 fluid.defaults("fluid.prefs.uiEnhancerRelay", {
20457 gradeNames: ["fluid.modelComponent"],
20458 listeners: {
20459 "onCreate.addListener": "{that}.addListener",
20460 "onDestroy.removeListener": "{that}.removeListener"
20461 },
20462 events: {
20463 updateEnhancerModel: "{fluid.prefs.prefsEditor}.events.onUpdateEnhancerModel"
20464 },
20465 invokers: {
20466 addListener: {
20467 funcName: "fluid.prefs.uiEnhancerRelay.addListener",
20468 args: ["{that}.events.updateEnhancerModel", "{that}.updateEnhancerModel"]
20469 },
20470 removeListener: {
20471 funcName: "fluid.prefs.uiEnhancerRelay.removeListener",
20472 args: ["{that}.events.updateEnhancerModel", "{that}.updateEnhancerModel"]
20473 },
20474 updateEnhancerModel: {
20475 funcName: "fluid.prefs.uiEnhancerRelay.updateEnhancerModel",
20476 args: ["{uiEnhancer}", "{fluid.prefs.prefsEditor}.model.preferences"]
20477 }
20478 }
20479 });
20480
20481 fluid.prefs.uiEnhancerRelay.addListener = function (modelChanged, listener) {
20482 modelChanged.addListener(listener);
20483 };
20484
20485 fluid.prefs.uiEnhancerRelay.removeListener = function (modelChanged, listener) {
20486 modelChanged.removeListener(listener);
20487 };
20488
20489 fluid.prefs.uiEnhancerRelay.updateEnhancerModel = function (uiEnhancer, newModel) {
20490 uiEnhancer.updateModel(newModel);
20491 };
20492
20493 /**
20494 * A component that works in conjunction with the UI Enhancer component
20495 * to allow users to set personal user interface preferences. The Preferences Editor component provides a user
20496 * interface for setting and saving personal preferences, and the UI Enhancer component carries out the
20497 * work of applying those preferences to the user interface.
20498 *
20499 * @param container {Object}
20500 * @param options {Object}
20501 */
20502 fluid.defaults("fluid.prefs.prefsEditor", {
20503 gradeNames: ["fluid.prefs.settingsGetter", "fluid.prefs.settingsSetter", "fluid.prefs.initialModel", "fluid.remoteModelComponent", "fluid.viewComponent"],
20504 invokers: {
20505 /**
20506 * Updates the change applier and fires modelChanged on subcomponent fluid.prefs.controls
20507 *
20508 * @param newModel {Object}
20509 * @param source {Object}
20510 */
20511 fetchImpl: {
20512 funcName: "fluid.prefs.prefsEditor.fetchImpl",
20513 args: ["{that}"]
20514 },
20515 writeImpl: {
20516 funcName: "fluid.prefs.prefsEditor.writeImpl",
20517 args: ["{that}", "{arguments}.0"]
20518 },
20519 applyChanges: {
20520 funcName: "fluid.prefs.prefsEditor.applyChanges",
20521 args: ["{that}"]
20522 },
20523 save: {
20524 funcName: "fluid.prefs.prefsEditor.save",
20525 args: ["{that}"]
20526 },
20527 saveAndApply: {
20528 funcName: "fluid.prefs.prefsEditor.saveAndApply",
20529 args: ["{that}"]
20530 },
20531 reset: {
20532 funcName: "fluid.prefs.prefsEditor.reset",
20533 args: ["{that}"]
20534 },
20535 cancel: {
20536 funcName: "fluid.prefs.prefsEditor.cancel",
20537 args: ["{that}"]
20538 }
20539 },
20540 selectors: {
20541 panels: ".flc-prefsEditor-panel",
20542 cancel: ".flc-prefsEditor-cancel",
20543 reset: ".flc-prefsEditor-reset",
20544 save: ".flc-prefsEditor-save",
20545 previewFrame : ".flc-prefsEditor-preview-frame"
20546 },
20547 events: {
20548 onSave: null,
20549 onCancel: null,
20550 beforeReset: null,
20551 afterReset: null,
20552 onAutoSave: null,
20553 modelChanged: null,
20554 onPrefsEditorRefresh: null,
20555 onUpdateEnhancerModel: null,
20556 onPrefsEditorMarkupReady: null,
20557 onReady: null
20558 },
20559 listeners: {
20560 "onCreate.init": "fluid.prefs.prefsEditor.init",
20561 "onAutoSave.save": "{that}.save"
20562 },
20563 model: {
20564 local: {
20565 preferences: "{that}.model.preferences"
20566 }
20567 },
20568 modelListeners: {
20569 "preferences": [{
20570 listener: "fluid.prefs.prefsEditor.handleAutoSave",
20571 args: ["{that}"],
20572 namespace: "autoSave",
20573 excludeSource: ["init"]
20574 }, {
20575 listener: "{that}.events.modelChanged.fire",
20576 args: ["{change}.value"],
20577 namespace: "modelChange"
20578 }]
20579 },
20580 resources: {
20581 template: "{templateLoader}.resources.prefsEditor"
20582 },
20583 autoSave: false
20584 });
20585
20586 /*
20587 * Refresh PrefsEditor
20588 */
20589 fluid.prefs.prefsEditor.applyChanges = function (that) {
20590 that.events.onUpdateEnhancerModel.fire();
20591 };
20592
20593 fluid.prefs.prefsEditor.fetchImpl = function (that) {
20594 var promise = fluid.promise(),
20595 fetchPromise = that.getSettings();
20596
20597 fetchPromise.then(function (savedModel) {
20598 var completeModel = $.extend(true, {}, that.initialModel, savedModel);
20599 promise.resolve(completeModel);
20600 }, promise.reject);
20601
20602 return promise;
20603 };
20604
20605 fluid.prefs.prefsEditor.writeImpl = function (that, modelToSave) {
20606 var promise = fluid.promise(),
20607 stats = {changes: 0, unchanged: 0, changeMap: {}},
20608 changedPrefs = {};
20609
20610 modelToSave = fluid.copy(modelToSave);
20611
20612 // To address https://issues.fluidproject.org/browse/FLUID-4686
20613 fluid.model.diff(modelToSave.preferences, fluid.get(that.initialModel, ["preferences"]), stats);
20614
20615 if (stats.changes === 0) {
20616 delete modelToSave.preferences;
20617 } else {
20618 fluid.each(stats.changeMap, function (state, pref) {
20619 fluid.set(changedPrefs, pref, modelToSave.preferences[pref]);
20620 });
20621 modelToSave.preferences = changedPrefs;
20622 }
20623
20624 that.events.onSave.fire(modelToSave);
20625 var setPromise = that.setSettings(modelToSave);
20626
20627 fluid.promise.follow(setPromise, promise);
20628 return promise;
20629 };
20630
20631 /**
20632 * Sends the prefsEditor.model to the store and fires onSave
20633 * @param {Object} that: A fluid.prefs.prefsEditor instance
20634 * @return {Promise} A promise that will be resolved with the saved model or rejected on error.
20635 */
20636 fluid.prefs.prefsEditor.save = function (that) {
20637 var promise = fluid.promise();
20638 if (!that.model || $.isEmptyObject(that.model)) { // Don't save a reset model
20639 promise.resolve({});
20640 } else {
20641 var writePromise = that.write();
20642 fluid.promise.follow(writePromise, promise);
20643 }
20644
20645 return promise;
20646 };
20647
20648 fluid.prefs.prefsEditor.saveAndApply = function (that) {
20649 var promise = fluid.promise();
20650 var prevSettingsPromise = that.getSettings(),
20651 savePromise = that.save();
20652
20653 prevSettingsPromise.then(function (prevSettings) {
20654 savePromise.then(function (changedSelections) {
20655 // Only when preferences are changed, re-render panels and trigger enactors to apply changes
20656 if (!fluid.model.diff(fluid.get(changedSelections, "preferences"), fluid.get(prevSettings, "preferences"))) {
20657 that.events.onPrefsEditorRefresh.fire();
20658 that.applyChanges();
20659 }
20660 });
20661 fluid.promise.follow(savePromise, promise);
20662 });
20663
20664 return promise;
20665 };
20666
20667 /*
20668 * Resets the selections to the integrator's defaults and fires afterReset
20669 */
20670 fluid.prefs.prefsEditor.reset = function (that) {
20671 var transaction = that.applier.initiate();
20672 that.events.beforeReset.fire(that);
20673 transaction.fireChangeRequest({path: "preferences", type: "DELETE"});
20674 transaction.change("", fluid.copy(that.initialModel));
20675 transaction.commit();
20676 that.events.onPrefsEditorRefresh.fire();
20677 that.events.afterReset.fire(that);
20678 };
20679
20680 /*
20681 * Resets the selections to the last saved selections and fires onCancel
20682 */
20683 fluid.prefs.prefsEditor.cancel = function (that) {
20684 that.events.onCancel.fire();
20685 var fetchPromise = that.fetch();
20686 fetchPromise.then(function () {
20687 var transaction = that.applier.initiate();
20688 transaction.fireChangeRequest({path: "preferences", type: "DELETE"});
20689 transaction.change("", that.model.remote);
20690 transaction.commit();
20691 that.events.onPrefsEditorRefresh.fire();
20692 });
20693 };
20694
20695 // called once markup is applied to the document containing tab component roots
20696 fluid.prefs.prefsEditor.finishInit = function (that) {
20697 var bindHandlers = function (that) {
20698 var saveButton = that.locate("save");
20699 if (saveButton.length > 0) {
20700 saveButton.click(that.saveAndApply);
20701 var form = fluid.findForm(saveButton);
20702 $(form).submit(function () {
20703 that.saveAndApply();
20704 });
20705 }
20706 that.locate("reset").click(that.reset);
20707 that.locate("cancel").click(that.cancel);
20708 };
20709
20710 that.container.append(that.options.resources.template.resourceText);
20711 bindHandlers(that);
20712
20713 var fetchPromise = that.fetch();
20714 fetchPromise.then(function () {
20715 that.events.onPrefsEditorMarkupReady.fire();
20716 that.events.onPrefsEditorRefresh.fire();
20717 that.applyChanges();
20718 that.events.onReady.fire(that);
20719
20720 });
20721 };
20722
20723 fluid.prefs.prefsEditor.handleAutoSave = function (that) {
20724 if (that.options.autoSave) {
20725 that.events.onAutoSave.fire();
20726 }
20727 };
20728
20729 fluid.prefs.prefsEditor.init = function (that) {
20730 // This setTimeout is to ensure that fetching of resources is asynchronous,
20731 // and so that component construction does not run ahead of subcomponents for SeparatedPanel
20732 // (FLUID-4453 - this may be a replacement for a branch removed for a FLUID-2248 fix)
20733 setTimeout(function () {
20734 if (!fluid.isDestroyed(that)) {
20735 fluid.prefs.prefsEditor.finishInit(that);
20736 }
20737 }, 1);
20738 };
20739
20740 /******************************
20741 * Preferences Editor Preview *
20742 ******************************/
20743
20744 fluid.defaults("fluid.prefs.preview", {
20745 gradeNames: ["fluid.viewComponent"],
20746 components: {
20747 enhancer: {
20748 type: "fluid.uiEnhancer",
20749 container: "{preview}.enhancerContainer",
20750 createOnEvent: "onReady"
20751 },
20752 templateLoader: "{templateLoader}"
20753 },
20754 invokers: {
20755 updateModel: {
20756 funcName: "fluid.prefs.preview.updateModel",
20757 args: [
20758 "{preview}",
20759 "{prefsEditor}.model.preferences"
20760 ]
20761 }
20762 },
20763 events: {
20764 onReady: null
20765 },
20766 listeners: {
20767 "onCreate.startLoadingContainer": "fluid.prefs.preview.startLoadingContainer",
20768 "{prefsEditor}.events.modelChanged": {
20769 listener: "{that}.updateModel",
20770 namespace: "updateModel"
20771 },
20772 "onReady.updateModel": "{that}.updateModel"
20773 },
20774 templateUrl: "%prefix/PrefsEditorPreview.html"
20775 });
20776
20777 fluid.prefs.preview.updateModel = function (that, preferences) {
20778 /**
20779 * SetTimeout is temp fix for http://issues.fluidproject.org/browse/FLUID-2248
20780 */
20781 setTimeout(function () {
20782 if (that.enhancer) {
20783 that.enhancer.updateModel(preferences);
20784 }
20785 }, 0);
20786 };
20787
20788 fluid.prefs.preview.startLoadingContainer = function (that) {
20789 var templateUrl = that.templateLoader.transformURL(that.options.templateUrl);
20790 that.container.on("load", function () {
20791 that.enhancerContainer = $("body", that.container.contents());
20792 that.events.onReady.fire();
20793 });
20794 that.container.attr("src", templateUrl);
20795 };
20796
20797})(jQuery, fluid_3_0_0);
20798;
20799/*
20800Copyright The Infusion copyright holders
20801See the AUTHORS.md file at the top-level directory of this distribution and at
20802https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
20803
20804Licensed under the Educational Community License (ECL), Version 2.0 or the New
20805BSD license. You may not use this file except in compliance with one these
20806Licenses.
20807
20808You may obtain a copy of the ECL 2.0 License and BSD License at
20809https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
20810*/
20811
20812var fluid_3_0_0 = fluid_3_0_0 || {};
20813
20814
20815(function ($, fluid) {
20816 "use strict";
20817
20818 /**********************
20819 * msgLookup grade *
20820 **********************/
20821
20822 fluid.defaults("fluid.prefs.msgLookup", {
20823 gradeNames: ["fluid.component"],
20824 members: {
20825 msgLookup: {
20826 expander: {
20827 funcName: "fluid.prefs.stringLookup",
20828 args: ["{msgResolver}", "{that}.options.stringArrayIndex"]
20829 }
20830 }
20831 },
20832 stringArrayIndex: {}
20833 });
20834
20835 fluid.prefs.stringLookup = function (messageResolver, stringArrayIndex) {
20836 var that = {id: fluid.allocateGuid()};
20837 that.singleLookup = function (value) {
20838 var looked = messageResolver.lookup([value]);
20839 return fluid.get(looked, "template");
20840 };
20841 that.multiLookup = function (values) {
20842 return fluid.transform(values, function (value) {
20843 return that.singleLookup(value);
20844 });
20845 };
20846 that.lookup = function (value) {
20847 var values = fluid.get(stringArrayIndex, value) || value;
20848 var lookupFn = fluid.isArrayable(values) ? "multiLookup" : "singleLookup";
20849 return that[lookupFn](values);
20850 };
20851 that.resolvePathSegment = that.lookup;
20852 return that;
20853 };
20854
20855 /***********************************************
20856 * Base grade panel
20857 ***********************************************/
20858
20859 fluid.defaults("fluid.prefs.panel", {
20860 gradeNames: ["fluid.prefs.msgLookup", "fluid.rendererComponent"],
20861 events: {
20862 onDomBind: null
20863 },
20864 // Any listener that requires a DOM element, should be registered
20865 // to the onDomBind listener. By default it is fired by onCreate, but
20866 // when used as a subpanel, it will be triggered by the resetDomBinder invoker.
20867 listeners: {
20868 "onCreate.onDomBind": "{that}.events.onDomBind"
20869 },
20870 components: {
20871 msgResolver: {
20872 type: "fluid.messageResolver"
20873 }
20874 },
20875 rendererOptions: {
20876 messageLocator: "{msgResolver}.resolve"
20877 },
20878 distributeOptions: {
20879 "panel.msgResolver.messageBase": {
20880 source: "{that}.options.messageBase",
20881 target: "{that > msgResolver}.options.messageBase"
20882 }
20883 }
20884 });
20885
20886 /***************************
20887 * Base grade for subpanel *
20888 ***************************/
20889
20890 fluid.defaults("fluid.prefs.subPanel", {
20891 gradeNames: ["fluid.prefs.panel", "{that}.getDomBindGrade"],
20892 listeners: {
20893 "{compositePanel}.events.afterRender": {
20894 listener: "{that}.events.afterRender",
20895 args: ["{that}"],
20896 namespce: "boilAfterRender"
20897 },
20898 // Changing the firing of onDomBind from the onCreate.
20899 // This is due to the fact that the rendering process, controlled by the
20900 // composite panel, will set/replace the DOM elements.
20901 "onCreate.onDomBind": null, // remove listener
20902 "afterRender.onDomBind": "{that}.resetDomBinder"
20903 },
20904 rules: {
20905 expander: {
20906 func: "fluid.prefs.subPanel.generateRules",
20907 args: ["{that}.options.preferenceMap"]
20908 }
20909 },
20910 invokers: {
20911 refreshView: "{compositePanel}.refreshView",
20912 // resetDomBinder must fire the onDomBind event
20913 resetDomBinder: {
20914 funcName: "fluid.prefs.subPanel.resetDomBinder",
20915 args: ["{that}"]
20916 },
20917 getDomBindGrade: {
20918 funcName: "fluid.prefs.subPanel.getDomBindGrade",
20919 args: ["{prefsEditor}"]
20920 }
20921 },
20922 strings: {},
20923 parentBundle: "{compositePanel}.messageResolver",
20924 renderOnInit: false
20925 });
20926
20927 fluid.defaults("fluid.prefs.subPanel.domBind", {
20928 gradeNames: ["fluid.component"],
20929 listeners: {
20930 "onDomBind.domChange": {
20931 listener: "{prefsEditor}.events.onSignificantDOMChange"
20932 }
20933 }
20934 });
20935
20936 fluid.prefs.subPanel.getDomBindGrade = function (prefsEditor) {
20937 var hasListener = fluid.get(prefsEditor, "options.events.onSignificantDOMChange") !== undefined;
20938 if (hasListener) {
20939 return "fluid.prefs.subPanel.domBind";
20940 }
20941 };
20942
20943 /*
20944 * Since the composite panel manages the rendering of the subpanels
20945 * the markup used by subpanels needs to be completely replaced.
20946 * The subpanel's container is refereshed to point at the newly
20947 * rendered markup, and the domBinder is re-initialized. Once
20948 * this is all done, the onDomBind event is fired.
20949 */
20950 fluid.prefs.subPanel.resetDomBinder = function (that) {
20951 // TODO: The line below to find the container jQuery instance was copied from the framework code -
20952 // https://github.com/fluid-project/infusion/blob/master/src/framework/core/js/FluidView.js#L145
20953 // in order to reset the dom binder when panels are in an iframe.
20954 // It can be be eliminated once we have the new renderer.
20955 var userJQuery = that.container.constructor;
20956 var context = that.container[0].ownerDocument;
20957 var selector = that.container.selector;
20958 that.container = userJQuery(selector, context);
20959 // To address FLUID-5966, manually adding back the selector and context properties that were removed from jQuery v3.0.
20960 // ( see: https://jquery.com/upgrade-guide/3.0/#breaking-change-deprecated-context-and-selector-properties-removed )
20961 // In most cases the "selector" property will already be restored through the DOM binder or fluid.container.
20962 // However, in this case we are manually recreating the container to ensure that it is referencing an element currently added
20963 // to the correct Document ( e.g. iframe ) (also see: FLUID-4536). This manual recreation of the container requires us to
20964 // manually add back the selector and context from the original container. This code and fix parallels that in
20965 // FluidView.js fluid.container line 129
20966 that.container.selector = selector;
20967 that.container.context = context;
20968 if (that.container.length === 0) {
20969 fluid.fail("resetDomBinder got no elements in DOM for container searching for selector " + that.container.selector);
20970 }
20971 fluid.initDomBinder(that, that.options.selectors);
20972 that.events.onDomBind.fire(that);
20973 };
20974
20975 fluid.prefs.subPanel.safePrefKey = function (prefKey) {
20976 return prefKey.replace(/[.]/g, "_");
20977 };
20978
20979 /*
20980 * Generates the model relay rules for a subpanel.
20981 * Takes advantage of the fact that compositePanel
20982 * uses the preference key (with "." replaced by "_"),
20983 * as its model path.
20984 */
20985 fluid.prefs.subPanel.generateRules = function (preferenceMap) {
20986 var rules = {};
20987 fluid.each(preferenceMap, function (prefObj, prefKey) {
20988 fluid.each(prefObj, function (value, prefRule) {
20989 if (prefRule.indexOf("model.") === 0) {
20990 rules[fluid.prefs.subPanel.safePrefKey(prefKey)] = prefRule.slice("model.".length);
20991 }
20992 });
20993 });
20994 return rules;
20995 };
20996
20997 /**********************************
20998 * Base grade for composite panel *
20999 **********************************/
21000
21001 fluid.registerNamespace("fluid.prefs.compositePanel");
21002
21003 fluid.prefs.compositePanel.arrayMergePolicy = function (target, source) {
21004 target = fluid.makeArray(target);
21005 source = fluid.makeArray(source);
21006 fluid.each(source, function (selector) {
21007 if (target.indexOf(selector) < 0) {
21008 target.push(selector);
21009 }
21010 });
21011 return target;
21012 };
21013
21014 fluid.defaults("fluid.prefs.compositePanel", {
21015 gradeNames: ["fluid.prefs.panel", "{that}.getDistributeOptionsGrade", "{that}.getSubPanelLifecycleBindings"],
21016 mergePolicy: {
21017 subPanelOverrides: "noexpand",
21018 selectorsToIgnore: fluid.prefs.compositePanel.arrayMergePolicy
21019 },
21020 selectors: {}, // requires selectors into the template which will act as the containers for the subpanels
21021 selectorsToIgnore: [], // should match the selectors that are used to identify the containers for the subpanels
21022 repeatingSelectors: [],
21023 events: {
21024 initSubPanels: null
21025 },
21026 listeners: {
21027 "onCreate.combineResources": "{that}.combineResources",
21028 "onCreate.appendTemplate": {
21029 "this": "{that}.container",
21030 "method": "append",
21031 "args": ["{that}.options.resources.template.resourceText"]
21032 },
21033 "onCreate.initSubPanels": "{that}.events.initSubPanels",
21034 "onCreate.hideInactive": "{that}.hideInactive",
21035 "onCreate.surfaceSubpanelRendererSelectors": "{that}.surfaceSubpanelRendererSelectors",
21036 "afterRender.hideInactive": "{that}.hideInactive"
21037 },
21038 invokers: {
21039 getDistributeOptionsGrade: {
21040 funcName: "fluid.prefs.compositePanel.assembleDistributeOptions",
21041 args: ["{that}.options.components"]
21042 },
21043 getSubPanelLifecycleBindings: {
21044 funcName: "fluid.prefs.compositePanel.subPanelLifecycleBindings",
21045 args: ["{that}.options.components"]
21046 },
21047 combineResources: {
21048 funcName: "fluid.prefs.compositePanel.combineTemplates",
21049 args: ["{that}.options.resources", "{that}.options.selectors"]
21050 },
21051 surfaceSubpanelRendererSelectors: {
21052 funcName: "fluid.prefs.compositePanel.surfaceSubpanelRendererSelectors",
21053 args: ["{that}", "{that}.options.components", "{that}.options.selectors"]
21054 },
21055 produceSubPanelTrees: {
21056 funcName: "fluid.prefs.compositePanel.produceSubPanelTrees",
21057 args: ["{that}"]
21058 },
21059 expandProtoTree: {
21060 funcName: "fluid.prefs.compositePanel.expandProtoTree",
21061 args: ["{that}"]
21062 },
21063 produceTree: {
21064 funcName: "fluid.prefs.compositePanel.produceTree",
21065 args: ["{that}"]
21066 },
21067 hideInactive: {
21068 funcName: "fluid.prefs.compositePanel.hideInactive",
21069 args: ["{that}"]
21070 },
21071 handleRenderOnPreference: {
21072 funcName: "fluid.prefs.compositePanel.handleRenderOnPreference",
21073 args: ["{that}", "{that}.refreshView", "{that}.conditionalCreateEvent", "{arguments}.0", "{arguments}.1", "{arguments}.2"]
21074 },
21075 conditionalCreateEvent: {
21076 funcName: "fluid.prefs.compositePanel.conditionalCreateEvent"
21077 }
21078 },
21079 subPanelOverrides: {
21080 gradeNames: ["fluid.prefs.subPanel"]
21081 },
21082 rendererFnOptions: {
21083 noexpand: true,
21084 cutpointGenerator: "fluid.prefs.compositePanel.cutpointGenerator",
21085 subPanelRepeatingSelectors: {
21086 expander: {
21087 funcName: "fluid.prefs.compositePanel.surfaceRepeatingSelectors",
21088 args: ["{that}.options.components"]
21089 }
21090 }
21091 },
21092 components: {},
21093 resources: {} // template is reserved for the compositePanel's template, the subpanel template should have same key as the selector for its container.
21094 });
21095
21096 /*
21097 * Attempts to prefetch a components options before it is instantiated.
21098 * Only use in cases where the instatiated component cannot be used.
21099 */
21100 fluid.prefs.compositePanel.prefetchComponentOptions = function (type, options) {
21101 var baseOptions = fluid.getMergedDefaults(type, fluid.get(options, "gradeNames"));
21102 // TODO: awkwardly, fluid.merge is destructive on each argument!
21103 return fluid.merge(baseOptions.mergePolicy, fluid.copy(baseOptions), options);
21104 };
21105 /*
21106 * Should only be used when fluid.prefs.compositePanel.isActivatePanel cannot.
21107 * While this implementation doesn't require an instantiated component, it may in
21108 * the process miss some configuration provided by distribute options and demands.
21109 */
21110 fluid.prefs.compositePanel.isPanel = function (type, options) {
21111 var opts = fluid.prefs.compositePanel.prefetchComponentOptions(type, options);
21112 return fluid.hasGrade(opts, "fluid.prefs.panel");
21113 };
21114
21115 fluid.prefs.compositePanel.isActivePanel = function (comp) {
21116 return comp && fluid.hasGrade(comp.options, "fluid.prefs.panel");
21117 };
21118
21119 /*
21120 * Creates a grade containing the distributeOptions rules needed for the subcomponents
21121 */
21122 fluid.prefs.compositePanel.assembleDistributeOptions = function (components) {
21123 var gradeName = "fluid.prefs.compositePanel.distributeOptions_" + fluid.allocateGuid();
21124 var distributeOptions = {};
21125 var relayOption = {};
21126 fluid.each(components, function (componentOptions, componentName) {
21127 if (fluid.prefs.compositePanel.isPanel(componentOptions.type, componentOptions.options)) {
21128 distributeOptions[componentName + ".subPanelOverrides"] = {
21129 source: "{that}.options.subPanelOverrides",
21130 target: "{that > " + componentName + "}.options"
21131 };
21132 }
21133
21134 // Construct the model relay btw the composite panel and its subpanels
21135 var componentRelayRules = {};
21136 var definedOptions = fluid.prefs.compositePanel.prefetchComponentOptions(componentOptions.type, componentOptions.options);
21137 var preferenceMap = fluid.get(definedOptions, ["preferenceMap"]);
21138 fluid.each(preferenceMap, function (prefObj, prefKey) {
21139 fluid.each(prefObj, function (value, prefRule) {
21140 if (prefRule.indexOf("model.") === 0) {
21141 fluid.set(componentRelayRules, prefRule.slice("model.".length), "{compositePanel}.model." + fluid.prefs.subPanel.safePrefKey(prefKey));
21142 }
21143 });
21144 });
21145 relayOption[componentName] = componentRelayRules;
21146 distributeOptions[componentName + ".modelRelay"] = {
21147 source: "{that}.options.relayOption." + componentName,
21148 target: "{that > " + componentName + "}.options.model"
21149 };
21150 });
21151 fluid.defaults(gradeName, {
21152 gradeNames: ["fluid.component"],
21153 relayOption: relayOption,
21154 distributeOptions: distributeOptions
21155 });
21156 return gradeName;
21157 };
21158
21159 fluid.prefs.compositePanel.conditionalCreateEvent = function (value, createEvent) {
21160 if (value) {
21161 createEvent();
21162 }
21163 };
21164
21165
21166 fluid.prefs.compositePanel.handleRenderOnPreference = function (that, refreshViewFunc, conditionalCreateEventFunc, value, createEvent, componentNames) {
21167 componentNames = fluid.makeArray(componentNames);
21168 conditionalCreateEventFunc(value, createEvent);
21169 fluid.each(componentNames, function (componentName) {
21170 var comp = that[componentName];
21171 if (!value && comp) {
21172 comp.destroy();
21173 }
21174 });
21175 refreshViewFunc();
21176 };
21177
21178 fluid.prefs.compositePanel.creationEventName = function (pref) {
21179 return "initOn_" + pref;
21180 };
21181
21182 fluid.prefs.compositePanel.generateModelListeners = function (conditionals) {
21183 return fluid.transform(conditionals, function (componentNames, pref) {
21184 var eventName = fluid.prefs.compositePanel.creationEventName(pref);
21185 return {
21186 func: "{that}.handleRenderOnPreference",
21187 args: ["{change}.value", "{that}.events." + eventName + ".fire", componentNames],
21188 namespace: "handleRenderOnPreference_" + pref
21189 };
21190 });
21191 };
21192
21193 /*
21194 * Creates a grade containing all of the lifecycle binding configuration needed for the subpanels.
21195 * This includes the following:
21196 * - adding events used to trigger the initialization of the subpanels
21197 * - adding the createOnEvent configuration for the subpanels
21198 * - binding handlers to model changed events
21199 * - binding handlers to afterRender and onCreate
21200 */
21201 fluid.prefs.compositePanel.subPanelLifecycleBindings = function (components) {
21202 var gradeName = "fluid.prefs.compositePanel.subPanelCreationTimingDistibution_" + fluid.allocateGuid();
21203 var distributeOptions = {};
21204 var subPanelCreationOpts = {
21205 "default": "initSubPanels"
21206 };
21207 var conditionals = {};
21208 var listeners = {};
21209 var events = {};
21210 fluid.each(components, function (componentOptions, componentName) {
21211 if (fluid.prefs.compositePanel.isPanel(componentOptions.type, componentOptions.options)) {
21212 var creationEventOpt = "default";
21213 // would have had renderOnPreference directly sourced from the componentOptions
21214 // however, the set of configuration specified there is restricted.
21215 var renderOnPreference = fluid.get(componentOptions, "options.renderOnPreference");
21216 if (renderOnPreference) {
21217 var pref = fluid.prefs.subPanel.safePrefKey(renderOnPreference);
21218 var onCreateListener = "onCreate." + pref;
21219 creationEventOpt = fluid.prefs.compositePanel.creationEventName(pref);
21220 subPanelCreationOpts[creationEventOpt] = creationEventOpt;
21221 events[creationEventOpt] = null;
21222 conditionals[pref] = conditionals[pref] || [];
21223 conditionals[pref].push(componentName);
21224 listeners[onCreateListener] = {
21225 listener: "{that}.conditionalCreateEvent",
21226 args: ["{that}.model." + pref, "{that}.events." + creationEventOpt + ".fire"]
21227 };
21228 }
21229 distributeOptions[componentName + ".subPanelCreationOpts"] = {
21230 source: "{that}.options.subPanelCreationOpts." + creationEventOpt,
21231 target: "{that}.options.components." + componentName + ".createOnEvent"
21232 };
21233 }
21234 });
21235
21236 fluid.defaults(gradeName, {
21237 gradeNames: ["fluid.component"],
21238 events: events,
21239 listeners: listeners,
21240 modelListeners: fluid.prefs.compositePanel.generateModelListeners(conditionals),
21241 subPanelCreationOpts: subPanelCreationOpts,
21242 distributeOptions: distributeOptions
21243 });
21244 return gradeName;
21245 };
21246
21247 /*
21248 * Used to hide the containers of inactive sub panels.
21249 * This is necessary as the composite panel's template is the one that has their containers and
21250 * it would be undesirable to have them visible when their associated panel has not been created.
21251 * Also, hiding them allows for the subpanel to initialize, as it requires their container to be present.
21252 * The subpanels need to be initialized before rendering, for the produce function to source the rendering
21253 * information from it.
21254 */
21255 fluid.prefs.compositePanel.hideInactive = function (that) {
21256 fluid.each(that.options.components, function (componentOpts, componentName) {
21257 if (fluid.prefs.compositePanel.isPanel(componentOpts.type, componentOpts.options) && !fluid.prefs.compositePanel.isActivePanel(that[componentName])) {
21258 that.locate(componentName).hide();
21259 }
21260 });
21261 };
21262
21263 /*
21264 * Use the renderer directly to combine the templates into a single
21265 * template to be used by the components actual rendering.
21266 */
21267 fluid.prefs.compositePanel.combineTemplates = function (resources, selectors) {
21268 var cutpoints = [];
21269 var tree = {children: []};
21270
21271 fluid.each(resources, function (resource, resourceName) {
21272 if (resourceName !== "template") {
21273 tree.children.push({
21274 ID: resourceName,
21275 markup: resource.resourceText
21276 });
21277 cutpoints.push({
21278 id: resourceName,
21279 selector: selectors[resourceName]
21280 });
21281 }
21282 });
21283
21284 var resourceSpec = {
21285 base: {
21286 resourceText: resources.template.resourceText,
21287 href: ".",
21288 resourceKey: ".",
21289 cutpoints: cutpoints
21290 }
21291 };
21292
21293 var templates = fluid.parseTemplates(resourceSpec, ["base"]);
21294 var renderer = fluid.renderer(templates, tree, {cutpoints: cutpoints, debugMode: true});
21295 resources.template.resourceText = renderer.renderTemplates();
21296 };
21297
21298 fluid.prefs.compositePanel.rebaseSelectorName = function (memberName, selectorName) {
21299 return memberName + "_" + selectorName;
21300 };
21301
21302 /*
21303 * Surfaces the rendering selectors from the subpanels to the compositePanel,
21304 * and scopes them to the subpanel's container.
21305 * Since this is used by the cutpoint generator, which only gets run once, we need to
21306 * surface all possible subpanel selectors, and not just the active ones.
21307 */
21308 fluid.prefs.compositePanel.surfaceSubpanelRendererSelectors = function (that, components, selectors) {
21309 fluid.each(components, function (compOpts, compName) {
21310 if (fluid.prefs.compositePanel.isPanel(compOpts.type, compOpts.options)) {
21311 var opts = fluid.prefs.compositePanel.prefetchComponentOptions(compOpts.type, compOpts.options);
21312 fluid.each(opts.selectors, function (selector, selName) {
21313 if (!opts.selectorsToIgnore || opts.selectorsToIgnore.indexOf(selName) < 0) {
21314 fluid.set(selectors, fluid.prefs.compositePanel.rebaseSelectorName(compName, selName), selectors[compName] + " " + selector);
21315 }
21316 });
21317 }
21318 });
21319 };
21320
21321 fluid.prefs.compositePanel.surfaceRepeatingSelectors = function (components) {
21322 var repeatingSelectors = [];
21323 fluid.each(components, function (compOpts, compName) {
21324 if (fluid.prefs.compositePanel.isPanel(compOpts.type, compOpts.options)) {
21325 var opts = fluid.prefs.compositePanel.prefetchComponentOptions(compOpts.type, compOpts.options);
21326 var rebasedRepeatingSelectors = fluid.transform(opts.repeatingSelectors, function (selector) {
21327 return fluid.prefs.compositePanel.rebaseSelectorName(compName, selector);
21328 });
21329 repeatingSelectors = repeatingSelectors.concat(rebasedRepeatingSelectors);
21330 }
21331 });
21332 return repeatingSelectors;
21333 };
21334
21335 fluid.prefs.compositePanel.cutpointGenerator = function (selectors, options) {
21336 var opts = {
21337 selectorsToIgnore: options.selectorsToIgnore,
21338 repeatingSelectors: options.repeatingSelectors.concat(options.subPanelRepeatingSelectors)
21339 };
21340 return fluid.renderer.selectorsToCutpoints(selectors, opts);
21341 };
21342
21343 fluid.prefs.compositePanel.rebaseID = function (value, memberName) {
21344 return memberName + "_" + value;
21345 };
21346
21347 fluid.prefs.compositePanel.rebaseParentRelativeID = function (val, memberName) {
21348 var slicePos = "..::".length; // ..:: refers to the parentRelativeID prefix used in the renderer
21349 return val.slice(0, slicePos) + fluid.prefs.compositePanel.rebaseID(val.slice(slicePos), memberName);
21350 };
21351
21352 fluid.prefs.compositePanel.rebaseValueBinding = function (value, modelRelayRules) {
21353 return fluid.find(modelRelayRules, function (oldModelPath, newModelPath) {
21354 if (value === oldModelPath) {
21355 return newModelPath;
21356 } else if (value.indexOf(oldModelPath) === 0) {
21357 return value.replace(oldModelPath, newModelPath);
21358 }
21359 }) || value;
21360 };
21361
21362 fluid.prefs.compositePanel.rebaseTreeComp = function (msgResolver, model, treeComp, memberName, modelRelayRules) {
21363 var rebased = fluid.copy(treeComp);
21364
21365 if (rebased.ID) {
21366 rebased.ID = fluid.prefs.compositePanel.rebaseID(rebased.ID, memberName);
21367 }
21368
21369 if (rebased.children) {
21370 rebased.children = fluid.prefs.compositePanel.rebaseTree(msgResolver, model, rebased.children, memberName, modelRelayRules);
21371 } else if (rebased.selection) {
21372 rebased.selection = fluid.prefs.compositePanel.rebaseTreeComp(msgResolver, model, rebased.selection, memberName, modelRelayRules);
21373 } else if (rebased.messagekey) {
21374 // converts the "UIMessage" renderer component into a "UIBound"
21375 // and passes in the resolved message as the value.
21376 rebased.componentType = "UIBound";
21377 rebased.value = msgResolver.resolve(rebased.messagekey.value, rebased.messagekey.args);
21378 delete rebased.messagekey;
21379 } else if (rebased.parentRelativeID) {
21380 rebased.parentRelativeID = fluid.prefs.compositePanel.rebaseParentRelativeID(rebased.parentRelativeID, memberName);
21381 } else if (rebased.valuebinding) {
21382 rebased.valuebinding = fluid.prefs.compositePanel.rebaseValueBinding(rebased.valuebinding, modelRelayRules);
21383
21384 if (rebased.value) {
21385 var modelValue = fluid.get(model, rebased.valuebinding);
21386 rebased.value = modelValue !== undefined ? modelValue : rebased.value;
21387 }
21388 }
21389
21390 return rebased;
21391 };
21392
21393 fluid.prefs.compositePanel.rebaseTree = function (msgResolver, model, tree, memberName, modelRelayRules) {
21394 var rebased;
21395
21396 if (fluid.isArrayable(tree)) {
21397 rebased = fluid.transform(tree, function (treeComp) {
21398 return fluid.prefs.compositePanel.rebaseTreeComp(msgResolver, model, treeComp, memberName, modelRelayRules);
21399 });
21400 } else {
21401 rebased = fluid.prefs.compositePanel.rebaseTreeComp(msgResolver, model, tree, memberName, modelRelayRules);
21402 }
21403
21404 return rebased;
21405 };
21406
21407 fluid.prefs.compositePanel.produceTree = function (that) {
21408 var produceTreeOption = that.options.produceTree;
21409 var ownTree = produceTreeOption ?
21410 (typeof (produceTreeOption) === "string" ? fluid.getGlobalValue(produceTreeOption) : produceTreeOption)(that) :
21411 that.expandProtoTree();
21412 var subPanelTree = that.produceSubPanelTrees();
21413 var tree = {
21414 children: ownTree.children.concat(subPanelTree.children)
21415 };
21416 return tree;
21417 };
21418
21419 fluid.prefs.compositePanel.expandProtoTree = function (that) {
21420 var expanderOptions = fluid.renderer.modeliseOptions(that.options.expanderOptions, {ELstyle: "${}"}, that);
21421 var expander = fluid.renderer.makeProtoExpander(expanderOptions, that);
21422 return expander(that.options.protoTree || {});
21423 };
21424
21425 fluid.prefs.compositePanel.produceSubPanelTrees = function (that) {
21426 var tree = {children: []};
21427 fluid.each(that.options.components, function (options, componentName) {
21428 var subPanel = that[componentName];
21429 if (fluid.prefs.compositePanel.isActivePanel(subPanel)) {
21430 var expanderOptions = fluid.renderer.modeliseOptions(subPanel.options.expanderOptions, {ELstyle: "${}"}, subPanel);
21431 var expander = fluid.renderer.makeProtoExpander(expanderOptions, subPanel);
21432 var subTree = subPanel.produceTree();
21433 subTree = fluid.get(subPanel.options, "rendererFnOptions.noexpand") ? subTree : expander(subTree);
21434 var rebasedTree = fluid.prefs.compositePanel.rebaseTree(subPanel.msgResolver, that.model, subTree, componentName, subPanel.options.rules);
21435 tree.children = tree.children.concat(rebasedTree.children);
21436 }
21437 });
21438 return tree;
21439 };
21440
21441 /********************************************************************************
21442 * The grade that contains the connections between a panel and the prefs editor *
21443 ********************************************************************************/
21444
21445 fluid.defaults("fluid.prefs.prefsEditorConnections", {
21446 gradeNames: ["fluid.component"],
21447 listeners: {
21448 // No namespace supplied because this grade is added to every panel. Suppling a
21449 // namespace would mean that only one panel's refreshView method was bound to the
21450 // onPrefsEditorRefresh event.
21451 "{fluid.prefs.prefsEditor}.events.onPrefsEditorRefresh": "{fluid.prefs.panel}.refreshView"
21452 },
21453 strings: {},
21454 parentBundle: "{fluid.prefs.prefsEditorLoader}.msgResolver"
21455 });
21456
21457 /*******************************************
21458 * A base grade for switch adjuster panels *
21459 *******************************************/
21460
21461 fluid.defaults("fluid.prefs.panel.switchAdjuster", {
21462 gradeNames: ["fluid.prefs.panel"],
21463 // preferences maps should map model values to "model.value"
21464 // model: {value: ""}
21465 selectors: {
21466 header: ".flc-prefsEditor-header",
21467 switchContainer: ".flc-prefsEditor-switch",
21468 label: ".flc-prefsEditor-label",
21469 description: ".flc-prefsEditor-description"
21470 },
21471 selectorsToIgnore: ["header", "switchContainer"],
21472 components: {
21473 switchUI: {
21474 type: "fluid.switchUI",
21475 container: "{that}.dom.switchContainer",
21476 createOnEvent: "afterRender",
21477 options: {
21478 strings: {
21479 on: "{fluid.prefs.panel.switchAdjuster}.msgLookup.switchOn",
21480 off: "{fluid.prefs.panel.switchAdjuster}.msgLookup.switchOff"
21481 },
21482 model: {
21483 enabled: "{fluid.prefs.panel.switchAdjuster}.model.value"
21484 },
21485 attrs: {
21486 "aria-labelledby": {
21487 expander: {
21488 funcName: "fluid.allocateSimpleId",
21489 args: ["{fluid.prefs.panel.switchAdjuster}.dom.description"]
21490 }
21491 }
21492 }
21493 }
21494 }
21495 },
21496 protoTree: {
21497 label: {messagekey: "label"},
21498 description: {messagekey: "description"}
21499 }
21500 });
21501
21502 /************************************************
21503 * A base grade for themePicker adjuster panels *
21504 ************************************************/
21505
21506 fluid.defaults("fluid.prefs.panel.themePicker", {
21507 gradeNames: ["fluid.prefs.panel"],
21508 mergePolicy: {
21509 "controlValues.theme": "replace",
21510 "stringArrayIndex.theme": "replace"
21511 },
21512 // The controlValues are the ordered set of possible modelValues corresponding to each theme option.
21513 // The order in which they are listed will determine the order they are presented in the UI.
21514 // The stringArrayIndex contains the ordered set of namespaced strings in the message bundle.
21515 // The order must match the controlValues in order to provide the proper labels to the theme options.
21516 controlValues: {
21517 theme: [] // must be supplied by the integrator
21518 },
21519 stringArrayIndex: {
21520 theme: [] // must be supplied by the integrator
21521 },
21522 selectID: "{that}.id", // used for the name attribute to group the selection options
21523 listeners: {
21524 "afterRender.style": "{that}.style"
21525 },
21526 selectors: {
21527 themeRow: ".flc-prefsEditor-themeRow",
21528 themeLabel: ".flc-prefsEditor-theme-label",
21529 themeInput: ".flc-prefsEditor-themeInput",
21530 label: ".flc-prefsEditor-themePicker-label",
21531 description: ".flc-prefsEditor-themePicker-descr"
21532 },
21533 styles: {
21534 defaultThemeLabel: "fl-prefsEditor-themePicker-defaultThemeLabel"
21535 },
21536 repeatingSelectors: ["themeRow"],
21537 protoTree: {
21538 label: {messagekey: "label"},
21539 description: {messagekey: "description"},
21540 expander: {
21541 type: "fluid.renderer.selection.inputs",
21542 rowID: "themeRow",
21543 labelID: "themeLabel",
21544 inputID: "themeInput",
21545 selectID: "{that}.options.selectID",
21546 tree: {
21547 optionnames: "${{that}.msgLookup.theme}",
21548 optionlist: "${{that}.options.controlValues.theme}",
21549 selection: "${value}"
21550 }
21551 }
21552 },
21553 markup: {
21554 // Aria-hidden needed on fl-preview-A and Display 'a' created as pseudo-content in css to prevent AT from reading out display 'a' on IE, Chrome, and Safari
21555 // Aria-hidden needed on fl-crossout to prevent AT from trying to read crossout symbol in Safari
21556 label: "<span class=\"fl-preview-A\" aria-hidden=\"true\"></span><span class=\"fl-hidden-accessible\">%theme</span><div class=\"fl-crossout\" aria-hidden=\"true\"></div>"
21557 },
21558 invokers: {
21559 style: {
21560 funcName: "fluid.prefs.panel.themePicker.style",
21561 args: [
21562 "{that}.dom.themeLabel",
21563 "{that}.msgLookup.theme",
21564 "{that}.options.markup.label",
21565 "{that}.options.controlValues.theme",
21566 "default",
21567 "{that}.options.classnameMap.theme",
21568 "{that}.options.styles.defaultThemeLabel"
21569 ]
21570 }
21571 }
21572 });
21573
21574 fluid.prefs.panel.themePicker.style = function (labels, strings, markup, theme, defaultThemeName, style, defaultLabelStyle) {
21575 fluid.each(labels, function (label, index) {
21576 label = $(label);
21577
21578 var themeValue = strings[index];
21579 label.html(fluid.stringTemplate(markup, {
21580 theme: themeValue
21581 }));
21582
21583 // Aria-label set to prevent Firefox from reading out the display 'a'
21584 label.attr("aria-label", themeValue);
21585
21586 var labelTheme = theme[index];
21587 if (labelTheme === defaultThemeName) {
21588 label.addClass(defaultLabelStyle);
21589 }
21590 label.addClass(style[labelTheme]);
21591 });
21592 };
21593
21594 /******************************************************
21595 * A base grade for textfield stepper adjuster panels *
21596 ******************************************************/
21597
21598 fluid.defaults("fluid.prefs.panel.stepperAdjuster", {
21599 gradeNames: ["fluid.prefs.panel"],
21600 // preferences maps should map model values to "model.value"
21601 // model: {value: ""}
21602 selectors: {
21603 header: ".flc-prefsEditor-header",
21604 textfieldStepperContainer: ".flc-prefsEditor-textfieldStepper",
21605 label: ".flc-prefsEditor-label",
21606 descr: ".flc-prefsEditor-descr"
21607 },
21608 selectorsToIgnore: ["header", "textfieldStepperContainer"],
21609 components: {
21610 textfieldStepper: {
21611 type: "fluid.textfieldStepper",
21612 container: "{that}.dom.textfieldStepperContainer",
21613 createOnEvent: "afterRender",
21614 options: {
21615 model: {
21616 value: "{fluid.prefs.panel.stepperAdjuster}.model.value",
21617 range: {
21618 min: "{fluid.prefs.panel.stepperAdjuster}.options.range.min",
21619 max: "{fluid.prefs.panel.stepperAdjuster}.options.range.max"
21620 },
21621 step: "{fluid.prefs.panel.stepperAdjuster}.options.step"
21622 },
21623 scale: 1,
21624 strings: {
21625 increaseLabel: "{fluid.prefs.panel.stepperAdjuster}.msgLookup.increaseLabel",
21626 decreaseLabel: "{fluid.prefs.panel.stepperAdjuster}.msgLookup.decreaseLabel"
21627 },
21628 attrs: {
21629 "aria-labelledby": "{fluid.prefs.panel.stepperAdjuster}.options.panelOptions.labelId"
21630 }
21631 }
21632 }
21633 },
21634 protoTree: {
21635 label: {
21636 messagekey: "label",
21637 decorators: {
21638 attrs: {id: "{that}.options.panelOptions.labelId"}
21639 }
21640 },
21641 descr: {messagekey: "description"}
21642 },
21643 panelOptions: {
21644 labelIdTemplate: "%guid",
21645 labelId: {
21646 expander: {
21647 funcName: "fluid.prefs.panel.stepperAdjuster.setLabelID",
21648 args: ["{that}.options.panelOptions.labelIdTemplate"]
21649 }
21650 }
21651 }
21652 });
21653
21654 /**
21655 * @param {String} template - take string template with a token "%guid" to be replaced by the a unique ID.
21656 * @return {String} - the resolved templated with the injected unique ID.
21657 */
21658 fluid.prefs.panel.stepperAdjuster.setLabelID = function (template) {
21659 return fluid.stringTemplate(template, {
21660 guid: fluid.allocateGuid()
21661 });
21662 };
21663
21664 /********************************
21665 * Preferences Editor Text Size *
21666 ********************************/
21667
21668 /**
21669 * A sub-component of fluid.prefs that renders the "text size" panel of the user preferences interface.
21670 */
21671 fluid.defaults("fluid.prefs.panel.textSize", {
21672 gradeNames: ["fluid.prefs.panel.stepperAdjuster"],
21673 preferenceMap: {
21674 "fluid.prefs.textSize": {
21675 "model.value": "value",
21676 "range.min": "minimum",
21677 "range.max": "maximum",
21678 "step": "divisibleBy"
21679 }
21680 },
21681 panelOptions: {
21682 labelIdTemplate: "textSize-label-%guid"
21683 }
21684 });
21685
21686 /********************************
21687 * Preferences Editor Text Font *
21688 ********************************/
21689
21690 /**
21691 * A sub-component of fluid.prefs that renders the "text font" panel of the user preferences interface.
21692 */
21693 fluid.defaults("fluid.prefs.panel.textFont", {
21694 gradeNames: ["fluid.prefs.panel"],
21695 preferenceMap: {
21696 "fluid.prefs.textFont": {
21697 "model.value": "value",
21698 "controlValues.textFont": "enum"
21699 }
21700 },
21701 mergePolicy: {
21702 "controlValues.textFont": "replace",
21703 "stringArrayIndex.textFont": "replace"
21704 },
21705 selectors: {
21706 header: ".flc-prefsEditor-text-font-header",
21707 textFont: ".flc-prefsEditor-text-font",
21708 label: ".flc-prefsEditor-text-font-label",
21709 textFontDescr: ".flc-prefsEditor-text-font-descr"
21710 },
21711 selectorsToIgnore: ["header"],
21712 stringArrayIndex: {
21713 textFont: ["textFont-default", "textFont-times", "textFont-comic", "textFont-arial", "textFont-verdana", "textFont-open-dyslexic"]
21714 },
21715 protoTree: {
21716 label: {messagekey: "textFontLabel"},
21717 textFontDescr: {messagekey: "textFontDescr"},
21718 textFont: {
21719 optionnames: "${{that}.msgLookup.textFont}",
21720 optionlist: "${{that}.options.controlValues.textFont}",
21721 selection: "${value}",
21722 decorators: {
21723 type: "fluid",
21724 func: "fluid.prefs.selectDecorator",
21725 options: {
21726 styles: "{that}.options.classnameMap.textFont"
21727 }
21728 }
21729 }
21730 },
21731 classnameMap: null, // must be supplied by implementors
21732 controlValues: {
21733 textFont: ["default", "times", "comic", "arial", "verdana", "open-dyslexic"]
21734 }
21735 });
21736
21737 /*********************************
21738 * Preferences Editor Line Space *
21739 *********************************/
21740
21741 /**
21742 * A sub-component of fluid.prefs that renders the "line space" panel of the user preferences interface.
21743 */
21744 fluid.defaults("fluid.prefs.panel.lineSpace", {
21745 gradeNames: ["fluid.prefs.panel.stepperAdjuster"],
21746 preferenceMap: {
21747 "fluid.prefs.lineSpace": {
21748 "model.value": "value",
21749 "range.min": "minimum",
21750 "range.max": "maximum",
21751 "step": "divisibleBy"
21752 }
21753 },
21754 panelOptions: {
21755 labelIdTemplate: "lineSpace-label-%guid"
21756 }
21757 });
21758
21759 /*******************************
21760 * Preferences Editor Contrast *
21761 *******************************/
21762
21763 /**
21764 * A sub-component of fluid.prefs that renders the "contrast" panel of the user preferences interface.
21765 */
21766 fluid.defaults("fluid.prefs.panel.contrast", {
21767 gradeNames: ["fluid.prefs.panel.themePicker"],
21768 preferenceMap: {
21769 "fluid.prefs.contrast": {
21770 "model.value": "value",
21771 "controlValues.theme": "enum"
21772 }
21773 },
21774 listeners: {
21775 "afterRender.style": "{that}.style"
21776 },
21777 selectors: {
21778 header: ".flc-prefsEditor-contrast-header",
21779 themeRow: ".flc-prefsEditor-themeRow",
21780 themeLabel: ".flc-prefsEditor-theme-label",
21781 themeInput: ".flc-prefsEditor-themeInput",
21782 label: ".flc-prefsEditor-themePicker-label",
21783 contrastDescr: ".flc-prefsEditor-themePicker-descr"
21784 },
21785 selectorsToIgnore: ["header"],
21786 styles: {
21787 defaultThemeLabel: "fl-prefsEditor-themePicker-defaultThemeLabel"
21788 },
21789 stringArrayIndex: {
21790 theme: [
21791 "contrast-default",
21792 "contrast-bw",
21793 "contrast-wb",
21794 "contrast-by",
21795 "contrast-yb",
21796 "contrast-lgdg",
21797 "contrast-gw",
21798 "contrast-gd",
21799 "contrast-bbr"
21800 ]
21801 },
21802 controlValues: {
21803 theme: ["default", "bw", "wb", "by", "yb", "lgdg", "gw", "gd", "bbr"]
21804 }
21805 });
21806
21807 /**************************************
21808 * Preferences Editor Layout Controls *
21809 **************************************/
21810
21811 /**
21812 * A sub-component of fluid.prefs that renders the "layout and navigation" panel of the user preferences interface.
21813 */
21814 fluid.defaults("fluid.prefs.panel.layoutControls", {
21815 gradeNames: ["fluid.prefs.panel.switchAdjuster"],
21816 preferenceMap: {
21817 "fluid.prefs.tableOfContents": {
21818 "model.value": "value"
21819 }
21820 }
21821 });
21822
21823 /*************************************
21824 * Preferences Editor Enhance Inputs *
21825 *************************************/
21826
21827 /**
21828 * A sub-component of fluid.prefs that renders the "enhance inputs" panel of the user preferences interface.
21829 */
21830 fluid.defaults("fluid.prefs.panel.enhanceInputs", {
21831 gradeNames: ["fluid.prefs.panel.switchAdjuster"],
21832 preferenceMap: {
21833 "fluid.prefs.enhanceInputs": {
21834 "model.value": "value"
21835 }
21836 }
21837 });
21838
21839 /********************************************************
21840 * Preferences Editor Select Dropdown Options Decorator *
21841 ********************************************************/
21842
21843 /**
21844 * A sub-component that decorates the options on the select dropdown list box with the css style
21845 */
21846 fluid.defaults("fluid.prefs.selectDecorator", {
21847 gradeNames: ["fluid.viewComponent"],
21848 listeners: {
21849 "onCreate.decorateOptions": "fluid.prefs.selectDecorator.decorateOptions"
21850 },
21851 styles: {
21852 preview: "fl-preview-theme"
21853 }
21854 });
21855
21856 fluid.prefs.selectDecorator.decorateOptions = function (that) {
21857 fluid.each($("option", that.container), function (option) {
21858 var styles = that.options.styles;
21859 $(option).addClass(styles.preview + " " + styles[fluid.value(option)]);
21860 });
21861 };
21862
21863})(jQuery, fluid_3_0_0);
21864;
21865/*
21866Copyright The Infusion copyright holders
21867See the AUTHORS.md file at the top-level directory of this distribution and at
21868https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
21869
21870Licensed under the Educational Community License (ECL), Version 2.0 or the New
21871BSD license. You may not use this file except in compliance with one these
21872Licenses.
21873
21874You may obtain a copy of the ECL 2.0 License and BSD License at
21875https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
21876*/
21877
21878var fluid_3_0_0 = fluid_3_0_0 || {};
21879
21880(function ($, fluid) {
21881 "use strict";
21882
21883 /**********************************************************************************
21884 * Captions Panel
21885 **********************************************************************************/
21886 fluid.defaults("fluid.prefs.panel.captions", {
21887 gradeNames: ["fluid.prefs.panel.switchAdjuster"],
21888 preferenceMap: {
21889 "fluid.prefs.captions": {
21890 "model.value": "value"
21891 }
21892 }
21893 });
21894
21895})(jQuery, fluid_3_0_0);
21896;
21897/*
21898Copyright The Infusion copyright holders
21899See the AUTHORS.md file at the top-level directory of this distribution and at
21900https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
21901
21902Licensed under the Educational Community License (ECL), Version 2.0 or the New
21903BSD license. You may not use this file except in compliance with one these
21904Licenses.
21905
21906You may obtain a copy of the ECL 2.0 License and BSD License at
21907https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
21908*/
21909
21910var fluid_3_0_0 = fluid_3_0_0 || {};
21911
21912(function ($, fluid) {
21913 "use strict";
21914
21915 /*************************************
21916 * Preferences Editor Letter Spacing *
21917 *************************************/
21918
21919 /**
21920 * A sub-component of fluid.prefs that renders the "letter spacing" panel of the user preferences interface.
21921 */
21922 fluid.defaults("fluid.prefs.panel.letterSpace", {
21923 gradeNames: ["fluid.prefs.panel.stepperAdjuster"],
21924 preferenceMap: {
21925 "fluid.prefs.letterSpace": {
21926 "model.value": "value",
21927 "range.min": "minimum",
21928 "range.max": "maximum",
21929 "step": "divisibleBy"
21930 }
21931 },
21932 panelOptions: {
21933 labelIdTemplate: "letterSpace-label-%guid"
21934 }
21935 });
21936
21937})(jQuery, fluid_3_0_0);
21938;
21939/*
21940Copyright The Infusion copyright holders
21941See the AUTHORS.md file at the top-level directory of this distribution and at
21942https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
21943
21944Licensed under the Educational Community License (ECL), Version 2.0 or the New
21945BSD license. You may not use this file except in compliance with one these
21946Licenses.
21947
21948You may obtain a copy of the ECL 2.0 License and BSD License at
21949https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
21950*/
21951
21952var fluid_3_0_0 = fluid_3_0_0 || {};
21953
21954(function ($, fluid) {
21955 "use strict";
21956
21957 /**********************************************************************************
21958 * speakPanel
21959 **********************************************************************************/
21960 fluid.defaults("fluid.prefs.panel.speak", {
21961 gradeNames: ["fluid.prefs.panel.switchAdjuster"],
21962 preferenceMap: {
21963 "fluid.prefs.speak": {
21964 "model.value": "value"
21965 }
21966 }
21967 });
21968
21969})(jQuery, fluid_3_0_0);
21970;
21971/*
21972Copyright The Infusion copyright holders
21973See the AUTHORS.md file at the top-level directory of this distribution and at
21974https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
21975
21976Licensed under the Educational Community License (ECL), Version 2.0 or the New
21977BSD license. You may not use this file except in compliance with one these
21978Licenses.
21979
21980You may obtain a copy of the ECL 2.0 License and BSD License at
21981https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
21982*/
21983
21984var fluid_3_0_0 = fluid_3_0_0 || {};
21985
21986(function ($, fluid) {
21987 "use strict";
21988
21989 /**********************************************************************************
21990 * Captions Panel
21991 **********************************************************************************/
21992 fluid.defaults("fluid.prefs.panel.syllabification", {
21993 gradeNames: ["fluid.prefs.panel.switchAdjuster"],
21994 preferenceMap: {
21995 "fluid.prefs.syllabification": {
21996 "model.value": "value"
21997 }
21998 }
21999 });
22000
22001})(jQuery, fluid_3_0_0);
22002;
22003/*
22004Copyright The Infusion copyright holders
22005See the AUTHORS.md file at the top-level directory of this distribution and at
22006https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
22007
22008Licensed under the Educational Community License (ECL), Version 2.0 or the New
22009BSD license. You may not use this file except in compliance with one these
22010Licenses.
22011
22012You may obtain a copy of the ECL 2.0 License and BSD License at
22013https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
22014*/
22015
22016var fluid_3_0_0 = fluid_3_0_0 || {};
22017
22018(function ($, fluid) {
22019 "use strict";
22020
22021 /***********************************
22022 * Preferences Editor Localization *
22023 ***********************************/
22024
22025 /**
22026 * A sub-component of fluid.prefs that renders the "localization" panel of the user preferences interface.
22027 */
22028 fluid.defaults("fluid.prefs.panel.localization", {
22029 gradeNames: ["fluid.prefs.panel"],
22030 preferenceMap: {
22031 "fluid.prefs.localization": {
22032 "model.value": "value",
22033 "controlValues.localization": "enum"
22034 }
22035 },
22036 mergePolicy: {
22037 "controlValues.localization": "replace",
22038 "stringArrayIndex.localization": "replace"
22039 },
22040 selectors: {
22041 header: ".flc-prefsEditor-localization-header",
22042 localization: ".flc-prefsEditor-localization",
22043 label: ".flc-prefsEditor-localization-label",
22044 localizationDescr: ".flc-prefsEditor-localization-descr"
22045 },
22046 selectorsToIgnore: ["header"],
22047 stringArrayIndex: {
22048 localization: ["localization-default", "localization-en", "localization-fr", "localization-es", "localization-fa"]
22049 },
22050 protoTree: {
22051 label: {messagekey: "label"},
22052 localizationDescr: {messagekey: "description"},
22053 localization: {
22054 optionnames: "${{that}.msgLookup.localization}",
22055 optionlist: "${{that}.options.controlValues.localization}",
22056 selection: "${value}"
22057 }
22058 },
22059 controlValues: {
22060 localization: ["default", "en", "fr", "es", "fa"]
22061 }
22062 });
22063
22064})(jQuery, fluid_3_0_0);
22065;
22066/*
22067Copyright The Infusion copyright holders
22068See the AUTHORS.md file at the top-level directory of this distribution and at
22069https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
22070
22071Licensed under the Educational Community License (ECL), Version 2.0 or the New
22072BSD license. You may not use this file except in compliance with one these
22073Licenses.
22074
22075You may obtain a copy of the ECL 2.0 License and BSD License at
22076https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
22077*/
22078
22079var fluid_3_0_0 = fluid_3_0_0 || {};
22080
22081(function ($, fluid) {
22082 "use strict";
22083
22084 /*************************************
22085 * Preferences Editor Word Spacing *
22086 *************************************/
22087
22088 /**
22089 * A sub-component of fluid.prefs that renders the "word spacing" panel of the user preferences interface.
22090 */
22091 fluid.defaults("fluid.prefs.panel.wordSpace", {
22092 gradeNames: ["fluid.prefs.panel.stepperAdjuster"],
22093 preferenceMap: {
22094 "fluid.prefs.wordSpace": {
22095 "model.value": "value",
22096 "range.min": "minimum",
22097 "range.max": "maximum",
22098 "step": "divisibleBy"
22099 }
22100 },
22101 panelOptions: {
22102 labelIdTemplate: "wordSpace-label-%guid"
22103 }
22104 });
22105
22106})(jQuery, fluid_3_0_0);
22107;
22108/*
22109Copyright The Infusion copyright holders
22110See the AUTHORS.md file at the top-level directory of this distribution and at
22111https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
22112
22113Licensed under the Educational Community License (ECL), Version 2.0 or the New
22114BSD license. You may not use this file except in compliance with one these
22115Licenses.
22116
22117You may obtain a copy of the ECL 2.0 License and BSD License at
22118https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
22119*/
22120
22121var fluid_3_0_0 = fluid_3_0_0 || {};
22122
22123(function ($, fluid) {
22124 "use strict";
22125
22126 fluid.defaults("fluid.prefs.enactor", {
22127 gradeNames: ["fluid.modelComponent"]
22128 });
22129
22130 /**********************************************************************************
22131 * styleElements
22132 *
22133 * Adds or removes the classname to/from the elements based upon the model value.
22134 * This component is used as a grade by enhanceInputs
22135 **********************************************************************************/
22136 fluid.defaults("fluid.prefs.enactor.styleElements", {
22137 gradeNames: ["fluid.prefs.enactor"],
22138 cssClass: null, // Must be supplied by implementors
22139 elementsToStyle: null, // Must be supplied by implementors
22140 invokers: {
22141 applyStyle: {
22142 funcName: "fluid.prefs.enactor.styleElements.applyStyle",
22143 args: ["{arguments}.0", "{arguments}.1"]
22144 },
22145 resetStyle: {
22146 funcName: "fluid.prefs.enactor.styleElements.resetStyle",
22147 args: ["{arguments}.0", "{arguments}.1"]
22148 },
22149 handleStyle: {
22150 funcName: "fluid.prefs.enactor.styleElements.handleStyle",
22151 args: ["{arguments}.0", "{that}.options.elementsToStyle", "{that}.options.cssClass", "{that}.applyStyle", "{that}.resetStyle"]
22152 }
22153 },
22154 modelListeners: {
22155 value: {
22156 listener: "{that}.handleStyle",
22157 args: ["{change}.value"],
22158 namespace: "handleStyle"
22159 }
22160 }
22161 });
22162
22163 fluid.prefs.enactor.styleElements.applyStyle = function (elements, cssClass) {
22164 elements.addClass(cssClass);
22165 };
22166
22167 fluid.prefs.enactor.styleElements.resetStyle = function (elements, cssClass) {
22168 $(elements, "." + cssClass).addBack().removeClass(cssClass);
22169 };
22170
22171 fluid.prefs.enactor.styleElements.handleStyle = function (value, elements, cssClass, applyStyleFunc, resetStyleFunc) {
22172 var func = value ? applyStyleFunc : resetStyleFunc;
22173 func(elements, cssClass);
22174 };
22175
22176 /*******************************************************************************
22177 * ClassSwapper
22178 *
22179 * Has a hash of classes it cares about and will remove all those classes from
22180 * its container before setting the new class.
22181 * This component tends to be used as a grade by textFont and contrast
22182 *******************************************************************************/
22183
22184 fluid.defaults("fluid.prefs.enactor.classSwapper", {
22185 gradeNames: ["fluid.prefs.enactor", "fluid.viewComponent"],
22186 classes: {}, // Must be supplied by implementors
22187 invokers: {
22188 clearClasses: {
22189 funcName: "fluid.prefs.enactor.classSwapper.clearClasses",
22190 args: ["{that}.container", "{that}.classStr"]
22191 },
22192 swap: {
22193 funcName: "fluid.prefs.enactor.classSwapper.swap",
22194 args: ["{arguments}.0", "{that}", "{that}.clearClasses"]
22195 }
22196 },
22197 modelListeners: {
22198 value: {
22199 listener: "{that}.swap",
22200 args: ["{change}.value"],
22201 namespace: "swapClass"
22202 }
22203 },
22204 members: {
22205 classStr: {
22206 expander: {
22207 func: "fluid.prefs.enactor.classSwapper.joinClassStr",
22208 args: "{that}.options.classes"
22209 }
22210 }
22211 }
22212 });
22213
22214 fluid.prefs.enactor.classSwapper.clearClasses = function (container, classStr) {
22215 container.removeClass(classStr);
22216 };
22217
22218 fluid.prefs.enactor.classSwapper.swap = function (value, that, clearClassesFunc) {
22219 clearClassesFunc();
22220 that.container.addClass(that.options.classes[value]);
22221 };
22222
22223 fluid.prefs.enactor.classSwapper.joinClassStr = function (classes) {
22224 var classStr = "";
22225
22226 fluid.each(classes, function (oneClassName) {
22227 if (oneClassName) {
22228 classStr += classStr ? " " + oneClassName : oneClassName;
22229 }
22230 });
22231 return classStr;
22232 };
22233
22234 /*******************************************************************************
22235 * enhanceInputs
22236 *
22237 * The enactor to enhance inputs in the container according to the value
22238 *******************************************************************************/
22239
22240 // Note that the implementors need to provide the container for this view component
22241 fluid.defaults("fluid.prefs.enactor.enhanceInputs", {
22242 gradeNames: ["fluid.prefs.enactor.styleElements", "fluid.viewComponent"],
22243 preferenceMap: {
22244 "fluid.prefs.enhanceInputs": {
22245 "model.value": "value"
22246 }
22247 },
22248 cssClass: null, // Must be supplied by implementors
22249 elementsToStyle: "{that}.container"
22250 });
22251
22252 /*******************************************************************************
22253 * textFont
22254 *
22255 * The enactor to change the font face used according to the value
22256 *******************************************************************************/
22257 // Note that the implementors need to provide the container for this view component
22258 fluid.defaults("fluid.prefs.enactor.textFont", {
22259 gradeNames: ["fluid.prefs.enactor.classSwapper"],
22260 preferenceMap: {
22261 "fluid.prefs.textFont": {
22262 "model.value": "value"
22263 }
22264 }
22265 });
22266
22267 /*******************************************************************************
22268 * contrast
22269 *
22270 * The enactor to change the contrast theme according to the value
22271 *******************************************************************************/
22272 // Note that the implementors need to provide the container for this view component
22273 fluid.defaults("fluid.prefs.enactor.contrast", {
22274 gradeNames: ["fluid.prefs.enactor.classSwapper"],
22275 preferenceMap: {
22276 "fluid.prefs.contrast": {
22277 "model.value": "value"
22278 }
22279 }
22280 });
22281
22282
22283
22284 /*******************************************************************************
22285 * Functions shared by textSize and lineSpace
22286 *******************************************************************************/
22287
22288 /**
22289 * return "font-size" in px
22290 * @param {Object} container - The container to evaluate.
22291 * @param {Object} fontSizeMap - The mapping between the font size string values ("small", "medium" etc) to px values.
22292 * @return {Number} - The size of the container, in px units.
22293 */
22294 fluid.prefs.enactor.getTextSizeInPx = function (container, fontSizeMap) {
22295 var fontSize = container.css("font-size");
22296
22297 if (fontSizeMap[fontSize]) {
22298 fontSize = fontSizeMap[fontSize];
22299 }
22300
22301 // return fontSize in px
22302 return parseFloat(fontSize);
22303 };
22304
22305 /*******************************************************************************
22306 * textRelatedSizer
22307 *
22308 * Provides an abstraction for enactors that need to adjust sizes based on
22309 * a text size value from the DOM. This could include things such as:
22310 * font-size, line-height, letter-spacing, and etc.
22311 *******************************************************************************/
22312
22313 fluid.defaults("fluid.prefs.enactor.textRelatedSizer", {
22314 gradeNames: ["fluid.prefs.enactor", "fluid.viewComponent"],
22315 fontSizeMap: {}, // must be supplied by implementors
22316 invokers: {
22317 set: "fluid.notImplemented", // must be supplied by a concrete implementation
22318 getTextSizeInPx: {
22319 funcName: "fluid.prefs.enactor.getTextSizeInPx",
22320 args: ["{that}.container", "{that}.options.fontSizeMap"]
22321 }
22322 },
22323 modelListeners: {
22324 value: {
22325 listener: "{that}.set",
22326 args: ["{change}.value"],
22327 namespace: "setAdaptation"
22328 }
22329 }
22330 });
22331
22332 /*******************************************************************************
22333 * spacingSetter
22334 *
22335 * Sets the css spacing value on the container to the number of units to
22336 * increase the space by. If a negative number is provided, the space between
22337 * will decrease. Setting the value to 1 or unit to 0 will use the default.
22338 *******************************************************************************/
22339
22340 fluid.defaults("fluid.prefs.enactor.spacingSetter", {
22341 gradeNames: ["fluid.prefs.enactor.textRelatedSizer"],
22342 members: {
22343 originalSpacing: {
22344 expander: {
22345 func: "{that}.getSpacing"
22346 }
22347 }
22348 },
22349 cssProp: "",
22350 invokers: {
22351 set: {
22352 funcName: "fluid.prefs.enactor.spacingSetter.set",
22353 args: ["{that}", "{that}.options.cssProp", "{arguments}.0"]
22354 },
22355 getSpacing: {
22356 funcName: "fluid.prefs.enactor.spacingSetter.getSpacing",
22357 args: ["{that}", "{that}.options.cssProp", "{that}.getTextSizeInPx"]
22358 }
22359 },
22360 modelListeners: {
22361 unit: {
22362 listener: "{that}.set",
22363 args: ["{change}.value"],
22364 namespace: "setAdaptation"
22365 },
22366 // Replace default model listener, because `value` needs be transformed before being applied.
22367 // The `unit` model value should be used for setting the adaptation.
22368 value: {
22369 listener: "fluid.identity",
22370 namespace: "setAdaptation"
22371 }
22372 },
22373 modelRelay: {
22374 target: "unit",
22375 namespace: "toUnit",
22376 singleTransform: {
22377 type: "fluid.transforms.round",
22378 scale: 1,
22379 input: {
22380 transform: {
22381 "type": "fluid.transforms.linearScale",
22382 "offset": -1,
22383 "input": "{that}.model.value"
22384 }
22385 }
22386 }
22387 }
22388 });
22389
22390 fluid.prefs.enactor.spacingSetter.getSpacing = function (that, cssProp, getTextSizeFn) {
22391 var current = parseFloat(that.container.css(cssProp));
22392 var textSize = getTextSizeFn();
22393 return fluid.roundToDecimal(current / textSize, 2);
22394 };
22395
22396 fluid.prefs.enactor.spacingSetter.set = function (that, cssProp, units) {
22397 var targetSize = that.originalSpacing;
22398
22399 if (units) {
22400 targetSize = targetSize + units;
22401 }
22402
22403 // setting the style value to "" will remove it.
22404 var spacingSetter = targetSize ? fluid.roundToDecimal(targetSize, 2) + "em" : "";
22405 that.container.css(cssProp, spacingSetter);
22406 };
22407
22408 /*******************************************************************************
22409 * textSize
22410 *
22411 * Sets the text size on the root element to the multiple provided.
22412 *******************************************************************************/
22413
22414 // Note that the implementors need to provide the container for this view component
22415 fluid.defaults("fluid.prefs.enactor.textSize", {
22416 gradeNames: ["fluid.prefs.enactor.textRelatedSizer"],
22417 preferenceMap: {
22418 "fluid.prefs.textSize": {
22419 "model.value": "value"
22420 }
22421 },
22422 members: {
22423 root: {
22424 expander: {
22425 "this": "{that}.container",
22426 "method": "closest", // ensure that the correct document is being used. i.e. in an iframe
22427 "args": ["html"]
22428 }
22429 }
22430 },
22431 invokers: {
22432 set: {
22433 funcName: "fluid.prefs.enactor.textSize.set",
22434 args: ["{arguments}.0", "{that}", "{that}.getTextSizeInPx"]
22435 },
22436 getTextSizeInPx: {
22437 args: ["{that}.root", "{that}.options.fontSizeMap"]
22438 }
22439 }
22440 });
22441
22442 fluid.prefs.enactor.textSize.set = function (times, that, getTextSizeInPxFunc) {
22443 times = times || 1;
22444 // Calculating the initial size here rather than using a members expand because the "font-size"
22445 // cannot be detected on hidden containers such as separated paenl iframe.
22446 if (!that.initialSize) {
22447 that.initialSize = getTextSizeInPxFunc();
22448 }
22449
22450 if (that.initialSize) {
22451 var targetSize = times * that.initialSize;
22452 that.root.css("font-size", targetSize + "px");
22453 }
22454 };
22455
22456 /*******************************************************************************
22457 * lineSpace
22458 *
22459 * Sets the line space on the container to the multiple provided.
22460 *******************************************************************************/
22461
22462 // Note that the implementors need to provide the container for this view component
22463 fluid.defaults("fluid.prefs.enactor.lineSpace", {
22464 gradeNames: ["fluid.prefs.enactor.textRelatedSizer"],
22465 preferenceMap: {
22466 "fluid.prefs.lineSpace": {
22467 "model.value": "value"
22468 }
22469 },
22470 invokers: {
22471 set: {
22472 funcName: "fluid.prefs.enactor.lineSpace.set",
22473 args: ["{that}", "{arguments}.0"]
22474 },
22475 getLineHeight: {
22476 funcName: "fluid.prefs.enactor.lineSpace.getLineHeight",
22477 args: "{that}.container"
22478 },
22479 getLineHeightMultiplier: {
22480 funcName: "fluid.prefs.enactor.lineSpace.getLineHeightMultiplier",
22481 args: [{expander: {func: "{that}.getLineHeight"}}, {expander: {func: "{that}.getTextSizeInPx"}}]
22482 }
22483 }
22484 });
22485
22486 // Get the line-height of an element
22487 // In IE8 and IE9 this will return the line-height multiplier
22488 // In other browsers it will return the pixel value of the line height.
22489 fluid.prefs.enactor.lineSpace.getLineHeight = function (container) {
22490 return container.css("line-height");
22491 };
22492
22493 // Interprets browser returned "line-height" value, either a string "normal", a number with "px" suffix or "undefined"
22494 // into a numeric value in em.
22495 // Return 0 when the given "lineHeight" argument is "undefined" (http://issues.fluidproject.org/browse/FLUID-4500).
22496 fluid.prefs.enactor.lineSpace.getLineHeightMultiplier = function (lineHeight, fontSize) {
22497 // Handle the given "lineHeight" argument is "undefined", which occurs when firefox detects
22498 // "line-height" css value on a hidden container. (http://issues.fluidproject.org/browse/FLUID-4500)
22499 if (!lineHeight) {
22500 return 0;
22501 }
22502
22503 // Needs a better solution. For now, "line-height" value "normal" is defaulted to 1.2em
22504 // according to https://developer.mozilla.org/en/CSS/line-height
22505 if (lineHeight === "normal") {
22506 return 1.2;
22507 }
22508
22509 // Continuing the work-around of jQuery + IE bug - http://bugs.jquery.com/ticket/2671
22510 if (lineHeight.match(/[0-9]$/)) {
22511 return Number(lineHeight);
22512 }
22513
22514 return fluid.roundToDecimal(parseFloat(lineHeight) / fontSize, 2);
22515 };
22516
22517 fluid.prefs.enactor.lineSpace.set = function (that, times) {
22518 // Calculating the initial size here rather than using a members expand because the "line-height"
22519 // cannot be detected on hidden containers such as separated panel iframe.
22520 if (!that.initialSize) {
22521 that.initialSize = that.getLineHeight();
22522 that.lineHeightMultiplier = that.getLineHeightMultiplier();
22523 }
22524
22525 // that.initialSize === 0 when the browser returned "lineHeight" css value is undefined,
22526 // which occurs when firefox detects "line-height" value on a hidden container.
22527 // @ See getLineHeightMultiplier() & http://issues.fluidproject.org/browse/FLUID-4500
22528 if (that.lineHeightMultiplier) {
22529 var targetLineSpace = that.initialSize === "normal" && times === 1 ? that.initialSize : times * that.lineHeightMultiplier;
22530 that.container.css("line-height", targetLineSpace);
22531 }
22532 };
22533
22534 /*******************************************************************************
22535 * tableOfContents
22536 *
22537 * To create and show/hide table of contents
22538 *******************************************************************************/
22539
22540 // Note that the implementors need to provide the container for this view component
22541 fluid.defaults("fluid.prefs.enactor.tableOfContents", {
22542 gradeNames: ["fluid.prefs.enactor", "fluid.viewComponent"],
22543 preferenceMap: {
22544 "fluid.prefs.tableOfContents": {
22545 "model.toc": "value"
22546 }
22547 },
22548 tocTemplate: null, // must be supplied by implementors
22549 tocMessage: null, // must be supplied by implementors
22550 components: {
22551 // TODO: When FLUID-6312 and FLUID-6300 are addressed, make sure that this message loader is updated when
22552 // the locale is changed. It should also trigger the table of contents to re-render with the
22553 // correct message bundle applied.
22554 messageLoader: {
22555 type: "fluid.resourceLoader",
22556 options: {
22557 resourceOptions: {
22558 dataType: "json"
22559 },
22560 events: {
22561 onResourcesLoaded: "{fluid.prefs.enactor.tableOfContents}.events.onMessagesLoaded"
22562 }
22563 }
22564 },
22565 tableOfContents: {
22566 type: "fluid.tableOfContents",
22567 container: "{fluid.prefs.enactor.tableOfContents}.container",
22568 createOnEvent: "onCreateTOCReady",
22569 options: {
22570 listeners: {
22571 "afterRender.boilAfterTocRender": "{fluid.prefs.enactor.tableOfContents}.events.afterTocRender"
22572 },
22573 strings: {
22574 tocHeader: "{messageLoader}.resources.tocMessage.resourceText.tocHeader"
22575 }
22576 }
22577 }
22578 },
22579 invokers: {
22580 applyToc: {
22581 funcName: "fluid.prefs.enactor.tableOfContents.applyToc",
22582 args: ["{arguments}.0", "{that}"]
22583 }
22584 },
22585 events: {
22586 afterTocRender: null,
22587 onCreateTOC: null,
22588 onMessagesLoaded: null,
22589 onCreateTOCReady: {
22590 events: {
22591 onCreateTOC: "onCreateTOC",
22592 onMessagesLoaded: "onMessagesLoaded"
22593 }
22594 }
22595 },
22596 modelListeners: {
22597 toc: {
22598 listener: "{that}.applyToc",
22599 args: ["{change}.value"],
22600 namespace: "toggleToc"
22601 }
22602 },
22603 distributeOptions: {
22604 "tocEnactor.tableOfContents.ignoreForToC": {
22605 source: "{that}.options.ignoreForToC",
22606 target: "{that tableOfContents}.options.ignoreForToC"
22607 },
22608 "tocEnactor.tableOfContents.tocTemplate": {
22609 source: "{that}.options.tocTemplate",
22610 target: "{that > tableOfContents > levels}.options.resources.template.url"
22611 },
22612 "tocEnactor.messageLoader.tocMessage": {
22613 source: "{that}.options.tocMessage",
22614 target: "{that messageLoader}.options.resources.tocMessage"
22615 }
22616 }
22617 });
22618
22619 fluid.prefs.enactor.tableOfContents.applyToc = function (value, that) {
22620 if (value) {
22621 if (that.tableOfContents) {
22622 that.tableOfContents.show();
22623 } else {
22624 that.events.onCreateTOC.fire();
22625 }
22626 } else if (that.tableOfContents) {
22627 that.tableOfContents.hide();
22628 }
22629 };
22630
22631})(jQuery, fluid_3_0_0);
22632;
22633/*
22634Copyright The Infusion copyright holders
22635See the AUTHORS.md file at the top-level directory of this distribution and at
22636https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
22637
22638Licensed under the Educational Community License (ECL), Version 2.0 or the New
22639BSD license. You may not use this file except in compliance with one these
22640Licenses.
22641
22642You may obtain a copy of the ECL 2.0 License and BSD License at
22643https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
22644*/
22645
22646/* global YT */
22647
22648var fluid_3_0_0 = fluid_3_0_0 || {};
22649
22650(function ($, fluid) {
22651 "use strict";
22652
22653 /*******************************************************************************
22654 * captions
22655 *
22656 * An enactor that is capable of enabling captions on embedded YouTube videos
22657 *******************************************************************************/
22658
22659 fluid.defaults("fluid.prefs.enactor.captions", {
22660 gradeNames: ["fluid.prefs.enactor", "fluid.viewComponent"],
22661 preferenceMap: {
22662 "fluid.prefs.captions": {
22663 "model.enabled": "value"
22664 }
22665 },
22666 events: {
22667 onVideoElementLocated: null
22668 },
22669 selectors: {
22670 videos: "iframe[src^=\"https://www.youtube.com/embed/\"]"
22671 },
22672 model: {
22673 enabled: false
22674 },
22675 components: {
22676 ytAPI: {
22677 type: "fluid.prefs.enactor.captions.ytAPI"
22678 }
22679 },
22680 dynamicComponents: {
22681 player: {
22682 type: "fluid.prefs.enactor.captions.youTubePlayer",
22683 createOnEvent: "onVideoElementLocated",
22684 container: "{arguments}.0",
22685 options: {
22686 model: {
22687 captions: "{captions}.model.enabled"
22688 }
22689 }
22690 }
22691 },
22692 listeners: {
22693 "onCreate.initPlayers": "{that}.initPlayers"
22694 },
22695 invokers: {
22696 initPlayers: {
22697 funcName: "fluid.prefs.enactor.captions.initPlayers",
22698 args: ["{that}", "{ytAPI}.notifyWhenLoaded", "{that}.dom.videos"]
22699 }
22700 }
22701 });
22702
22703 /**
22704 * When the YouTube API is available, the onVideoElementLocated event will fire for each video element located by
22705 * the `videos` argument. Each of these event calls will fire with a jQuery object containing a single video
22706 * element. This allows for initializing dynamicComponents (fluid.prefs.enactor.captions.youTubePlayer) for each
22707 * video element.
22708 *
22709 * @param {Component} that - the component
22710 * @param {Function} getYtApi - a function that returns a promise indicating if the YouTube API is available
22711 * @param {jQuery|Element} videos - the videos to fire onVideoElementLocated events with
22712 *
22713 * @return {Promise} - A promise that follows the promise returned by the getYtApi function
22714 */
22715 fluid.prefs.enactor.captions.initPlayers = function (that, getYtApi, videos) {
22716 var promise = fluid.promise();
22717 var ytAPINotice = getYtApi();
22718
22719 promise.then(function () {
22720 $(videos).each(function (index, elm) {
22721 that.events.onVideoElementLocated.fire($(elm));
22722 });
22723 });
22724
22725 fluid.promise.follow(ytAPINotice, promise);
22726 return promise;
22727 };
22728
22729 /*********************************************************************************************
22730 * fluid.prefs.enactor.captions.window is a singleton component to be used for assigning *
22731 * values onto the window object. *
22732 *********************************************************************************************/
22733
22734 fluid.defaults("fluid.prefs.enactor.captions.ytAPI", {
22735 gradeNames: ["fluid.component", "fluid.resolveRootSingle"],
22736 singleRootType: "fluid.prefs.enactor.captions.window",
22737 events: {
22738 onYouTubeAPILoaded: null
22739 },
22740 members: {
22741 global: window
22742 },
22743 invokers: {
22744 notifyWhenLoaded: {
22745 funcName: "fluid.prefs.enactor.captions.ytAPI.notifyWhenLoaded",
22746 args: ["{that}"]
22747 }
22748 }
22749 });
22750
22751 /**
22752 * Used to determine when the YouTube API is available for use. It will test if the API is already available, and if
22753 * not, will bind to the onYouTubeIframeAPIReady method that is called when the YouTube API finishes loading.
22754 * When the YouTube API is ready, the promise will resolve an the onYouTubeAPILoaded event will fire.
22755 *
22756 * NOTE: After FLUID-6148 (https://issues.fluidproject.org/browse/FLUID-6148) is complete, it should be possible for
22757 * the framework to handle this asynchrony directly in an expander for the player member in
22758 * fluid.prefs.enactor.captions.youTubePlayer.
22759 *
22760 * @param {Component} that - the component itself
22761 *
22762 * @return {Promise} - a promise resolved after the YouTube API has loaded.
22763 */
22764 fluid.prefs.enactor.captions.ytAPI.notifyWhenLoaded = function (that) {
22765 var promise = fluid.promise();
22766 promise.then(function () {
22767 that.events.onYouTubeAPILoaded.fire();
22768 }, function (error) {
22769 fluid.log(fluid.logLevel.WARN, error);
22770 });
22771
22772 if (fluid.get(window, ["YT", "Player"])) {
22773 promise.resolve();
22774 } else {
22775 // the YouTube iframe api will call onYouTubeIframeAPIReady after the api has loaded
22776 fluid.set(that.global, "onYouTubeIframeAPIReady", promise.resolve);
22777 }
22778
22779 return promise;
22780 };
22781
22782 /**
22783 * See: https://developers.google.com/youtube/iframe_api_reference#Events for details on the YouTube player
22784 * events. This includes when they are fired and what data is passed along.
22785 */
22786 fluid.defaults("fluid.prefs.enactor.captions.youTubePlayer", {
22787 gradeNames: ["fluid.viewComponent"],
22788 events: {
22789 onReady: null,
22790 onStateChange: null,
22791 onPlaybackQualityChange: null,
22792 onPlaybackRateChange: null,
22793 onError: null,
22794 onApiChange: null
22795 },
22796 model: {
22797 captions: false,
22798 track: {}
22799 },
22800 members: {
22801 player: {
22802 expander: {
22803 funcName: "fluid.prefs.enactor.captions.youTubePlayer.initYTPlayer",
22804 args: ["{that}"]
22805 }
22806 },
22807 tracklist: []
22808 },
22809 invokers: {
22810 applyCaptions: {
22811 funcName: "fluid.prefs.enactor.captions.youTubePlayer.applyCaptions",
22812 args: ["{that}.player", "{that}.model.track", "{that}.model.captions"]
22813 }
22814 },
22815 modelListeners: {
22816 "setCaptions": {
22817 listener: "{that}.applyCaptions",
22818 path: ["captions", "track"],
22819 excludeSource: "init"
22820 }
22821 },
22822 listeners: {
22823 "onApiChange.prepTrack": {
22824 listener: "fluid.prefs.enactor.captions.youTubePlayer.prepTrack",
22825 args: ["{that}", "{that}.player"]
22826 },
22827 "onApiChange.applyCaptions": {
22828 listener: "{that}.applyCaptions",
22829 priority: "after:prepTrack"
22830 }
22831 }
22832 });
22833
22834 /**
22835 * Adds the "enablejsapi=1" query parameter to the query string at the end of the src attribute.
22836 * If "enablejsapi" already exists it will modify its value to 1. This is required for API access
22837 * to the embedded YouTube video.
22838 *
22839 * @param {jQuery|Element} videoElm - a reference to the existing embedded YouTube video.
22840 */
22841 fluid.prefs.enactor.captions.youTubePlayer.enableJSAPI = function (videoElm) {
22842 videoElm = $(videoElm);
22843 var url = new URL(videoElm.attr("src"));
22844
22845 url.searchParams.set("enablejsapi", 1);
22846 videoElm.attr("src", url.toString());
22847 };
22848
22849 /**
22850 * An instance of a YouTube player from the YouTube iframe API
22851 *
22852 * @typedef {Object} YTPlayer
22853 */
22854
22855 /**
22856 * Initializes the YT.Player using the existing embedded video (component's container). An ID will be added to the
22857 * video element if one does not already exist.
22858 *
22859 * @param {Component} that - the component
22860
22861 * @return {YTPlayer} - an instance of a YouTube player controlling the embedded video
22862 */
22863 fluid.prefs.enactor.captions.youTubePlayer.initYTPlayer = function (that) {
22864 var id = fluid.allocateSimpleId(that.container);
22865 fluid.prefs.enactor.captions.youTubePlayer.enableJSAPI(that.container);
22866 return new YT.Player(id, {
22867 events: {
22868 onReady: that.events.onReady.fire,
22869 onStateChange: that.events.onStateChange.fire,
22870 onPlaybackQualityChange: that.events.onPlaybackQualityChange.fire,
22871 onPlaybackRateChange: that.events.onPlaybackRateChange.fire,
22872 onError: that.events.onError.fire,
22873 onApiChange: that.events.onApiChange.fire
22874 }
22875 });
22876 };
22877
22878 /**
22879 * Enables/disables the captions on an embedded YouTube video. Requires that the player be initiallized and the API
22880 * ready for use.
22881 *
22882 * @param {YTPlayer} player - an instance of a YouTube player
22883 * @param {Object} track - a track object for the {YTPlayer}
22884 * @param {Boolean} state - true - captions enabled; false - captions disabled.
22885 */
22886 fluid.prefs.enactor.captions.youTubePlayer.applyCaptions = function (player, track, state) {
22887 // The loadModule method from the player must be ready first. This is made available after
22888 // the onApiChange event has fired.
22889 if (player.loadModule) {
22890 if (state) {
22891 player.loadModule("captions");
22892 player.setOption("captions", "track", track);
22893 } else {
22894 player.unloadModule("captions");
22895 }
22896 }
22897 };
22898
22899 /**
22900 * Prepares the track to be used when captions are enabled. It will use the first track in the tracklist, and update
22901 * the "track" model path with it.
22902 *
22903 * @param {Component} that - the component
22904 * @param {YTPlayer} player - an instance of a YouTube player
22905 */
22906 fluid.prefs.enactor.captions.youTubePlayer.prepTrack = function (that, player) {
22907 player.loadModule("captions");
22908 var tracklist = player.getOption("captions", "tracklist");
22909
22910 if (tracklist.length && !that.tracklist.length) {
22911 // set the tracklist and use first track for the captions
22912 that.tracklist = tracklist;
22913 that.applier.change("track", tracklist[0], "ADD", "prepTrack");
22914 }
22915 };
22916
22917
22918})(jQuery, fluid_3_0_0);
22919;
22920/*
22921Copyright The Infusion copyright holders
22922See the AUTHORS.md file at the top-level directory of this distribution and at
22923https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
22924
22925Licensed under the Educational Community License (ECL), Version 2.0 or the New
22926BSD license. You may not use this file except in compliance with one these
22927Licenses.
22928
22929You may obtain a copy of the ECL 2.0 License and BSD License at
22930https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
22931*/
22932
22933var fluid_3_0_0 = fluid_3_0_0 || {};
22934
22935(function ($, fluid) {
22936 "use strict";
22937
22938 /*******************************************************************************
22939 * letterSpace
22940 *
22941 * Sets the letter space on the container to the number of units to increase
22942 * the letter space by. If a negative number is provided, the space between
22943 * characters will decrease. Setting the value to 1 or unit to 0 will use the
22944 * default letter space.
22945 *******************************************************************************/
22946
22947 // Note that the implementors need to provide the container for this view component
22948 fluid.defaults("fluid.prefs.enactor.letterSpace", {
22949 gradeNames: ["fluid.prefs.enactor.spacingSetter"],
22950 preferenceMap: {
22951 "fluid.prefs.letterSpace": {
22952 "model.value": "value"
22953 }
22954 },
22955 cssProp: "letter-spacing"
22956 });
22957
22958})(jQuery, fluid_3_0_0);
22959;
22960/*
22961Copyright The Infusion copyright holders
22962See the AUTHORS.md file at the top-level directory of this distribution and at
22963https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
22964
22965Licensed under the Educational Community License (ECL), Version 2.0 or the New
22966BSD license. You may not use this file except in compliance with one these
22967Licenses.
22968
22969You may obtain a copy of the ECL 2.0 License and BSD License at
22970https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
22971*/
22972
22973var fluid_3_0_0 = fluid_3_0_0 || {};
22974
22975(function ($, fluid) {
22976 "use strict";
22977
22978 /*******************************************************************************
22979 * selfVoicing
22980 *
22981 * The enactor that enables self voicing of the DOM
22982 *******************************************************************************/
22983
22984 fluid.defaults("fluid.prefs.enactor.selfVoicing", {
22985 gradeNames: ["fluid.prefs.enactor", "fluid.viewComponent"],
22986 preferenceMap: {
22987 "fluid.prefs.speak": {
22988 "model.enabled": "value"
22989 }
22990 },
22991 selectors: {
22992 controller: ".flc-prefs-selfVoicingWidget"
22993 },
22994 events: {
22995 onInitOrator: null
22996 },
22997 modelListeners: {
22998 "enabled": {
22999 funcName: "fluid.prefs.enactor.selfVoicing.initOrator",
23000 args: ["{that}", "{change}.value"],
23001 namespace: "initOrator"
23002 }
23003 },
23004 components: {
23005 orator: {
23006 type: "fluid.orator",
23007 createOnEvent: "onInitOrator",
23008 container: "{fluid.prefs.enactor.selfVoicing}.container",
23009 options: {
23010 model: {
23011 enabled: "{selfVoicing}.model.enabled"
23012 },
23013 controller: {
23014 parentContainer: "{fluid.prefs.enactor.selfVoicing}.dom.controller"
23015 }
23016 }
23017 }
23018 },
23019 distributeOptions: [{
23020 source: "{that}.options.orator",
23021 target: "{that > orator}.options",
23022 removeSource: true,
23023 namespace: "oratorOpts"
23024 }]
23025 });
23026
23027 fluid.prefs.enactor.selfVoicing.initOrator = function (that, enabled) {
23028 if (enabled && !that.orator) {
23029 that.events.onInitOrator.fire();
23030 }
23031 };
23032
23033})(jQuery, fluid_3_0_0);
23034;
23035/*
23036Copyright The Infusion copyright holders
23037See the AUTHORS.md file at the top-level directory of this distribution and at
23038https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
23039
23040Licensed under the Educational Community License (ECL), Version 2.0 or the New
23041BSD license. You may not use this file except in compliance with one these
23042Licenses.
23043
23044You may obtain a copy of the ECL 2.0 License and BSD License at
23045https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
23046*/
23047
23048var fluid_3_0_0 = fluid_3_0_0 || {};
23049
23050(function ($, fluid) {
23051 "use strict";
23052
23053 /*******************************************************************************
23054 * syllabification
23055 *
23056 * An enactor that is capable of breaking words down into syllables
23057 *******************************************************************************/
23058
23059 /*
23060 * `fluid.prefs.enactor.syllabification` makes use of the "hypher" library to split up words into their phonetic
23061 * parts. Because different localizations may have different means of splitting up words, pattern files for the
23062 * supported languages are used. The language patterns are pulled in dynamically based on the language codes
23063 * encountered in the content. The language patterns available are configured through the patterns option,
23064 * populated by the `fluid.prefs.enactor.syllabification.patterns` grade.
23065 */
23066 fluid.defaults("fluid.prefs.enactor.syllabification", {
23067 gradeNames: ["fluid.prefs.enactor", "fluid.prefs.enactor.syllabification.patterns", "fluid.viewComponent"],
23068 preferenceMap: {
23069 "fluid.prefs.syllabification": {
23070 "model.enabled": "value"
23071 }
23072 },
23073 selectors: {
23074 separator: ".flc-syllabification-separator"
23075 },
23076 strings: {
23077 languageUnavailable: "Syllabification not available for %lang",
23078 patternLoadError: "The pattern file %src could not be loaded. %errorMsg"
23079 },
23080 markup: {
23081 separator: "<span class=\"flc-syllabification-separator fl-syllabification-separator\"></span>"
23082 },
23083 model: {
23084 enabled: false
23085 },
23086 events: {
23087 afterParse: null,
23088 afterSyllabification: null,
23089 onParsedTextNode: null,
23090 onNodeAdded: null,
23091 onError: null
23092 },
23093 listeners: {
23094 "afterParse.waitForHyphenators": {
23095 listener: "fluid.prefs.enactor.syllabification.waitForHyphenators",
23096 args: ["{that}"]
23097 },
23098 "onParsedTextNode.syllabify": {
23099 listener: "{that}.apply",
23100 args: ["{arguments}.0", "{arguments}.1"]
23101 },
23102 "onNodeAdded.syllabify": {
23103 listener: "{that}.parse",
23104 args: ["{arguments}.0", "{that}.model.enabled"]
23105 }
23106 },
23107 components: {
23108 parser: {
23109 type: "fluid.textNodeParser",
23110 options: {
23111 listeners: {
23112 "afterParse.boil": "{syllabification}.events.afterParse",
23113 "onParsedTextNode.boil": "{syllabification}.events.onParsedTextNode"
23114 }
23115 }
23116 },
23117 observer: {
23118 type: "fluid.mutationObserver",
23119 container: "{that}.container",
23120 options: {
23121 defaultObserveConfig: {
23122 attributes: false
23123 },
23124 modelListeners: {
23125 "{syllabification}.model.enabled": {
23126 funcName: "fluid.prefs.enactor.syllabification.disconnectObserver",
23127 priority: "before:setPresentation",
23128 args: ["{that}", "{change}.value"],
23129 namespace: "disconnectObserver"
23130 }
23131 },
23132 listeners: {
23133 "onNodeAdded.boil": "{syllabification}.events.onNodeAdded",
23134 "{syllabification}.events.afterSyllabification": {
23135 listener: "{that}.observe",
23136 namespace: "enableObserver"
23137 }
23138 }
23139 }
23140 }
23141 },
23142 members: {
23143 // `hyphenators` is a mapping of strings, representing the source paths of pattern files, to Promises
23144 // linked to the resolutions of loading and initially applying those syllabification patterns.
23145 hyphenators: {}
23146 },
23147 modelListeners: {
23148 "enabled": {
23149 listener: "{that}.setPresentation",
23150 args: ["{that}.container", "{change}.value"],
23151 namespace: "setPresentation"
23152 }
23153 },
23154 invokers: {
23155 apply: {
23156 funcName: "fluid.prefs.enactor.syllabification.syllabify",
23157 args: ["{that}", "{arguments}.0", "{arguments}.1"]
23158 },
23159 remove: {
23160 funcName: "fluid.prefs.enactor.syllabification.removeSyllabification",
23161 args: ["{that}"]
23162 },
23163 setPresentation: {
23164 funcName: "fluid.prefs.enactor.syllabification.setPresentation",
23165 args: ["{that}", "{arguments}.0", "{arguments}.1"]
23166 },
23167 parse: {
23168 funcName: "fluid.prefs.enactor.syllabification.parse",
23169 args: ["{that}", "{arguments}.0"]
23170 },
23171 createHyphenator: {
23172 funcName: "fluid.prefs.enactor.syllabification.createHyphenator",
23173 args: ["{that}", "{arguments}.0", "{arguments}.1"]
23174 },
23175 getHyphenator: {
23176 funcName: "fluid.prefs.enactor.syllabification.getHyphenator",
23177 args: ["{that}", "{arguments}.0"]
23178 },
23179 getPattern: "fluid.prefs.enactor.syllabification.getPattern",
23180 hyphenateNode: {
23181 funcName: "fluid.prefs.enactor.syllabification.hyphenateNode",
23182 args: ["{arguments}.0", "{arguments}.1", "{that}.options.markup.separator"]
23183 },
23184 injectScript: {
23185 this: "$",
23186 method: "ajax",
23187 args: [{
23188 url: "{arguments}.0",
23189 dataType: "script",
23190 cache: true
23191 }]
23192 }
23193 }
23194 });
23195
23196 /**
23197 * Only disconnect the observer if the state is set to false.
23198 * This corresponds to the syllabification's `enabled` model path being set to false.
23199 *
23200 * @param {Component} that - an instance of `fluid.mutationObserver`
23201 * @param {Boolean} state - if `false` disconnect, otherwise do nothing
23202 */
23203 fluid.prefs.enactor.syllabification.disconnectObserver = function (that, state) {
23204 if (!state) {
23205 that.disconnect();
23206 }
23207 };
23208
23209 /**
23210 * Wait for all hyphenators to be resolved. After they are resolved the `afterSyllabification` event is fired.
23211 * If any of the hyphenator promises is rejected, the `onError` event is fired instead.
23212 *
23213 * @param {Component} that - an instance of `fluid.prefs.enactor.syllabification`
23214 *
23215 * @return {Promise} - returns the sequence promise; which is constructed from the hyphenator promises.
23216 */
23217 fluid.prefs.enactor.syllabification.waitForHyphenators = function (that) {
23218 var hyphenatorPromises = fluid.values(that.hyphenators);
23219 var promise = fluid.promise.sequence(hyphenatorPromises);
23220 promise.then(function () {
23221 that.events.afterSyllabification.fire();
23222 }, that.events.onError.fire);
23223 return promise;
23224 };
23225
23226 fluid.prefs.enactor.syllabification.parse = function (that, elm) {
23227 elm = fluid.unwrap(elm);
23228 elm = elm.nodeType === Node.ELEMENT_NODE ? $(elm) : $(elm.parentNode);
23229 that.parser.parse(elm);
23230 };
23231
23232 /**
23233 * Creates a hyphenator instance making use of the pattern supplied by the path; which is injected into the Document
23234 * if it hasn't already been loaded. If the pattern file cannot be loaded, the onError event is fired.
23235 *
23236 * @param {Component} that - an instance of `fluid.prefs.enactor.syllabification`
23237 * @param {Object} pattern - the `file path` to the pattern file. The path may include a string template token to
23238 * resolve a portion of its path from. The token will be resolved from the component's
23239 * `terms` option. (e.g. "%patternPrefix/en-us.js");
23240 * @param {String} lang - a valid BCP 47 language code. (NOTE: supported lang codes are defined in the
23241 * `patterns`) option.
23242 *
23243 * @return {Promise} - If a hyphenator is successfully created, the promise is resolved with it. Otherwise it is
23244 * resolved with undefined and the `onError` event fired.
23245 */
23246 fluid.prefs.enactor.syllabification.createHyphenator = function (that, pattern, lang) {
23247 var promise = fluid.promise();
23248 var globalPath = ["Hypher", "languages", lang];
23249 var hyphenator = fluid.getGlobalValue(globalPath);
23250
23251 // If the pattern file has already been loaded, return the hyphenator.
23252 // This could happen if the pattern file is statically linked to the page.
23253 if (hyphenator) {
23254 promise.resolve(hyphenator);
23255 return promise;
23256 }
23257
23258 var src = fluid.stringTemplate(pattern, that.options.terms);
23259
23260 var injectPromise = that.injectScript(src);
23261 injectPromise.then(function () {
23262 hyphenator = fluid.getGlobalValue(globalPath);
23263 promise.resolve(hyphenator);
23264 }, function (error) {
23265 var errorInfo = {
23266 src: src,
23267 errorMsg: typeof(error) === "string" ? error : ""
23268 };
23269
23270 var errorMessage = fluid.stringTemplate(that.options.strings.patternLoadError, errorInfo);
23271 fluid.log(fluid.logLevel.WARN, errorMessage, error);
23272 that.events.onError.fire(errorMessage, error);
23273
23274 //TODO: A promise rejection would be more appropriate. However, we need to know when all of the hyphenators
23275 // have attempted to load and apply syllabification. The current promise utility,
23276 // fluid.promise.sequence, will reject the whole sequence if a promise is rejected, and prevent us from
23277 // knowing if all of the hyphenators have been attempted. We should be able to improve this
23278 // implementation once https://issues.fluidproject.org/browse/FLUID-5938 has been resolved.
23279 //
23280 // If the pattern file could not be loaded, resolve the promise without a hyphenator (undefined).
23281 promise.resolve();
23282 });
23283
23284 return promise;
23285 };
23286
23287 /**
23288 * Information about a pattern, including the resolved language code and the file path to the pattern file.
23289 *
23290 * @typedef {Object} PatternInfo
23291 * @property {String} lang - The resolved language code
23292 * @property {String|Undefined} src - The file path to the pattern file for the resolved language. If no pattern
23293 * file is available, the value should be `undefined`.
23294 */
23295
23296 /**
23297 * Assembles an Object containing the information for locating the pattern file. If a pattern for the specific
23298 * requested language code cannot be located, it will attempt to locate a fall back, by looking for a pattern
23299 * supporting the generic language code. If no pattern can be found, `undefined` is returned as the `src` value.
23300 *
23301 *
23302 * @param {String} lang - a valid BCP 47 language code. (NOTE: supported lang codes are defined in the
23303 * `patterns`) option.
23304 * @param {Object.<String, String>} patterns - an object mapping language codes to file paths for the pattern files. For example:
23305 * {"en": "./patterns/en-us.js"}
23306 *
23307 * @return {PatternInfo} - returns a PatternInfo Object for the resolved language code. If a pattern file is not
23308 * available for the language, the `src` property will be `undefined`.
23309 */
23310 fluid.prefs.enactor.syllabification.getPattern = function (lang, patterns) {
23311 var src = patterns[lang];
23312
23313 if (!src) {
23314 lang = lang.split("-")[0];
23315 src = patterns[lang];
23316 }
23317
23318 return {
23319 lang: lang,
23320 src: src
23321 };
23322 };
23323
23324 /**
23325 * Retrieves a promise for the appropriate hyphenator. If a hyphenator has not already been created, it will attempt
23326 * to create one and assign the related promise to the `hyphenators` member for future retrieval.
23327 *
23328 * When creating a hyphenator, it first checks if there is configuration for the specified `lang`. If that fails,
23329 * it attempts to fall back to a less specific localization.
23330 *
23331 * @param {Component} that - an instance of `fluid.prefs.enactor.syllabification`
23332 * @param {String} lang - a valid BCP 47 language code. (NOTE: supported lang codes are defined in the
23333 * `patterns`) option.
23334 *
23335 * @return {Promise} - returns a promise. If a hyphenator is successfully created, it is resolved with it.
23336 * Otherwise, it resolves with undefined.
23337 */
23338 fluid.prefs.enactor.syllabification.getHyphenator = function (that, lang) {
23339 //TODO: For all of the instances where an empty promise is resolved, a promise rejection would be more
23340 // appropriate. However, we need to know when all of the hyphenators have attempted to load and apply
23341 // syllabification. The current promise utility, fluid.promise.sequence, will reject the whole sequence if
23342 // a promise is rejected, and prevent us from knowing if all of the hyphenators have been attempted. We
23343 // should be able to improve this implementation once https://issues.fluidproject.org/browse/FLUID-5938 has
23344 // been resolved.
23345 var promise = fluid.promise();
23346 var hyphenatorPromise;
23347
23348 if (!lang) {
23349 promise.resolve();
23350 return promise;
23351 }
23352
23353 var pattern = that.getPattern(lang.toLowerCase(), that.options.patterns);
23354
23355 if (!pattern.src) {
23356 hyphenatorPromise = promise;
23357 promise.resolve();
23358 return promise;
23359 }
23360
23361 if (that.hyphenators[pattern.src]) {
23362 return that.hyphenators[pattern.src];
23363 }
23364
23365 hyphenatorPromise = that.createHyphenator(pattern.src, pattern.lang);
23366 fluid.promise.follow(hyphenatorPromise, promise);
23367 that.hyphenators[pattern.src] = hyphenatorPromise;
23368
23369 return promise;
23370 };
23371
23372 fluid.prefs.enactor.syllabification.syllabify = function (that, node, lang) {
23373 var hyphenatorPromise = that.getHyphenator(lang);
23374 hyphenatorPromise.then(function (hyphenator) {
23375 that.hyphenateNode(hyphenator, node);
23376 });
23377 };
23378
23379 fluid.prefs.enactor.syllabification.hyphenateNode = function (hyphenator, node, separatorMarkup) {
23380 if (!hyphenator) {
23381 return;
23382 }
23383
23384 // remove \u200B characters added hyphenateText
23385 var hyphenated = hyphenator.hyphenateText(node.textContent).replace(/\u200B/gi, "");
23386
23387 // split words on soft hyphens
23388 var segs = hyphenated.split("\u00AD");
23389 // remove the last segment as we only need to place separators in between the parts of the words
23390 segs.pop();
23391 fluid.each(segs, function (seg) {
23392 var separator = $(separatorMarkup)[0];
23393 node = node.splitText(seg.length);
23394 node.parentNode.insertBefore(separator, node);
23395 });
23396 };
23397
23398 /**
23399 * Collapses adjacent text nodes within an element.
23400 * Similar to NODE.normalize() but works in IE 11.
23401 * See: https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8727426/
23402 *
23403 * @param {jQuery|DomElement} elm - The element to normalize.
23404 */
23405 fluid.prefs.enactor.syllabification.normalize = function (elm) {
23406 elm = fluid.unwrap(elm);
23407 var childNode = elm.childNodes[0];
23408 while (childNode && childNode.nextSibling) {
23409 var nextSibling = childNode.nextSibling;
23410 if (childNode.nodeType === Node.TEXT_NODE && nextSibling.nodeType === Node.TEXT_NODE) {
23411 childNode.textContent += nextSibling.textContent;
23412 elm.removeChild(nextSibling);
23413 } else {
23414 childNode = nextSibling;
23415 }
23416 }
23417 };
23418
23419 fluid.prefs.enactor.syllabification.removeSyllabification = function (that) {
23420 that.locate("separator").each(function (index, elm) {
23421 var parent = elm.parentNode;
23422 $(elm).remove();
23423 // Because Node.normalize doesn't work properly in IE 11, we use a custom function
23424 // to normalize the text nodes in the parent.
23425 fluid.prefs.enactor.syllabification.normalize(parent);
23426 });
23427 };
23428
23429 fluid.prefs.enactor.syllabification.setPresentation = function (that, elm, state) {
23430 if (state) {
23431 that.parse(elm);
23432 } else {
23433 that.remove();
23434 }
23435 };
23436
23437 /**********************************************************************
23438 * Language Pattern File Configuration
23439 *
23440 *
23441 * Supplies the source paths for the language pattern files used to
23442 * separate words into their phonetic parts.
23443 **********************************************************************/
23444
23445 fluid.defaults("fluid.prefs.enactor.syllabification.patterns", {
23446 terms: {
23447 patternPrefix: "../../../lib/hypher/patterns"
23448 },
23449 patterns: {
23450 be: "%patternPrefix/bg.js",
23451 bn: "%patternPrefix/bn.js",
23452 ca: "%patternPrefix/ca.js",
23453 cs: "%patternPrefix/cs.js",
23454 da: "%patternPrefix/da.js",
23455 de: "%patternPrefix/de.js",
23456 el: "%patternPrefix/el-monoton.js",
23457 "el-monoton": "%patternPrefix/el-monoton.js",
23458 "el-polyton": "%patternPrefix/el-polyton.js",
23459 en: "%patternPrefix/en-us.js",
23460 "en-gb": "%patternPrefix/en-gb.js",
23461 "en-us": "%patternPrefix/en-us.js",
23462 es: "%patternPrefix/es.js",
23463 fi: "%patternPrefix/fi.js",
23464 fr: "%patternPrefix/fr.js",
23465 grc: "%patternPrefix/grc.js",
23466 gu: "%patternPrefix/gu.js",
23467 hi: "%patternPrefix/hi.js",
23468 hu: "%patternPrefix/hu.js",
23469 hy: "%patternPrefix/hy.js",
23470 is: "%patternPrefix/is.js",
23471 it: "%patternPrefix/it.js",
23472 kn: "%patternPrefix/kn.js",
23473 la: "%patternPrefix/la.js",
23474 lt: "%patternPrefix/lt.js",
23475 lv: "%patternPrefix/lv.js",
23476 ml: "%patternPrefix/ml.js",
23477 nb: "%patternPrefix/nb-no.js",
23478 "nb-no": "%patternPrefix/nb-no.js",
23479 no: "%patternPrefix/nb-no.js",
23480 nl: "%patternPrefix/nl.js",
23481 or: "%patternPrefix/or.js",
23482 pa: "%patternPrefix/pa.js",
23483 pl: "%patternPrefix/pl.js",
23484 pt: "%patternPrefix/pt.js",
23485 ru: "%patternPrefix/ru.js",
23486 sk: "%patternPrefix/sk.js",
23487 sl: "%patternPrefix/sl.js",
23488 sv: "%patternPrefix/sv.js",
23489 ta: "%patternPrefix/ta.js",
23490 te: "%patternPrefix/te.js",
23491 tr: "%patternPrefix/tr.js",
23492 uk: "%patternPrefix/uk.js"
23493 }
23494 });
23495})(jQuery, fluid_3_0_0);
23496;
23497/*
23498Copyright The Infusion copyright holders
23499See the AUTHORS.md file at the top-level directory of this distribution and at
23500https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
23501
23502Licensed under the Educational Community License (ECL), Version 2.0 or the New
23503BSD license. You may not use this file except in compliance with one these
23504Licenses.
23505
23506You may obtain a copy of the ECL 2.0 License and BSD License at
23507https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
23508*/
23509
23510var fluid_3_0_0 = fluid_3_0_0 || {};
23511
23512(function ($, fluid) {
23513 "use strict";
23514
23515 /*******************************************************************************
23516 * Localization
23517 *
23518 * The enactor to change the locale shown according to the value
23519 *
23520 * This grade is resolvable from the root to allow for setting up of model relays
23521 * from other components on a page that may want to be notified of a language
23522 * change and update their own UI automatically.
23523 *******************************************************************************/
23524
23525 fluid.defaults("fluid.prefs.enactor.localization", {
23526 gradeNames: ["fluid.prefs.enactor", "fluid.contextAware", "fluid.resolveRoot"],
23527 preferenceMap: {
23528 "fluid.prefs.localization": {
23529 "model.lang": "value"
23530 }
23531 },
23532 contextAwareness: {
23533 localeChange: {
23534 checks: {
23535 // This check determines if the enactor is being run inside of the separated panel's iframe.
23536 // At the moment, all enactors are copied into the iframe to apply settings to the panel as well.
23537 // However, the strings for the panel will be localized through the prefsEditorLoader and do not
23538 // require the iframe URL to change. When in the panel, we do not run the urlPathLocale changes.
23539 inPanel: {
23540 contextValue: "{iframeRenderer}.id",
23541 // The following undefined grade is needed to prevent the `urlPath` check from supplying its
23542 // grade even when the `inPanel` check passes.
23543 gradeNames: "fluid.prefs.enactor.localization.inPanel"
23544 },
23545 urlPath: {
23546 contextValue: "{localization}.options.localizationScheme",
23547 equals: "urlPath",
23548 gradeNames: "fluid.prefs.enactor.localization.urlPathLocale",
23549 priority: "after:inPanel"
23550 }
23551 }
23552 }
23553 }
23554 });
23555
23556 /*******************************************************************************
23557 * URL Path
23558 *
23559 * Changes the URL path to specify which language should be displayed. Useful
23560 * if languages are served at different URLs based on a language resource.
23561 * E.g. www.example.com/about/ -> www.example.com/fr/about/
23562 *******************************************************************************/
23563 fluid.defaults("fluid.prefs.enactor.localization.urlPathLocale", {
23564 langMap: {}, // must be supplied by integrator
23565 langSegValues: {
23566 expander: {
23567 funcName: "fluid.values",
23568 args: ["{that}.options.langMap"]
23569 }
23570 },
23571 // langSegIndex: 1, should be supplied by the integrator. Will default to 1 in `fluid.prefs.enactor.localization.urlPathLocale.updatePathname`
23572 modelRelay: [{
23573 target: "urlLangSeg",
23574 singleTransform: {
23575 type: "fluid.transforms.valueMapper",
23576 defaultInput: "{that}.model.lang",
23577 match: "{that}.options.langMap"
23578 }
23579 }],
23580 modelListeners: {
23581 urlLangSeg: {
23582 funcName: "{that}.updatePathname",
23583 args: ["{change}.value"],
23584 namespace: "updateURLPathname"
23585 }
23586 },
23587 invokers: {
23588 updatePathname: {
23589 funcName: "fluid.prefs.enactor.localization.urlPathLocale.updatePathname",
23590 args: ["{that}", "{arguments}.0", "{that}.options.langSegValues", "{that}.options.langSegIndex"]
23591 },
23592 getPathname: "fluid.prefs.enactor.localization.urlPathLocale.getPathname",
23593 setPathname: "fluid.prefs.enactor.localization.urlPathLocale.setPathname"
23594 }
23595 });
23596
23597 /**
23598 * A simple wrapper around the location.pathname getter.
23599 *
23600 * @return {String} - If the `pathname` argument is not provided, the current pathname is returned
23601 */
23602 fluid.prefs.enactor.localization.urlPathLocale.getPathname = function () {
23603 return location.pathname;
23604 };
23605
23606 /**
23607 * A simple wrapper around the location.pathname setter.
23608 *
23609 * @param {String} pathname - The pathname to set.
23610 */
23611 fluid.prefs.enactor.localization.urlPathLocale.setPathname = function (pathname) {
23612 location.pathname = pathname;
23613 };
23614
23615 /**
23616 * Modifies the URL Path to navigate to the specified localized version of the page. If the "default" language is
23617 * selected. The function exits without modifying the URL. This allows for the server to automatically, or the user
23618 * to manually, navigate to a localized page when a language preference hasn't been set.
23619 *
23620 * @param {Component} that - an instance of `fluid.prefs.enactor.localization.urlPathLocale`
23621 * @param {String} urlLangSeg - a language value used in the URL pathname
23622 * @param {Object} langSegValues - An array of the potential `urlLangSeg` values that can be set.
23623 * @param {Integer} langSegIndex - (Optional) An index into the path where the language resource identifier is held.
23624 * By default this value is 1, which represents the first path segment.
23625 */
23626 fluid.prefs.enactor.localization.urlPathLocale.updatePathname = function (that, urlLangSeg, langSegValues, langSegIndex) {
23627
23628 if (fluid.isValue(urlLangSeg)) {
23629 langSegIndex = langSegIndex || 1;
23630 var pathname = that.getPathname();
23631 var pathSegs = pathname.split("/");
23632
23633 var currentLang = pathSegs[langSegIndex];
23634 var hasLang = !!currentLang && langSegValues.indexOf(currentLang) >= 0;
23635
23636 if (hasLang) {
23637 if (urlLangSeg) {
23638 pathSegs[langSegIndex] = urlLangSeg;
23639 } else {
23640 if (langSegIndex === pathSegs.length - 1) {
23641 pathSegs.pop();
23642 } else {
23643 pathSegs.splice(langSegIndex, 1);
23644 }
23645 }
23646 } else if (urlLangSeg) {
23647 pathSegs.splice(langSegIndex, 0, urlLangSeg);
23648 }
23649
23650 var newPathname = pathSegs.join("/");
23651
23652 if (newPathname !== pathname) {
23653 that.setPathname(newPathname);
23654 }
23655 }
23656 };
23657
23658})(jQuery, fluid_3_0_0);
23659;
23660/*
23661Copyright The Infusion copyright holders
23662See the AUTHORS.md file at the top-level directory of this distribution and at
23663https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
23664
23665Licensed under the Educational Community License (ECL), Version 2.0 or the New
23666BSD license. You may not use this file except in compliance with one these
23667Licenses.
23668
23669You may obtain a copy of the ECL 2.0 License and BSD License at
23670https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
23671*/
23672
23673var fluid_3_0_0 = fluid_3_0_0 || {};
23674
23675(function ($, fluid) {
23676 "use strict";
23677
23678 /*******************************************************************************
23679 * wordSpace
23680 *
23681 * Sets the word space on the container to the number of units to increase
23682 * the word space by. If a negative number is provided, the space between
23683 * characters will decrease. Setting the value to 1 or unit to 0 will use the
23684 * default word space.
23685 *******************************************************************************/
23686
23687 // Note that the implementors need to provide the container for this view component
23688 fluid.defaults("fluid.prefs.enactor.wordSpace", {
23689 gradeNames: ["fluid.prefs.enactor.spacingSetter"],
23690 preferenceMap: {
23691 "fluid.prefs.wordSpace": {
23692 "model.value": "value"
23693 }
23694 },
23695 cssProp: "word-spacing"
23696 });
23697
23698})(jQuery, fluid_3_0_0);
23699;
23700/*
23701Copyright The Infusion copyright holders
23702See the AUTHORS.md file at the top-level directory of this distribution and at
23703https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
23704
23705Licensed under the Educational Community License (ECL), Version 2.0 or the New
23706BSD license. You may not use this file except in compliance with one these
23707Licenses.
23708
23709You may obtain a copy of the ECL 2.0 License and BSD License at
23710https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
23711*/
23712
23713var fluid_3_0_0 = fluid_3_0_0 || {};
23714
23715(function ($, fluid) {
23716 "use strict";
23717
23718 /*******************************************************************************
23719 * Starter prefsEditor Model
23720 *
23721 * Provides the default values for the starter prefsEditor model
23722 *******************************************************************************/
23723
23724 fluid.defaults("fluid.prefs.initialModel.starter", {
23725 gradeNames: ["fluid.prefs.initialModel"],
23726 members: {
23727 // TODO: This information is supposed to be generated from the JSON
23728 // schema describing various preferences. For now it's kept in top
23729 // level prefsEditor to avoid further duplication.
23730 initialModel: {
23731 preferences: {
23732 textFont: "default", // key from classname map
23733 theme: "default", // key from classname map
23734 textSize: 1, // in points
23735 lineSpace: 1, // in ems
23736 toc: false, // boolean
23737 inputs: false // boolean
23738 }
23739 }
23740 }
23741 });
23742
23743 /*******************************************************************************
23744 * CSSClassEnhancerBase
23745 *
23746 * Provides the map between the settings and css classes to be applied.
23747 * Used as a UIEnhancer base grade that can be pulled in as requestd.
23748 *******************************************************************************/
23749
23750 fluid.defaults("fluid.uiEnhancer.cssClassEnhancerBase", {
23751 gradeNames: ["fluid.component"],
23752 classnameMap: {
23753 "textFont": {
23754 "default": "",
23755 "times": "fl-font-times",
23756 "comic": "fl-font-comic-sans",
23757 "arial": "fl-font-arial",
23758 "verdana": "fl-font-verdana",
23759 "open-dyslexic": "fl-font-open-dyslexic"
23760 },
23761 "theme": {
23762 "default": "fl-theme-prefsEditor-default",
23763 "bw": "fl-theme-bw",
23764 "wb": "fl-theme-wb",
23765 "by": "fl-theme-by",
23766 "yb": "fl-theme-yb",
23767 "lgdg": "fl-theme-lgdg",
23768 "gd": "fl-theme-gd",
23769 "gw": "fl-theme-gw",
23770 "bbr": "fl-theme-bbr"
23771 },
23772 "inputs": "fl-input-enhanced"
23773 }
23774 });
23775
23776 /*******************************************************************************
23777 * BrowserTextEnhancerBase
23778 *
23779 * Provides the default font size translation between the strings and actual pixels.
23780 * Used as a UIEnhancer base grade that can be pulled in as requestd.
23781 *******************************************************************************/
23782
23783 fluid.defaults("fluid.uiEnhancer.browserTextEnhancerBase", {
23784 gradeNames: ["fluid.component"],
23785 fontSizeMap: {
23786 "xx-small": "9px",
23787 "x-small": "11px",
23788 "small": "13px",
23789 "medium": "15px",
23790 "large": "18px",
23791 "x-large": "23px",
23792 "xx-large": "30px"
23793 }
23794 });
23795
23796 /*******************************************************************************
23797 * UI Enhancer Starter Enactors
23798 *
23799 * A grade component for UIEnhancer. It is a collection of default UI Enhancer
23800 * action ants.
23801 *******************************************************************************/
23802
23803 fluid.defaults("fluid.uiEnhancer.starterEnactors", {
23804 gradeNames: ["fluid.uiEnhancer", "fluid.uiEnhancer.cssClassEnhancerBase", "fluid.uiEnhancer.browserTextEnhancerBase"],
23805 model: "{fluid.prefs.initialModel}.initialModel.preferences",
23806 components: {
23807 textSize: {
23808 type: "fluid.prefs.enactor.textSize",
23809 container: "{uiEnhancer}.container",
23810 options: {
23811 fontSizeMap: "{uiEnhancer}.options.fontSizeMap",
23812 model: {
23813 value: "{uiEnhancer}.model.textSize"
23814 }
23815 }
23816 },
23817 textFont: {
23818 type: "fluid.prefs.enactor.textFont",
23819 container: "{uiEnhancer}.container",
23820 options: {
23821 classes: "{uiEnhancer}.options.classnameMap.textFont",
23822 model: {
23823 value: "{uiEnhancer}.model.textFont"
23824 }
23825 }
23826 },
23827 lineSpace: {
23828 type: "fluid.prefs.enactor.lineSpace",
23829 container: "{uiEnhancer}.container",
23830 options: {
23831 fontSizeMap: "{uiEnhancer}.options.fontSizeMap",
23832 model: {
23833 value: "{uiEnhancer}.model.lineSpace"
23834 }
23835 }
23836 },
23837 contrast: {
23838 type: "fluid.prefs.enactor.contrast",
23839 container: "{uiEnhancer}.container",
23840 options: {
23841 classes: "{uiEnhancer}.options.classnameMap.theme",
23842 model: {
23843 value: "{uiEnhancer}.model.theme"
23844 }
23845 }
23846 },
23847 enhanceInputs: {
23848 type: "fluid.prefs.enactor.enhanceInputs",
23849 container: "{uiEnhancer}.container",
23850 options: {
23851 cssClass: "{uiEnhancer}.options.classnameMap.inputs",
23852 model: {
23853 value: "{uiEnhancer}.model.inputs"
23854 }
23855 }
23856 },
23857 tableOfContents: {
23858 type: "fluid.prefs.enactor.tableOfContents",
23859 container: "{uiEnhancer}.container",
23860 options: {
23861 tocTemplate: "{uiEnhancer}.options.tocTemplate",
23862 tocMessage: "{uiEnhancer}.options.tocMessage",
23863 model: {
23864 toc: "{uiEnhancer}.model.toc"
23865 }
23866 }
23867 }
23868 }
23869 });
23870
23871 /*********************************************************************************************************
23872 * Starter Settings Panels
23873 *
23874 * A collection of all the default Preferences Editorsetting panels.
23875 *********************************************************************************************************/
23876 fluid.defaults("fluid.prefs.starterPanels", {
23877 gradeNames: ["fluid.prefs.prefsEditor"],
23878 selectors: {
23879 textSize: ".flc-prefsEditor-text-size",
23880 textFont: ".flc-prefsEditor-text-font",
23881 lineSpace: ".flc-prefsEditor-line-space",
23882 contrast: ".flc-prefsEditor-contrast",
23883 layoutControls: ".flc-prefsEditor-layout-controls",
23884 enhanceInputs: ".flc-prefsEditor-enhanceInputs"
23885 },
23886 components: {
23887 textSize: {
23888 type: "fluid.prefs.panel.textSize",
23889 container: "{prefsEditor}.dom.textSize",
23890 createOnEvent: "onPrefsEditorMarkupReady",
23891 options: {
23892 gradeNames: "fluid.prefs.prefsEditorConnections",
23893 model: {
23894 value: "{prefsEditor}.model.preferences.textSize"
23895 },
23896 messageBase: "{messageLoader}.resources.textSize.resourceText",
23897 resources: {
23898 template: "{templateLoader}.resources.textSize"
23899 },
23900 step: 0.1,
23901 range: {
23902 min: 1,
23903 max: 2
23904 }
23905 }
23906 },
23907 lineSpace: {
23908 type: "fluid.prefs.panel.lineSpace",
23909 container: "{prefsEditor}.dom.lineSpace",
23910 createOnEvent: "onPrefsEditorMarkupReady",
23911 options: {
23912 gradeNames: "fluid.prefs.prefsEditorConnections",
23913 model: {
23914 value: "{prefsEditor}.model.preferences.lineSpace"
23915 },
23916 messageBase: "{messageLoader}.resources.lineSpace.resourceText",
23917 resources: {
23918 template: "{templateLoader}.resources.lineSpace"
23919 },
23920 step: 0.1,
23921 range: {
23922 min: 1,
23923 max: 2
23924 }
23925 }
23926 },
23927 textFont: {
23928 type: "fluid.prefs.panel.textFont",
23929 container: "{prefsEditor}.dom.textFont",
23930 createOnEvent: "onPrefsEditorMarkupReady",
23931 options: {
23932 gradeNames: "fluid.prefs.prefsEditorConnections",
23933 classnameMap: "{uiEnhancer}.options.classnameMap",
23934 model: {
23935 value: "{prefsEditor}.model.preferences.textFont"
23936 },
23937 messageBase: "{messageLoader}.resources.textFont.resourceText",
23938 resources: {
23939 template: "{templateLoader}.resources.textFont"
23940 }
23941 }
23942 },
23943 contrast: {
23944 type: "fluid.prefs.panel.contrast",
23945 container: "{prefsEditor}.dom.contrast",
23946 createOnEvent: "onPrefsEditorMarkupReady",
23947 options: {
23948 gradeNames: "fluid.prefs.prefsEditorConnections",
23949 classnameMap: "{uiEnhancer}.options.classnameMap",
23950 model: {
23951 value: "{prefsEditor}.model.preferences.theme"
23952 },
23953 messageBase: "{messageLoader}.resources.contrast.resourceText",
23954 resources: {
23955 template: "{templateLoader}.resources.contrast"
23956 }
23957 }
23958 },
23959 layoutControls: {
23960 type: "fluid.prefs.panel.layoutControls",
23961 container: "{prefsEditor}.dom.layoutControls",
23962 createOnEvent: "onPrefsEditorMarkupReady",
23963 options: {
23964 gradeNames: "fluid.prefs.prefsEditorConnections",
23965 model: {
23966 value: "{prefsEditor}.model.preferences.toc"
23967 },
23968 messageBase: "{messageLoader}.resources.layoutControls.resourceText",
23969 resources: {
23970 template: "{templateLoader}.resources.layoutControls"
23971 }
23972 }
23973 },
23974 enhanceInputs: {
23975 type: "fluid.prefs.panel.enhanceInputs",
23976 container: "{prefsEditor}.dom.enhanceInputs",
23977 createOnEvent: "onPrefsEditorMarkupReady",
23978 options: {
23979 gradeNames: "fluid.prefs.prefsEditorConnections",
23980 model: {
23981 value: "{prefsEditor}.model.preferences.inputs"
23982 },
23983 messageBase: "{messageLoader}.resources.enhanceInputs.resourceText",
23984 resources: {
23985 template: "{templateLoader}.resources.enhanceInputs"
23986 }
23987 }
23988 }
23989 }
23990 });
23991
23992 /******************************
23993 * Starter Template Loader
23994 ******************************/
23995
23996 /**
23997 * A template loader component that expands the resources blocks for loading resources used by starterPanels
23998 *
23999 * @param options {Object}
24000 */
24001
24002 fluid.defaults("fluid.prefs.starterTemplateLoader", {
24003 gradeNames: ["fluid.resourceLoader"],
24004 resources: {
24005 textSize: "%templatePrefix/PrefsEditorTemplate-textSize.html",
24006 lineSpace: "%templatePrefix/PrefsEditorTemplate-lineSpace.html",
24007 textFont: "%templatePrefix/PrefsEditorTemplate-textFont.html",
24008 contrast: "%templatePrefix/PrefsEditorTemplate-contrast.html",
24009 layoutControls: "%templatePrefix/PrefsEditorTemplate-layout.html",
24010 enhanceInputs: "%templatePrefix/PrefsEditorTemplate-enhanceInputs.html"
24011 }
24012 });
24013
24014 fluid.defaults("fluid.prefs.starterSeparatedPanelTemplateLoader", {
24015 gradeNames: ["fluid.prefs.starterTemplateLoader"],
24016 resources: {
24017 prefsEditor: "%templatePrefix/SeparatedPanelPrefsEditor.html"
24018 }
24019 });
24020
24021 fluid.defaults("fluid.prefs.starterFullPreviewTemplateLoader", {
24022 gradeNames: ["fluid.prefs.starterTemplateLoader"],
24023 resources: {
24024 prefsEditor: "%templatePrefix/FullPreviewPrefsEditor.html"
24025 }
24026 });
24027
24028 fluid.defaults("fluid.prefs.starterFullNoPreviewTemplateLoader", {
24029 gradeNames: ["fluid.prefs.starterTemplateLoader"],
24030 resources: {
24031 prefsEditor: "%templatePrefix/FullNoPreviewPrefsEditor.html"
24032 }
24033 });
24034
24035 /******************************
24036 * Starter Message Loader
24037 ******************************/
24038
24039 /**
24040 * A message loader component that expands the resources blocks for loading messages for starter panels
24041 *
24042 * @param options {Object}
24043 */
24044 fluid.defaults("fluid.prefs.starterMessageLoader", {
24045 gradeNames: ["fluid.resourceLoader"],
24046 resources: {
24047 prefsEditor: "%messagePrefix/prefsEditor.json",
24048 textSize: "%messagePrefix/textSize.json",
24049 textFont: "%messagePrefix/textFont.json",
24050 lineSpace: "%messagePrefix/lineSpace.json",
24051 contrast: "%messagePrefix/contrast.json",
24052 layoutControls: "%messagePrefix/tableOfContents.json",
24053 enhanceInputs: "%messagePrefix/enhanceInputs.json"
24054 }
24055 });
24056
24057})(jQuery, fluid_3_0_0);
24058;
24059/*
24060Copyright The Infusion copyright holders
24061See the AUTHORS.md file at the top-level directory of this distribution and at
24062https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
24063
24064Licensed under the Educational Community License (ECL), Version 2.0 or the New
24065BSD license. You may not use this file except in compliance with one these
24066Licenses.
24067
24068You may obtain a copy of the ECL 2.0 License and BSD License at
24069https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
24070*/
24071
24072var fluid_3_0_0 = fluid_3_0_0 || {};
24073
24074(function ($, fluid) {
24075 "use strict";
24076
24077 /************************************************************************************
24078 * Scrolling Panel Prefs Editor: *
24079 * This is a mixin grade to be applied to a fluid.prefs.prefsEditor type component. *
24080 * Typically used for responsive small screen presentations of the separated panel *
24081 * to allow for scrolling by clicking on left/right arrows *
24082 ************************************************************************************/
24083
24084 fluid.defaults("fluid.prefs.arrowScrolling", {
24085 gradeNames: ["fluid.modelComponent"],
24086 selectors: {
24087 // panels: "", // should be supplied by the fluid.prefs.prefsEditor grade.
24088 scrollContainer: ".flc-prefsEditor-scrollContainer"
24089 },
24090 onScrollDelay: 100, // in ms, used to set the delay for debouncing the scroll event relay
24091 model: {
24092 // panelMaxIndex: null, // determined by the number of panels calculated after the onPrefsEditorMarkupReady event fired
24093
24094 // Due to FLUID-6249 ( https://issues.fluidproject.org/browse/FLUID-6249 ) the default value for panelIndex
24095 // needs to be commented out or it will interfere with reading in the panelIndex value saved in the store.
24096 // panelIndex: 0 // the index of the panel to open on
24097 },
24098 events: {
24099 beforeReset: null, // should be fired by the fluid.prefs.prefsEditor grade
24100 onScroll: null
24101 },
24102 modelRelay: {
24103 target: "panelIndex",
24104 forward: {excludeSource: "init"},
24105 namespace: "limitPanelIndex",
24106 singleTransform: {
24107 type: "fluid.transforms.limitRange",
24108 input: "{that}.model.panelIndex",
24109 min: 0,
24110 max: "{that}.model.panelMaxIndex"
24111 }
24112 },
24113 modelListeners: {
24114 "panelIndex": {
24115 listener: "fluid.prefs.arrowScrolling.scrollToPanel",
24116 args: ["{that}", "{change}.value"],
24117 excludeSource: ["scrollEvent"],
24118 namespace: "scrollToPanel"
24119 }
24120 },
24121 listeners: {
24122 "onReady.scrollEvent": {
24123 "this": "{that}.dom.scrollContainer",
24124 method: "scroll",
24125 args: [{
24126 expander: {
24127 // Relaying the scroll event to onScroll but debounced to reduce the rate of fire. A high rate
24128 // of fire may negatively effect performance for complex handlers.
24129 func: "fluid.debounce",
24130 args: ["{that}.events.onScroll.fire", "{that}.options.onScrollDelay"]
24131 }
24132 }]
24133 },
24134 "onReady.windowResize": {
24135 "this": window,
24136 method: "addEventListener",
24137 args: ["resize", "{that}.events.onSignificantDOMChange.fire"]
24138 },
24139 "onDestroy.removeWindowResize": {
24140 "this": window,
24141 method: "removeEventListener",
24142 args: ["resize", "{that}.events.onSignificantDOMChange.fire"]
24143 },
24144 // Need to set panelMaxIndex after onPrefsEditorMarkupReady to ensure that the template has been
24145 // rendered before we try to get the number of panels.
24146 "onPrefsEditorMarkupReady.setPanelMaxIndex": {
24147 changePath: "panelMaxIndex",
24148 value: {
24149 expander: {
24150 funcName: "fluid.prefs.arrowScrolling.calculatePanelMaxIndex",
24151 args: ["{that}.dom.panels"]
24152 }
24153 }
24154 },
24155 "beforeReset.resetPanelIndex": {
24156 listener: "{that}.applier.fireChangeRequest",
24157 args: {path: "panelIndex", value: 0, type: "ADD", source: "reset"}
24158 },
24159 "onScroll.setPanelIndex": {
24160 changePath: "panelIndex",
24161 value: {
24162 expander: {
24163 funcName: "fluid.prefs.arrowScrolling.getClosestPanelIndex",
24164 args: "{that}.dom.panels"
24165 }
24166 },
24167 source: "scrollEvent"
24168 }
24169 },
24170 invokers: {
24171 eventToScrollIndex: {
24172 funcName: "fluid.prefs.arrowScrolling.eventToScrollIndex",
24173 args: ["{that}", "{arguments}.0"]
24174 }
24175 },
24176 distributeOptions: {
24177 "arrowScrolling.panel.listeners.bindScrollArrows": {
24178 record: {
24179 "afterRender.bindScrollArrows": {
24180 "this": "{that}.dom.header",
24181 method: "click",
24182 args: ["{prefsEditor}.eventToScrollIndex"]
24183 }
24184 },
24185 target: "{that > fluid.prefs.panel}.options.listeners"
24186 }
24187 }
24188 });
24189
24190 fluid.prefs.arrowScrolling.calculatePanelMaxIndex = function (panels) {
24191 return Math.max(0, panels.length - 1);
24192 };
24193
24194 fluid.prefs.arrowScrolling.eventToScrollIndex = function (that, event) {
24195 event.preventDefault();
24196 var target = $(event.target);
24197 var midPoint = target.width() / 2;
24198 var currentIndex = that.model.panelIndex || 0;
24199 var scrollToIndex = currentIndex + (event.offsetX < midPoint ? -1 : 1);
24200 that.applier.change("panelIndex", scrollToIndex, "ADD", "eventToScrollIndex");
24201 };
24202
24203 fluid.prefs.arrowScrolling.scrollToPanel = function (that, panelIndex) {
24204 panelIndex = panelIndex || 0;
24205 var panels = that.locate("panels");
24206 var scrollContainer = that.locate("scrollContainer");
24207 var panel = panels.eq(panelIndex);
24208
24209 // only attempt to scroll the container if the panel exists and has been rendered.
24210 if (panel.width()) {
24211 scrollContainer.scrollLeft(scrollContainer.scrollLeft() + panels.eq(panelIndex).offset().left);
24212 }
24213 };
24214
24215 fluid.prefs.arrowScrolling.getClosestPanelIndex = function (panels) {
24216 var panelArray = fluid.transform(panels, function (panel, idx) {
24217 return {
24218 index: idx,
24219 offset: Math.abs($(panel).offset().left)
24220 };
24221 });
24222 panelArray.sort(function (a, b) {
24223 return a.offset - b.offset;
24224 });
24225 return fluid.get(panelArray, ["0", "index"]) || 0;
24226 };
24227
24228})(jQuery, fluid_3_0_0);
24229;
24230/*
24231Copyright The Infusion copyright holders
24232See the AUTHORS.md file at the top-level directory of this distribution and at
24233https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
24234
24235Licensed under the Educational Community License (ECL), Version 2.0 or the New
24236BSD license. You may not use this file except in compliance with one these
24237Licenses.
24238
24239You may obtain a copy of the ECL 2.0 License and BSD License at
24240https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
24241*/
24242
24243var fluid_3_0_0 = fluid_3_0_0 || {};
24244
24245(function ($, fluid) {
24246 "use strict";
24247
24248 fluid.registerNamespace("fluid.dom");
24249
24250 fluid.dom.getDocumentHeight = function (dokkument) {
24251 var body = $("body", dokkument)[0];
24252 return body.offsetHeight;
24253 };
24254
24255 /*******************************************************
24256 * Separated Panel Preferences Editor Top Level Driver *
24257 *******************************************************/
24258
24259 fluid.defaults("fluid.prefs.separatedPanel", {
24260 gradeNames: ["fluid.prefs.prefsEditorLoader", "fluid.contextAware"],
24261 events: {
24262 afterRender: null,
24263 onReady: null,
24264 onCreateSlidingPanelReady: {
24265 events: {
24266 iframeRendered: "afterRender",
24267 onPrefsEditorMessagesLoaded: "onPrefsEditorMessagesLoaded"
24268 }
24269 },
24270 templatesAndIframeReady: {
24271 events: {
24272 iframeReady: "afterRender",
24273 templatesLoaded: "onPrefsEditorTemplatesLoaded",
24274 messagesLoaded: "onPrefsEditorMessagesLoaded"
24275 }
24276 }
24277 },
24278 lazyLoad: false,
24279 contextAwareness: {
24280 lazyLoad: {
24281 checks: {
24282 lazyLoad: {
24283 contextValue: "{fluid.prefs.separatedPanel}.options.lazyLoad",
24284 gradeNames: "fluid.prefs.separatedPanel.lazyLoad"
24285 }
24286 }
24287 }
24288 },
24289 selectors: {
24290 reset: ".flc-prefsEditor-reset",
24291 iframe: ".flc-prefsEditor-iframe"
24292 },
24293 listeners: {
24294 "onReady.bindEvents": {
24295 listener: "fluid.prefs.separatedPanel.bindEvents",
24296 args: ["{separatedPanel}.prefsEditor", "{iframeRenderer}.iframeEnhancer", "{separatedPanel}"]
24297 },
24298 "onCreate.hideReset": {
24299 listener: "fluid.prefs.separatedPanel.hideReset",
24300 args: ["{separatedPanel}"]
24301 }
24302 },
24303 invokers: {
24304 bindReset: {
24305 funcName: "fluid.bind",
24306 args: ["{separatedPanel}.dom.reset", "click", "{arguments}.0"]
24307 }
24308 },
24309 components: {
24310 slidingPanel: {
24311 type: "fluid.slidingPanel",
24312 container: "{separatedPanel}.container",
24313 createOnEvent: "onCreateSlidingPanelReady",
24314 options: {
24315 gradeNames: ["fluid.prefs.msgLookup"],
24316 strings: {
24317 showText: "{that}.msgLookup.slidingPanelShowText",
24318 hideText: "{that}.msgLookup.slidingPanelHideText",
24319 showTextAriaLabel: "{that}.msgLookup.showTextAriaLabel",
24320 hideTextAriaLabel: "{that}.msgLookup.hideTextAriaLabel",
24321 panelLabel: "{that}.msgLookup.slidingPanelPanelLabel"
24322 },
24323 invokers: {
24324 operateShow: {
24325 funcName: "fluid.prefs.separatedPanel.showPanel",
24326 args: ["{that}.dom.panel", "{that}.events.afterPanelShow.fire"],
24327 // override default implementation
24328 "this": null,
24329 "method": null
24330 },
24331 operateHide: {
24332 funcName: "fluid.prefs.separatedPanel.hidePanel",
24333 args: ["{that}.dom.panel", "{iframeRenderer}.iframe", "{that}.events.afterPanelHide.fire"],
24334 // override default implementation
24335 "this": null,
24336 "method": null
24337 }
24338 },
24339 components: {
24340 msgResolver: {
24341 type: "fluid.messageResolver",
24342 options: {
24343 messageBase: "{messageLoader}.resources.prefsEditor.resourceText"
24344 }
24345 }
24346 }
24347 }
24348 },
24349 iframeRenderer: {
24350 type: "fluid.prefs.separatedPanel.renderIframe",
24351 container: "{separatedPanel}.dom.iframe",
24352 options: {
24353 events: {
24354 afterRender: "{separatedPanel}.events.afterRender"
24355 },
24356 components: {
24357 iframeEnhancer: {
24358 type: "fluid.uiEnhancer",
24359 container: "{iframeRenderer}.renderPrefsEditorContainer",
24360 createOnEvent: "afterRender",
24361 options: {
24362 gradeNames: ["{pageEnhancer}.uiEnhancer.options.userGrades"],
24363 jQuery: "{iframeRenderer}.jQuery",
24364 tocTemplate: "{pageEnhancer}.uiEnhancer.options.tocTemplate",
24365 inSeparatedPanel: true
24366 }
24367 }
24368 }
24369 }
24370 },
24371 prefsEditor: {
24372 createOnEvent: "templatesAndIframeReady",
24373 container: "{iframeRenderer}.renderPrefsEditorContainer",
24374 options: {
24375 gradeNames: ["fluid.prefs.uiEnhancerRelay", "fluid.prefs.arrowScrolling"],
24376 // ensure that model and applier are available to users at top level
24377 model: {
24378 preferences: "{separatedPanel}.model.preferences",
24379 panelIndex: "{separatedPanel}.model.panelIndex",
24380 panelMaxIndex: "{separatedPanel}.model.panelMaxIndex",
24381 // The `local` model path is used by the `fluid.remoteModelComponent` grade
24382 // for persisting and synchronizing model values with remotely stored data.
24383 // Below, the panelIndex is being tracked for such persistence and synchronization.
24384 local: {
24385 panelIndex: "{that}.model.panelIndex"
24386 }
24387 },
24388 autoSave: true,
24389 events: {
24390 onSignificantDOMChange: null,
24391 updateEnhancerModel: "{that}.events.modelChanged"
24392 },
24393 modelListeners: {
24394 "panelIndex": [{
24395 listener: "fluid.prefs.prefsEditor.handleAutoSave",
24396 args: ["{that}"],
24397 namespace: "autoSavePanelIndex"
24398 }]
24399 },
24400 listeners: {
24401 "onCreate.bindReset": {
24402 listener: "{separatedPanel}.bindReset",
24403 args: ["{that}.reset"]
24404 },
24405 "afterReset.applyChanges": "{that}.applyChanges",
24406 // Scroll to active panel after opening the separate Panel.
24407 // This is when the panels are all rendered and the actual sizes are available.
24408 "{separatedPanel}.slidingPanel.events.afterPanelShow": {
24409 listener: "fluid.prefs.arrowScrolling.scrollToPanel",
24410 args: ["{that}", "{that}.model.panelIndex"],
24411 priority: "after:updateView",
24412 namespace: "scrollToPanel"
24413 }
24414 }
24415 }
24416 }
24417 },
24418 outerEnhancerOptions: "{originalEnhancerOptions}.options.originalUserOptions",
24419 distributeOptions: {
24420 "separatedPanel.slidingPanel": {
24421 source: "{that}.options.slidingPanel",
24422 removeSource: true,
24423 target: "{that > slidingPanel}.options"
24424 },
24425 "separatedPanel.iframeRenderer": {
24426 source: "{that}.options.iframeRenderer",
24427 removeSource: true,
24428 target: "{that > iframeRenderer}.options"
24429 },
24430 "separatedPanel.iframeRendered.terms": {
24431 source: "{that}.options.terms",
24432 target: "{that > iframeRenderer}.options.terms"
24433 },
24434 "separatedPanel.selectors.iframe": {
24435 source: "{that}.options.iframe",
24436 removeSource: true,
24437 target: "{that}.options.selectors.iframe"
24438 },
24439 "separatedPanel.iframeEnhancer.outerEnhancerOptions": {
24440 source: "{that}.options.outerEnhancerOptions",
24441 removeSource: true,
24442 target: "{that iframeEnhancer}.options"
24443 }
24444 }
24445 });
24446
24447 fluid.prefs.separatedPanel.hideReset = function (separatedPanel) {
24448 separatedPanel.locate("reset").hide();
24449 };
24450
24451 /*****************************************
24452 * fluid.prefs.separatedPanel.renderIframe *
24453 *****************************************/
24454
24455 fluid.defaults("fluid.prefs.separatedPanel.renderIframe", {
24456 gradeNames: ["fluid.viewComponent"],
24457 events: {
24458 afterRender: null
24459 },
24460 styles: {
24461 container: "fl-prefsEditor-separatedPanel-iframe"
24462 },
24463 terms: {
24464 templatePrefix: "."
24465 },
24466 markupProps: {
24467 "class": "flc-iframe",
24468 src: "%templatePrefix/SeparatedPanelPrefsEditorFrame.html"
24469 },
24470 listeners: {
24471 "onCreate.startLoadingIframe": "fluid.prefs.separatedPanel.renderIframe.startLoadingIframe"
24472 }
24473 });
24474
24475 fluid.prefs.separatedPanel.renderIframe.startLoadingIframe = function (that) {
24476 var styles = that.options.styles;
24477 // TODO: get earlier access to templateLoader,
24478 that.options.markupProps.src = fluid.stringTemplate(that.options.markupProps.src, that.options.terms);
24479 that.iframeSrc = that.options.markupProps.src;
24480
24481 // Create iframe and append to container
24482 that.iframe = $("<iframe/>");
24483 that.iframe.on("load", function () {
24484 var iframeWindow = that.iframe[0].contentWindow;
24485 that.iframeDocument = iframeWindow.document;
24486 // The iframe should prefer its own version of jQuery if a separate
24487 // one is loaded
24488 that.jQuery = iframeWindow.jQuery || $;
24489
24490 that.renderPrefsEditorContainer = that.jQuery("body", that.iframeDocument);
24491 that.jQuery(that.iframeDocument).ready(that.events.afterRender.fire);
24492 });
24493 that.iframe.attr(that.options.markupProps);
24494
24495 that.iframe.addClass(styles.container);
24496 that.iframe.hide();
24497
24498 that.iframe.appendTo(that.container);
24499 };
24500
24501 fluid.prefs.separatedPanel.updateView = function (prefsEditor) {
24502 prefsEditor.events.onPrefsEditorRefresh.fire();
24503 prefsEditor.events.onSignificantDOMChange.fire();
24504 };
24505
24506
24507 fluid.prefs.separatedPanel.bindEvents = function (prefsEditor, iframeEnhancer, separatedPanel) {
24508 // FLUID-5740: This binding should be done declaratively - needs ginger world in order to bind onto slidingPanel
24509 // which is a child of this component
24510
24511 var separatedPanelId = separatedPanel.slidingPanel.panelId;
24512 separatedPanel.locate("reset").attr({
24513 "aria-controls": separatedPanelId,
24514 "role": "button"
24515 });
24516
24517 separatedPanel.slidingPanel.events.afterPanelShow.addListener(function () {
24518 fluid.prefs.separatedPanel.updateView(prefsEditor);
24519 }, "updateView", "after:openPanel");
24520
24521 prefsEditor.events.onPrefsEditorRefresh.addListener(function () {
24522 iframeEnhancer.updateModel(prefsEditor.model.preferences);
24523 }, "updateModel");
24524 prefsEditor.events.afterReset.addListener(function (prefsEditor) {
24525 fluid.prefs.separatedPanel.updateView(prefsEditor);
24526 }, "updateView");
24527 prefsEditor.events.onSignificantDOMChange.addListener(function () {
24528 // ensure that the panel is open before trying to adjust its height
24529 if ( fluid.get(separatedPanel, "slidingPanel.model.isShowing") ) {
24530 var dokkument = prefsEditor.container[0].ownerDocument;
24531 var height = fluid.dom.getDocumentHeight(dokkument);
24532 var iframe = separatedPanel.iframeRenderer.iframe;
24533 var attrs = {height: height};
24534 var panel = separatedPanel.slidingPanel.locate("panel");
24535 panel.css({height: ""});
24536 iframe.clearQueue();
24537 iframe.animate(attrs, 400);
24538 }
24539 }, "adjustHeight");
24540
24541 separatedPanel.slidingPanel.events.afterPanelHide.addListener(function () {
24542 separatedPanel.iframeRenderer.iframe.height(0);
24543
24544 // Prevent the hidden Preferences Editorpanel from being keyboard and screen reader accessible
24545 separatedPanel.iframeRenderer.iframe.hide();
24546 }, "collapseFrame");
24547 separatedPanel.slidingPanel.events.afterPanelShow.addListener(function () {
24548 separatedPanel.iframeRenderer.iframe.show();
24549
24550 // FLUID-6183: Required for bug in MS EDGE that clips off the bottom of adjusters
24551 // The height needs to be recalculated in order for the panel to show up completely
24552 separatedPanel.iframeRenderer.iframe.height();
24553 separatedPanel.locate("reset").show();
24554 }, "openPanel");
24555 separatedPanel.slidingPanel.events.onPanelHide.addListener(function () {
24556 separatedPanel.locate("reset").hide();
24557 }, "hideReset");
24558 };
24559
24560 // Replace the standard animator since we don't want the panel to become hidden
24561 // (potential cause of jumping)
24562 fluid.prefs.separatedPanel.hidePanel = function (panel, iframe, callback) {
24563 iframe.clearQueue(); // FLUID-5334: clear the animation queue
24564 $(panel).animate({height: 0}, {duration: 400, complete: callback});
24565 };
24566
24567 // no activity - the kickback to the updateView listener will automatically trigger the
24568 // DOMChangeListener above. This ordering is preferable to avoid causing the animation to
24569 // jump by refreshing the view inside the iframe
24570 fluid.prefs.separatedPanel.showPanel = function (panel, callback) {
24571 // A bizarre race condition has emerged under FF where the iframe held within the panel does not
24572 // react synchronously to being shown
24573 fluid.invokeLater(callback);
24574 };
24575
24576 /**
24577 * FLUID-5926: Some of our users have asked for ways to improve the initial page load
24578 * performance when using the separated panel prefs editor / UI Options. One option,
24579 * provided here, is to implement a scheme for lazy loading the instantiation of the
24580 * prefs editor, only instantiating enough of the workflow to allow display the
24581 * sliding panel tab.
24582 *
24583 * fluid.prefs.separatedPanel.lazyLoad modifies the typical separatedPanel workflow
24584 * by delaying the instantiation and loading of resources for the prefs editor until
24585 * the first time it is opened.
24586 *
24587 * Lazy Load Workflow:
24588 *
24589 * - On instantiation of the prefsEditorLoader only the messageLoader and slidingPanel are instantiated
24590 * - On instantiation, the messageLoader only loads preLoadResources, these are the messages required by
24591 * the slidingPanel. The remaining message bundles will not be loaded until the "onLazyLoad" event is fired.
24592 * - After the preLoadResources have been loaded, the onPrefsEditorMessagesPreloaded event is fired, and triggers the
24593 * sliding panel to instantiate.
24594 * - When a user opens the separated panel prefs editor / UI Options, it checks to see if the prefs editor has been
24595 * instantiated. If it hasn't, a listener is temporarily bound to the onReady event, which gets fired
24596 * after the prefs editor is ready. This is used to continue the process of opening the sliding panel for the first time.
24597 * Additionally the onLazyLoad event is fired, which kicks off the remainder of the instantiation process.
24598 * - onLazyLoad triggers the templateLoader to fetch all of the templates and the messageLoader to fetch the remaining
24599 * message bundles. From here the standard instantiation workflow takes place.
24600 */
24601 fluid.defaults("fluid.prefs.separatedPanel.lazyLoad", {
24602 events: {
24603 onLazyLoad: null,
24604 onPrefsEditorMessagesPreloaded: null,
24605 onCreateSlidingPanelReady: {
24606 events: {
24607 onPrefsEditorMessagesLoaded: "onPrefsEditorMessagesPreloaded"
24608 }
24609 },
24610 templatesAndIframeReady: {
24611 events: {
24612 onLazyLoad: "onLazyLoad"
24613 }
24614 }
24615 },
24616 components: {
24617 templateLoader: {
24618 createOnEvent: "onLazyLoad"
24619 },
24620 messageLoader: {
24621 options: {
24622 events: {
24623 onResourcesPreloaded: "{separatedPanel}.events.onPrefsEditorMessagesPreloaded"
24624 },
24625 preloadResources: "prefsEditor",
24626 listeners: {
24627 "onCreate.loadResources": {
24628 listener: "fluid.prefs.separatedPanel.lazyLoad.preloadResources",
24629 args: ["{that}", {expander: {func: "{that}.resolveResources"}}, "{that}.options.preloadResources"]
24630 },
24631 "{separatedPanel}.events.onLazyLoad": {
24632 listener: "fluid.resourceLoader.loadResources",
24633 args: ["{messageLoader}", {expander: {func: "{messageLoader}.resolveResources"}}],
24634 namespace: "loadResources"
24635 }
24636 }
24637 }
24638 },
24639 slidingPanel: {
24640 options: {
24641 invokers: {
24642 operateShow: {
24643 funcName: "fluid.prefs.separatedPanel.lazyLoad.showPanel",
24644 args: ["{separatedPanel}", "{that}.events.afterPanelShow.fire"]
24645 }
24646 }
24647 }
24648 }
24649 }
24650 });
24651
24652 fluid.prefs.separatedPanel.lazyLoad.showPanel = function (separatedPanel, callback) {
24653 if (separatedPanel.prefsEditor) {
24654 fluid.invokeLater(callback);
24655 } else {
24656 separatedPanel.events.onReady.addListener(function (that) {
24657 that.events.onReady.removeListener("showPanelCallBack");
24658 fluid.invokeLater(callback);
24659 }, "showPanelCallBack");
24660 separatedPanel.events.onLazyLoad.fire();
24661 }
24662
24663 };
24664
24665 /**
24666 * Used to override the standard "onCreate.loadResources" listener for fluid.resourceLoader component,
24667 * allowing for pre-loading of a subset of resources. This is required for the lazyLoading workflow
24668 * for the "fluid.prefs.separatedPanel.lazyLoad".
24669 *
24670 * @param {Object} that - the component
24671 * @param {Object} resources - all of the resourceSpecs to load, including preload and others.
24672 * see: fluid.fetchResources
24673 * @param {Array|String} toPreload - a String or an String[]s corresponding to the names
24674 * of the resources, supplied in the resource argument, that
24675 * should be loaded. Only these resources will be loaded.
24676 */
24677 fluid.prefs.separatedPanel.lazyLoad.preloadResources = function (that, resources, toPreload) {
24678 toPreload = fluid.makeArray(toPreload);
24679 var preloadResources = {};
24680
24681 fluid.each(toPreload, function (resourceName) {
24682 preloadResources[resourceName] = resources[resourceName];
24683 });
24684
24685 // This portion of code was copied from fluid.resourceLoader.loadResources
24686 // and will likely need to track any changes made there.
24687 fluid.fetchResources(preloadResources, function () {
24688 that.resources = preloadResources;
24689 that.events.onResourcesPreloaded.fire(preloadResources);
24690 });
24691 };
24692
24693})(jQuery, fluid_3_0_0);
24694;
24695/*
24696Copyright The Infusion copyright holders
24697See the AUTHORS.md file at the top-level directory of this distribution and at
24698https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
24699
24700Licensed under the Educational Community License (ECL), Version 2.0 or the New
24701BSD license. You may not use this file except in compliance with one these
24702Licenses.
24703
24704You may obtain a copy of the ECL 2.0 License and BSD License at
24705https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
24706*/
24707
24708var fluid_3_0_0 = fluid_3_0_0 || {};
24709
24710(function ($, fluid) {
24711 "use strict";
24712
24713 /**************************************
24714 * Full No Preview Preferences Editor *
24715 **************************************/
24716
24717 fluid.defaults("fluid.prefs.fullNoPreview", {
24718 gradeNames: ["fluid.prefs.prefsEditorLoader"],
24719 components: {
24720 prefsEditor: {
24721 container: "{that}.container",
24722 options: {
24723 listeners: {
24724 "afterReset.applyChanges": {
24725 listener: "{that}.applyChanges"
24726 },
24727 "afterReset.save": {
24728 listener: "{that}.save",
24729 priority: "after:applyChanges"
24730 }
24731 }
24732 }
24733 }
24734 },
24735 events: {
24736 onReady: null
24737 }
24738 });
24739
24740})(jQuery, fluid_3_0_0);
24741;
24742/*
24743Copyright The Infusion copyright holders
24744See the AUTHORS.md file at the top-level directory of this distribution and at
24745https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
24746
24747Licensed under the Educational Community License (ECL), Version 2.0 or the New
24748BSD license. You may not use this file except in compliance with one these
24749Licenses.
24750
24751You may obtain a copy of the ECL 2.0 License and BSD License at
24752https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
24753*/
24754
24755var fluid_3_0_0 = fluid_3_0_0 || {};
24756
24757(function ($, fluid) {
24758 "use strict";
24759
24760 /***********************************
24761 * Full Preview Preferences Editor *
24762 ***********************************/
24763
24764 fluid.defaults("fluid.prefs.fullPreview", {
24765 gradeNames: ["fluid.prefs.prefsEditorLoader"],
24766 outerUiEnhancerOptions: "{originalEnhancerOptions}.options.originalUserOptions",
24767 outerUiEnhancerGrades: "{originalEnhancerOptions}.uiEnhancer.options.userGrades",
24768 components: {
24769 prefsEditor: {
24770 container: "{that}.container",
24771 options: {
24772 components: {
24773 preview: {
24774 type: "fluid.prefs.preview",
24775 createOnEvent: "onReady",
24776 container: "{prefsEditor}.dom.previewFrame",
24777 options: {
24778 listeners: {
24779 "onReady.boilOnPreviewReady": "{fullPreview}.events.onPreviewReady"
24780 }
24781 }
24782 }
24783 },
24784 listeners: {
24785 "onReady.boil": {
24786 listener: "{prefsEditorLoader}.events.onPrefsEditorReady"
24787 }
24788 },
24789 distributeOptions: {
24790 "fullPreview.prefsEditor.preview": {
24791 source: "{that}.options.preview",
24792 removeSource: true,
24793 target: "{that > preview}.options"
24794 }
24795 }
24796 }
24797 }
24798 },
24799 events: {
24800 onPrefsEditorReady: null,
24801 onPreviewReady: null,
24802 onReady: {
24803 events: {
24804 onPrefsEditorReady: "onPrefsEditorReady",
24805 onPreviewReady: "onPreviewReady"
24806 },
24807 args: "{that}"
24808 }
24809 },
24810 distributeOptions: {
24811 "fullPreview.enhancer.outerUiEnhancerOptions": {
24812 source: "{that}.options.outerUiEnhancerOptions",
24813 target: "{that enhancer}.options"
24814 },
24815 "fullPreview.enhancer.previewEnhancer": {
24816 source: "{that}.options.previewEnhancer",
24817 target: "{that enhancer}.options"
24818 },
24819 "fullPreviw.preview": {
24820 source: "{that}.options.preview",
24821 target: "{that preview}.options"
24822 },
24823 "fullPreview.enhancer.outerUiEnhancerGrades": {
24824 source: "{that}.options.outerUiEnhancerGrades",
24825 target: "{that enhancer}.options.gradeNames"
24826 }
24827 }
24828 });
24829
24830})(jQuery, fluid_3_0_0);
24831;
24832/*
24833Copyright The Infusion copyright holders
24834See the AUTHORS.md file at the top-level directory of this distribution and at
24835https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
24836
24837Licensed under the Educational Community License (ECL), Version 2.0 or the New
24838BSD license. You may not use this file except in compliance with one these
24839Licenses.
24840
24841You may obtain a copy of the ECL 2.0 License and BSD License at
24842https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
24843*/
24844
24845var fluid_3_0_0 = fluid_3_0_0 || {};
24846
24847(function ($, fluid) {
24848 "use strict";
24849
24850 fluid.registerNamespace("fluid.prefs.schemas");
24851
24852 /**
24853 * A custom merge policy that merges primary schema blocks and
24854 * places them in the right location (consistent with the JSON schema
24855 * format).
24856 * @param {JSON} target - A base for merging the options.
24857 * @param {JSON} source - Options being merged.
24858 * @return {JSON} - The updated target.
24859 */
24860 fluid.prefs.schemas.merge = function (target, source) {
24861 if (!target) {
24862 target = {
24863 type: "object",
24864 properties: {}
24865 };
24866 }
24867 // We can handle both schema blocks in options directly and also inside
24868 // the |properties| field.
24869 source = source.properties || source;
24870 $.extend(true, target.properties, source);
24871 return target;
24872 };
24873
24874 /*******************************************************************************
24875 * Primary builder grade
24876 *******************************************************************************/
24877
24878 fluid.defaults("fluid.prefs.primaryBuilder", {
24879 gradeNames: ["fluid.component", "{that}.buildPrimary"],
24880 // An index of all schema grades registered with the framework.
24881 schemaIndex: {
24882 expander: {
24883 func: "fluid.indexDefaults",
24884 args: ["schemaIndex", {
24885 gradeNames: "fluid.prefs.schemas",
24886 indexFunc: "fluid.prefs.primaryBuilder.defaultSchemaIndexer"
24887 }]
24888 }
24889 },
24890 primarySchema: {},
24891 // A list of all necessarry top level preference names.
24892 typeFilter: [],
24893 invokers: {
24894 // An invoker used to generate a set of grades that comprise a
24895 // final version of the primary schema to be used by the PrefsEditor
24896 // builder.
24897 buildPrimary: {
24898 funcName: "fluid.prefs.primaryBuilder.buildPrimary",
24899 args: [
24900 "{that}.options.schemaIndex",
24901 "{that}.options.typeFilter",
24902 "{that}.options.primarySchema"
24903 ]
24904 }
24905 }
24906 });
24907
24908 /**
24909 * An invoker method that builds a list of grades that comprise a final version of the primary schema.
24910 * @param {JSON} schemaIndex - A global index of all schema grades registered with the framework.
24911 * @param {Array} typeFilter - A list of all necessarry top level preference names.
24912 * @param {JSON} primarySchema - Primary schema provided as an option to the primary builder.
24913 * @return {Array} - A list of schema grades.
24914 */
24915 fluid.prefs.primaryBuilder.buildPrimary = function (schemaIndex, typeFilter, primarySchema) {
24916 var suppliedPrimaryGradeName = "fluid.prefs.schemas.suppliedPrimary" + fluid.allocateGuid();
24917 // Create a grade that has a primary schema passed as an option inclosed.
24918 fluid.defaults(suppliedPrimaryGradeName, {
24919 gradeNames: ["fluid.prefs.schemas"],
24920 schema: fluid.filterKeys(primarySchema.properties || primarySchema,
24921 typeFilter, false)
24922 });
24923 var primary = [];
24924 // Lookup all available schema grades from the index that match the
24925 // top level preference name.
24926 fluid.each(typeFilter, function merge(type) {
24927 var schemaGrades = schemaIndex[type];
24928 if (schemaGrades) {
24929 primary.push.apply(primary, schemaGrades);
24930 }
24931 });
24932 primary.push(suppliedPrimaryGradeName);
24933 return primary;
24934 };
24935
24936 /**
24937 * An index function that indexes all shcema grades based on their
24938 * preference name.
24939 * @param {JSON} defaults - Registered defaults for a schema grade.
24940 * @return {String} A preference name.
24941 */
24942 fluid.prefs.primaryBuilder.defaultSchemaIndexer = function (defaults) {
24943 if (defaults.schema) {
24944 return fluid.keys(defaults.schema.properties);
24945 }
24946 };
24947
24948 /*******************************************************************************
24949 * Base primary schema grade
24950 *******************************************************************************/
24951 fluid.defaults("fluid.prefs.schemas", {
24952 gradeNames: ["fluid.component"],
24953 mergePolicy: {
24954 schema: fluid.prefs.schemas.merge
24955 }
24956 });
24957
24958})(jQuery, fluid_3_0_0);
24959;
24960/*
24961Copyright The Infusion copyright holders
24962See the AUTHORS.md file at the top-level directory of this distribution and at
24963https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
24964
24965Licensed under the Educational Community License (ECL), Version 2.0 or the New
24966BSD license. You may not use this file except in compliance with one these
24967Licenses.
24968
24969You may obtain a copy of the ECL 2.0 License and BSD License at
24970https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
24971*/
24972
24973var fluid_3_0_0 = fluid_3_0_0 || {};
24974
24975
24976(function ($, fluid) {
24977 "use strict";
24978
24979 fluid.registerNamespace("fluid.prefs");
24980
24981 /*******************************************************************************
24982 * Base auxiliary schema grade
24983 *******************************************************************************/
24984
24985 fluid.defaults("fluid.prefs.auxSchema", {
24986 gradeNames: ["fluid.component"],
24987 auxiliarySchema: {
24988 "loaderGrades": ["fluid.prefs.separatedPanel"]
24989 }
24990 });
24991
24992 /**
24993 * Look up the value on the given source object by using the path.
24994 * Takes a template string containing tokens in the form of "@source-path-to-value".
24995 * Returns a value (any type) or undefined if the path is not found.
24996 *
24997 * Example:
24998 * 1. Parameters:
24999 * source:
25000 * {
25001 * path1: {
25002 * path2: "here"
25003 * }
25004 * }
25005 *
25006 * template: "@path1.path2"
25007 *
25008 * 2. Return: "here"
25009 *
25010 * @param {Object} root - An object to retrieve the returned value from.
25011 * @param {String} pathRef - A string that the path to the requested value is embedded into.
25012 * @return {Any} - Returns a value (any type) or undefined if the path is not found.
25013 *
25014 */
25015 fluid.prefs.expandSchemaValue = function (root, pathRef) {
25016 if (pathRef.charAt(0) !== "@") {
25017 return pathRef;
25018 }
25019
25020 return fluid.get(root, pathRef.substring(1));
25021 };
25022
25023 fluid.prefs.addAtPath = function (root, path, object) {
25024 var existingObject = fluid.get(root, path);
25025 fluid.set(root, path, $.extend(true, {}, existingObject, object));
25026
25027 return root;
25028 };
25029
25030 // only works with top level elements
25031 fluid.prefs.removeKey = function (root, key) {
25032 var value = root[key];
25033 delete root[key];
25034 return value;
25035 };
25036
25037 fluid.prefs.rearrangeDirect = function (root, toPath, sourcePath) {
25038 var result = {};
25039 var sourceValue = fluid.prefs.removeKey(root, sourcePath);
25040 if (sourceValue) {
25041 fluid.set(result, toPath, sourceValue);
25042 }
25043 return result;
25044 };
25045
25046 fluid.prefs.addCommonOptions = function (root, path, commonOptions, templateValues) {
25047 templateValues = templateValues || {};
25048
25049 var existingValue = fluid.get(root, path);
25050
25051 if (!existingValue) {
25052 return root;
25053 }
25054
25055 var opts = {}, mergePolicy = {};
25056
25057 fluid.each(commonOptions, function (value, key) {
25058 // Adds "container" option only for view and renderer components
25059 if (key === "container") {
25060 var componentType = fluid.get(root, [path, "type"]);
25061 var componentOptions = fluid.defaults(componentType);
25062 // Note that this approach is not completely reliable, although it has been reviewed as "good enough" -
25063 // a grade which modifies the creation signature of its principal type would cause numerous other problems.
25064 // We can review this awkward kind of "anticipatory logic" when the new renderer arrives.
25065 if (fluid.get(componentOptions, ["argumentMap", "container"]) === undefined) {
25066 return false;
25067 }
25068 }
25069 // Merge grade names defined in aux schema and system default grades
25070 if (key.indexOf("gradeNames") !== -1) {
25071 mergePolicy[key] = fluid.arrayConcatPolicy;
25072 }
25073
25074 key = fluid.stringTemplate(key, templateValues);
25075 value = typeof (value) === "string" ? fluid.stringTemplate(value, templateValues) : value;
25076
25077 fluid.set(opts, key, value);
25078 });
25079
25080 fluid.set(root, path, fluid.merge(mergePolicy, existingValue, opts));
25081
25082 return root;
25083 };
25084
25085 fluid.prefs.containerNeeded = function (root, path) {
25086 var componentType = fluid.get(root, [path, "type"]);
25087 var componentOptions = fluid.defaults(componentType);
25088 return (fluid.hasGrade(componentOptions, "fluid.viewComponent") || fluid.hasGrade(componentOptions, "fluid.rendererComponent"));
25089 };
25090
25091 fluid.prefs.checkPrimarySchema = function (primarySchema, prefKey) {
25092 if (!primarySchema) {
25093 fluid.fail("The primary schema for " + prefKey + " is not defined.");
25094 }
25095 return !!primarySchema;
25096 };
25097
25098 fluid.prefs.flattenName = function (name) {
25099 var regexp = new RegExp("\\.", "g");
25100 return name.replace(regexp, "_");
25101 };
25102
25103 fluid.prefs.constructAliases = function (auxSchema, flattenedPrefKey, aliases) {
25104 aliases = fluid.makeArray(aliases);
25105
25106 var prefsEditorModel = {};
25107 var enhancerModel = {};
25108 fluid.each(aliases, function (alias) {
25109 prefsEditorModel[alias] = "{that}.model.preferences." + flattenedPrefKey;
25110 enhancerModel[alias] = "{that}.model." + flattenedPrefKey;
25111 });
25112
25113 fluid.prefs.addAtPath(auxSchema, ["aliases_prefsEditor", "model", "preferences"], prefsEditorModel);
25114 fluid.prefs.addAtPath(auxSchema, ["aliases_enhancer", "model"], enhancerModel);
25115 };
25116
25117 fluid.prefs.expandSchemaComponents = function (auxSchema, type, prefKey, alias, componentConfig, index, commonOptions, modelCommonOptions, mappedDefaults) {
25118 var componentOptions = fluid.copy(componentConfig) || {};
25119 var components = {};
25120 var initialModel = {};
25121
25122 var componentName = fluid.prefs.removeKey(componentOptions, "type");
25123 var memberName = fluid.prefs.flattenName(componentName);
25124 var flattenedPrefKey = fluid.prefs.flattenName(prefKey);
25125
25126 if (componentName) {
25127
25128 components[memberName] = {
25129 type: componentName,
25130 options: componentOptions
25131 };
25132
25133 var selectors = fluid.prefs.rearrangeDirect(componentOptions, memberName, "container");
25134 var templates = fluid.prefs.rearrangeDirect(componentOptions, memberName, "template");
25135 var messages = fluid.prefs.rearrangeDirect(componentOptions, memberName, "message");
25136
25137 var preferenceMap = fluid.defaults(componentName).preferenceMap;
25138
25139 var map = preferenceMap[prefKey];
25140 var prefSchema = mappedDefaults[prefKey];
25141
25142 fluid.each(map, function (primaryPath, internalPath) {
25143 if (fluid.prefs.checkPrimarySchema(prefSchema, prefKey)) {
25144 var opts = {};
25145 if (internalPath.indexOf("model.") === 0 && primaryPath === "value") {
25146 var internalModelName = internalPath.slice(6);
25147 // Set up the binding in "rules" accepted by the modelRelay base grade of every panel
25148 fluid.set(opts, "model", fluid.get(opts, "model") || {});
25149 fluid.prefs.addCommonOptions(opts, "model", modelCommonOptions, {
25150 internalModelName: internalModelName,
25151 externalModelName: flattenedPrefKey
25152 });
25153 fluid.set(initialModel, ["members", "initialModel", "preferences", flattenedPrefKey], prefSchema["default"]);
25154
25155 if (alias) {
25156 fluid.set(initialModel, ["members", "initialModel", "preferences", alias], prefSchema["default"]);
25157 }
25158 } else {
25159 fluid.set(opts, internalPath, prefSchema[primaryPath]);
25160 }
25161 $.extend(true, componentOptions, opts);
25162 }
25163 });
25164
25165 fluid.prefs.addCommonOptions(components, memberName, commonOptions, {
25166 prefKey: memberName
25167 });
25168
25169 fluid.prefs.addAtPath(auxSchema, [type, "components"], components);
25170 fluid.prefs.addAtPath(auxSchema, [type, "selectors"], selectors);
25171 fluid.prefs.addAtPath(auxSchema, ["templateLoader", "resources"], templates);
25172 fluid.prefs.addAtPath(auxSchema, ["messageLoader", "resources"], messages);
25173 fluid.prefs.addAtPath(auxSchema, "initialModel", initialModel);
25174
25175 fluid.prefs.constructAliases(auxSchema, flattenedPrefKey, alias);
25176 }
25177
25178 return auxSchema;
25179 };
25180
25181 /**
25182 * Expands a all "@" path references from an auxiliary schema.
25183 * Note that you cannot chain "@" paths.
25184 *
25185 * @param {Object} schemaToExpand - the schema which will be expanded
25186 * @param {Object} altSource - an alternative look up object. This is primarily used for the internal recursive call.
25187 * @return {Object} an expanded version of the schema.
25188 */
25189 fluid.prefs.expandSchemaImpl = function (schemaToExpand, altSource) {
25190 var expandedSchema = fluid.copy(schemaToExpand);
25191 altSource = altSource || expandedSchema;
25192
25193 fluid.each(expandedSchema, function (value, key) {
25194 if (typeof value === "object") {
25195 expandedSchema[key] = fluid.prefs.expandSchemaImpl(value, altSource);
25196 } else if (typeof value === "string") {
25197 var expandedVal = fluid.prefs.expandSchemaValue(altSource, value);
25198 if (expandedVal !== undefined) {
25199 expandedSchema[key] = expandedVal;
25200 } else {
25201 delete expandedSchema[key];
25202 }
25203 }
25204 });
25205 return expandedSchema;
25206 };
25207
25208 fluid.prefs.expandCompositePanels = function (auxSchema, compositePanelList, panelIndex, panelCommonOptions, subPanelCommonOptions,
25209 compositePanelBasedOnSubCommonOptions, panelModelCommonOptions, mappedDefaults) {
25210 var panelsToIgnore = [];
25211
25212 fluid.each(compositePanelList, function (compositeDetail, compositeKey) {
25213 var compositePanelOptions = {};
25214 var components = {};
25215 var initialModel = {};
25216 var selectors = {};
25217 var templates = {};
25218 var messages = {};
25219 var selectorsToIgnore = [];
25220
25221 var thisCompositeOptions = fluid.copy(compositeDetail);
25222 fluid.set(compositePanelOptions, "type", thisCompositeOptions.type);
25223 delete thisCompositeOptions.type;
25224
25225 selectors = fluid.prefs.rearrangeDirect(thisCompositeOptions, compositeKey, "container");
25226 templates = fluid.prefs.rearrangeDirect(thisCompositeOptions, compositeKey, "template");
25227 messages = fluid.prefs.rearrangeDirect(thisCompositeOptions, compositeKey, "message");
25228
25229 var subPanelList = []; // list of subpanels to generate options for
25230 var subPanels = {};
25231 var subPanelRenderOn = {};
25232
25233 // thisCompositeOptions.panels can be in two forms:
25234 // 1. an array of names of panels that should always be rendered;
25235 // 2. an object that describes what panels should always be rendered,
25236 // and what panels should be rendered when a preference is turned on
25237 // The loop below is only needed for processing the latter.
25238 if (fluid.isPlainObject(thisCompositeOptions.panels) && !fluid.isArrayable(thisCompositeOptions.panels)) {
25239 fluid.each(thisCompositeOptions.panels, function (subpanelArray, pref) {
25240 subPanelList = subPanelList.concat(subpanelArray);
25241 if (pref !== "always") {
25242 fluid.each(subpanelArray, function (onePanel) {
25243 fluid.set(subPanelRenderOn, onePanel, pref);
25244 });
25245 }
25246 });
25247 } else {
25248 subPanelList = thisCompositeOptions.panels;
25249 }
25250
25251 fluid.each(subPanelList, function (subPanelID) {
25252 panelsToIgnore.push(subPanelID);
25253 var subPanelPrefsKey = fluid.get(auxSchema, [subPanelID, "type"]);
25254 var safeSubPanelPrefsKey = fluid.prefs.subPanel.safePrefKey(subPanelPrefsKey);
25255 selectorsToIgnore.push(safeSubPanelPrefsKey);
25256
25257 var subPanelOptions = fluid.copy(fluid.get(auxSchema, [subPanelID, "panel"]));
25258 var subPanelType = fluid.get(subPanelOptions, "type");
25259
25260 fluid.set(subPanels, [safeSubPanelPrefsKey, "type"], subPanelType);
25261 var renderOn = fluid.get(subPanelRenderOn, subPanelID);
25262 if (renderOn) {
25263 fluid.set(subPanels, [safeSubPanelPrefsKey, "options", "renderOnPreference"], renderOn);
25264 }
25265
25266 // Deal with preferenceMap related options
25267 var map = fluid.defaults(subPanelType).preferenceMap[subPanelPrefsKey];
25268 var prefSchema = mappedDefaults[subPanelPrefsKey];
25269
25270 fluid.each(map, function (primaryPath, internalPath) {
25271 if (fluid.prefs.checkPrimarySchema(prefSchema, subPanelPrefsKey)) {
25272 var opts;
25273 if (internalPath.indexOf("model.") === 0 && primaryPath === "value") {
25274 // Set up the binding in "rules" accepted by the modelRelay base grade of every panel
25275 fluid.set(compositePanelOptions, ["options", "model"], fluid.get(compositePanelOptions, ["options", "model"]) || {});
25276 fluid.prefs.addCommonOptions(compositePanelOptions, ["options", "model"], panelModelCommonOptions, {
25277 internalModelName: safeSubPanelPrefsKey,
25278 externalModelName: safeSubPanelPrefsKey
25279 });
25280 fluid.set(initialModel, ["members", "initialModel", "preferences", safeSubPanelPrefsKey], prefSchema["default"]);
25281 } else {
25282 opts = opts || {options: {}};
25283 fluid.set(opts, "options." + internalPath, prefSchema[primaryPath]);
25284 }
25285 $.extend(true, subPanels[safeSubPanelPrefsKey], opts);
25286 }
25287 });
25288
25289 fluid.set(templates, safeSubPanelPrefsKey, fluid.get(subPanelOptions, "template"));
25290 fluid.set(messages, safeSubPanelPrefsKey, fluid.get(subPanelOptions, "message"));
25291
25292 fluid.set(compositePanelOptions, ["options", "selectors", safeSubPanelPrefsKey], fluid.get(subPanelOptions, "container"));
25293 fluid.set(compositePanelOptions, ["options", "resources"], fluid.get(compositePanelOptions, ["options", "resources"]) || {});
25294
25295 fluid.prefs.addCommonOptions(compositePanelOptions.options, "resources", compositePanelBasedOnSubCommonOptions, {
25296 subPrefKey: safeSubPanelPrefsKey
25297 });
25298
25299 // add additional options from the aux schema for subpanels
25300 delete subPanelOptions.type;
25301 delete subPanelOptions.template;
25302 delete subPanelOptions.message;
25303 delete subPanelOptions.container;
25304 fluid.set(subPanels, [safeSubPanelPrefsKey, "options"], $.extend(true, {}, fluid.get(subPanels, [safeSubPanelPrefsKey, "options"]), subPanelOptions));
25305
25306 fluid.prefs.addCommonOptions(subPanels, safeSubPanelPrefsKey, subPanelCommonOptions, {
25307 compositePanel: compositeKey,
25308 prefKey: safeSubPanelPrefsKey
25309 });
25310 });
25311 delete thisCompositeOptions.panels;
25312
25313 // add additional options from the aux schema for the composite panel
25314 fluid.set(compositePanelOptions, ["options"], $.extend(true, {}, compositePanelOptions.options, thisCompositeOptions));
25315 fluid.set(compositePanelOptions, ["options", "selectorsToIgnore"], selectorsToIgnore);
25316 fluid.set(compositePanelOptions, ["options", "components"], subPanels);
25317
25318 components[compositeKey] = compositePanelOptions;
25319
25320 fluid.prefs.addCommonOptions(components, compositeKey, panelCommonOptions, {
25321 prefKey: compositeKey
25322 });
25323
25324 // Add onto auxSchema
25325 fluid.prefs.addAtPath(auxSchema, ["panels", "components"], components);
25326 fluid.prefs.addAtPath(auxSchema, ["panels", "selectors"], selectors);
25327 fluid.prefs.addAtPath(auxSchema, ["templateLoader", "resources"], templates);
25328 fluid.prefs.addAtPath(auxSchema, ["messageLoader", "resources"], messages);
25329 fluid.prefs.addAtPath(auxSchema, "initialModel", initialModel);
25330 $.extend(true, auxSchema, {panelsToIgnore: panelsToIgnore});
25331 });
25332
25333 return auxSchema;
25334 };
25335
25336 // Processes the auxiliary schema to output an object that contains all grade component definitions
25337 // required for building the preferences editor, uiEnhancer and the settings store. These grade components
25338 // are: panels, enactors, initialModel, messageLoader, templateLoader and terms.
25339 // These grades are consumed and integrated by builder.js
25340 // (https://github.com/fluid-project/infusion/blob/master/src/framework/preferences/js/Builder.js)
25341 fluid.prefs.expandSchema = function (schemaToExpand, indexes, topCommonOptions, elementCommonOptions, mappedDefaults) {
25342 var auxSchema = fluid.prefs.expandSchemaImpl(schemaToExpand);
25343 auxSchema.namespace = auxSchema.namespace || "fluid.prefs.created_" + fluid.allocateGuid();
25344
25345 var terms = fluid.get(auxSchema, "terms");
25346 if (terms) {
25347 delete auxSchema.terms;
25348 fluid.set(auxSchema, ["terms", "terms"], terms);
25349 }
25350
25351 var compositePanelList = fluid.get(auxSchema, "groups");
25352 if (compositePanelList) {
25353 fluid.prefs.expandCompositePanels(auxSchema, compositePanelList, fluid.get(indexes, "panel"),
25354 fluid.get(elementCommonOptions, "panel"), fluid.get(elementCommonOptions, "subPanel"),
25355 fluid.get(elementCommonOptions, "compositePanelBasedOnSub"), fluid.get(elementCommonOptions, "panelModel"),
25356 mappedDefaults);
25357 }
25358
25359 fluid.each(auxSchema, function (category, prefName) {
25360 // TODO: Replace this cumbersome scheme with one based on an extensible lookup to handlers
25361
25362 var type = "panel";
25363 // Ignore the subpanels that are only for composing composite panels
25364 if (category[type] && !fluid.contains(auxSchema.panelsToIgnore, prefName)) {
25365 fluid.prefs.expandSchemaComponents(auxSchema, "panels", category.type, category.alias, category[type], fluid.get(indexes, type),
25366 fluid.get(elementCommonOptions, type), fluid.get(elementCommonOptions, type + "Model"), mappedDefaults);
25367 }
25368
25369 type = "enactor";
25370 if (category[type]) {
25371 fluid.prefs.expandSchemaComponents(auxSchema, "enactors", category.type, category.alias, category[type], fluid.get(indexes, type),
25372 fluid.get(elementCommonOptions, type), fluid.get(elementCommonOptions, type + "Model"), mappedDefaults);
25373 }
25374
25375 fluid.each(["template", "message"], function (type) {
25376 if (prefName === type) {
25377 fluid.set(auxSchema, [type + "Loader", "resources", "prefsEditor"], auxSchema[type]);
25378 delete auxSchema[type];
25379 }
25380 });
25381
25382 });
25383
25384 // Remove subPanels array. It is to keep track of the panels that are only used as sub-components of composite panels.
25385 if (auxSchema.panelsToIgnore) {
25386 delete auxSchema.panelsToIgnore;
25387 }
25388
25389 // Add top common options
25390 fluid.each(topCommonOptions, function (topOptions, type) {
25391 fluid.prefs.addCommonOptions(auxSchema, type, topOptions);
25392 });
25393
25394 return auxSchema;
25395 };
25396
25397 fluid.defaults("fluid.prefs.auxBuilder", {
25398 gradeNames: ["fluid.prefs.auxSchema"],
25399 mergePolicy: {
25400 elementCommonOptions: "noexpand"
25401 },
25402 topCommonOptions: {
25403 panels: {
25404 gradeNames: ["fluid.prefs.prefsEditor"]
25405 },
25406 enactors: {
25407 gradeNames: ["fluid.uiEnhancer"]
25408 },
25409 templateLoader: {
25410 gradeNames: ["fluid.resourceLoader"]
25411 },
25412 messageLoader: {
25413 gradeNames: ["fluid.resourceLoader"]
25414 },
25415 initialModel: {
25416 gradeNames: ["fluid.prefs.initialModel"]
25417 },
25418 terms: {
25419 gradeNames: ["fluid.component"]
25420 },
25421 aliases_prefsEditor: {
25422 gradeNames: ["fluid.modelComponent"]
25423 },
25424 aliases_enhancer: {
25425 gradeNames: ["fluid.modelComponent"]
25426 }
25427 },
25428 elementCommonOptions: {
25429 panel: {
25430 "createOnEvent": "onPrefsEditorMarkupReady",
25431 "container": "{prefsEditor}.dom.%prefKey",
25432 "options.gradeNames": "fluid.prefs.prefsEditorConnections",
25433 "options.resources.template": "{templateLoader}.resources.%prefKey",
25434 "options.messageBase": "{messageLoader}.resources.%prefKey.resourceText"
25435 },
25436 panelModel: {
25437 "%internalModelName": "{prefsEditor}.model.preferences.%externalModelName"
25438 },
25439 compositePanelBasedOnSub: {
25440 "%subPrefKey": "{templateLoader}.resources.%subPrefKey"
25441 },
25442 subPanel: {
25443 "container": "{%compositePanel}.dom.%prefKey",
25444 "options.messageBase": "{messageLoader}.resources.%prefKey.resourceText"
25445 },
25446 enactor: {
25447 "container": "{uiEnhancer}.container"
25448 },
25449 enactorModel: {
25450 "%internalModelName": "{uiEnhancer}.model.%externalModelName"
25451 }
25452 },
25453 indexes: {
25454 panel: {
25455 expander: {
25456 func: "fluid.indexDefaults",
25457 args: ["panelsIndex", {
25458 gradeNames: "fluid.prefs.panel",
25459 indexFunc: "fluid.prefs.auxBuilder.prefMapIndexer"
25460 }]
25461 }
25462 },
25463 enactor: {
25464 expander: {
25465 func: "fluid.indexDefaults",
25466 args: ["enactorsIndex", {
25467 gradeNames: "fluid.prefs.enactor",
25468 indexFunc: "fluid.prefs.auxBuilder.prefMapIndexer"
25469 }]
25470 }
25471 }
25472 },
25473 mappedDefaults: {},
25474 expandedAuxSchema: {
25475 expander: {
25476 func: "fluid.prefs.expandSchema",
25477 args: [
25478 "{that}.options.auxiliarySchema",
25479 "{that}.options.indexes",
25480 "{that}.options.topCommonOptions",
25481 "{that}.options.elementCommonOptions",
25482 "{that}.options.mappedDefaults"
25483 ]
25484 }
25485 }
25486 });
25487
25488 fluid.prefs.auxBuilder.prefMapIndexer = function (defaults) {
25489 return fluid.keys(defaults.preferenceMap);
25490 };
25491
25492})(jQuery, fluid_3_0_0);
25493;
25494/*
25495Copyright The Infusion copyright holders
25496See the AUTHORS.md file at the top-level directory of this distribution and at
25497https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
25498
25499Licensed under the Educational Community License (ECL), Version 2.0 or the New
25500BSD license. You may not use this file except in compliance with one these
25501Licenses.
25502
25503You may obtain a copy of the ECL 2.0 License and BSD License at
25504https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
25505*/
25506
25507var fluid_3_0_0 = fluid_3_0_0 || {};
25508
25509(function (fluid) {
25510 "use strict";
25511
25512 /*******************************************************************************
25513 * Starter auxiliary schema grade
25514 *
25515 * Contains the settings for 7 preferences: text size, line space, text font,
25516 * contrast, table of contents, inputs larger and emphasize links
25517 *******************************************************************************/
25518
25519 fluid.defaults("fluid.prefs.auxSchema.starter", {
25520 gradeNames: ["fluid.prefs.auxSchema"],
25521 auxiliarySchema: {
25522 "loaderGrades": ["fluid.prefs.separatedPanel"],
25523 "namespace": "fluid.prefs.constructed", // The author of the auxiliary schema will provide this and will be the component to call to initialize the constructed PrefsEditor.
25524 "terms": {
25525 "templatePrefix": "../../framework/preferences/html", // Must match the keyword used below to identify the common path to settings panel templates.
25526 "messagePrefix": "../../framework/preferences/messages" // Must match the keyword used below to identify the common path to message files.
25527 },
25528 "template": "%templatePrefix/SeparatedPanelPrefsEditor.html",
25529 "message": "%messagePrefix/prefsEditor.json",
25530 "defaultLocale": "en",
25531 "textSize": {
25532 "type": "fluid.prefs.textSize",
25533 "alias": "textSize",
25534 "enactor": {
25535 "type": "fluid.prefs.enactor.textSize"
25536 },
25537 "panel": {
25538 "type": "fluid.prefs.panel.textSize",
25539 "container": ".flc-prefsEditor-text-size", // the css selector in the template where the panel is rendered
25540 "message": "%messagePrefix/textSize.json",
25541 "template": "%templatePrefix/PrefsEditorTemplate-textSize.html"
25542 }
25543 },
25544 "textFont": {
25545 "type": "fluid.prefs.textFont",
25546 "alias": "textFont",
25547 "classes": {
25548 "default": "",
25549 "times": "fl-font-times",
25550 "comic": "fl-font-comic-sans",
25551 "arial": "fl-font-arial",
25552 "verdana": "fl-font-verdana",
25553 "open-dyslexic": "fl-font-open-dyslexic"
25554 },
25555 "enactor": {
25556 "type": "fluid.prefs.enactor.textFont",
25557 "classes": "@textFont.classes"
25558 },
25559 "panel": {
25560 "type": "fluid.prefs.panel.textFont",
25561 "container": ".flc-prefsEditor-text-font", // the css selector in the template where the panel is rendered
25562 "classnameMap": {"textFont": "@textFont.classes"},
25563 "template": "%templatePrefix/PrefsEditorTemplate-textFont.html",
25564 "message": "%messagePrefix/textFont.json"
25565 }
25566 },
25567 "lineSpace": {
25568 "type": "fluid.prefs.lineSpace",
25569 "alias": "lineSpace",
25570 "enactor": {
25571 "type": "fluid.prefs.enactor.lineSpace",
25572 "fontSizeMap": {
25573 "xx-small": "9px",
25574 "x-small": "11px",
25575 "small": "13px",
25576 "medium": "15px",
25577 "large": "18px",
25578 "x-large": "23px",
25579 "xx-large": "30px"
25580 }
25581 },
25582 "panel": {
25583 "type": "fluid.prefs.panel.lineSpace",
25584 "container": ".flc-prefsEditor-line-space", // the css selector in the template where the panel is rendered
25585 "message": "%messagePrefix/lineSpace.json",
25586 "template": "%templatePrefix/PrefsEditorTemplate-lineSpace.html"
25587 }
25588 },
25589 "contrast": {
25590 "type": "fluid.prefs.contrast",
25591 "alias": "theme",
25592 "classes": {
25593 "default": "fl-theme-prefsEditor-default",
25594 "bw": "fl-theme-bw",
25595 "wb": "fl-theme-wb",
25596 "by": "fl-theme-by",
25597 "yb": "fl-theme-yb",
25598 "lgdg": "fl-theme-lgdg",
25599 "gd": "fl-theme-gd",
25600 "gw": "fl-theme-gw",
25601 "bbr": "fl-theme-bbr"
25602
25603 },
25604 "enactor": {
25605 "type": "fluid.prefs.enactor.contrast",
25606 "classes": "@contrast.classes"
25607 },
25608 "panel": {
25609 "type": "fluid.prefs.panel.contrast",
25610 "container": ".flc-prefsEditor-contrast", // the css selector in the template where the panel is rendered
25611 "classnameMap": {"theme": "@contrast.classes"},
25612 "template": "%templatePrefix/PrefsEditorTemplate-contrast.html",
25613 "message": "%messagePrefix/contrast.json"
25614 }
25615 },
25616 "tableOfContents": {
25617 "type": "fluid.prefs.tableOfContents",
25618 "alias": "toc",
25619 "enactor": {
25620 "type": "fluid.prefs.enactor.tableOfContents",
25621 "tocTemplate": "../../components/tableOfContents/html/TableOfContents.html",
25622 "tocMessage": "../../framework/preferences/messages/tableOfContents-enactor.json"
25623 },
25624 "panel": {
25625 "type": "fluid.prefs.panel.layoutControls",
25626 "container": ".flc-prefsEditor-layout-controls", // the css selector in the template where the panel is rendered
25627 "template": "%templatePrefix/PrefsEditorTemplate-layout.html",
25628 "message": "%messagePrefix/tableOfContents.json"
25629 }
25630 },
25631 "enhanceInputs": {
25632 "type": "fluid.prefs.enhanceInputs",
25633 "alias": "inputs",
25634 "enactor": {
25635 "type": "fluid.prefs.enactor.enhanceInputs",
25636 "cssClass": "fl-input-enhanced"
25637 },
25638 "panel": {
25639 "type": "fluid.prefs.panel.enhanceInputs",
25640 "container": ".flc-prefsEditor-enhanceInputs", // the css selector in the template where the panel is rendered
25641 "template": "%templatePrefix/PrefsEditorTemplate-enhanceInputs.html",
25642 "message": "%messagePrefix/enhanceInputs.json"
25643 }
25644 }
25645 }
25646 });
25647
25648 /*******************************************************************************
25649 * Starter primary schema grades
25650 *
25651 * Contains the settings for 7 preferences: text size, line space, text font,
25652 * contrast, table of contents, inputs larger and emphasize links
25653 *******************************************************************************/
25654
25655 fluid.defaults("fluid.prefs.schemas.textSize", {
25656 gradeNames: ["fluid.prefs.schemas"],
25657 schema: {
25658 "fluid.prefs.textSize": {
25659 "type": "number",
25660 "default": 1,
25661 "minimum": 0.5,
25662 "maximum": 2,
25663 "divisibleBy": 0.1
25664 }
25665 }
25666 });
25667
25668 fluid.defaults("fluid.prefs.schemas.lineSpace", {
25669 gradeNames: ["fluid.prefs.schemas"],
25670 schema: {
25671 "fluid.prefs.lineSpace": {
25672 "type": "number",
25673 "default": 1,
25674 "minimum": 0.7,
25675 "maximum": 2,
25676 "divisibleBy": 0.1
25677 }
25678 }
25679 });
25680
25681 fluid.defaults("fluid.prefs.schemas.textFont", {
25682 gradeNames: ["fluid.prefs.schemas"],
25683 schema: {
25684 "fluid.prefs.textFont": {
25685 "type": "string",
25686 "default": "default",
25687 "enum": ["default", "times", "comic", "arial", "verdana", "open-dyslexic"]
25688 }
25689 }
25690 });
25691
25692 fluid.defaults("fluid.prefs.schemas.contrast", {
25693 gradeNames: ["fluid.prefs.schemas"],
25694 schema: {
25695 "fluid.prefs.contrast": {
25696 "type": "string",
25697 "default": "default",
25698 "enum": ["default", "bw", "wb", "by", "yb", "lgdg", "gw", "gd", "bbr"]
25699 }
25700 }
25701 });
25702
25703 fluid.defaults("fluid.prefs.schemas.tableOfContents", {
25704 gradeNames: ["fluid.prefs.schemas"],
25705 schema: {
25706 "fluid.prefs.tableOfContents": {
25707 "type": "boolean",
25708 "default": false
25709 }
25710 }
25711 });
25712
25713 fluid.defaults("fluid.prefs.schemas.enhanceInputs", {
25714 gradeNames: ["fluid.prefs.schemas"],
25715 schema: {
25716 "fluid.prefs.enhanceInputs": {
25717 "type": "boolean",
25718 "default": false
25719 }
25720 }
25721 });
25722})(fluid_3_0_0);
25723;
25724/*
25725Copyright The Infusion copyright holders
25726See the AUTHORS.md file at the top-level directory of this distribution and at
25727https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
25728
25729Licensed under the Educational Community License (ECL), Version 2.0 or the New
25730BSD license. You may not use this file except in compliance with one these
25731Licenses.
25732
25733You may obtain a copy of the ECL 2.0 License and BSD License at
25734https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
25735*/
25736
25737var fluid_3_0_0 = fluid_3_0_0 || {};
25738
25739(function (fluid) {
25740 "use strict";
25741
25742 /*******************************************************************************
25743 * Starter auxiliary schema grade
25744 *
25745 * Contains the settings for captions
25746 *******************************************************************************/
25747
25748 // Fine-tune the starter aux schema and add captions panel
25749 fluid.defaults("fluid.prefs.auxSchema.captions", {
25750 gradeNames: ["fluid.prefs.auxSchema"],
25751 auxiliarySchema: {
25752 "namespace": "fluid.prefs.constructed",
25753 "terms": {
25754 "templatePrefix": "../../framework/preferences/html",
25755 "messagePrefix": "../../framework/preferences/messages"
25756 },
25757 "template": "%templatePrefix/SeparatedPanelPrefsEditor.html",
25758 "message": "%messagePrefix/prefsEditor.json",
25759
25760 captions: {
25761 type: "fluid.prefs.captions",
25762 enactor: {
25763 type: "fluid.prefs.enactor.captions",
25764 container: "body"
25765 },
25766 panel: {
25767 type: "fluid.prefs.panel.captions",
25768 container: ".flc-prefsEditor-captions",
25769 template: "%templatePrefix/PrefsEditorTemplate-captions.html",
25770 message: "%messagePrefix/captions.json"
25771 }
25772 }
25773 }
25774 });
25775
25776
25777 /*******************************************************************************
25778 * Primary Schema
25779 *******************************************************************************/
25780
25781 // add extra prefs to the starter primary schemas
25782
25783 fluid.defaults("fluid.prefs.schemas.captions", {
25784 gradeNames: ["fluid.prefs.schemas"],
25785 schema: {
25786 "fluid.prefs.captions": {
25787 "type": "boolean",
25788 "default": false
25789 }
25790 }
25791 });
25792
25793})(fluid_3_0_0);
25794;
25795/*
25796Copyright The Infusion copyright holders
25797See the AUTHORS.md file at the top-level directory of this distribution and at
25798https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
25799
25800Licensed under the Educational Community License (ECL), Version 2.0 or the New
25801BSD license. You may not use this file except in compliance with one these
25802Licenses.
25803
25804You may obtain a copy of the ECL 2.0 License and BSD License at
25805https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
25806*/
25807
25808var fluid_3_0_0 = fluid_3_0_0 || {};
25809
25810(function (fluid) {
25811 "use strict";
25812
25813 /*******************************************************************************
25814 * Starter auxiliary schema grade
25815 *
25816 * Contains the settings for the letter space preference
25817 *******************************************************************************/
25818
25819 // Fine-tune the starter aux schema and add letter space preference
25820 fluid.defaults("fluid.prefs.auxSchema.letterSpace", {
25821 gradeNames: ["fluid.prefs.auxSchema"],
25822 auxiliarySchema: {
25823 "namespace": "fluid.prefs.constructed",
25824 "terms": {
25825 "templatePrefix": "../../framework/preferences/html/",
25826 "messagePrefix": "../../framework/preferences/messages/"
25827 },
25828 "template": "%templatePrefix/SeparatedPanelPrefsEditor.html",
25829 "message": "%messagePrefix/prefsEditor.json",
25830
25831 letterSpace: {
25832 type: "fluid.prefs.letterSpace",
25833 enactor: {
25834 type: "fluid.prefs.enactor.letterSpace",
25835 fontSizeMap: {
25836 "xx-small": "9px",
25837 "x-small": "11px",
25838 "small": "13px",
25839 "medium": "15px",
25840 "large": "18px",
25841 "x-large": "23px",
25842 "xx-large": "30px"
25843 }
25844 },
25845 panel: {
25846 type: "fluid.prefs.panel.letterSpace",
25847 container: ".flc-prefsEditor-letter-space",
25848 template: "%templatePrefix/PrefsEditorTemplate-letterSpace.html",
25849 message: "%messagePrefix/letterSpace.json"
25850 }
25851 }
25852 }
25853 });
25854
25855
25856 /*******************************************************************************
25857 * Primary Schema
25858 *******************************************************************************/
25859
25860 // add extra prefs to the starter primary schemas
25861
25862 fluid.defaults("fluid.prefs.schemas.letterSpace", {
25863 gradeNames: ["fluid.prefs.schemas"],
25864 schema: {
25865 "fluid.prefs.letterSpace": {
25866 "type": "number",
25867 "default": 1,
25868 "minimum": 0.9,
25869 "maximum": 2,
25870 "divisibleBy": 0.1
25871 }
25872 }
25873 });
25874
25875})(fluid_3_0_0);
25876;
25877/*
25878Copyright The Infusion copyright holders
25879See the AUTHORS.md file at the top-level directory of this distribution and at
25880https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
25881
25882Licensed under the Educational Community License (ECL), Version 2.0 or the New
25883BSD license. You may not use this file except in compliance with one these
25884Licenses.
25885
25886You may obtain a copy of the ECL 2.0 License and BSD License at
25887https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
25888*/
25889
25890var fluid_3_0_0 = fluid_3_0_0 || {};
25891
25892(function (fluid) {
25893 "use strict";
25894
25895 /*******************************************************************************
25896 * Starter auxiliary schema grade
25897 *
25898 * Contains the settings for text-to-speech
25899 *******************************************************************************/
25900
25901 // Fine-tune the starter aux schema and add speak panel
25902 fluid.defaults("fluid.prefs.auxSchema.speak", {
25903 gradeNames: ["fluid.prefs.auxSchema"],
25904 auxiliarySchema: {
25905 "namespace": "fluid.prefs.constructed",
25906 "terms": {
25907 "templatePrefix": "../../framework/preferences/html/",
25908 "messagePrefix": "../../framework/preferences/messages/"
25909 },
25910 "template": "%templatePrefix/SeparatedPanelPrefsEditor.html",
25911 "message": "%messagePrefix/prefsEditor.json",
25912
25913 speak: {
25914 type: "fluid.prefs.speak",
25915 enactor: {
25916 type: "fluid.prefs.enactor.selfVoicing"
25917 },
25918 panel: {
25919 type: "fluid.prefs.panel.speak",
25920 container: ".flc-prefsEditor-speak",
25921 template: "%templatePrefix/PrefsEditorTemplate-speak.html",
25922 message: "%messagePrefix/speak.json"
25923 }
25924 }
25925 }
25926 });
25927
25928
25929 /*******************************************************************************
25930 * Primary Schema
25931 *******************************************************************************/
25932
25933 // add extra prefs to the starter primary schemas
25934
25935 fluid.defaults("fluid.prefs.schemas.speak", {
25936 gradeNames: ["fluid.prefs.schemas"],
25937 schema: {
25938 "fluid.prefs.speak": {
25939 "type": "boolean",
25940 "default": false
25941 }
25942 }
25943 });
25944
25945})(fluid_3_0_0);
25946;
25947/*
25948Copyright The Infusion copyright holders
25949See the AUTHORS.md file at the top-level directory of this distribution and at
25950https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
25951
25952Licensed under the Educational Community License (ECL), Version 2.0 or the New
25953BSD license. You may not use this file except in compliance with one these
25954Licenses.
25955
25956You may obtain a copy of the ECL 2.0 License and BSD License at
25957https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
25958*/
25959
25960var fluid_3_0_0 = fluid_3_0_0 || {};
25961
25962(function (fluid) {
25963 "use strict";
25964
25965 /*******************************************************************************
25966 * Starter auxiliary schema grade
25967 *
25968 * Contains the settings for syllabification
25969 *******************************************************************************/
25970
25971 // Fine-tune the starter aux schema and add syllabification panel
25972 fluid.defaults("fluid.prefs.auxSchema.syllabification", {
25973 gradeNames: ["fluid.prefs.auxSchema"],
25974 auxiliarySchema: {
25975 "namespace": "fluid.prefs.constructed",
25976 "terms": {
25977 "templatePrefix": "../../framework/preferences/html",
25978 "messagePrefix": "../../framework/preferences/messages"
25979 },
25980 "template": "%templatePrefix/SeparatedPanelPrefsEditor.html",
25981 "message": "%messagePrefix/prefsEditor.json",
25982 syllabification: {
25983 type: "fluid.prefs.syllabification",
25984 enactor: {
25985 type: "fluid.prefs.enactor.syllabification",
25986 container: "body"
25987 },
25988 panel: {
25989 type: "fluid.prefs.panel.syllabification",
25990 container: ".flc-prefsEditor-syllabification",
25991 template: "%templatePrefix/PrefsEditorTemplate-syllabification.html",
25992 message: "%messagePrefix/syllabification.json"
25993 }
25994 }
25995 }
25996 });
25997
25998
25999 /*******************************************************************************
26000 * Primary Schema
26001 *******************************************************************************/
26002
26003 // add extra prefs to the starter primary schemas
26004
26005 fluid.defaults("fluid.prefs.schemas.syllabification", {
26006 gradeNames: ["fluid.prefs.schemas"],
26007 schema: {
26008 "fluid.prefs.syllabification": {
26009 "type": "boolean",
26010 "default": false
26011 }
26012 }
26013 });
26014
26015})(fluid_3_0_0);
26016;
26017/*
26018Copyright The Infusion copyright holders
26019See the AUTHORS.md file at the top-level directory of this distribution and at
26020https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
26021
26022Licensed under the Educational Community License (ECL), Version 2.0 or the New
26023BSD license. You may not use this file except in compliance with one these
26024Licenses.
26025
26026You may obtain a copy of the ECL 2.0 License and BSD License at
26027https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
26028*/
26029
26030var fluid_3_0_0 = fluid_3_0_0 || {};
26031
26032(function (fluid) {
26033 "use strict";
26034
26035 /*******************************************************************************
26036 * Starter auxiliary schema grade
26037 *
26038 * Contains the settings for the localization preference
26039 *******************************************************************************/
26040
26041 // Fine-tune the starter aux schema and add localization preference
26042 fluid.defaults("fluid.prefs.auxSchema.localization", {
26043 gradeNames: ["fluid.prefs.auxSchema"],
26044 auxiliarySchema: {
26045 "terms": {
26046 "templatePrefix": "../../framework/preferences/html/",
26047 "messagePrefix": "../../framework/preferences/messages/"
26048 },
26049 "template": "%templatePrefix/SeparatedPanelPrefsEditor.html",
26050 "message": "%messagePrefix/prefsEditor.json",
26051 localization: {
26052 "type": "fluid.prefs.localization",
26053 "alias": "locale",
26054 "enactor": {
26055 "type": "fluid.prefs.enactor.localization"
26056 },
26057 "panel": {
26058 "type": "fluid.prefs.panel.localization",
26059 "container": ".flc-prefsEditor-localization", // the css selector in the template where the panel is rendered
26060 "template": "%templatePrefix/PrefsEditorTemplate-localization.html",
26061 "message": "%messagePrefix/localization.json"
26062 }
26063 }
26064 }
26065 });
26066
26067 /*******************************************************************************
26068 * Primary Schema
26069 *******************************************************************************/
26070
26071 // add extra prefs to the starter primary schemas
26072
26073 fluid.defaults("fluid.prefs.schemas.localization", {
26074 gradeNames: ["fluid.prefs.schemas"],
26075 schema: {
26076 "fluid.prefs.localization": {
26077 "type": "string",
26078 "default": "",
26079 "enum": ["", "en", "en_CA", "en_US", "fr", "es", "fa"]
26080 }
26081 }
26082 });
26083
26084})(fluid_3_0_0);
26085;
26086/*
26087Copyright The Infusion copyright holders
26088See the AUTHORS.md file at the top-level directory of this distribution and at
26089https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
26090
26091Licensed under the Educational Community License (ECL), Version 2.0 or the New
26092BSD license. You may not use this file except in compliance with one these
26093Licenses.
26094
26095You may obtain a copy of the ECL 2.0 License and BSD License at
26096https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
26097*/
26098
26099var fluid_3_0_0 = fluid_3_0_0 || {};
26100
26101(function (fluid) {
26102 "use strict";
26103
26104 /*******************************************************************************
26105 * Starter auxiliary schema grade
26106 *
26107 * Contains the settings for the localization preference
26108 *******************************************************************************/
26109
26110 // Fine-tune the starter aux schema and add localization preference
26111 fluid.defaults("fluid.prefs.constructed.localizationPrefsEditorConfig", {
26112 gradeNames: ["fluid.contextAware"],
26113 contextAwareness: {
26114 localeChange: {
26115 checks: {
26116 urlPath: {
26117 contextValue: "{localizationPrefsEditorConfig}.options.localizationScheme",
26118 equals: "urlPath",
26119 gradeNames: "fluid.prefs.constructed.localizationPrefsEditorConfig.urlPathLocale"
26120 }
26121 }
26122 }
26123 },
26124 distributeOptions: {
26125 "prefsEditor.localization.enactor.localizationScheme": {
26126 source: "{that}.options.localizationScheme",
26127 target: "{that uiEnhancer fluid.prefs.enactor.localization}.options.localizationScheme"
26128 },
26129 "prefsEditor.localization.panel.locales": {
26130 source: "{that}.options.locales",
26131 target: "{that prefsEditor fluid.prefs.panel.localization}.options.controlValues.localization"
26132 },
26133 "prefsEditor.localization.panel.localeNames": {
26134 source: "{that}.options.localeNames",
26135 target: "{that prefsEditor fluid.prefs.panel.localization}.options.stringArrayIndex.localization"
26136 }
26137 }
26138 });
26139
26140 fluid.defaults("fluid.prefs.constructed.localizationPrefsEditorConfig.urlPathLocale", {
26141 distributeOptions: {
26142 "prefsEditor.localization.enactor.langMap": {
26143 source: "{that}.options.langMap",
26144 target: "{that uiEnhancer fluid.prefs.enactor.localization}.options.langMap"
26145 },
26146 "prefsEditor.localization.enactor.langSegIndex": {
26147 source: "{that}.options.langSegIndex",
26148 target: "{that uiEnhancer fluid.prefs.enactor.localization}.options.langSegIndex"
26149 }
26150 }
26151 });
26152
26153})(fluid_3_0_0);
26154;
26155/*
26156Copyright The Infusion copyright holders
26157See the AUTHORS.md file at the top-level directory of this distribution and at
26158https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
26159
26160Licensed under the Educational Community License (ECL), Version 2.0 or the New
26161BSD license. You may not use this file except in compliance with one these
26162Licenses.
26163
26164You may obtain a copy of the ECL 2.0 License and BSD License at
26165https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
26166*/
26167
26168var fluid_3_0_0 = fluid_3_0_0 || {};
26169
26170(function (fluid) {
26171 "use strict";
26172
26173 /*******************************************************************************
26174 * Starter auxiliary schema grade
26175 *
26176 * Contains the settings for the word space preference
26177 *******************************************************************************/
26178
26179 // Fine-tune the starter aux schema and add word space preference
26180 fluid.defaults("fluid.prefs.auxSchema.wordSpace", {
26181 gradeNames: ["fluid.prefs.auxSchema"],
26182 auxiliarySchema: {
26183 "namespace": "fluid.prefs.constructed",
26184 "terms": {
26185 "templatePrefix": "../../framework/preferences/html/",
26186 "messagePrefix": "../../framework/preferences/messages/"
26187 },
26188 "template": "%templatePrefix/SeparatedPanelPrefsEditor.html",
26189 "message": "%messagePrefix/prefsEditor.json",
26190
26191 wordSpace: {
26192 type: "fluid.prefs.wordSpace",
26193 enactor: {
26194 type: "fluid.prefs.enactor.wordSpace",
26195 fontSizeMap: {
26196 "xx-small": "9px",
26197 "x-small": "11px",
26198 "small": "13px",
26199 "medium": "15px",
26200 "large": "18px",
26201 "x-large": "23px",
26202 "xx-large": "30px"
26203 }
26204 },
26205 panel: {
26206 type: "fluid.prefs.panel.wordSpace",
26207 container: ".flc-prefsEditor-word-space",
26208 template: "%templatePrefix/PrefsEditorTemplate-wordSpace.html",
26209 message: "%messagePrefix/wordSpace.json"
26210 }
26211 }
26212 }
26213 });
26214
26215
26216 /*******************************************************************************
26217 * Primary Schema
26218 *******************************************************************************/
26219
26220 // add extra prefs to the starter primary schemas
26221
26222 fluid.defaults("fluid.prefs.schemas.wordSpace", {
26223 gradeNames: ["fluid.prefs.schemas"],
26224 schema: {
26225 "fluid.prefs.wordSpace": {
26226 "type": "number",
26227 "default": 1,
26228 "minimum": 0.7,
26229 "maximum": 2,
26230 "divisibleBy": 0.1
26231 }
26232 }
26233 });
26234
26235})(fluid_3_0_0);
26236;
26237/*
26238Copyright The Infusion copyright holders
26239See the AUTHORS.md file at the top-level directory of this distribution and at
26240https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
26241
26242Licensed under the Educational Community License (ECL), Version 2.0 or the New
26243BSD license. You may not use this file except in compliance with one these
26244Licenses.
26245
26246You may obtain a copy of the ECL 2.0 License and BSD License at
26247https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
26248*/
26249
26250var fluid_3_0_0 = fluid_3_0_0 || {};
26251
26252
26253(function ($, fluid) {
26254 "use strict";
26255
26256 fluid.registerNamespace("fluid.prefs");
26257
26258 fluid.defaults("fluid.prefs.builder", {
26259 gradeNames: ["fluid.component", "fluid.prefs.auxBuilder"],
26260 mergePolicy: {
26261 auxSchema: "expandedAuxSchema"
26262 },
26263 assembledPrefsEditorGrade: {
26264 expander: {
26265 func: "fluid.prefs.builder.generateGrade",
26266 args: ["prefsEditor", "{that}.options.auxSchema.namespace", {
26267 gradeNames: ["fluid.prefs.assembler.prefsEd", "fluid.viewComponent"],
26268 componentGrades: "{that}.options.constructedGrades",
26269 loaderGrades: "{that}.options.auxSchema.loaderGrades",
26270 defaultLocale: "{that}.options.auxSchema.defaultLocale",
26271 enhancer: {
26272 defaultLocale: "{that}.options.auxSchema.defaultLocale"
26273 }
26274 }]
26275 }
26276 },
26277 assembledUIEGrade: {
26278 expander: {
26279 func: "fluid.prefs.builder.generateGrade",
26280 args: ["uie", "{that}.options.auxSchema.namespace", {
26281 gradeNames: ["fluid.viewComponent", "fluid.prefs.assembler.uie"],
26282 componentGrades: "{that}.options.constructedGrades"
26283 }]
26284 }
26285 },
26286 constructedGrades: {
26287 expander: {
26288 func: "fluid.prefs.builder.constructGrades",
26289 args: [
26290 "{that}.options.auxSchema",
26291 [
26292 "enactors",
26293 "messages",
26294 "panels",
26295 "initialModel",
26296 "templateLoader",
26297 "messageLoader",
26298 "terms",
26299 "aliases_prefsEditor",
26300 "aliases_enhancer"
26301 ]
26302 ]
26303 }
26304 },
26305 mappedDefaults: "{primaryBuilder}.options.schema.properties",
26306 components: {
26307 primaryBuilder: {
26308 type: "fluid.prefs.primaryBuilder",
26309 options: {
26310 typeFilter: {
26311 expander: {
26312 func: "fluid.prefs.builder.parseAuxSchema",
26313 args: "{builder}.options.auxiliarySchema"
26314 }
26315 }
26316 }
26317 }
26318 },
26319 distributeOptions: {
26320 "builder.primaryBuilder.primarySchema": {
26321 source: "{that}.options.primarySchema",
26322 removeSource: true,
26323 target: "{that > primaryBuilder}.options.primarySchema"
26324 }
26325 }
26326 });
26327
26328 fluid.defaults("fluid.prefs.assembler.uie", {
26329 gradeNames: ["fluid.viewComponent"],
26330 components: {
26331 // These two components become global
26332 store: {
26333 type: "fluid.prefs.globalSettingsStore",
26334 options: {
26335 distributeOptions: {
26336 "uie.store.context.checkUser": {
26337 target: "{that fluid.prefs.store}.options.contextAwareness.strategy.checks.user",
26338 record: {
26339 contextValue: "{fluid.prefs.assembler.uie}.options.storeType",
26340 gradeNames: "{fluid.prefs.assembler.uie}.options.storeType"
26341 }
26342 }
26343 }
26344 }
26345 },
26346 enhancer: {
26347 type: "fluid.component",
26348 options: {
26349 gradeNames: "{that}.options.enhancerType",
26350 enhancerType: "fluid.pageEnhancer",
26351 components: {
26352 uiEnhancer: {
26353 options: {
26354 gradeNames: [
26355 "{fluid.prefs.assembler.uie}.options.componentGrades.enactors",
26356 "{fluid.prefs.assembler.prefsEd}.options.componentGrades.aliases_enhancer"
26357 ]
26358 }
26359 }
26360 }
26361 }
26362 }
26363 },
26364 distributeOptions: {
26365 "uie.enhancer": {
26366 source: "{that}.options.enhancer",
26367 target: "{that uiEnhancer}.options",
26368 removeSource: true
26369 },
26370 "uie.enhancer.enhancerType": {
26371 source: "{that}.options.enhancerType",
26372 target: "{that > enhancer}.options.enhancerType"
26373 },
26374 "uie.store": { // TODO: not clear that this hits anything since settings store is not a subcomponent
26375 source: "{that}.options.store",
26376 target: "{that fluid.prefs.store}.options"
26377 }
26378 }
26379 });
26380
26381 fluid.defaults("fluid.prefs.assembler.prefsEd", {
26382 gradeNames: ["fluid.viewComponent", "fluid.prefs.assembler.uie"],
26383 components: {
26384 prefsEditorLoader: {
26385 type: "fluid.viewComponent",
26386 container: "{fluid.prefs.assembler.prefsEd}.container",
26387 priority: "last",
26388 options: {
26389 gradeNames: [
26390 "{fluid.prefs.assembler.prefsEd}.options.componentGrades.terms",
26391 "{fluid.prefs.assembler.prefsEd}.options.componentGrades.messages",
26392 "{fluid.prefs.assembler.prefsEd}.options.componentGrades.initialModel",
26393 "{that}.options.loaderGrades"
26394 ],
26395 templateLoader: {
26396 gradeNames: ["{fluid.prefs.assembler.prefsEd}.options.componentGrades.templateLoader"]
26397 },
26398 messageLoader: {
26399 gradeNames: ["{fluid.prefs.assembler.prefsEd}.options.componentGrades.messageLoader"]
26400 },
26401 prefsEditor: {
26402 gradeNames: [
26403 "{fluid.prefs.assembler.prefsEd}.options.componentGrades.panels",
26404 "{fluid.prefs.assembler.prefsEd}.options.componentGrades.aliases_prefsEditor",
26405 "fluid.prefs.uiEnhancerRelay"
26406 ]
26407 },
26408 events: {
26409 onReady: "{fluid.prefs.assembler.prefsEd}.events.onPrefsEditorReady"
26410 }
26411 }
26412 }
26413 },
26414 events: {
26415 onPrefsEditorReady: null,
26416 onReady: {
26417 events: {
26418 onPrefsEditorReady: "onPrefsEditorReady",
26419 onCreate: "onCreate"
26420 },
26421 args: ["{that}"]
26422 }
26423 },
26424 distributeOptions: {
26425 "prefsEdAssembler.prefsEditorLoader.loaderGrades": {
26426 source: "{that}.options.loaderGrades",
26427 removeSource: true,
26428 target: "{that > prefsEditorLoader}.options.loaderGrades"
26429 },
26430 "prefsEdAssembler.prefsEditorLoader.terms": {
26431 source: "{that}.options.terms",
26432 removeSource: true,
26433 target: "{that prefsEditorLoader}.options.terms"
26434 },
26435 "prefsEdAssembler.prefsEditorLoader.defaultLocale": {
26436 source: "{that}.options.defaultLocale",
26437 target: "{that prefsEditorLoader}.options.defaultLocale"
26438 },
26439 "prefsEdAssembler.uiEnhancer.defaultLocale": {
26440 source: "{that}.options.defaultLocale",
26441 target: "{that uiEnhancer}.options.defaultLocale"
26442 },
26443 "prefsEdAssembler.prefsEditor": {
26444 source: "{that}.options.prefsEditor",
26445 removeSource: true,
26446 target: "{that prefsEditor}.options"
26447 }
26448 }
26449 });
26450
26451 fluid.prefs.builder.generateGrade = function (name, namespace, options) {
26452 var gradeNameTemplate = "%namespace.%name";
26453 var gradeName = fluid.stringTemplate(gradeNameTemplate, {name: name, namespace: namespace});
26454 fluid.defaults(gradeName, options);
26455 return gradeName;
26456 };
26457
26458 fluid.prefs.builder.constructGrades = function (auxSchema, gradeCategories) {
26459 var constructedGrades = {};
26460 fluid.each(gradeCategories, function (category) {
26461 var gradeOpts = auxSchema[category];
26462 if (fluid.get(gradeOpts, "gradeNames")) {
26463 constructedGrades[category] = fluid.prefs.builder.generateGrade(category, auxSchema.namespace, gradeOpts);
26464 }
26465 });
26466 return constructedGrades;
26467 };
26468
26469 fluid.prefs.builder.parseAuxSchema = function (auxSchema) {
26470 var auxTypes = [];
26471 fluid.each(auxSchema, function parse(field) {
26472 var type = field.type;
26473 if (type) {
26474 auxTypes.push(type);
26475 }
26476 });
26477 return auxTypes;
26478 };
26479
26480 /*
26481 * A one-stop-shop function to build and instantiate a prefsEditor from a schema.
26482 */
26483 fluid.prefs.create = function (container, options) {
26484 options = options || {};
26485 var builder = fluid.prefs.builder(options.build);
26486 return fluid.invokeGlobalFunction(builder.options.assembledPrefsEditorGrade, [container, options.prefsEditor]);
26487 };
26488
26489})(jQuery, fluid_3_0_0);
26490;
26491/*
26492Copyright The Infusion copyright holders
26493See the AUTHORS.md file at the top-level directory of this distribution and at
26494https://github.com/fluid-project/infusion/raw/master/AUTHORS.md.
26495
26496Licensed under the Educational Community License (ECL), Version 2.0 or the New
26497BSD license. You may not use this file except in compliance with one these
26498Licenses.
26499
26500You may obtain a copy of the ECL 2.0 License and BSD License at
26501https://github.com/fluid-project/infusion/raw/master/Infusion-LICENSE.txt
26502*/
26503
26504var fluid_3_0_0 = fluid_3_0_0 || {};
26505(function ($, fluid) {
26506 "use strict";
26507
26508 // Gradename to invoke "fluid.uiOptions.prefsEditor"
26509 fluid.prefs.builder({
26510 gradeNames: ["fluid.prefs.auxSchema.starter"]
26511 });
26512
26513 fluid.defaults("fluid.uiOptions.prefsEditor", {
26514 gradeNames: ["fluid.prefs.constructed.prefsEditor"],
26515 lazyLoad: false,
26516 distributeOptions: {
26517 "uio.separatedPanel.lazyLoad": {
26518 record: "{that}.options.lazyLoad",
26519 target: "{that separatedPanel}.options.lazyLoad"
26520 },
26521 "uio.uiEnhancer.tocTemplate": {
26522 source: "{that}.options.tocTemplate",
26523 target: "{that uiEnhancer > tableOfContents}.options.tocTemplate"
26524 },
26525 "uio.uiEnhancer.tocMessage": {
26526 source: "{that}.options.tocMessage",
26527 target: "{that uiEnhancer > tableOfContents}.options.tocMessage"
26528 },
26529 "uio.uiEnhancer.ignoreForToC": {
26530 source: "{that}.options.ignoreForToC",
26531 target: "{that uiEnhancer > tableOfContents}.options.ignoreForToC"
26532 }
26533 }
26534 });
26535
26536})(jQuery, fluid_3_0_0);
26537
26538//# sourceMappingURL=infusion-uio-no-jquery.js.map
\No newline at end of file