PageRenderTime 72ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 2ms

/dev/_lib/Fluid/3akai_Infusion.js

https://github.com/baholladay/3akai-ux
JavaScript | 8170 lines | 6522 code | 667 blank | 981 comment | 639 complexity | 1dddd91ffc584e0d5f0cc34dee57dc03 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

  1. /* 3akai_Infusion.js
  2. * custom build for Sakai
  3. * built with: ant customBuild -Dinclude="uploader, reorderer" -Dexclude="jQuery, jQueryUICore, jQueryUIWidgets, jQueryTooltipPlugin" -Djsfilename="3akai_Infusion.js" -DnoMinify="true"
  4. */
  5. /*
  6. Copyright 2007-2010 University of Cambridge
  7. Copyright 2007-2009 University of Toronto
  8. Copyright 2007-2009 University of California, Berkeley
  9. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  10. BSD license. You may not use this file except in compliance with one these
  11. Licenses.
  12. You may obtain a copy of the ECL 2.0 License and BSD License at
  13. https://source.fluidproject.org/svn/LICENSE.txt
  14. */
  15. // Declare dependencies.
  16. /*global jQuery, YAHOO, opera*/
  17. var fluid_1_2 = fluid_1_2 || {};
  18. var fluid = fluid || fluid_1_2;
  19. (function ($, fluid) {
  20. fluid.version = "Infusion 1.2";
  21. fluid.environment = {
  22. fluid: fluid
  23. };
  24. var globalObject = window || {};
  25. /**
  26. * Causes an error message to be logged to the console and a real runtime error to be thrown.
  27. *
  28. * @param {String|Error} message the error message to log
  29. */
  30. fluid.fail = function (message) {
  31. fluid.setLogging(true);
  32. fluid.log(message.message? message.message : message);
  33. throw new Error(message);
  34. //message.fail(); // Intentionally cause a browser error by invoking a nonexistent function.
  35. };
  36. /**
  37. * Wraps an object in a jQuery if it isn't already one. This function is useful since
  38. * it ensures to wrap a null or otherwise falsy argument to itself, rather than the
  39. * often unhelpful jQuery default of returning the overall document node.
  40. *
  41. * @param {Object} obj the object to wrap in a jQuery
  42. */
  43. fluid.wrap = function (obj) {
  44. return ((!obj || obj.jquery) ? obj : $(obj));
  45. };
  46. /**
  47. * If obj is a jQuery, this function will return the first DOM element within it.
  48. *
  49. * @param {jQuery} obj the jQuery instance to unwrap into a pure DOM element
  50. */
  51. fluid.unwrap = function (obj) {
  52. return obj && obj.jquery && obj.length === 1 ? obj[0] : obj; // Unwrap the element if it's a jQuery.
  53. };
  54. /**
  55. * Searches through the supplied object for the first value which matches the one supplied.
  56. * @param obj {Object} the Object to be searched through
  57. * @param value {Object} the value to be found. This will be compared against the object's
  58. * member using === equality.
  59. * @return {String} The first key whose value matches the one supplied, or <code>null</code> if no
  60. * such key is found.
  61. */
  62. fluid.keyForValue = function (obj, value) {
  63. for (var key in obj) {
  64. if (obj[key] === value) {
  65. return key;
  66. }
  67. }
  68. return null;
  69. };
  70. /**
  71. * This method is now deprecated and will be removed in a future release of Infusion.
  72. * See fluid.keyForValue instead.
  73. */
  74. fluid.findKeyInObject = fluid.keyForValue;
  75. /**
  76. * Clears an object or array of its contents. For objects, each property is deleted.
  77. *
  78. * @param {Object|Array} target the target to be cleared
  79. */
  80. fluid.clear = function (target) {
  81. if (target instanceof Array) {
  82. target.length = 0;
  83. }
  84. else {
  85. for (var i in target) {
  86. delete target[i];
  87. }
  88. }
  89. };
  90. /** A basic utility that returns its argument unchanged */
  91. fluid.identity = function(arg) {
  92. return arg;
  93. }
  94. // Framework and instantiation functions.
  95. /**
  96. * Fetches a single container element and returns it as a jQuery.
  97. *
  98. * @param {String||jQuery||element} an id string, a single-element jQuery, or a DOM element specifying a unique container
  99. * @return a single-element jQuery of container
  100. */
  101. fluid.container = function (containerSpec) {
  102. var container = containerSpec;
  103. if (typeof containerSpec === "string" ||
  104. containerSpec.nodeType && (containerSpec.nodeType === 1 || containerSpec.nodeType === 9)) {
  105. container = $(containerSpec);
  106. }
  107. // Throw an exception if we've got more or less than one element.
  108. if (!container || !container.jquery || container.length !== 1) {
  109. if (typeof(containerSpec) !== "string") {
  110. containerSpec = container.selector;
  111. }
  112. fluid.fail({
  113. name: "NotOne",
  114. message: "A single container element was not found for selector " + containerSpec
  115. });
  116. }
  117. return container;
  118. };
  119. // stubs for two functions in FluidDebugging.js
  120. fluid.dumpEl = fluid.identity;
  121. fluid.renderTimestamp = fluid.identity;
  122. /**
  123. * Retreives and stores a component's default settings centrally.
  124. * @param {boolean} (options) if true, manipulate a global option (for the head
  125. * component) rather than instance options.
  126. * @param {String} componentName the name of the component
  127. * @param {Object} (optional) an container of key/value pairs to set
  128. *
  129. */
  130. var defaultsStore = {};
  131. var globalDefaultsStore = {};
  132. fluid.defaults = function () {
  133. var offset = 0;
  134. var store = defaultsStore;
  135. if (typeof arguments[0] === "boolean") {
  136. store = globalDefaultsStore;
  137. offset = 1;
  138. }
  139. var componentName = arguments[offset];
  140. var defaultsObject = arguments[offset + 1];
  141. if (defaultsObject !== undefined) {
  142. store[componentName] = defaultsObject;
  143. return defaultsObject;
  144. }
  145. return store[componentName];
  146. };
  147. /**
  148. * Creates a new DOM Binder instance, used to locate elements in the DOM by name.
  149. *
  150. * @param {Object} container the root element in which to locate named elements
  151. * @param {Object} selectors a collection of named jQuery selectors
  152. */
  153. fluid.createDomBinder = function (container, selectors) {
  154. var cache = {}, that = {};
  155. function cacheKey(name, thisContainer) {
  156. return fluid.allocateSimpleId(thisContainer) + "-" + name;
  157. }
  158. function record(name, thisContainer, result) {
  159. cache[cacheKey(name, thisContainer)] = result;
  160. }
  161. that.locate = function (name, localContainer) {
  162. var selector, thisContainer, togo;
  163. selector = selectors[name];
  164. thisContainer = localContainer? localContainer: container;
  165. if (!thisContainer) {
  166. fluid.fail("DOM binder invoked for selector " + name + " without container");
  167. }
  168. if (!selector) {
  169. return thisContainer;
  170. }
  171. if (typeof(selector) === "function") {
  172. togo = $(selector.call(null, fluid.unwrap(thisContainer)));
  173. } else {
  174. togo = $(selector, thisContainer);
  175. }
  176. if (togo.get(0) === document) {
  177. togo = [];
  178. //fluid.fail("Selector " + name + " with value " + selectors[name] +
  179. // " did not find any elements with container " + fluid.dumpEl(container));
  180. }
  181. if (!togo.selector) {
  182. togo.selector = selector;
  183. togo.context = thisContainer;
  184. }
  185. togo.selectorName = name;
  186. record(name, thisContainer, togo);
  187. return togo;
  188. };
  189. that.fastLocate = function (name, localContainer) {
  190. var thisContainer = localContainer? localContainer: container;
  191. var key = cacheKey(name, thisContainer);
  192. var togo = cache[key];
  193. return togo? togo : that.locate(name, localContainer);
  194. };
  195. that.clear = function () {
  196. cache = {};
  197. };
  198. that.refresh = function (names, localContainer) {
  199. var thisContainer = localContainer? localContainer: container;
  200. if (typeof names === "string") {
  201. names = [names];
  202. }
  203. if (thisContainer.length === undefined) {
  204. thisContainer = [thisContainer];
  205. }
  206. for (var i = 0; i < names.length; ++ i) {
  207. for (var j = 0; j < thisContainer.length; ++ j) {
  208. that.locate(names[i], thisContainer[j]);
  209. }
  210. }
  211. };
  212. return that;
  213. };
  214. /** Determines whether the supplied object can be treated as an array, by
  215. * iterating an index towards its length. The test functions by detecting
  216. * a property named "length" which is of type "number", but excluding objects
  217. * which are themselves of type "string".
  218. */
  219. fluid.isArrayable = function(totest) {
  220. return typeof(totest) !== "string" && typeof(totest.length) === "number";
  221. }
  222. /**
  223. * Attaches the user's listeners to a set of events.
  224. *
  225. * @param {Object} events a collection of named event firers
  226. * @param {Object} listeners optional listeners to add
  227. */
  228. fluid.mergeListeners = function (events, listeners) {
  229. if (listeners) {
  230. for (var key in listeners) {
  231. var value = listeners[key];
  232. var keydot = key.indexOf(".");
  233. var namespace;
  234. if (keydot !== -1) {
  235. namespace = key.substring(keydot + 1);
  236. key = key.substring(0, keydot);
  237. }
  238. if (!events[key]) {
  239. events[key] = fluid.event.getEventFirer();
  240. }
  241. var firer = events[key];
  242. if (typeof(value) === "function") {
  243. firer.addListener(value, namespace);
  244. }
  245. else if (value && fluid.isArrayable(value)) {
  246. for (var i = 0; i < value.length; ++ i) {
  247. firer.addListener(value[i], namespace);
  248. }
  249. }
  250. }
  251. }
  252. };
  253. /**
  254. * Sets up a component's declared events.
  255. * Events are specified in the options object by name. There are three different types of events that can be
  256. * specified:
  257. * 1. an ordinary multicast event, specified by "null.
  258. * 2. a unicast event, which allows only one listener to be registered
  259. * 3. a preventable event
  260. *
  261. * @param {Object} that the component
  262. * @param {Object} options the component's options structure, containing the declared event names and types
  263. */
  264. fluid.instantiateFirers = function (that, options) {
  265. that.events = {};
  266. if (options.events) {
  267. for (var event in options.events) {
  268. var eventType = options.events[event];
  269. that.events[event] = fluid.event.getEventFirer(eventType === "unicast", eventType === "preventable");
  270. }
  271. }
  272. fluid.mergeListeners(that.events, options.listeners);
  273. };
  274. /**
  275. * Merges the component's declared defaults, as obtained from fluid.defaults(),
  276. * with the user's specified overrides.
  277. *
  278. * @param {Object} that the instance to attach the options to
  279. * @param {String} componentName the unique "name" of the component, which will be used
  280. * to fetch the default options from store. By recommendation, this should be the global
  281. * name of the component's creator function.
  282. * @param {Object} userOptions the user-specified configuration options for this component
  283. */
  284. fluid.mergeComponentOptions = function (that, componentName, userOptions) {
  285. var defaults = fluid.defaults(componentName);
  286. that.options = fluid.merge(defaults? defaults.mergePolicy: null, {}, defaults, userOptions);
  287. };
  288. /** Expect that an output from the DOM binder has resulted in a non-empty set of
  289. * results. If none are found, this function will fail with a diagnostic message,
  290. * with the supplied message prepended.
  291. */
  292. fluid.expectFilledSelector = function (result, message) {
  293. if (result && result.length === 0 && result.jquery) {
  294. fluid.fail(message + ": selector \"" + result.selector + "\" with name " + result.selectorName +
  295. " returned no results in context " + fluid.dumpEl(result.context));
  296. }
  297. };
  298. /**
  299. * The central initialiation method called as the first act of every Fluid
  300. * component. This function automatically merges user options with defaults,
  301. * attaches a DOM Binder to the instance, and configures events.
  302. *
  303. * @param {String} componentName The unique "name" of the component, which will be used
  304. * to fetch the default options from store. By recommendation, this should be the global
  305. * name of the component's creator function.
  306. * @param {jQueryable} container A specifier for the single root "container node" in the
  307. * DOM which will house all the markup for this component.
  308. * @param {Object} userOptions The configuration options for this component.
  309. */
  310. fluid.initView = function (componentName, container, userOptions) {
  311. var that = {};
  312. fluid.expectFilledSelector(container, "Error instantiating component with name \"" + componentName);
  313. fluid.mergeComponentOptions(that, componentName, userOptions);
  314. if (container) {
  315. that.container = fluid.container(container);
  316. fluid.initDomBinder(that);
  317. }
  318. fluid.instantiateFirers(that, that.options);
  319. return that;
  320. };
  321. /** A special "marker object" which is recognised as one of the arguments to
  322. * fluid.initSubcomponents. This object is recognised by reference equality -
  323. * where it is found, it is replaced in the actual argument position supplied
  324. * to the specific subcomponent instance, with the particular options block
  325. * for that instance attached to the overall "that" object.
  326. */
  327. fluid.COMPONENT_OPTIONS = {};
  328. /** Another special "marker object" representing that a distinguished
  329. * (probably context-dependent) value should be substituted.
  330. */
  331. fluid.VALUE = {};
  332. /** Construct a dummy or "placeholder" subcomponent, that optionally provides empty
  333. * implementations for a set of methods.
  334. */
  335. fluid.emptySubcomponent = function (options) {
  336. var that = {};
  337. options = $.makeArray(options);
  338. for (var i = 0; i < options.length; ++ i) {
  339. that[options[i]] = function () {};
  340. }
  341. return that;
  342. };
  343. /**
  344. * Creates a new "little component": a that-ist object with options merged into it by the framework.
  345. * This method is a convenience for creating small objects that have options but don't require full
  346. * View-like features such as the DOM Binder or events
  347. *
  348. * @param {Object} name the name of the little component to create
  349. * @param {Object} options user-supplied options to merge with the defaults
  350. */
  351. fluid.initLittleComponent = function(name, options) {
  352. var that = {};
  353. fluid.mergeComponentOptions(that, name, options);
  354. return that;
  355. };
  356. fluid.initSubcomponent = function (that, className, args) {
  357. return fluid.initSubcomponents(that, className, args)[0];
  358. };
  359. /** Initialise all the "subcomponents" which are configured to be attached to
  360. * the supplied top-level component, which share a particular "class name".
  361. * @param {Component} that The top-level component for which sub-components are
  362. * to be instantiated. It contains specifications for these subcomponents in its
  363. * <code>options</code> structure.
  364. * @param {String} className The "class name" or "category" for the subcomponents to
  365. * be instantiated. A class name specifies an overall "function" for a class of
  366. * subcomponents and represents a category which accept the same signature of
  367. * instantiation arguments.
  368. * @param {Array of Object} args The instantiation arguments to be passed to each
  369. * constructed subcomponent. These will typically be members derived from the
  370. * top-level <code>that</code> or perhaps globally discovered from elsewhere. One
  371. * of these arguments may be <code>fluid.COMPONENT_OPTIONS</code> in which case this
  372. * placeholder argument will be replaced by instance-specific options configured
  373. * into the member of the top-level <code>options</code> structure named for the
  374. * <code>className</code>
  375. * @return {Array of Object} The instantiated subcomponents, one for each member
  376. * of <code>that.options[className]</code>.
  377. */
  378. fluid.initSubcomponents = function (that, className, args) {
  379. var entry = that.options[className];
  380. if (!entry) {
  381. return;
  382. }
  383. var entries = $.makeArray(entry);
  384. var optindex = -1;
  385. var togo = [];
  386. args = $.makeArray(args);
  387. for (var i = 0; i < args.length; ++ i) {
  388. if (args[i] === fluid.COMPONENT_OPTIONS) {
  389. optindex = i;
  390. }
  391. }
  392. for (i = 0; i < entries.length; ++ i) {
  393. entry = entries[i];
  394. if (optindex !== -1 && entry.options) {
  395. args[optindex] = entry.options;
  396. }
  397. if (typeof(entry) !== "function") {
  398. var entryType = typeof(entry) === "string"? entry : entry.type;
  399. var globDef = fluid.defaults(true, entryType);
  400. fluid.merge("reverse", that.options, globDef);
  401. togo[i] = entryType === "fluid.emptySubcomponent"?
  402. fluid.emptySubcomponent(entry.options) :
  403. fluid.invokeGlobalFunction(entryType, args, {fluid: fluid});
  404. }
  405. else {
  406. togo[i] = entry.apply(null, args);
  407. }
  408. var returnedOptions = togo[i]? togo[i].returnedOptions : null;
  409. if (returnedOptions) {
  410. fluid.merge(that.options.mergePolicy, that.options, returnedOptions);
  411. if (returnedOptions.listeners) {
  412. fluid.mergeListeners(that.events, returnedOptions.listeners);
  413. }
  414. }
  415. }
  416. return togo;
  417. };
  418. /**
  419. * Creates a new DOM Binder instance for the specified component and mixes it in.
  420. *
  421. * @param {Object} that the component instance to attach the new DOM Binder to
  422. */
  423. fluid.initDomBinder = function (that) {
  424. that.dom = fluid.createDomBinder(that.container, that.options.selectors);
  425. that.locate = that.dom.locate;
  426. };
  427. /** Returns true if the argument is a primitive type **/
  428. fluid.isPrimitive = function (value) {
  429. var valueType = typeof(value);
  430. return !value || valueType === "string" || valueType === "boolean" || valueType === "number" || valueType === "function";
  431. };
  432. function mergeImpl(policy, basePath, target, source) {
  433. var thisPolicy = policy && typeof(policy) !== "string"? policy[basePath] : policy;
  434. if (typeof(thisPolicy) === "function") {
  435. thisPolicy.apply(null, target, source);
  436. return target;
  437. }
  438. if (thisPolicy === "replace") {
  439. fluid.clear(target);
  440. }
  441. for (var name in source) {
  442. var path = (basePath? basePath + ".": "") + name;
  443. var thisTarget = target[name];
  444. var thisSource = source[name];
  445. var primitiveTarget = fluid.isPrimitive(thisTarget);
  446. if (thisSource !== undefined) {
  447. if (thisSource !== null && typeof thisSource === 'object' &&
  448. !thisSource.nodeType && !thisSource.jquery && thisSource !== fluid.VALUE) {
  449. if (primitiveTarget) {
  450. target[name] = thisTarget = thisSource instanceof Array? [] : {};
  451. }
  452. mergeImpl(policy, path, thisTarget, thisSource);
  453. }
  454. else {
  455. if (thisTarget === null || thisTarget === undefined || thisPolicy !== "reverse") {
  456. target[name] = thisSource;
  457. }
  458. }
  459. }
  460. }
  461. return target;
  462. }
  463. /** Merge a collection of options structures onto a target, following an optional policy.
  464. * This function is typically called automatically, as a result of an invocation of
  465. * <code>fluid.iniView</code>. The behaviour of this function is explained more fully on
  466. * the page http://wiki.fluidproject.org/display/fluid/Options+Merging+for+Fluid+Components .
  467. * @param policy {Object/String} A "policy object" specifiying the type of merge to be performed.
  468. * If policy is of type {String} it should take on the value "reverse" or "replace" representing
  469. * a static policy. If it is an
  470. * Object, it should contain a mapping of EL paths onto these String values, representing a
  471. * fine-grained policy. If it is an Object, the values may also themselves be EL paths
  472. * representing that a default value is to be taken from that path.
  473. * @param target {Object} The options structure which is to be modified by receiving the merge results.
  474. * @param options1, options2, .... {Object} an arbitrary list of options structure which are to
  475. * be merged "on top of" the <code>target</code>. These will not be modified.
  476. */
  477. fluid.merge = function (policy, target) {
  478. var path = "";
  479. for (var i = 2; i < arguments.length; ++i) {
  480. var source = arguments[i];
  481. if (source !== null && source !== undefined) {
  482. mergeImpl(policy, path, target, source);
  483. }
  484. }
  485. if (policy && typeof(policy) !== "string") {
  486. for (var key in policy) {
  487. var elrh = policy[key];
  488. if (typeof(elrh) === 'string' && elrh !== "replace") {
  489. var oldValue = fluid.model.getBeanValue(target, key);
  490. if (oldValue === null || oldValue === undefined) {
  491. var value = fluid.model.getBeanValue(target, elrh);
  492. fluid.model.setBeanValue(target, key, value);
  493. }
  494. }
  495. }
  496. }
  497. return target;
  498. };
  499. /** Return an empty container as the same type as the argument (either an
  500. * array or hash */
  501. fluid.freshContainer = function(tocopy) {
  502. return fluid.isArrayable(tocopy)? [] : {};
  503. };
  504. /** Performs a deep copy (clone) of its argument **/
  505. fluid.copy = function (tocopy) {
  506. if (fluid.isPrimitive(tocopy)) {
  507. return tocopy;
  508. }
  509. return $.extend(true, fluid.freshContainer(tocopy), tocopy);
  510. };
  511. fluid.getGlobalValue = function(path, env) {
  512. env = env || fluid.environment;
  513. return fluid.model.getBeanValue(globalObject, path, env);
  514. };
  515. /**
  516. * Allows for the calling of a function from an EL expression "functionPath", with the arguments "args", scoped to an framework version "environment".
  517. * @param {Object} functionPath - An EL expression
  518. * @param {Object} args - An array of arguments to be applied to the function, specified in functionPath
  519. * @param {Object} environment - (optional) The object to scope the functionPath to (typically the framework root for version control)
  520. */
  521. fluid.invokeGlobalFunction = function (functionPath, args, environment) {
  522. var func = fluid.getGlobalValue(functionPath, environment);
  523. if (!func) {
  524. fluid.fail("Error invoking global function: " + functionPath + " could not be located");
  525. } else {
  526. return func.apply(null, args);
  527. }
  528. };
  529. /** Registers a new global function at a given path (currently assumes that
  530. * it lies within the fluid namespace)
  531. */
  532. fluid.registerGlobalFunction = function (functionPath, func, env) {
  533. env = env || fluid.environment;
  534. fluid.model.setBeanValue(globalObject, functionPath, func, env);
  535. };
  536. fluid.registerGlobal = fluid.registerGlobalFunction;
  537. /** Ensures that an entry in the global namespace exists **/
  538. fluid.registerNamespace = function (naimspace, env) {
  539. env = env || fluid.environment;
  540. var existing = fluid.getGlobalValue(naimspace, env);
  541. if (!existing) {
  542. existing = {};
  543. fluid.registerGlobal(naimspace, existing, env);
  544. }
  545. return existing;
  546. };
  547. // The Model Events system.
  548. fluid.event = {};
  549. var fluid_guid = 1;
  550. /** Construct an "event firer" object which can be used to register and deregister
  551. * listeners, to which "events" can be fired. These events consist of an arbitrary
  552. * function signature. General documentation on the Fluid events system is at
  553. * http://wiki.fluidproject.org/display/fluid/The+Fluid+Event+System .
  554. * @param {Boolean} unicast If <code>true</code>, this is a "unicast" event which may only accept
  555. * a single listener.
  556. * @param {Boolean} preventable If <code>true</code> the return value of each handler will
  557. * be checked for <code>false</code> in which case further listeners will be shortcircuited, and this
  558. * will be the return value of fire()
  559. */
  560. fluid.event.getEventFirer = function (unicast, preventable) {
  561. var log = fluid.log;
  562. var listeners = {};
  563. return {
  564. addListener: function (listener, namespace, predicate) {
  565. if (!listener) {
  566. return;
  567. }
  568. if (unicast) {
  569. namespace = "unicast";
  570. }
  571. if (!namespace) {
  572. if (!listener.$$guid) {
  573. listener.$$guid = fluid_guid++;
  574. }
  575. namespace = listener.$$guid;
  576. }
  577. listeners[namespace] = {listener: listener, predicate: predicate};
  578. },
  579. removeListener: function (listener) {
  580. if (typeof(listener) === 'string') {
  581. delete listeners[listener];
  582. }
  583. else if (typeof(listener) === 'object' && listener.$$guid) {
  584. delete listeners[listener.$$guid];
  585. }
  586. },
  587. fire: function () {
  588. for (var i in listeners) {
  589. var lisrec = listeners[i];
  590. var listener = lisrec.listener;
  591. if (lisrec.predicate && !lisrec.predicate(listener, arguments)) {
  592. continue;
  593. }
  594. try {
  595. var ret = listener.apply(null, arguments);
  596. if (preventable && ret === false) {
  597. return false;
  598. }
  599. }
  600. catch (e) {
  601. log("FireEvent received exception " + e.message + " e " + e + " firing to listener " + i);
  602. throw (e);
  603. }
  604. }
  605. }
  606. };
  607. };
  608. // Model functions
  609. fluid.model = {};
  610. /** Copy a source "model" onto a target **/
  611. fluid.model.copyModel = function (target, source) {
  612. fluid.clear(target);
  613. $.extend(true, target, source);
  614. };
  615. /** Parse an EL expression separated by periods (.) into its component segments.
  616. * @param {String} EL The EL expression to be split
  617. * @return {Array of String} the component path expressions.
  618. * TODO: This needs to be upgraded to handle (the same) escaping rules (as RSF), so that
  619. * path segments containing periods and backslashes etc. can be processed.
  620. */
  621. fluid.model.parseEL = function (EL) {
  622. return String(EL).split('.');
  623. };
  624. fluid.model.composePath = function (prefix, suffix) {
  625. return prefix === ""? suffix : prefix + "." + suffix;
  626. };
  627. fluid.model.getPenultimate = function (root, EL, environment, create) {
  628. var segs = fluid.model.parseEL(EL);
  629. for (var i = 0; i < segs.length - 1; ++i) {
  630. if (!root) {
  631. return root;
  632. }
  633. var segment = segs[i];
  634. if (environment && environment[segment]) {
  635. root = environment[segment];
  636. environment = null;
  637. }
  638. else {
  639. if (root[segment] === undefined && create) {
  640. root[segment] = {};
  641. }
  642. root = root[segment];
  643. }
  644. }
  645. return {root: root, last: segs[segs.length - 1]};
  646. };
  647. fluid.model.setBeanValue = function (root, EL, newValue, environment) {
  648. var pen = fluid.model.getPenultimate(root, EL, environment, true);
  649. pen.root[pen.last] = newValue;
  650. };
  651. /** Evaluates an EL expression by fetching a dot-separated list of members
  652. * recursively from a provided root.
  653. * @param root The root data structure in which the EL expression is to be evaluated
  654. * @param {string} EL The EL expression to be evaluated
  655. * @param environment An optional "environment" which, if it contains any members
  656. * at top level, will take priority over the root data structure.
  657. * @return The fetched data value.
  658. */
  659. fluid.model.getBeanValue = function (root, EL, environment) {
  660. if (EL === "" || EL === null || EL === undefined) {
  661. return root;
  662. }
  663. var pen = fluid.model.getPenultimate(root, EL, environment);
  664. return pen.root? pen.root[pen.last] : pen.root;
  665. };
  666. // Logging
  667. var logging;
  668. /** method to allow user to enable logging (off by default) */
  669. fluid.setLogging = function (enabled) {
  670. if (typeof enabled === "boolean") {
  671. logging = enabled;
  672. } else {
  673. logging = false;
  674. }
  675. };
  676. /** Log a message to a suitable environmental console. If the standard "console"
  677. * stream is available, the message will be sent there - otherwise either the
  678. * YAHOO logger or the Opera "postError" stream will be used. Logging must first
  679. * be enabled with a call fo the fluid.setLogging(true) function.
  680. */
  681. fluid.log = function (str) {
  682. if (logging) {
  683. str = fluid.renderTimestamp(new Date()) + ": " + str;
  684. if (typeof(console) !== "undefined") {
  685. if (console.debug) {
  686. console.debug(str);
  687. } else {
  688. console.log(str);
  689. }
  690. }
  691. else if (typeof(YAHOO) !== "undefined") {
  692. YAHOO.log(str);
  693. }
  694. else if (typeof(opera) !== "undefined") {
  695. opera.postError(str);
  696. }
  697. }
  698. };
  699. // DOM Utilities.
  700. /**
  701. * Finds the nearest ancestor of the element that passes the test
  702. * @param {Element} element DOM element
  703. * @param {Function} test A function which takes an element as a parameter and return true or false for some test
  704. */
  705. fluid.findAncestor = function (element, test) {
  706. element = fluid.unwrap(element);
  707. while (element) {
  708. if (test(element)) {
  709. return element;
  710. }
  711. element = element.parentNode;
  712. }
  713. };
  714. /**
  715. * Returns a jQuery object given the id of a DOM node. In the case the element
  716. * is not found, will return an empty list.
  717. */
  718. fluid.jById = function (id, dokkument) {
  719. dokkument = dokkument && dokkument.nodeType === 9? dokkument : document;
  720. var element = fluid.byId(id, dokkument);
  721. var togo = element? $(element) : [];
  722. togo.selector = "#" + id;
  723. togo.context = dokkument;
  724. return togo;
  725. };
  726. /**
  727. * Returns an DOM element quickly, given an id
  728. *
  729. * @param {Object} id the id of the DOM node to find
  730. * @param {Document} dokkument the document in which it is to be found (if left empty, use the current document)
  731. * @return The DOM element with this id, or null, if none exists in the document.
  732. */
  733. fluid.byId = function (id, dokkument) {
  734. dokkument = dokkument && dokkument.nodeType === 9? dokkument : document;
  735. var el = dokkument.getElementById(id);
  736. if (el) {
  737. if (el.getAttribute("id") !== id) {
  738. fluid.fail("Problem in document structure - picked up element " +
  739. fluid.dumpEl(el) +
  740. " for id " +
  741. id +
  742. " without this id - most likely the element has a name which conflicts with this id");
  743. }
  744. return el;
  745. }
  746. else {
  747. return null;
  748. }
  749. };
  750. /**
  751. * Returns the id attribute from a jQuery or pure DOM element.
  752. *
  753. * @param {jQuery||Element} element the element to return the id attribute for
  754. */
  755. fluid.getId = function (element) {
  756. return fluid.unwrap(element).getAttribute("id");
  757. };
  758. /**
  759. * Allocate an id to the supplied element if it has none already, by a simple
  760. * scheme resulting in ids "fluid-id-nnnn" where nnnn is an increasing integer.
  761. */
  762. fluid.allocateSimpleId = function (element) {
  763. element = fluid.unwrap(element);
  764. if (!element.id) {
  765. element.id = "fluid-id-" + (fluid_guid++);
  766. }
  767. return element.id;
  768. };
  769. // Functional programming utilities.
  770. function transformInternal(source, togo, key, args) {
  771. var transit = source[key];
  772. for (var j = 0; j < args.length - 1; ++ j) {
  773. transit = args[j + 1](transit, key);
  774. }
  775. togo[key] = transit;
  776. }
  777. /** Return a list or hash of objects, transformed by one or more functions. Similar to
  778. * jQuery.map, only will accept an arbitrary list of transformation functions and also
  779. * works on non-arrays.
  780. * @param list {Array or Object} The initial container of objects to be transformed.
  781. * @param fn1, fn2, etc. {Function} An arbitrary number of optional further arguments,
  782. * all of type Function, accepting the signature (object, index), where object is the
  783. * list member to be transformed, and index is its list index. Each function will be
  784. * applied in turn to each list member, which will be replaced by the return value
  785. * from the function.
  786. * @return The finally transformed list, where each member has been replaced by the
  787. * original member acted on by the function or functions.
  788. */
  789. fluid.transform = function (source) {
  790. var togo = fluid.freshContainer(source);
  791. if (fluid.isArrayable(source)) {
  792. for (var i = 0; i < source.length; ++ i) {
  793. transformInternal(source, togo, i, arguments);
  794. }
  795. }
  796. else {
  797. for (var key in source) {
  798. transformInternal(source, togo, key, arguments);
  799. }
  800. }
  801. return togo;
  802. };
  803. /** Scan through a list of objects, terminating on and returning the first member which
  804. * matches a predicate function.
  805. * @param list {Array} The list of objects to be searched.
  806. * @param fn {Function} A predicate function, acting on a list member. A predicate which
  807. * returns any value which is not <code>null</code> or <code>undefined</code> will terminate
  808. * the search. The function accepts (object, index).
  809. * @param deflt {Object} A value to be returned in the case no predicate function matches
  810. * a list member. The default will be the natural value of <code>undefined</code>
  811. * @return The first return value from the predicate function which is not <code>null</code>
  812. * or <code>undefined</code>
  813. */
  814. fluid.find = function (list, fn, deflt) {
  815. for (var i = 0; i < list.length; ++ i) {
  816. var transit = fn(list[i], i);
  817. if (transit !== null && transit !== undefined) {
  818. return transit;
  819. }
  820. }
  821. return deflt;
  822. };
  823. /** Scan through a list of objects, "accumulating" a value over them
  824. * (may be a straightforward "sum" or some other chained computation).
  825. * @param list {Array} The list of objects to be accumulated over.
  826. * @param fn {Function} An "accumulation function" accepting the signature (object, total, index) where
  827. * object is the list member, total is the "running total" object (which is the return value from the previous function),
  828. * and index is the index number.
  829. * @param arg {Object} The initial value for the "running total" object.
  830. * @return {Object} the final running total object as returned from the final invocation of the function on the last list member.
  831. */
  832. fluid.accumulate = function (list, fn, arg) {
  833. for (var i = 0; i < list.length; ++ i) {
  834. arg = fn(list[i], arg, i);
  835. }
  836. return arg;
  837. };
  838. /** Can through a list of objects, removing those which match a predicate. Similar to
  839. * jQuery.grep, only acts on the list in-place by removal, rather than by creating
  840. * a new list by inclusion.
  841. * @param list {Array} The list of objects to be scanned over.
  842. * @param fn {Function} A predicate function determining whether an element should be
  843. * removed. This accepts the standard signature (object, index) and returns a "truthy"
  844. * result in order to determine that the supplied object should be removed from the list.
  845. * @return The list, transformed by the operation of removing the matched elements. The
  846. * supplied list is modified by this operation.
  847. */
  848. fluid.remove_if = function (list, fn) {
  849. for (var i = 0; i < list.length; ++ i) {
  850. if (fn(list[i], i)) {
  851. list.splice(i, 1);
  852. --i;
  853. }
  854. }
  855. return list;
  856. };
  857. // Other useful helpers.
  858. /**
  859. * Simple string template system.
  860. * Takes a template string containing tokens in the form of "%value".
  861. * Returns a new string with the tokens replaced by the specified values.
  862. * Keys and values can be of any data type that can be coerced into a string. Arrays will work here as well.
  863. *
  864. * @param {String} template a string (can be HTML) that contains tokens embedded into it
  865. * @param {object} values a collection of token keys and values
  866. */
  867. fluid.stringTemplate = function (template, values) {
  868. var newString = template;
  869. for (var key in values) {
  870. var searchStr = "%" + key;
  871. newString = newString.replace(searchStr, values[key]);
  872. }
  873. return newString;
  874. };
  875. })(jQuery, fluid_1_2);
  876. /*
  877. Copyright 2008-2010 University of Cambridge
  878. Copyright 2008-2009 University of Toronto
  879. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  880. BSD license. You may not use this file except in compliance with one these
  881. Licenses.
  882. You may obtain a copy of the ECL 2.0 License and BSD License at
  883. https://source.fluidproject.org/svn/LICENSE.txt
  884. */
  885. // Declare dependencies.
  886. /*global jQuery */
  887. var fluid_1_2 = fluid_1_2 || {};
  888. (function ($, fluid) {
  889. fluid.dom = fluid.dom || {};
  890. // Node walker function for iterateDom.
  891. var getNextNode = function (iterator) {
  892. if (iterator.node.firstChild) {
  893. iterator.node = iterator.node.firstChild;
  894. iterator.depth += 1;
  895. return iterator;
  896. }
  897. while (iterator.node) {
  898. if (iterator.node.nextSibling) {
  899. iterator.node = iterator.node.nextSibling;
  900. return iterator;
  901. }
  902. iterator.node = iterator.node.parentNode;
  903. iterator.depth -= 1;
  904. }
  905. return iterator;
  906. };
  907. /**
  908. * Walks the DOM, applying the specified acceptor function to each element.
  909. * There is a special case for the acceptor, allowing for quick deletion of elements and their children.
  910. * Return "delete" from your acceptor function if you want to delete the element in question.
  911. * Return "stop" to terminate iteration.
  912. *
  913. * @param {Element} node the node to start walking from
  914. * @param {Function} acceptor the function to invoke with each DOM element
  915. * @param {Boolean} allnodes Use <code>true</code> to call acceptor on all nodes,
  916. * rather than just element nodes (type 1)
  917. */
  918. fluid.dom.iterateDom = function (node, acceptor, allNodes) {
  919. var currentNode = {node: node, depth: 0};
  920. var prevNode = node;
  921. var condition;
  922. while (currentNode.node !== null && currentNode.depth >= 0 && currentNode.depth < fluid.dom.iterateDom.DOM_BAIL_DEPTH) {
  923. condition = null;
  924. if (currentNode.node.nodeType === 1 || allNodes) {
  925. condition = acceptor(currentNode.node, currentNode.depth);
  926. }
  927. if (condition) {
  928. if (condition === "delete") {
  929. currentNode.node.parentNode.removeChild(currentNode.node);
  930. currentNode.node = prevNode;
  931. }
  932. else if (condition === "stop") {
  933. return currentNode.node;
  934. }
  935. }
  936. prevNode = currentNode.node;
  937. currentNode = getNextNode(currentNode);
  938. }
  939. };
  940. // Work around IE circular DOM issue. This is the default max DOM depth on IE.
  941. // http://msdn2.microsoft.com/en-us/library/ms761392(VS.85).aspx
  942. fluid.dom.iterateDom.DOM_BAIL_DEPTH = 256;
  943. /**
  944. * Checks if the sepcified container is actually the parent of containee.
  945. *
  946. * @param {Element} container the potential parent
  947. * @param {Element} containee the child in question
  948. */
  949. fluid.dom.isContainer = function (container, containee) {
  950. for (; containee; containee = containee.parentNode) {
  951. if (container === containee) {
  952. return true;
  953. }
  954. }
  955. return false;
  956. };
  957. /** Return the element text from the supplied DOM node as a single String */
  958. fluid.dom.getElementText = function(element) {
  959. var nodes = element.childNodes;
  960. var text = "";
  961. for (var i = 0; i < nodes.length; ++ i) {
  962. var child = nodes[i];
  963. if (child.nodeType == 3) {
  964. text = text + child.nodeValue;
  965. }
  966. }
  967. return text;
  968. };
  969. })(jQuery, fluid_1_2);
  970. /*
  971. Copyright 2008-2010 University of Cambridge
  972. Copyright 2008-2009 University of Toronto
  973. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  974. BSD license. You may not use this file except in compliance with one these
  975. Licenses.
  976. You may obtain a copy of the ECL 2.0 License and BSD License at
  977. https://source.fluidproject.org/svn/LICENSE.txt
  978. */
  979. /*global jQuery*/
  980. /*global fluid_1_2*/
  981. fluid_1_2 = fluid_1_2 || {};
  982. (function ($, fluid) {
  983. var unUnicode = /(\\u[\dabcdef]{4}|\\x[\dabcdef]{2})/g;
  984. fluid.unescapeProperties = function (string) {
  985. string = string.replace(unUnicode, function(match) {
  986. var code = match.substring(2);
  987. var parsed = parseInt(code, 16);
  988. return String.fromCharCode(parsed);
  989. }
  990. );
  991. var pos = 0;
  992. while (true) {
  993. var backpos = string.indexOf("\\", pos);
  994. if (backpos === -1) {
  995. break;
  996. }
  997. if (backpos === string.length - 1) {
  998. return [string.substring(0, string.length - 1), true];
  999. }
  1000. var replace = string.charAt(backpos + 1);
  1001. if (replace === "n") replace = "\n";
  1002. if (replace === "r") replace = "\r";
  1003. if (replace === "t") replace = "\t";
  1004. string = string.substring(0, backpos) + replace + string.substring(backpos + 2);
  1005. pos = backpos + 1;
  1006. }
  1007. return [string, false];
  1008. };
  1009. var breakPos = /[^\\][\s:=]/;
  1010. fluid.parseJavaProperties = function(text) {
  1011. // File format described at http://java.sun.com/javase/6/docs/api/java/util/Properties.html#load(java.io.Reader)
  1012. var togo = {};
  1013. text = text.replace(/\r\n/g, "\n");
  1014. text = text.replace(/\r/g, "\n");
  1015. lines = text.split("\n");
  1016. var contin, key, valueComp, valueRaw, valueEsc;
  1017. for (var i = 0; i < lines.length; ++ i) {
  1018. var line = $.trim(lines[i]);
  1019. if (!line || line.charAt(0) === "#" || line.charAt(0) === '!') {
  1020. continue;
  1021. }
  1022. if (!contin) {
  1023. valueComp = "";
  1024. var breakpos = line.search(breakPos);
  1025. if (breakpos === -1) {
  1026. key = line;
  1027. valueRaw = "";
  1028. }
  1029. else {
  1030. key = $.trim(line.substring(0, breakpos + 1)); // +1 since first char is escape exclusion
  1031. valueRaw = $.trim(line.substring(breakpos + 2));
  1032. if (valueRaw.charAt(0) === ":" || valueRaw.charAt(0) === "=") {
  1033. valueRaw = $.trim(valueRaw.substring(1));
  1034. }
  1035. }
  1036. key = fluid.unescapeProperties(key)[0];
  1037. valueEsc = fluid.unescapeProperties(valueRaw);
  1038. }
  1039. else {
  1040. valueEsc = fluid.unescapeProperties(line);
  1041. }
  1042. contin = valueEsc[1];
  1043. if (!valueEsc[1]) { // this line was not a continuation line - store the value
  1044. togo[key] = valueComp + valueEsc[0];
  1045. }
  1046. else {
  1047. valueComp += valueEsc[0];
  1048. }
  1049. }
  1050. return togo;
  1051. };
  1052. /**
  1053. * Expand a message string with respect to a set of arguments, following a basic
  1054. * subset of the Java MessageFormat rules.
  1055. * http://java.sun.com/j2se/1.4.2/docs/api/java/text/MessageFormat.html
  1056. *
  1057. * The message string is expected to contain replacement specifications such
  1058. * as {0}, {1}, {2}, etc.
  1059. * @param messageString {String} The message key to be expanded
  1060. * @param args {String/Array of String} An array of arguments to be substituted into the message.
  1061. * @return The expanded message string.
  1062. */
  1063. fluid.formatMessage = function (messageString, args) {
  1064. if (!args) {
  1065. return messageString;
  1066. }
  1067. if (typeof(args) === "string") {
  1068. args = [args];
  1069. }
  1070. for (var i = 0; i < args.length; ++ i) {
  1071. messageString = messageString.replace("{" + i + "}", args[i]);
  1072. }
  1073. return messageString;
  1074. };
  1075. })(jQuery, fluid_1_2);
  1076. /*
  1077. Copyright 2007-2010 University of Cambridge
  1078. Copyright 2007-2009 University of Toronto
  1079. Copyright 2007-2009 University of California, Berkeley
  1080. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  1081. BSD license. You may not use this file except in compliance with one these
  1082. Licenses.
  1083. You may obtain a copy of the ECL 2.0 License and BSD License at
  1084. https://source.fluidproject.org/svn/LICENSE.txt
  1085. */
  1086. // Declare dependencies.
  1087. /*global jQuery, YAHOO, opera*/
  1088. var fluid_1_2 = fluid_1_2 || {};
  1089. var fluid = fluid || fluid_1_2;
  1090. (function ($, fluid) {
  1091. fluid.renderTimestamp = function (date) {
  1092. var zeropad = function (num, width) {
  1093. if (!width) width = 2;
  1094. var numstr = (num == undefined? "" : num.toString());
  1095. return "00000".substring(5 - width + numstr.length) + numstr;
  1096. }
  1097. return zeropad(date.getHours()) + ":" + zeropad(date.getMinutes()) + ":" + zeropad(date.getSeconds()) + "." + zeropad(date.getMilliseconds(), 3);
  1098. };
  1099. /**
  1100. * Dumps a DOM element into a readily recognisable form for debugging - produces a
  1101. * "semi-selector" summarising its tag name, class and id, whichever are set.
  1102. *
  1103. * @param {jQueryable} element The element to be dumped
  1104. * @return A string representing the element.
  1105. */
  1106. fluid.dumpEl = function (element) {
  1107. var togo;
  1108. if (!element) {
  1109. return "null";
  1110. }
  1111. if (element.nodeType === 3 || element.nodeType === 8) {
  1112. return "[data: " + element.data + "]";
  1113. }
  1114. if (element.nodeType === 9) {
  1115. return "[document: location " + element.location + "]";
  1116. }
  1117. if (!element.nodeType && fluid.isArrayable(element)) {
  1118. togo = "[";
  1119. for (var i = 0; i < element.length; ++ i) {
  1120. togo += fluid.dumpEl(element[i]);
  1121. if (i < element.length - 1) {
  1122. togo += ", ";
  1123. }
  1124. }
  1125. return togo + "]";
  1126. }
  1127. element = $(element);
  1128. togo = element.get(0).tagName;
  1129. if (element.attr("id")) {
  1130. togo += "#" + element.attr("id");
  1131. }
  1132. if (element.attr("class")) {
  1133. togo += "." + element.attr("class");
  1134. }
  1135. return togo;
  1136. };
  1137. })(jQuery, fluid_1_2);
  1138. /*
  1139. Copyright 2008-2010 University of Cambridge
  1140. Copyright 2008-2009 University of Toronto
  1141. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  1142. BSD license. You may not use this file except in compliance with one these
  1143. Licenses.
  1144. You may obtain a copy of the ECL 2.0 License and BSD License at
  1145. https://source.fluidproject.org/svn/LICENSE.txt
  1146. */
  1147. /*global jQuery*/
  1148. /*global fluid_1_2*/
  1149. fluid_1_2 = fluid_1_2 || {};
  1150. (function ($, fluid) {
  1151. fluid.VALUE = {};
  1152. fluid.BINDING_ROOT_KEY = "fluid-binding-root";
  1153. /** Recursively find any data stored under a given name from a node upwards
  1154. * in its DOM hierarchy **/
  1155. fluid.findData = function(elem, name) {
  1156. while (elem) {
  1157. var data = $.data(elem, name);
  1158. if (data) {r

Large files files are truncated, but you can click here to view the full file