PageRenderTime 105ms CodeModel.GetById 31ms RepoModel.GetById 1ms app.codeStats 1ms

/dev/_lib/Fluid/3akai_Infusion.js

https://github.com/zqian/3akai-ux
JavaScript | 8226 lines | 6558 code | 671 blank | 997 comment | 642 complexity | 91d89959cfdaf5cbbe73468da5303662 MD5 | raw 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. * jQuery delegate plug-in v1.0
  7. *
  8. * Copyright (c) 2007 Jörn Zaefferer
  9. *
  10. * $Id: jquery.delegate.js 7175 2009-05-15 16:59:09Z antranig@caret.cam.ac.uk $
  11. *
  12. * Dual licensed under the MIT and GPL licenses:
  13. * http://www.opensource.org/licenses/mit-license.php
  14. * http://www.gnu.org/licenses/gpl.html
  15. */
  16. // provides cross-browser focusin and focusout events
  17. // IE has native support, in other browsers, use event caputuring (neither bubbles)
  18. // provides delegate(type: String, delegate: Selector, handler: Callback) plugin for easier event delegation
  19. // handler is only called when $(event.target).is(delegate), in the scope of the jQuery-object for event.target
  20. // provides triggerEvent(type: String, target: Element) to trigger delegated events
  21. ;(function($) {
  22. $.each({
  23. focus: 'focusin',
  24. blur: 'focusout'
  25. }, function( original, fix ){
  26. $.event.special[fix] = {
  27. setup:function() {
  28. if ( $.browser.msie ) return false;
  29. this.addEventListener( original, $.event.special[fix].handler, true );
  30. },
  31. teardown:function() {
  32. if ( $.browser.msie ) return false;
  33. this.removeEventListener( original,
  34. $.event.special[fix].handler, true );
  35. },
  36. handler: function(e) {
  37. arguments[0] = $.event.fix(e);
  38. arguments[0].type = fix;
  39. return $.event.handle.apply(this, arguments);
  40. }
  41. };
  42. });
  43. $.extend($.fn, {
  44. delegate: function(type, delegate, handler) {
  45. return this.bind(type, function(event) {
  46. var target = $(event.target);
  47. if (target.is(delegate)) {
  48. return handler.apply(target, arguments);
  49. }
  50. });
  51. },
  52. triggerEvent: function(type, target) {
  53. return this.triggerHandler(type, [jQuery.event.fix({ type: type, target: target })]);
  54. }
  55. })
  56. })(jQuery);
  57. /*
  58. Copyright 2007-2010 University of Cambridge
  59. Copyright 2007-2009 University of Toronto
  60. Copyright 2007-2009 University of California, Berkeley
  61. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  62. BSD license. You may not use this file except in compliance with one these
  63. Licenses.
  64. You may obtain a copy of the ECL 2.0 License and BSD License at
  65. https://source.fluidproject.org/svn/LICENSE.txt
  66. */
  67. // Declare dependencies.
  68. /*global jQuery, YAHOO, opera*/
  69. var fluid_1_2 = fluid_1_2 || {};
  70. var fluid = fluid || fluid_1_2;
  71. (function ($, fluid) {
  72. fluid.version = "Infusion 1.2";
  73. fluid.environment = {
  74. fluid: fluid
  75. };
  76. var globalObject = window || {};
  77. /**
  78. * Causes an error message to be logged to the console and a real runtime error to be thrown.
  79. *
  80. * @param {String|Error} message the error message to log
  81. */
  82. fluid.fail = function (message) {
  83. fluid.setLogging(true);
  84. fluid.log(message.message? message.message : message);
  85. throw new Error(message);
  86. //message.fail(); // Intentionally cause a browser error by invoking a nonexistent function.
  87. };
  88. /**
  89. * Wraps an object in a jQuery if it isn't already one. This function is useful since
  90. * it ensures to wrap a null or otherwise falsy argument to itself, rather than the
  91. * often unhelpful jQuery default of returning the overall document node.
  92. *
  93. * @param {Object} obj the object to wrap in a jQuery
  94. */
  95. fluid.wrap = function (obj) {
  96. return ((!obj || obj.jquery) ? obj : $(obj));
  97. };
  98. /**
  99. * If obj is a jQuery, this function will return the first DOM element within it.
  100. *
  101. * @param {jQuery} obj the jQuery instance to unwrap into a pure DOM element
  102. */
  103. fluid.unwrap = function (obj) {
  104. return obj && obj.jquery && obj.length === 1 ? obj[0] : obj; // Unwrap the element if it's a jQuery.
  105. };
  106. /**
  107. * Searches through the supplied object for the first value which matches the one supplied.
  108. * @param obj {Object} the Object to be searched through
  109. * @param value {Object} the value to be found. This will be compared against the object's
  110. * member using === equality.
  111. * @return {String} The first key whose value matches the one supplied, or <code>null</code> if no
  112. * such key is found.
  113. */
  114. fluid.keyForValue = function (obj, value) {
  115. for (var key in obj) {
  116. if (obj[key] === value) {
  117. return key;
  118. }
  119. }
  120. return null;
  121. };
  122. /**
  123. * This method is now deprecated and will be removed in a future release of Infusion.
  124. * See fluid.keyForValue instead.
  125. */
  126. fluid.findKeyInObject = fluid.keyForValue;
  127. /**
  128. * Clears an object or array of its contents. For objects, each property is deleted.
  129. *
  130. * @param {Object|Array} target the target to be cleared
  131. */
  132. fluid.clear = function (target) {
  133. if (target instanceof Array) {
  134. target.length = 0;
  135. }
  136. else {
  137. for (var i in target) {
  138. delete target[i];
  139. }
  140. }
  141. };
  142. /** A basic utility that returns its argument unchanged */
  143. fluid.identity = function(arg) {
  144. return arg;
  145. }
  146. // Framework and instantiation functions.
  147. /**
  148. * Fetches a single container element and returns it as a jQuery.
  149. *
  150. * @param {String||jQuery||element} an id string, a single-element jQuery, or a DOM element specifying a unique container
  151. * @return a single-element jQuery of container
  152. */
  153. fluid.container = function (containerSpec) {
  154. var container = containerSpec;
  155. if (typeof containerSpec === "string" ||
  156. containerSpec.nodeType && (containerSpec.nodeType === 1 || containerSpec.nodeType === 9)) {
  157. container = $(containerSpec);
  158. }
  159. // Throw an exception if we've got more or less than one element.
  160. if (!container || !container.jquery || container.length !== 1) {
  161. if (typeof(containerSpec) !== "string") {
  162. containerSpec = container.selector;
  163. }
  164. fluid.fail({
  165. name: "NotOne",
  166. message: "A single container element was not found for selector " + containerSpec
  167. });
  168. }
  169. return container;
  170. };
  171. // stubs for two functions in FluidDebugging.js
  172. fluid.dumpEl = fluid.identity;
  173. fluid.renderTimestamp = fluid.identity;
  174. /**
  175. * Retreives and stores a component's default settings centrally.
  176. * @param {boolean} (options) if true, manipulate a global option (for the head
  177. * component) rather than instance options.
  178. * @param {String} componentName the name of the component
  179. * @param {Object} (optional) an container of key/value pairs to set
  180. *
  181. */
  182. var defaultsStore = {};
  183. var globalDefaultsStore = {};
  184. fluid.defaults = function () {
  185. var offset = 0;
  186. var store = defaultsStore;
  187. if (typeof arguments[0] === "boolean") {
  188. store = globalDefaultsStore;
  189. offset = 1;
  190. }
  191. var componentName = arguments[offset];
  192. var defaultsObject = arguments[offset + 1];
  193. if (defaultsObject !== undefined) {
  194. store[componentName] = defaultsObject;
  195. return defaultsObject;
  196. }
  197. return store[componentName];
  198. };
  199. /**
  200. * Creates a new DOM Binder instance, used to locate elements in the DOM by name.
  201. *
  202. * @param {Object} container the root element in which to locate named elements
  203. * @param {Object} selectors a collection of named jQuery selectors
  204. */
  205. fluid.createDomBinder = function (container, selectors) {
  206. var cache = {}, that = {};
  207. function cacheKey(name, thisContainer) {
  208. return fluid.allocateSimpleId(thisContainer) + "-" + name;
  209. }
  210. function record(name, thisContainer, result) {
  211. cache[cacheKey(name, thisContainer)] = result;
  212. }
  213. that.locate = function (name, localContainer) {
  214. var selector, thisContainer, togo;
  215. selector = selectors[name];
  216. thisContainer = localContainer? localContainer: container;
  217. if (!thisContainer) {
  218. fluid.fail("DOM binder invoked for selector " + name + " without container");
  219. }
  220. if (!selector) {
  221. return thisContainer;
  222. }
  223. if (typeof(selector) === "function") {
  224. togo = $(selector.call(null, fluid.unwrap(thisContainer)));
  225. } else {
  226. togo = $(selector, thisContainer);
  227. }
  228. if (togo.get(0) === document) {
  229. togo = [];
  230. //fluid.fail("Selector " + name + " with value " + selectors[name] +
  231. // " did not find any elements with container " + fluid.dumpEl(container));
  232. }
  233. if (!togo.selector) {
  234. togo.selector = selector;
  235. togo.context = thisContainer;
  236. }
  237. togo.selectorName = name;
  238. record(name, thisContainer, togo);
  239. return togo;
  240. };
  241. that.fastLocate = function (name, localContainer) {
  242. var thisContainer = localContainer? localContainer: container;
  243. var key = cacheKey(name, thisContainer);
  244. var togo = cache[key];
  245. return togo? togo : that.locate(name, localContainer);
  246. };
  247. that.clear = function () {
  248. cache = {};
  249. };
  250. that.refresh = function (names, localContainer) {
  251. var thisContainer = localContainer? localContainer: container;
  252. if (typeof names === "string") {
  253. names = [names];
  254. }
  255. if (thisContainer.length === undefined) {
  256. thisContainer = [thisContainer];
  257. }
  258. for (var i = 0; i < names.length; ++ i) {
  259. for (var j = 0; j < thisContainer.length; ++ j) {
  260. that.locate(names[i], thisContainer[j]);
  261. }
  262. }
  263. };
  264. return that;
  265. };
  266. /** Determines whether the supplied object can be treated as an array, by
  267. * iterating an index towards its length. The test functions by detecting
  268. * a property named "length" which is of type "number", but excluding objects
  269. * which are themselves of type "string".
  270. */
  271. fluid.isArrayable = function(totest) {
  272. return typeof(totest) !== "string" && typeof(totest.length) === "number";
  273. }
  274. /**
  275. * Attaches the user's listeners to a set of events.
  276. *
  277. * @param {Object} events a collection of named event firers
  278. * @param {Object} listeners optional listeners to add
  279. */
  280. fluid.mergeListeners = function (events, listeners) {
  281. if (listeners) {
  282. for (var key in listeners) {
  283. var value = listeners[key];
  284. var keydot = key.indexOf(".");
  285. var namespace;
  286. if (keydot !== -1) {
  287. namespace = key.substring(keydot + 1);
  288. key = key.substring(0, keydot);
  289. }
  290. if (!events[key]) {
  291. events[key] = fluid.event.getEventFirer();
  292. }
  293. var firer = events[key];
  294. if (typeof(value) === "function") {
  295. firer.addListener(value, namespace);
  296. }
  297. else if (value && fluid.isArrayable(value)) {
  298. for (var i = 0; i < value.length; ++ i) {
  299. firer.addListener(value[i], namespace);
  300. }
  301. }
  302. }
  303. }
  304. };
  305. /**
  306. * Sets up a component's declared events.
  307. * Events are specified in the options object by name. There are three different types of events that can be
  308. * specified:
  309. * 1. an ordinary multicast event, specified by "null.
  310. * 2. a unicast event, which allows only one listener to be registered
  311. * 3. a preventable event
  312. *
  313. * @param {Object} that the component
  314. * @param {Object} options the component's options structure, containing the declared event names and types
  315. */
  316. fluid.instantiateFirers = function (that, options) {
  317. that.events = {};
  318. if (options.events) {
  319. for (var event in options.events) {
  320. var eventType = options.events[event];
  321. that.events[event] = fluid.event.getEventFirer(eventType === "unicast", eventType === "preventable");
  322. }
  323. }
  324. fluid.mergeListeners(that.events, options.listeners);
  325. };
  326. /**
  327. * Merges the component's declared defaults, as obtained from fluid.defaults(),
  328. * with the user's specified overrides.
  329. *
  330. * @param {Object} that the instance to attach the options to
  331. * @param {String} componentName the unique "name" of the component, which will be used
  332. * to fetch the default options from store. By recommendation, this should be the global
  333. * name of the component's creator function.
  334. * @param {Object} userOptions the user-specified configuration options for this component
  335. */
  336. fluid.mergeComponentOptions = function (that, componentName, userOptions) {
  337. var defaults = fluid.defaults(componentName);
  338. that.options = fluid.merge(defaults? defaults.mergePolicy: null, {}, defaults, userOptions);
  339. };
  340. /** Expect that an output from the DOM binder has resulted in a non-empty set of
  341. * results. If none are found, this function will fail with a diagnostic message,
  342. * with the supplied message prepended.
  343. */
  344. fluid.expectFilledSelector = function (result, message) {
  345. if (result && result.length === 0 && result.jquery) {
  346. fluid.fail(message + ": selector \"" + result.selector + "\" with name " + result.selectorName +
  347. " returned no results in context " + fluid.dumpEl(result.context));
  348. }
  349. };
  350. /**
  351. * The central initialiation method called as the first act of every Fluid
  352. * component. This function automatically merges user options with defaults,
  353. * attaches a DOM Binder to the instance, and configures events.
  354. *
  355. * @param {String} componentName The unique "name" of the component, which will be used
  356. * to fetch the default options from store. By recommendation, this should be the global
  357. * name of the component's creator function.
  358. * @param {jQueryable} container A specifier for the single root "container node" in the
  359. * DOM which will house all the markup for this component.
  360. * @param {Object} userOptions The configuration options for this component.
  361. */
  362. fluid.initView = function (componentName, container, userOptions) {
  363. var that = {};
  364. fluid.expectFilledSelector(container, "Error instantiating component with name \"" + componentName);
  365. fluid.mergeComponentOptions(that, componentName, userOptions);
  366. if (container) {
  367. that.container = fluid.container(container);
  368. fluid.initDomBinder(that);
  369. }
  370. fluid.instantiateFirers(that, that.options);
  371. return that;
  372. };
  373. /** A special "marker object" which is recognised as one of the arguments to
  374. * fluid.initSubcomponents. This object is recognised by reference equality -
  375. * where it is found, it is replaced in the actual argument position supplied
  376. * to the specific subcomponent instance, with the particular options block
  377. * for that instance attached to the overall "that" object.
  378. */
  379. fluid.COMPONENT_OPTIONS = {};
  380. /** Another special "marker object" representing that a distinguished
  381. * (probably context-dependent) value should be substituted.
  382. */
  383. fluid.VALUE = {};
  384. /** Construct a dummy or "placeholder" subcomponent, that optionally provides empty
  385. * implementations for a set of methods.
  386. */
  387. fluid.emptySubcomponent = function (options) {
  388. var that = {};
  389. options = $.makeArray(options);
  390. for (var i = 0; i < options.length; ++ i) {
  391. that[options[i]] = function () {};
  392. }
  393. return that;
  394. };
  395. /**
  396. * Creates a new "little component": a that-ist object with options merged into it by the framework.
  397. * This method is a convenience for creating small objects that have options but don't require full
  398. * View-like features such as the DOM Binder or events
  399. *
  400. * @param {Object} name the name of the little component to create
  401. * @param {Object} options user-supplied options to merge with the defaults
  402. */
  403. fluid.initLittleComponent = function(name, options) {
  404. var that = {};
  405. fluid.mergeComponentOptions(that, name, options);
  406. return that;
  407. };
  408. fluid.initSubcomponent = function (that, className, args) {
  409. return fluid.initSubcomponents(that, className, args)[0];
  410. };
  411. /** Initialise all the "subcomponents" which are configured to be attached to
  412. * the supplied top-level component, which share a particular "class name".
  413. * @param {Component} that The top-level component for which sub-components are
  414. * to be instantiated. It contains specifications for these subcomponents in its
  415. * <code>options</code> structure.
  416. * @param {String} className The "class name" or "category" for the subcomponents to
  417. * be instantiated. A class name specifies an overall "function" for a class of
  418. * subcomponents and represents a category which accept the same signature of
  419. * instantiation arguments.
  420. * @param {Array of Object} args The instantiation arguments to be passed to each
  421. * constructed subcomponent. These will typically be members derived from the
  422. * top-level <code>that</code> or perhaps globally discovered from elsewhere. One
  423. * of these arguments may be <code>fluid.COMPONENT_OPTIONS</code> in which case this
  424. * placeholder argument will be replaced by instance-specific options configured
  425. * into the member of the top-level <code>options</code> structure named for the
  426. * <code>className</code>
  427. * @return {Array of Object} The instantiated subcomponents, one for each member
  428. * of <code>that.options[className]</code>.
  429. */
  430. fluid.initSubcomponents = function (that, className, args) {
  431. var entry = that.options[className];
  432. if (!entry) {
  433. return;
  434. }
  435. var entries = $.makeArray(entry);
  436. var optindex = -1;
  437. var togo = [];
  438. args = $.makeArray(args);
  439. for (var i = 0; i < args.length; ++ i) {
  440. if (args[i] === fluid.COMPONENT_OPTIONS) {
  441. optindex = i;
  442. }
  443. }
  444. for (i = 0; i < entries.length; ++ i) {
  445. entry = entries[i];
  446. if (optindex !== -1 && entry.options) {
  447. args[optindex] = entry.options;
  448. }
  449. if (typeof(entry) !== "function") {
  450. var entryType = typeof(entry) === "string"? entry : entry.type;
  451. var globDef = fluid.defaults(true, entryType);
  452. fluid.merge("reverse", that.options, globDef);
  453. togo[i] = entryType === "fluid.emptySubcomponent"?
  454. fluid.emptySubcomponent(entry.options) :
  455. fluid.invokeGlobalFunction(entryType, args, {fluid: fluid});
  456. }
  457. else {
  458. togo[i] = entry.apply(null, args);
  459. }
  460. var returnedOptions = togo[i]? togo[i].returnedOptions : null;
  461. if (returnedOptions) {
  462. fluid.merge(that.options.mergePolicy, that.options, returnedOptions);
  463. if (returnedOptions.listeners) {
  464. fluid.mergeListeners(that.events, returnedOptions.listeners);
  465. }
  466. }
  467. }
  468. return togo;
  469. };
  470. /**
  471. * Creates a new DOM Binder instance for the specified component and mixes it in.
  472. *
  473. * @param {Object} that the component instance to attach the new DOM Binder to
  474. */
  475. fluid.initDomBinder = function (that) {
  476. that.dom = fluid.createDomBinder(that.container, that.options.selectors);
  477. that.locate = that.dom.locate;
  478. };
  479. /** Returns true if the argument is a primitive type **/
  480. fluid.isPrimitive = function (value) {
  481. var valueType = typeof(value);
  482. return !value || valueType === "string" || valueType === "boolean" || valueType === "number" || valueType === "function";
  483. };
  484. function mergeImpl(policy, basePath, target, source) {
  485. var thisPolicy = policy && typeof(policy) !== "string"? policy[basePath] : policy;
  486. if (typeof(thisPolicy) === "function") {
  487. thisPolicy.apply(null, target, source);
  488. return target;
  489. }
  490. if (thisPolicy === "replace") {
  491. fluid.clear(target);
  492. }
  493. for (var name in source) {
  494. var path = (basePath? basePath + ".": "") + name;
  495. var thisTarget = target[name];
  496. var thisSource = source[name];
  497. var primitiveTarget = fluid.isPrimitive(thisTarget);
  498. if (thisSource !== undefined) {
  499. if (thisSource !== null && typeof thisSource === 'object' &&
  500. !thisSource.nodeType && !thisSource.jquery && thisSource !== fluid.VALUE) {
  501. if (primitiveTarget) {
  502. target[name] = thisTarget = thisSource instanceof Array? [] : {};
  503. }
  504. mergeImpl(policy, path, thisTarget, thisSource);
  505. }
  506. else {
  507. if (thisTarget === null || thisTarget === undefined || thisPolicy !== "reverse") {
  508. target[name] = thisSource;
  509. }
  510. }
  511. }
  512. }
  513. return target;
  514. }
  515. /** Merge a collection of options structures onto a target, following an optional policy.
  516. * This function is typically called automatically, as a result of an invocation of
  517. * <code>fluid.iniView</code>. The behaviour of this function is explained more fully on
  518. * the page http://wiki.fluidproject.org/display/fluid/Options+Merging+for+Fluid+Components .
  519. * @param policy {Object/String} A "policy object" specifiying the type of merge to be performed.
  520. * If policy is of type {String} it should take on the value "reverse" or "replace" representing
  521. * a static policy. If it is an
  522. * Object, it should contain a mapping of EL paths onto these String values, representing a
  523. * fine-grained policy. If it is an Object, the values may also themselves be EL paths
  524. * representing that a default value is to be taken from that path.
  525. * @param target {Object} The options structure which is to be modified by receiving the merge results.
  526. * @param options1, options2, .... {Object} an arbitrary list of options structure which are to
  527. * be merged "on top of" the <code>target</code>. These will not be modified.
  528. */
  529. fluid.merge = function (policy, target) {
  530. var path = "";
  531. for (var i = 2; i < arguments.length; ++i) {
  532. var source = arguments[i];
  533. if (source !== null && source !== undefined) {
  534. mergeImpl(policy, path, target, source);
  535. }
  536. }
  537. if (policy && typeof(policy) !== "string") {
  538. for (var key in policy) {
  539. var elrh = policy[key];
  540. if (typeof(elrh) === 'string' && elrh !== "replace") {
  541. var oldValue = fluid.model.getBeanValue(target, key);
  542. if (oldValue === null || oldValue === undefined) {
  543. var value = fluid.model.getBeanValue(target, elrh);
  544. fluid.model.setBeanValue(target, key, value);
  545. }
  546. }
  547. }
  548. }
  549. return target;
  550. };
  551. /** Return an empty container as the same type as the argument (either an
  552. * array or hash */
  553. fluid.freshContainer = function(tocopy) {
  554. return fluid.isArrayable(tocopy)? [] : {};
  555. };
  556. /** Performs a deep copy (clone) of its argument **/
  557. fluid.copy = function (tocopy) {
  558. if (fluid.isPrimitive(tocopy)) {
  559. return tocopy;
  560. }
  561. return $.extend(true, fluid.freshContainer(tocopy), tocopy);
  562. };
  563. fluid.getGlobalValue = function(path, env) {
  564. env = env || fluid.environment;
  565. return fluid.model.getBeanValue(globalObject, path, env);
  566. };
  567. /**
  568. * Allows for the calling of a function from an EL expression "functionPath", with the arguments "args", scoped to an framework version "environment".
  569. * @param {Object} functionPath - An EL expression
  570. * @param {Object} args - An array of arguments to be applied to the function, specified in functionPath
  571. * @param {Object} environment - (optional) The object to scope the functionPath to (typically the framework root for version control)
  572. */
  573. fluid.invokeGlobalFunction = function (functionPath, args, environment) {
  574. var func = fluid.getGlobalValue(functionPath, environment);
  575. if (!func) {
  576. fluid.fail("Error invoking global function: " + functionPath + " could not be located");
  577. } else {
  578. return func.apply(null, args);
  579. }
  580. };
  581. /** Registers a new global function at a given path (currently assumes that
  582. * it lies within the fluid namespace)
  583. */
  584. fluid.registerGlobalFunction = function (functionPath, func, env) {
  585. env = env || fluid.environment;
  586. fluid.model.setBeanValue(globalObject, functionPath, func, env);
  587. };
  588. fluid.registerGlobal = fluid.registerGlobalFunction;
  589. /** Ensures that an entry in the global namespace exists **/
  590. fluid.registerNamespace = function (naimspace, env) {
  591. env = env || fluid.environment;
  592. var existing = fluid.getGlobalValue(naimspace, env);
  593. if (!existing) {
  594. existing = {};
  595. fluid.registerGlobal(naimspace, existing, env);
  596. }
  597. return existing;
  598. };
  599. // The Model Events system.
  600. fluid.event = {};
  601. var fluid_guid = 1;
  602. /** Construct an "event firer" object which can be used to register and deregister
  603. * listeners, to which "events" can be fired. These events consist of an arbitrary
  604. * function signature. General documentation on the Fluid events system is at
  605. * http://wiki.fluidproject.org/display/fluid/The+Fluid+Event+System .
  606. * @param {Boolean} unicast If <code>true</code>, this is a "unicast" event which may only accept
  607. * a single listener.
  608. * @param {Boolean} preventable If <code>true</code> the return value of each handler will
  609. * be checked for <code>false</code> in which case further listeners will be shortcircuited, and this
  610. * will be the return value of fire()
  611. */
  612. fluid.event.getEventFirer = function (unicast, preventable) {
  613. var log = fluid.log;
  614. var listeners = {};
  615. return {
  616. addListener: function (listener, namespace, predicate) {
  617. if (!listener) {
  618. return;
  619. }
  620. if (unicast) {
  621. namespace = "unicast";
  622. }
  623. if (!namespace) {
  624. if (!listener.$$guid) {
  625. listener.$$guid = fluid_guid++;
  626. }
  627. namespace = listener.$$guid;
  628. }
  629. listeners[namespace] = {listener: listener, predicate: predicate};
  630. },
  631. removeListener: function (listener) {
  632. if (typeof(listener) === 'string') {
  633. delete listeners[listener];
  634. }
  635. else if (typeof(listener) === 'object' && listener.$$guid) {
  636. delete listeners[listener.$$guid];
  637. }
  638. },
  639. fire: function () {
  640. for (var i in listeners) {
  641. var lisrec = listeners[i];
  642. var listener = lisrec.listener;
  643. if (lisrec.predicate && !lisrec.predicate(listener, arguments)) {
  644. continue;
  645. }
  646. try {
  647. var ret = listener.apply(null, arguments);
  648. if (preventable && ret === false) {
  649. return false;
  650. }
  651. }
  652. catch (e) {
  653. log("FireEvent received exception " + e.message + " e " + e + " firing to listener " + i);
  654. throw (e);
  655. }
  656. }
  657. }
  658. };
  659. };
  660. // Model functions
  661. fluid.model = {};
  662. /** Copy a source "model" onto a target **/
  663. fluid.model.copyModel = function (target, source) {
  664. fluid.clear(target);
  665. $.extend(true, target, source);
  666. };
  667. /** Parse an EL expression separated by periods (.) into its component segments.
  668. * @param {String} EL The EL expression to be split
  669. * @return {Array of String} the component path expressions.
  670. * TODO: This needs to be upgraded to handle (the same) escaping rules (as RSF), so that
  671. * path segments containing periods and backslashes etc. can be processed.
  672. */
  673. fluid.model.parseEL = function (EL) {
  674. return String(EL).split('.');
  675. };
  676. fluid.model.composePath = function (prefix, suffix) {
  677. return prefix === ""? suffix : prefix + "." + suffix;
  678. };
  679. fluid.model.getPenultimate = function (root, EL, environment, create) {
  680. var segs = fluid.model.parseEL(EL);
  681. for (var i = 0; i < segs.length - 1; ++i) {
  682. if (!root) {
  683. return root;
  684. }
  685. var segment = segs[i];
  686. if (environment && environment[segment]) {
  687. root = environment[segment];
  688. environment = null;
  689. }
  690. else {
  691. if (root[segment] === undefined && create) {
  692. root[segment] = {};
  693. }
  694. root = root[segment];
  695. }
  696. }
  697. return {root: root, last: segs[segs.length - 1]};
  698. };
  699. fluid.model.setBeanValue = function (root, EL, newValue, environment) {
  700. var pen = fluid.model.getPenultimate(root, EL, environment, true);
  701. pen.root[pen.last] = newValue;
  702. };
  703. /** Evaluates an EL expression by fetching a dot-separated list of members
  704. * recursively from a provided root.
  705. * @param root The root data structure in which the EL expression is to be evaluated
  706. * @param {string} EL The EL expression to be evaluated
  707. * @param environment An optional "environment" which, if it contains any members
  708. * at top level, will take priority over the root data structure.
  709. * @return The fetched data value.
  710. */
  711. fluid.model.getBeanValue = function (root, EL, environment) {
  712. if (EL === "" || EL === null || EL === undefined) {
  713. return root;
  714. }
  715. var pen = fluid.model.getPenultimate(root, EL, environment);
  716. return pen.root? pen.root[pen.last] : pen.root;
  717. };
  718. // Logging
  719. var logging;
  720. /** method to allow user to enable logging (off by default) */
  721. fluid.setLogging = function (enabled) {
  722. if (typeof enabled === "boolean") {
  723. logging = enabled;
  724. } else {
  725. logging = false;
  726. }
  727. };
  728. /** Log a message to a suitable environmental console. If the standard "console"
  729. * stream is available, the message will be sent there - otherwise either the
  730. * YAHOO logger or the Opera "postError" stream will be used. Logging must first
  731. * be enabled with a call fo the fluid.setLogging(true) function.
  732. */
  733. fluid.log = function (str) {
  734. if (logging) {
  735. str = fluid.renderTimestamp(new Date()) + ": " + str;
  736. if (typeof(console) !== "undefined") {
  737. if (console.debug) {
  738. console.debug(str);
  739. } else {
  740. console.log(str);
  741. }
  742. }
  743. else if (typeof(YAHOO) !== "undefined") {
  744. YAHOO.log(str);
  745. }
  746. else if (typeof(opera) !== "undefined") {
  747. opera.postError(str);
  748. }
  749. }
  750. };
  751. // DOM Utilities.
  752. /**
  753. * Finds the nearest ancestor of the element that passes the test
  754. * @param {Element} element DOM element
  755. * @param {Function} test A function which takes an element as a parameter and return true or false for some test
  756. */
  757. fluid.findAncestor = function (element, test) {
  758. element = fluid.unwrap(element);
  759. while (element) {
  760. if (test(element)) {
  761. return element;
  762. }
  763. element = element.parentNode;
  764. }
  765. };
  766. /**
  767. * Returns a jQuery object given the id of a DOM node. In the case the element
  768. * is not found, will return an empty list.
  769. */
  770. fluid.jById = function (id, dokkument) {
  771. dokkument = dokkument && dokkument.nodeType === 9? dokkument : document;
  772. var element = fluid.byId(id, dokkument);
  773. var togo = element? $(element) : [];
  774. togo.selector = "#" + id;
  775. togo.context = dokkument;
  776. return togo;
  777. };
  778. /**
  779. * Returns an DOM element quickly, given an id
  780. *
  781. * @param {Object} id the id of the DOM node to find
  782. * @param {Document} dokkument the document in which it is to be found (if left empty, use the current document)
  783. * @return The DOM element with this id, or null, if none exists in the document.
  784. */
  785. fluid.byId = function (id, dokkument) {
  786. dokkument = dokkument && dokkument.nodeType === 9? dokkument : document;
  787. var el = dokkument.getElementById(id);
  788. if (el) {
  789. if (el.getAttribute("id") !== id) {
  790. fluid.fail("Problem in document structure - picked up element " +
  791. fluid.dumpEl(el) +
  792. " for id " +
  793. id +
  794. " without this id - most likely the element has a name which conflicts with this id");
  795. }
  796. return el;
  797. }
  798. else {
  799. return null;
  800. }
  801. };
  802. /**
  803. * Returns the id attribute from a jQuery or pure DOM element.
  804. *
  805. * @param {jQuery||Element} element the element to return the id attribute for
  806. */
  807. fluid.getId = function (element) {
  808. return fluid.unwrap(element).getAttribute("id");
  809. };
  810. /**
  811. * Allocate an id to the supplied element if it has none already, by a simple
  812. * scheme resulting in ids "fluid-id-nnnn" where nnnn is an increasing integer.
  813. */
  814. fluid.allocateSimpleId = function (element) {
  815. element = fluid.unwrap(element);
  816. if (!element.id) {
  817. element.id = "fluid-id-" + (fluid_guid++);
  818. }
  819. return element.id;
  820. };
  821. // Functional programming utilities.
  822. function transformInternal(source, togo, key, args) {
  823. var transit = source[key];
  824. for (var j = 0; j < args.length - 1; ++ j) {
  825. transit = args[j + 1](transit, key);
  826. }
  827. togo[key] = transit;
  828. }
  829. /** Return a list or hash of objects, transformed by one or more functions. Similar to
  830. * jQuery.map, only will accept an arbitrary list of transformation functions and also
  831. * works on non-arrays.
  832. * @param list {Array or Object} The initial container of objects to be transformed.
  833. * @param fn1, fn2, etc. {Function} An arbitrary number of optional further arguments,
  834. * all of type Function, accepting the signature (object, index), where object is the
  835. * list member to be transformed, and index is its list index. Each function will be
  836. * applied in turn to each list member, which will be replaced by the return value
  837. * from the function.
  838. * @return The finally transformed list, where each member has been replaced by the
  839. * original member acted on by the function or functions.
  840. */
  841. fluid.transform = function (source) {
  842. var togo = fluid.freshContainer(source);
  843. if (fluid.isArrayable(source)) {
  844. for (var i = 0; i < source.length; ++ i) {
  845. transformInternal(source, togo, i, arguments);
  846. }
  847. }
  848. else {
  849. for (var key in source) {
  850. transformInternal(source, togo, key, arguments);
  851. }
  852. }
  853. return togo;
  854. };
  855. /** Scan through a list of objects, terminating on and returning the first member which
  856. * matches a predicate function.
  857. * @param list {Array} The list of objects to be searched.
  858. * @param fn {Function} A predicate function, acting on a list member. A predicate which
  859. * returns any value which is not <code>null</code> or <code>undefined</code> will terminate
  860. * the search. The function accepts (object, index).
  861. * @param deflt {Object} A value to be returned in the case no predicate function matches
  862. * a list member. The default will be the natural value of <code>undefined</code>
  863. * @return The first return value from the predicate function which is not <code>null</code>
  864. * or <code>undefined</code>
  865. */
  866. fluid.find = function (list, fn, deflt) {
  867. for (var i = 0; i < list.length; ++ i) {
  868. var transit = fn(list[i], i);
  869. if (transit !== null && transit !== undefined) {
  870. return transit;
  871. }
  872. }
  873. return deflt;
  874. };
  875. /** Scan through a list of objects, "accumulating" a value over them
  876. * (may be a straightforward "sum" or some other chained computation).
  877. * @param list {Array} The list of objects to be accumulated over.
  878. * @param fn {Function} An "accumulation function" accepting the signature (object, total, index) where
  879. * object is the list member, total is the "running total" object (which is the return value from the previous function),
  880. * and index is the index number.
  881. * @param arg {Object} The initial value for the "running total" object.
  882. * @return {Object} the final running total object as returned from the final invocation of the function on the last list member.
  883. */
  884. fluid.accumulate = function (list, fn, arg) {
  885. for (var i = 0; i < list.length; ++ i) {
  886. arg = fn(list[i], arg, i);
  887. }
  888. return arg;
  889. };
  890. /** Can through a list of objects, removing those which match a predicate. Similar to
  891. * jQuery.grep, only acts on the list in-place by removal, rather than by creating
  892. * a new list by inclusion.
  893. * @param list {Array} The list of objects to be scanned over.
  894. * @param fn {Function} A predicate function determining whether an element should be
  895. * removed. This accepts the standard signature (object, index) and returns a "truthy"
  896. * result in order to determine that the supplied object should be removed from the list.
  897. * @return The list, transformed by the operation of removing the matched elements. The
  898. * supplied list is modified by this operation.
  899. */
  900. fluid.remove_if = function (list, fn) {
  901. for (var i = 0; i < list.length; ++ i) {
  902. if (fn(list[i], i)) {
  903. list.splice(i, 1);
  904. --i;
  905. }
  906. }
  907. return list;
  908. };
  909. // Other useful helpers.
  910. /**
  911. * Simple string template system.
  912. * Takes a template string containing tokens in the form of "%value".
  913. * Returns a new string with the tokens replaced by the specified values.
  914. * Keys and values can be of any data type that can be coerced into a string. Arrays will work here as well.
  915. *
  916. * @param {String} template a string (can be HTML) that contains tokens embedded into it
  917. * @param {object} values a collection of token keys and values
  918. */
  919. fluid.stringTemplate = function (template, values) {
  920. var newString = template;
  921. for (var key in values) {
  922. var searchStr = "%" + key;
  923. newString = newString.replace(searchStr, values[key]);
  924. }
  925. return newString;
  926. };
  927. })(jQuery, fluid_1_2);
  928. /*
  929. Copyright 2008-2010 University of Cambridge
  930. Copyright 2008-2009 University of Toronto
  931. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  932. BSD license. You may not use this file except in compliance with one these
  933. Licenses.
  934. You may obtain a copy of the ECL 2.0 License and BSD License at
  935. https://source.fluidproject.org/svn/LICENSE.txt
  936. */
  937. // Declare dependencies.
  938. /*global jQuery */
  939. var fluid_1_2 = fluid_1_2 || {};
  940. (function ($, fluid) {
  941. fluid.dom = fluid.dom || {};
  942. // Node walker function for iterateDom.
  943. var getNextNode = function (iterator) {
  944. if (iterator.node.firstChild) {
  945. iterator.node = iterator.node.firstChild;
  946. iterator.depth += 1;
  947. return iterator;
  948. }
  949. while (iterator.node) {
  950. if (iterator.node.nextSibling) {
  951. iterator.node = iterator.node.nextSibling;
  952. return iterator;
  953. }
  954. iterator.node = iterator.node.parentNode;
  955. iterator.depth -= 1;
  956. }
  957. return iterator;
  958. };
  959. /**
  960. * Walks the DOM, applying the specified acceptor function to each element.
  961. * There is a special case for the acceptor, allowing for quick deletion of elements and their children.
  962. * Return "delete" from your acceptor function if you want to delete the element in question.
  963. * Return "stop" to terminate iteration.
  964. *
  965. * @param {Element} node the node to start walking from
  966. * @param {Function} acceptor the function to invoke with each DOM element
  967. * @param {Boolean} allnodes Use <code>true</code> to call acceptor on all nodes,
  968. * rather than just element nodes (type 1)
  969. */
  970. fluid.dom.iterateDom = function (node, acceptor, allNodes) {
  971. var currentNode = {node: node, depth: 0};
  972. var prevNode = node;
  973. var condition;
  974. while (currentNode.node !== null && currentNode.depth >= 0 && currentNode.depth < fluid.dom.iterateDom.DOM_BAIL_DEPTH) {
  975. condition = null;
  976. if (currentNode.node.nodeType === 1 || allNodes) {
  977. condition = acceptor(currentNode.node, currentNode.depth);
  978. }
  979. if (condition) {
  980. if (condition === "delete") {
  981. currentNode.node.parentNode.removeChild(currentNode.node);
  982. currentNode.node = prevNode;
  983. }
  984. else if (condition === "stop") {
  985. return currentNode.node;
  986. }
  987. }
  988. prevNode = currentNode.node;
  989. currentNode = getNextNode(currentNode);
  990. }
  991. };
  992. // Work around IE circular DOM issue. This is the default max DOM depth on IE.
  993. // http://msdn2.microsoft.com/en-us/library/ms761392(VS.85).aspx
  994. fluid.dom.iterateDom.DOM_BAIL_DEPTH = 256;
  995. /**
  996. * Checks if the sepcified container is actually the parent of containee.
  997. *
  998. * @param {Element} container the potential parent
  999. * @param {Element} containee the child in question
  1000. */
  1001. fluid.dom.isContainer = function (container, containee) {
  1002. for (; containee; containee = containee.parentNode) {
  1003. if (container === containee) {
  1004. return true;
  1005. }
  1006. }
  1007. return false;
  1008. };
  1009. /** Return the element text from the supplied DOM node as a single String */
  1010. fluid.dom.getElementText = function(element) {
  1011. var nodes = element.childNodes;
  1012. var text = "";
  1013. for (var i = 0; i < nodes.length; ++ i) {
  1014. var child = nodes[i];
  1015. if (child.nodeType == 3) {
  1016. text = text + child.nodeValue;
  1017. }
  1018. }
  1019. return text;
  1020. };
  1021. })(jQuery, fluid_1_2);
  1022. /*
  1023. Copyright 2008-2010 University of Cambridge
  1024. Copyright 2008-2009 University of Toronto
  1025. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  1026. BSD license. You may not use this file except in compliance with one these
  1027. Licenses.
  1028. You may obtain a copy of the ECL 2.0 License and BSD License at
  1029. https://source.fluidproject.org/svn/LICENSE.txt
  1030. */
  1031. /*global jQuery*/
  1032. /*global fluid_1_2*/
  1033. fluid_1_2 = fluid_1_2 || {};
  1034. (function ($, fluid) {
  1035. var unUnicode = /(\\u[\dabcdef]{4}|\\x[\dabcdef]{2})/g;
  1036. fluid.unescapeProperties = function (string) {
  1037. string = string.replace(unUnicode, function(match) {
  1038. var code = match.substring(2);
  1039. var parsed = parseInt(code, 16);
  1040. return String.fromCharCode(parsed);
  1041. }
  1042. );
  1043. var pos = 0;
  1044. while (true) {
  1045. var backpos = string.indexOf("\\", pos);
  1046. if (backpos === -1) {
  1047. break;
  1048. }
  1049. if (backpos === string.length - 1) {
  1050. return [string.substring(0, string.length - 1), true];
  1051. }
  1052. var replace = string.charAt(backpos + 1);
  1053. if (replace === "n") replace = "\n";
  1054. if (replace === "r") replace = "\r";
  1055. if (replace === "t") replace = "\t";
  1056. string = string.substring(0, backpos) + replace + string.substring(backpos + 2);
  1057. pos = backpos + 1;
  1058. }
  1059. return [string, false];
  1060. };
  1061. var breakPos = /[^\\][\s:=]/;
  1062. fluid.parseJavaProperties = function(text) {
  1063. // File format described at http://java.sun.com/javase/6/docs/api/java/util/Properties.html#load(java.io.Reader)
  1064. var togo = {};
  1065. text = text.replace(/\r\n/g, "\n");
  1066. text = text.replace(/\r/g, "\n");
  1067. lines = text.split("\n");
  1068. var contin, key, valueComp, valueRaw, valueEsc;
  1069. for (var i = 0; i < lines.length; ++ i) {
  1070. var line = $.trim(lines[i]);
  1071. if (!line || line.charAt(0) === "#" || line.charAt(0) === '!') {
  1072. continue;
  1073. }
  1074. if (!contin) {
  1075. valueComp = "";
  1076. var breakpos = line.search(breakPos);
  1077. if (breakpos === -1) {
  1078. key = line;
  1079. valueRaw = "";
  1080. }
  1081. else {
  1082. key = $.trim(line.substring(0, breakpos + 1)); // +1 since first char is escape exclusion
  1083. valueRaw = $.trim(line.substring(breakpos + 2));
  1084. if (valueRaw.charAt(0) === ":" || valueRaw.charAt(0) === "=") {
  1085. valueRaw = $.trim(valueRaw.substring(1));
  1086. }
  1087. }
  1088. key = fluid.unescapeProperties(key)[0];
  1089. valueEsc = fluid.unescapeProperties(valueRaw);
  1090. }
  1091. else {
  1092. valueEsc = fluid.unescapeProperties(line);
  1093. }
  1094. contin = valueEsc[1];
  1095. if (!valueEsc[1]) { // this line was not a continuation line - store the value
  1096. togo[key] = valueComp + valueEsc[0];
  1097. }
  1098. else {
  1099. valueComp += valueEsc[0];
  1100. }
  1101. }
  1102. return togo;
  1103. };
  1104. /**
  1105. * Expand a message string with respect to a set of arguments, following a basic
  1106. * subset of the Java MessageFormat rules.
  1107. * http://java.sun.com/j2se/1.4.2/docs/api/java/text/MessageFormat.html
  1108. *
  1109. * The message string is expected to contain replacement specifications such
  1110. * as {0}, {1}, {2}, etc.
  1111. * @param messageString {String} The message key to be expanded
  1112. * @param args {String/Array of String} An array of arguments to be substituted into the message.
  1113. * @return The expanded message string.
  1114. */
  1115. fluid.formatMessage = function (messageString, args) {
  1116. if (!args) {
  1117. return messageString;
  1118. }
  1119. if (typeof(args) === "string") {
  1120. args = [args];
  1121. }
  1122. for (var i = 0; i < args.length; ++ i) {
  1123. messageString = messageString.replace("{" + i + "}", args[i]);
  1124. }
  1125. return messageString;
  1126. };
  1127. })(jQuery, fluid_1_2);
  1128. /*
  1129. Copyright 2007-2010 University of Cambridge
  1130. Copyright 2007-2009 University of Toronto
  1131. Copyright 2007-2009 University of California, Berkeley
  1132. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  1133. BSD license. You may not use this file except in compliance with one these
  1134. Licenses.
  1135. You may obtain a copy of the ECL 2.0 License and BSD License at
  1136. https://source.fluidproject.org/svn/LICENSE.txt
  1137. */
  1138. // Declare dependencies.
  1139. /*global jQuery, YAHOO, opera*/
  1140. var fluid_1_2 = fluid_1_2 || {};
  1141. var fluid = fluid || fluid_1_2;
  1142. (function ($, fluid) {
  1143. fluid.renderTimestamp = function (date) {
  1144. var zeropad = function (num, width) {
  1145. if (!width) width = 2;
  1146. var numstr = (num == undefined? "" : num.toString());
  1147. return "00000".substring(5 - width + numstr.length) + numstr;
  1148. }
  1149. return zeropad(date.getHours()) + ":" + zeropad(date.getMinutes()) + ":" + zeropad(date.getSeconds()) + "." + zeropad(date.getMilliseconds(), 3);
  1150. };
  1151. /**
  1152. * Dumps a DOM element into a readily recognisable form for debugging - produces a
  1153. * "semi-selector" summarising its tag name, class and id, whichever are set.
  1154. *
  1155. * @param {jQueryable} element The element to be dumped
  1156. * @return A string representing the element.
  1157. */
  1158. fluid.dumpEl = function (element) {
  1159. var togo;
  1160. if (!element) {
  1161. return "null";
  1162. }
  1163. if (element.nodeType === 3 || element.nodeType === 8) {
  1164. return "[data: " + element.data + "]";
  1165. }
  1166. if (element.nodeType === 9) {
  1167. return "[document: location " + element.location + "]";
  1168. }
  1169. if (!element.nodeType && fluid.isArrayable(element)) {
  1170. togo = "[";
  1171. for (var i = 0; i < element.length; ++ i) {
  1172. togo += fluid.dumpEl(element[i]);
  1173. if (i < element.length - 1) {
  1174. togo += ", ";
  1175. }
  1176. }
  1177. return togo + "]";
  1178. }
  1179. element = $(element);
  1180. togo = element.get(0).tagName;
  1181. if (element.attr("id")) {
  1182. togo += "#" + element.attr("id");
  1183. }
  1184. if (element.attr("class")) {
  1185. togo += "." + element.attr("class");
  1186. }
  1187. return togo;
  1188. };
  1189. })(jQuery, fluid_1_2);
  1190. /*
  1191. Copyright 2008-2010 University of Cambridge
  1192. Copyright 2008-2009 University of Toronto
  1193. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  1194. BSD license. You may not use this file except in compliance with one these
  1195. Licenses.
  1196. You may obtain a copy of the ECL 2.0 License and BSD License at
  1197. https://source.fluidproject.org/svn/LICENSE.txt
  1198. */
  1199. /*global jQuery*/
  1200. /*global fluid_1_2*/
  1201. fluid_1_2 = fluid_1_2 || {};
  1202. (function ($, fluid) {
  1203. fluid.VALUE = {};
  1204. fluid.BINDING_ROOT_KEY = "fluid-binding-root";
  1205. /** Recursively find any data stored under a given name from a node upwards
  1206. * in its DOM hierarchy **/
  1207. fluid.findData = function(elem, name) {
  1208. while (elem) {
  1209. var data = $.data(elem, name);
  1210. if (data) {return data;}
  1211. elem = elem.parentNode;
  1212. }
  1213. };
  1214. fluid.bindFossils = function(node, data, fossils) {
  1215. $.data(node, fluid.BINDING_ROOT_KEY, {data: data, fossils: fossils});
  1216. };
  1217. fluid.findForm = function (node) {
  1218. return fluid.findAncestor(node,
  1219. function(element) {return element.nodeName.toLowerCase() === "form";});
  1220. };
  1221. /** A generalisation of jQuery.val to correctly handle the case of acquiring and
  1222. * setting the value of clustered radio button/checkbox sets, potentially, given
  1223. * a node corresponding to just one element.
  1224. */
  1225. fluid.value = function (nodeIn, newValue) {
  1226. var node = fluid.unwrap(nodeIn);
  1227. var multiple = false;
  1228. if (node.nodeType === undefined && node.length > 1) {
  1229. node = node[0];
  1230. multiple = true;
  1231. }
  1232. var jNode = $(node);
  1233. if ("input" !== node.nodeName.toLowerCase()
  1234. || ! /radio|checkbox/.test(node.type)) {return $(node).val(newValue);}
  1235. var name = node.name;
  1236. if (name === undefined) {
  1237. fluid.fail("Cannot acquire value from node " + fluid.dumpEl(node) + " which does not have name attribute set");
  1238. }
  1239. var elements;
  1240. if (multiple) {
  1241. elements = nodeIn;
  1242. }
  1243. else {
  1244. var elements = document.getElementsByName(name);
  1245. var scope = fluid.findForm(node);
  1246. elements = $.grep(elements,
  1247. function(element) {
  1248. if (element.name !== name) {return false;}
  1249. return !scope || fluid.dom.isContainer(scope, element);
  1250. });
  1251. }
  1252. if (newValue !== undefined) {
  1253. if (typeof(newValue) === "boolean") {
  1254. newValue = (newValue? "true" : "false");
  1255. }
  1256. // jQuery gets this partially right, but when dealing with radio button array will
  1257. // set all of their values to "newValue" rather than setting the checked property
  1258. // of the corresponding control.
  1259. $.each(elements, function() {
  1260. this.checked = (newValue instanceof Array?
  1261. $.inArray(this.value, newValue) !== -1 : newValue === this.value);
  1262. });
  1263. }
  1264. else { // this part jQuery will not do - extracting value from <input> array
  1265. var checked = $.map(elements, function(element) {
  1266. return element.checked? element.value : null;
  1267. });
  1268. return node.type === "radio"? checked[0] : checked;
  1269. }
  1270. };
  1271. /** "Automatically" apply to whatever part of the data model is
  1272. * relevant, the changed value received at the given DOM node*/
  1273. fluid.applyChange = function(node, newValue, applier) {
  1274. node = fluid.unwrap(node);
  1275. if (newValue === undefined) {
  1276. newValue = fluid.value(node);
  1277. }
  1278. if (node.nodeType === undefined && node.length > 0) {node = node[0];} // assume here that they share name and parent
  1279. var root = fluid.findData(node, fluid.BINDING_ROOT_KEY);
  1280. if (!root) {
  1281. fluid.fail("Bound data could not be discovered in any node above " + fluid.dumpEl(node));
  1282. }
  1283. var name = node.name;
  1284. var fossil = root.fossils[name];
  1285. if (!fossil) {
  1286. fluid.fail("No fossil discovered for name " + name + " in fossil record above " + fluid.dumpEl(node));
  1287. }
  1288. if (typeof(fossil.oldvalue) === "boolean") { // deal with the case of an "isolated checkbox"
  1289. newValue = newValue[0]? true: false;
  1290. }
  1291. var EL = root.fossils[name].EL;
  1292. if (applier) {
  1293. applier.fireChangeRequest({path: EL, value: newValue, source: node.id});
  1294. }
  1295. else {
  1296. fluid.model.setBeanValue(root.data, EL, newValue);
  1297. }
  1298. };
  1299. fluid.pathUtil = {};
  1300. var getPathSegmentImpl = function(accept, path, i) {
  1301. var segment = null; // TODO: rewrite this with regexes and replaces
  1302. if (accept) {
  1303. segment = "";
  1304. }
  1305. var escaped = false;
  1306. var limit = path.length;
  1307. for (; i < limit; ++i) {
  1308. var c = path.charAt(i);
  1309. if (!escaped) {
  1310. if (c === '.') {
  1311. break;
  1312. }
  1313. else if (c === '\\') {
  1314. escaped = true;
  1315. }
  1316. else if (segment !== null) {
  1317. segment += c;
  1318. }
  1319. }
  1320. else {
  1321. escaped = false;
  1322. if (segment !== null)
  1323. accept += c;
  1324. }
  1325. }
  1326. if (segment !== null) {
  1327. accept[0] = segment;
  1328. }
  1329. return i;
  1330. };
  1331. var globalAccept = []; // reentrancy risk
  1332. fluid.pathUtil.getPathSegment = function(path, i) {
  1333. getPathSegmentImpl(globalAccept, path, i);
  1334. return globalAccept[0];
  1335. };
  1336. fluid.pathUtil.getHeadPath = function(path) {
  1337. return fluid.pathUtil.getPathSegment(path, 0);
  1338. };
  1339. fluid.pathUtil.getFromHeadPath = function(path) {
  1340. var firstdot = getPathSegmentImpl(null, path, 0);
  1341. return firstdot === path.length ? null
  1342. : path.substring(firstdot + 1);
  1343. };
  1344. function lastDotIndex(path) {
  1345. // TODO: proper escaping rules
  1346. return path.lastIndexOf(".");
  1347. }
  1348. fluid.pathUtil.getToTailPath = function(path) {
  1349. var lastdot = lastDotIndex(path);
  1350. return lastdot == -1 ? null : path.substring(0, lastdot);
  1351. };
  1352. /** Returns the very last path component of a bean path */
  1353. fluid.pathUtil.getTailPath = function(path) {
  1354. var lastdot = lastDotIndex(path);
  1355. return fluid.pathUtil.getPathSegment(path, lastdot + 1);
  1356. };
  1357. var composeSegment = function(prefix, toappend) {
  1358. for (var i = 0; i < toappend.length; ++i) {
  1359. var c = toappend.charAt(i);
  1360. if (c === '.' || c === '\\' || c === '}') {
  1361. prefix += '\\';
  1362. }
  1363. prefix += c;
  1364. }
  1365. return prefix;
  1366. };
  1367. /**
  1368. * Compose a prefix and suffix EL path, where the prefix is already escaped.
  1369. * Prefix may be empty, but not null. The suffix will become escaped.
  1370. */
  1371. fluid.pathUtil.composePath = function(prefix, suffix) {
  1372. if (prefix.length !== 0) {
  1373. prefix += '.';
  1374. }
  1375. return composeSegment(prefix, suffix);
  1376. };
  1377. fluid.pathUtil.matchPath = function(spec, path) {
  1378. var togo = "";
  1379. while (true) {
  1380. if (!spec) {break;}
  1381. if (!path) {return null;}
  1382. var spechead = fluid.pathUtil.getHeadPath(spec);
  1383. var pathhead = fluid.pathUtil.getHeadPath(path);
  1384. // if we fail to match on a specific component, fail.
  1385. if (spechead !== "*" && spechead !== pathhead) {
  1386. return null;
  1387. }
  1388. togo = fluid.pathUtil.composePath(togo, pathhead);
  1389. spec = fluid.pathUtil.getFromHeadPath(spec);
  1390. path = fluid.pathUtil.getFromHeadPath(path);
  1391. }
  1392. return togo;
  1393. };
  1394. fluid.model.applyChangeRequest = function(model, request) {
  1395. if (request.type === "ADD") {
  1396. fluid.model.setBeanValue(model, request.path, request.value);
  1397. }
  1398. else if (request.type === "DELETE") {
  1399. var totail = fluid.pathUtil.getToTailPath(request.path);
  1400. var tail = fluid.pathUtil.getTailPath(request.path);
  1401. var penult = fluid.model.getBeanValue(model, penult);
  1402. delete penult[tail];
  1403. }
  1404. };
  1405. fluid.model.bindRequestChange = function(that) {
  1406. that.requestChange = function(path, value, type) {
  1407. var changeRequest = {
  1408. path: path,
  1409. value: value,
  1410. type: type
  1411. };
  1412. that.fireChangeRequest(changeRequest);
  1413. }
  1414. };
  1415. fluid.makeChangeApplier = function(model) {
  1416. var baseEvents = {
  1417. guards: fluid.event.getEventFirer(false, true),
  1418. modelChanged: fluid.event.getEventFirer(false, false)
  1419. };
  1420. var that = {
  1421. model: model
  1422. };
  1423. function makePredicate(listenerMember, requestIndex) {
  1424. return function(listener, args) {
  1425. var changeRequest = args[requestIndex];
  1426. return fluid.pathUtil.matchPath(listener[listenerMember], changeRequest.path);
  1427. };
  1428. }
  1429. function adaptListener(that, name, listenerMember, requestIndex) {
  1430. var predicate = makePredicate(listenerMember, requestIndex);
  1431. that[name] = {
  1432. addListener: function(pathSpec, listener, namespace) {
  1433. listener[listenerMember] = pathSpec;
  1434. baseEvents[name].addListener(listener, namespace, predicate);
  1435. },
  1436. removeListener: function(listener) {
  1437. baseEvents[name].removeListener(listener);
  1438. }
  1439. };
  1440. }
  1441. adaptListener(that, "guards", "guardedPathSpec", 1);
  1442. adaptListener(that, "modelChanged", "triggerPathSpec", 2);
  1443. that.fireChangeRequest = function(changeRequest) {
  1444. if (!changeRequest.type) {
  1445. changeRequest.type = "ADD";
  1446. }
  1447. var prevent = baseEvents.guards.fire(model, changeRequest);
  1448. if (prevent === false) {
  1449. return;
  1450. }
  1451. var oldModel = {};
  1452. fluid.model.copyModel(oldModel, model);
  1453. fluid.model.applyChangeRequest(model, changeRequest);
  1454. baseEvents.modelChanged.fire(model, oldModel, changeRequest);
  1455. };
  1456. fluid.model.bindRequestChange(that);
  1457. return that;
  1458. };
  1459. fluid.makeSuperApplier = function() {
  1460. var subAppliers = [];
  1461. var that = {};
  1462. that.addSubApplier = function(path, subApplier) {
  1463. subAppliers.push({path: path, subApplier: subApplier});
  1464. };
  1465. that.fireChangeRequest = function(request) {
  1466. for (var i = 0; i < subAppliers.length; ++ i) {
  1467. var path = subAppliers[i].path;
  1468. if (request.path.indexOf(path) === 0) {
  1469. var subpath = request.path.substring(path.length + 1);
  1470. var subRequest = fluid.copy(request);
  1471. subRequest.path = subpath;
  1472. // TODO: Deal with the as yet unsupported case of an EL rvalue DAR
  1473. subAppliers[i].subApplier.fireChangeRequest(subRequest);
  1474. }
  1475. }
  1476. };
  1477. return that;
  1478. };
  1479. fluid.attachModel = function(baseModel, path, model) {
  1480. var segs = fluid.model.parseEL(path);
  1481. for (var i = 0; i < segs.length - 1; ++ i) {
  1482. var seg = segs[i];
  1483. var subModel = baseModel[seg];
  1484. if (!subModel) {
  1485. baseModel[seg] = subModel = {};
  1486. }
  1487. baseModel = subModel;
  1488. }
  1489. baseModel[segs[segs.length - 1]] = model;
  1490. };
  1491. fluid.assembleModel = function (modelSpec) {
  1492. var model = {};
  1493. var superApplier = fluid.makeSuperApplier();
  1494. var togo = {model: model, applier: superApplier};
  1495. for (path in modelSpec) {
  1496. var rec = modelSpec[path];
  1497. fluid.attachModel(model, path, rec.model);
  1498. if (rec.applier) {
  1499. superApplier.addSubApplier(path, rec.applier);
  1500. }
  1501. }
  1502. return togo;
  1503. };
  1504. })(jQuery, fluid_1_2);
  1505. /*
  1506. Copyright 2008-2010 University of Cambridge
  1507. Copyright 2008-2009 University of Toronto
  1508. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  1509. BSD license. You may not use this file except in compliance with one these
  1510. Licenses.
  1511. You may obtain a copy of the ECL 2.0 License and BSD License at
  1512. https://source.fluidproject.org/svn/LICENSE.txt
  1513. */
  1514. /*global jQuery*/
  1515. var fluid_1_2 = fluid_1_2 || {};
  1516. var fluid = fluid || fluid_1_2;
  1517. (function ($, fluid) {
  1518. // $().fluid("selectable", args)
  1519. // $().fluid("selectable".that()
  1520. // $().fluid("pager.pagerBar", args)
  1521. // $().fluid("reorderer", options)
  1522. /** Create a "bridge" from code written in the Fluid standard "that-ist" style,
  1523. * to the standard JQuery UI plugin architecture specified at http://docs.jquery.com/UI/Guidelines .
  1524. * Every Fluid component corresponding to the top-level standard signature (JQueryable, options)
  1525. * will automatically convert idiomatically to the JQuery UI standard via this adapter.
  1526. * Any return value which is a primitive or array type will become the return value
  1527. * of the "bridged" function - however, where this function returns a general hash
  1528. * (object) this is interpreted as forming part of the Fluid "return that" pattern,
  1529. * and the function will instead be bridged to "return this" as per JQuery standard,
  1530. * permitting chaining to occur. However, as a courtesy, the particular "this" returned
  1531. * will be augmented with a function that() which will allow the original return
  1532. * value to be retrieved if desired.
  1533. * @param {String} name The name under which the "plugin space" is to be injected into
  1534. * JQuery
  1535. * @param {Object} peer The root of the namespace corresponding to the peer object.
  1536. */
  1537. fluid.thatistBridge = function (name, peer) {
  1538. var togo = function(funcname) {
  1539. var segs = funcname.split(".");
  1540. var move = peer;
  1541. for (var i = 0; i < segs.length; ++i) {
  1542. move = move[segs[i]];
  1543. }
  1544. var args = [this];
  1545. if (arguments.length === 2) {
  1546. args = args.concat($.makeArray(arguments[1]));
  1547. }
  1548. var ret = move.apply(null, args);
  1549. this.that = function() {
  1550. return ret;
  1551. }
  1552. var type = typeof(ret);
  1553. return !ret || type === "string" || type === "number" || type === "boolean"
  1554. || ret && ret.length !== undefined? ret: this;
  1555. };
  1556. $.fn[name] = togo;
  1557. return togo;
  1558. };
  1559. fluid.thatistBridge("fluid", fluid);
  1560. fluid.thatistBridge("fluid_1_2", fluid_1_2);
  1561. // Private constants.
  1562. var NAMESPACE_KEY = "fluid-keyboard-a11y";
  1563. /**
  1564. * Gets stored state from the jQuery instance's data map.
  1565. */
  1566. var getData = function(target, key) {
  1567. var data = $(target).data(NAMESPACE_KEY);
  1568. return data ? data[key] : undefined;
  1569. };
  1570. /**
  1571. * Stores state in the jQuery instance's data map. Unlike jQuery's version,
  1572. * accepts multiple-element jQueries.
  1573. */
  1574. var setData = function(target, key, value) {
  1575. $(target).each(function() {
  1576. var data = $.data(this, NAMESPACE_KEY) || {};
  1577. data[key] = value;
  1578. $.data(this, NAMESPACE_KEY, data);
  1579. });
  1580. };
  1581. /** Global focus manager - makes use of jQuery delegate plugin if present,
  1582. * detecting presence of "focusin" event.
  1583. */
  1584. var lastFocusedElement = "disabled";
  1585. if ($.event.special["focusin"]) {
  1586. lastFocusedElement = null;
  1587. $(document).bind("focusin", function(event){
  1588. lastFocusedElement = event.target;
  1589. });
  1590. }
  1591. fluid.getLastFocusedElement = function () {
  1592. if (lastFocusedElement === "disabled") {
  1593. fluid.fail("Focus manager not enabled - please include jquery.delegate.js or equivalent for support of 'focusin' event");
  1594. }
  1595. return lastFocusedElement;
  1596. }
  1597. /*************************************************************************
  1598. * Tabindex normalization - compensate for browser differences in naming
  1599. * and function of "tabindex" attribute and tabbing order.
  1600. */
  1601. // -- Private functions --
  1602. var normalizeTabindexName = function() {
  1603. return $.browser.msie ? "tabIndex" : "tabindex";
  1604. };
  1605. var canHaveDefaultTabindex = function(elements) {
  1606. if (elements.length <= 0) {
  1607. return false;
  1608. }
  1609. return $(elements[0]).is("a, input, button, select, area, textarea, object");
  1610. };
  1611. var getValue = function(elements) {
  1612. if (elements.length <= 0) {
  1613. return undefined;
  1614. }
  1615. if (!fluid.tabindex.hasAttr(elements)) {
  1616. return canHaveDefaultTabindex(elements) ? Number(0) : undefined;
  1617. }
  1618. // Get the attribute and return it as a number value.
  1619. var value = elements.attr(normalizeTabindexName());
  1620. return Number(value);
  1621. };
  1622. var setValue = function(elements, toIndex) {
  1623. return elements.each(function(i, item) {
  1624. $(item).attr(normalizeTabindexName(), toIndex);
  1625. });
  1626. };
  1627. // -- Public API --
  1628. /**
  1629. * Gets the value of the tabindex attribute for the first item, or sets the tabindex value of all elements
  1630. * if toIndex is specified.
  1631. *
  1632. * @param {String|Number} toIndex
  1633. */
  1634. fluid.tabindex = function(target, toIndex) {
  1635. target = $(target);
  1636. if (toIndex !== null && toIndex !== undefined) {
  1637. return setValue(target, toIndex);
  1638. } else {
  1639. return getValue(target);
  1640. }
  1641. };
  1642. /**
  1643. * Removes the tabindex attribute altogether from each element.
  1644. */
  1645. fluid.tabindex.remove = function(target) {
  1646. target = $(target);
  1647. return target.each(function(i, item) {
  1648. $(item).removeAttr(normalizeTabindexName());
  1649. });
  1650. };
  1651. /**
  1652. * Determines if an element actually has a tabindex attribute present.
  1653. */
  1654. fluid.tabindex.hasAttr = function(target) {
  1655. target = $(target);
  1656. if (target.length <= 0) {
  1657. return false;
  1658. }
  1659. var togo = target.map(
  1660. function() {
  1661. var attributeNode = this.getAttributeNode(normalizeTabindexName());
  1662. return attributeNode ? attributeNode.specified : false;
  1663. }
  1664. );
  1665. return togo.length === 1? togo[0] : togo;
  1666. };
  1667. /**
  1668. * Determines if an element either has a tabindex attribute or is naturally tab-focussable.
  1669. */
  1670. fluid.tabindex.has = function(target) {
  1671. target = $(target);
  1672. return fluid.tabindex.hasAttr(target) || canHaveDefaultTabindex(target);
  1673. };
  1674. var ENABLEMENT_KEY = "enablement";
  1675. /** Queries or sets the enabled status of a control. An activatable node
  1676. * may be "disabled" in which case its keyboard bindings will be inoperable
  1677. * (but still stored) until it is reenabled again.
  1678. */
  1679. fluid.enabled = function(target, state) {
  1680. target = $(target);
  1681. if (state === undefined) {
  1682. return getData(target, ENABLEMENT_KEY) !== false;
  1683. }
  1684. else {
  1685. $("*", target).each(function() {
  1686. if (getData(this, ENABLEMENT_KEY) !== undefined) {
  1687. setData(this, ENABLEMENT_KEY, state);
  1688. }
  1689. else if (/select|textarea|input/i.test(this.nodeName)) {
  1690. $(this).attr("disabled", !state);
  1691. }
  1692. });
  1693. setData(target, ENABLEMENT_KEY, state);
  1694. }
  1695. };
  1696. // Keyboard navigation
  1697. // Public, static constants needed by the rest of the library.
  1698. fluid.a11y = $.a11y || {};
  1699. fluid.a11y.orientation = {
  1700. HORIZONTAL: 0,
  1701. VERTICAL: 1,
  1702. BOTH: 2
  1703. };
  1704. var UP_DOWN_KEYMAP = {
  1705. next: $.ui.keyCode.DOWN,
  1706. previous: $.ui.keyCode.UP
  1707. };
  1708. var LEFT_RIGHT_KEYMAP = {
  1709. next: $.ui.keyCode.RIGHT,
  1710. previous: $.ui.keyCode.LEFT
  1711. };
  1712. // Private functions.
  1713. var unwrap = function(element) {
  1714. return element.jquery ? element[0] : element; // Unwrap the element if it's a jQuery.
  1715. };
  1716. var makeElementsTabFocussable = function(elements) {
  1717. // If each element doesn't have a tabindex, or has one set to a negative value, set it to 0.
  1718. elements.each(function(idx, item) {
  1719. item = $(item);
  1720. if (!item.fluid("tabindex.has") || item.fluid("tabindex") < 0) {
  1721. item.fluid("tabindex", 0);
  1722. }
  1723. });
  1724. };
  1725. // Public API.
  1726. /**
  1727. * Makes all matched elements available in the tab order by setting their tabindices to "0".
  1728. */
  1729. fluid.tabbable = function(target) {
  1730. target = $(target);
  1731. makeElementsTabFocussable(target);
  1732. };
  1733. /***********************************************************************
  1734. * Selectable functionality - geometrising a set of nodes such that they
  1735. * can be navigated (by setting focus) using a set of directional keys
  1736. */
  1737. var CONTEXT_KEY = "selectionContext";
  1738. var NO_SELECTION = -32768;
  1739. var cleanUpWhenLeavingContainer = function(selectionContext) {
  1740. if (selectionContext.options.onLeaveContainer) {
  1741. selectionContext.options.onLeaveContainer(
  1742. selectionContext.selectables[selectionContext.activeItemIndex]);
  1743. } else if (selectionContext.options.onUnselect) {
  1744. selectionContext.options.onUnselect(
  1745. selectionContext.selectables[selectionContext.activeItemIndex]);
  1746. }
  1747. if (!selectionContext.options.rememberSelectionState) {
  1748. selectionContext.activeItemIndex = NO_SELECTION;
  1749. }
  1750. };
  1751. /**
  1752. * Does the work of selecting an element and delegating to the client handler.
  1753. */
  1754. var drawSelection = function(elementToSelect, handler) {
  1755. if (handler) {
  1756. handler(elementToSelect);
  1757. }
  1758. };
  1759. /**
  1760. * Does does the work of unselecting an element and delegating to the client handler.
  1761. */
  1762. var eraseSelection = function(selectedElement, handler) {
  1763. if (handler && selectedElement) {
  1764. handler(selectedElement);
  1765. }
  1766. };
  1767. var unselectElement = function(selectedElement, selectionContext) {
  1768. eraseSelection(selectedElement, selectionContext.options.onUnselect);
  1769. };
  1770. var selectElement = function(elementToSelect, selectionContext) {
  1771. // It's possible that we're being called programmatically, in which case we should clear any previous selection.
  1772. unselectElement(selectionContext.selectedElement(), selectionContext);
  1773. elementToSelect = unwrap(elementToSelect);
  1774. var newIndex = selectionContext.selectables.index(elementToSelect);
  1775. // Next check if the element is a known selectable. If not, do nothing.
  1776. if (newIndex === -1) {
  1777. return;
  1778. }
  1779. // Select the new element.
  1780. selectionContext.activeItemIndex = newIndex;
  1781. drawSelection(elementToSelect, selectionContext.options.onSelect);
  1782. };
  1783. var selectableFocusHandler = function(selectionContext) {
  1784. return function(evt) {
  1785. // FLUID-3590: newer browsers (FF 3.6, Webkit 4) have a form of "bug" in that they will go bananas
  1786. // on attempting to move focus off an element which has tabindex dynamically set to -1.
  1787. $(evt.target).fluid("tabindex", 0);
  1788. selectElement(evt.target, selectionContext);
  1789. // Force focus not to bubble on some browsers.
  1790. return evt.stopPropagation();
  1791. };
  1792. };
  1793. var selectableBlurHandler = function(selectionContext) {
  1794. return function(evt) {
  1795. $(evt.target).fluid("tabindex", selectionContext.options.selectablesTabindex);
  1796. unselectElement(evt.target, selectionContext);
  1797. // Force blur not to bubble on some browsers.
  1798. return evt.stopPropagation();
  1799. };
  1800. };
  1801. var reifyIndex = function(sc_that) {
  1802. var elements = sc_that.selectables;
  1803. if (sc_that.activeItemIndex >= elements.length) {
  1804. sc_that.activeItemIndex = 0;
  1805. }
  1806. if (sc_that.activeItemIndex < 0 && sc_that.activeItemIndex !== NO_SELECTION) {
  1807. sc_that.activeItemIndex = elements.length - 1;
  1808. }
  1809. if (sc_that.activeItemIndex >= 0) {
  1810. $(elements[sc_that.activeItemIndex]).focus();
  1811. }
  1812. };
  1813. var prepareShift = function(selectionContext) {
  1814. // FLUID-3590: FF 3.6 and Safari 4.x won't fire blur() when programmatically moving focus.
  1815. var selElm = selectionContext.selectedElement();
  1816. if (selElm) {
  1817. selElm.blur();
  1818. }
  1819. unselectElement(selectionContext.selectedElement(), selectionContext);
  1820. if (selectionContext.activeItemIndex === NO_SELECTION) {
  1821. selectionContext.activeItemIndex = -1;
  1822. }
  1823. };
  1824. var focusNextElement = function(selectionContext) {
  1825. prepareShift(selectionContext);
  1826. ++selectionContext.activeItemIndex;
  1827. reifyIndex(selectionContext);
  1828. };
  1829. var focusPreviousElement = function(selectionContext) {
  1830. prepareShift(selectionContext);
  1831. --selectionContext.activeItemIndex;
  1832. reifyIndex(selectionContext);
  1833. };
  1834. var arrowKeyHandler = function(selectionContext, keyMap, userHandlers) {
  1835. return function(evt) {
  1836. if (evt.which === keyMap.next) {
  1837. focusNextElement(selectionContext);
  1838. evt.preventDefault();
  1839. } else if (evt.which === keyMap.previous) {
  1840. focusPreviousElement(selectionContext);
  1841. evt.preventDefault();
  1842. }
  1843. };
  1844. };
  1845. var getKeyMapForDirection = function(direction) {
  1846. // Determine the appropriate mapping for next and previous based on the specified direction.
  1847. var keyMap;
  1848. if (direction === fluid.a11y.orientation.HORIZONTAL) {
  1849. keyMap = LEFT_RIGHT_KEYMAP;
  1850. }
  1851. else if (direction === fluid.a11y.orientation.VERTICAL) {
  1852. // Assume vertical in any other case.
  1853. keyMap = UP_DOWN_KEYMAP;
  1854. }
  1855. return keyMap;
  1856. };
  1857. var tabKeyHandler = function(selectionContext) {
  1858. return function(evt) {
  1859. if (evt.which !== $.ui.keyCode.TAB) {
  1860. return;
  1861. }
  1862. cleanUpWhenLeavingContainer(selectionContext);
  1863. // Catch Shift-Tab and note that focus is on its way out of the container.
  1864. if (evt.shiftKey) {
  1865. selectionContext.focusIsLeavingContainer = true;
  1866. }
  1867. };
  1868. };
  1869. var containerFocusHandler = function(selectionContext) {
  1870. return function(evt) {
  1871. var shouldOrig = selectionContext.options.autoSelectFirstItem;
  1872. var shouldSelect = typeof(shouldOrig) === "function" ?
  1873. shouldOrig() : shouldOrig;
  1874. // Override the autoselection if we're on the way out of the container.
  1875. if (selectionContext.focusIsLeavingContainer) {
  1876. shouldSelect = false;
  1877. }
  1878. // This target check works around the fact that sometimes focus bubbles, even though it shouldn't.
  1879. if (shouldSelect && evt.target === selectionContext.container.get(0)) {
  1880. if (selectionContext.activeItemIndex === NO_SELECTION) {
  1881. selectionContext.activeItemIndex = 0;
  1882. }
  1883. $(selectionContext.selectables[selectionContext.activeItemIndex]).focus();
  1884. }
  1885. // Force focus not to bubble on some browsers.
  1886. return evt.stopPropagation();
  1887. };
  1888. };
  1889. var containerBlurHandler = function(selectionContext) {
  1890. return function(evt) {
  1891. selectionContext.focusIsLeavingContainer = false;
  1892. // Force blur not to bubble on some browsers.
  1893. return evt.stopPropagation();
  1894. };
  1895. };
  1896. var makeElementsSelectable = function(container, defaults, userOptions) {
  1897. var options = $.extend(true, {}, defaults, userOptions);
  1898. var keyMap = getKeyMapForDirection(options.direction);
  1899. var selectableElements = options.selectableElements? options.selectableElements :
  1900. container.find(options.selectableSelector);
  1901. // Context stores the currently active item(undefined to start) and list of selectables.
  1902. var that = {
  1903. container: container,
  1904. activeItemIndex: NO_SELECTION,
  1905. selectables: selectableElements,
  1906. focusIsLeavingContainer: false,
  1907. options: options
  1908. };
  1909. that.selectablesUpdated = function(focusedItem) {
  1910. // Remove selectables from the tab order and add focus/blur handlers
  1911. if (typeof(that.options.selectablesTabindex) === "number") {
  1912. that.selectables.fluid("tabindex", that.options.selectablesTabindex);
  1913. }
  1914. that.selectables.unbind("focus." + NAMESPACE_KEY);
  1915. that.selectables.unbind("blur." + NAMESPACE_KEY);
  1916. that.selectables.bind("focus."+ NAMESPACE_KEY, selectableFocusHandler(that));
  1917. that.selectables.bind("blur." + NAMESPACE_KEY, selectableBlurHandler(that));
  1918. if (focusedItem) {
  1919. selectElement(focusedItem, that);
  1920. }
  1921. else {
  1922. reifyIndex(that);
  1923. }
  1924. };
  1925. that.refresh = function() {
  1926. if (!that.options.selectableSelector) {
  1927. throw("Cannot refresh selectable context which was not initialised by a selector");
  1928. }
  1929. that.selectables = container.find(options.selectableSelector);
  1930. that.selectablesUpdated();
  1931. };
  1932. that.selectedElement = function() {
  1933. return that.activeItemIndex < 0? null : that.selectables[that.activeItemIndex];
  1934. };
  1935. // Add various handlers to the container.
  1936. if (keyMap) {
  1937. container.keydown(arrowKeyHandler(that, keyMap));
  1938. }
  1939. container.keydown(tabKeyHandler(that));
  1940. container.focus(containerFocusHandler(that));
  1941. container.blur(containerBlurHandler(that));
  1942. that.selectablesUpdated();
  1943. return that;
  1944. };
  1945. /**
  1946. * Makes all matched elements selectable with the arrow keys.
  1947. * Supply your own handlers object with onSelect: and onUnselect: properties for custom behaviour.
  1948. * Options provide configurability, including direction: and autoSelectFirstItem:
  1949. * Currently supported directions are jQuery.a11y.directions.HORIZONTAL and VERTICAL.
  1950. */
  1951. fluid.selectable = function(target, options) {
  1952. target = $(target);
  1953. var that = makeElementsSelectable(target, fluid.selectable.defaults, options);
  1954. setData(target, CONTEXT_KEY, that);
  1955. return that;
  1956. };
  1957. /**
  1958. * Selects the specified element.
  1959. */
  1960. fluid.selectable.select = function(target, toSelect) {
  1961. $(toSelect).focus();
  1962. };
  1963. /**
  1964. * Selects the next matched element.
  1965. */
  1966. fluid.selectable.selectNext = function(target) {
  1967. target = $(target);
  1968. focusNextElement(getData(target, CONTEXT_KEY));
  1969. };
  1970. /**
  1971. * Selects the previous matched element.
  1972. */
  1973. fluid.selectable.selectPrevious = function(target) {
  1974. target = $(target);
  1975. focusPreviousElement(getData(target, CONTEXT_KEY));
  1976. };
  1977. /**
  1978. * Returns the currently selected item wrapped as a jQuery object.
  1979. */
  1980. fluid.selectable.currentSelection = function(target) {
  1981. target = $(target);
  1982. var that = getData(target, CONTEXT_KEY);
  1983. return $(that.selectedElement());
  1984. };
  1985. fluid.selectable.defaults = {
  1986. direction: fluid.a11y.orientation.VERTICAL,
  1987. selectablesTabindex: -1,
  1988. autoSelectFirstItem: true,
  1989. rememberSelectionState: true,
  1990. selectableSelector: ".selectable",
  1991. selectableElements: null,
  1992. onSelect: null,
  1993. onUnselect: null,
  1994. onLeaveContainer: null
  1995. };
  1996. /********************************************************************
  1997. * Activation functionality - declaratively associating actions with
  1998. * a set of keyboard bindings.
  1999. */
  2000. var checkForModifier = function(binding, evt) {
  2001. // If no modifier was specified, just return true.
  2002. if (!binding.modifier) {
  2003. return true;
  2004. }
  2005. var modifierKey = binding.modifier;
  2006. var isCtrlKeyPresent = modifierKey && evt.ctrlKey;
  2007. var isAltKeyPresent = modifierKey && evt.altKey;
  2008. var isShiftKeyPresent = modifierKey && evt.shiftKey;
  2009. return isCtrlKeyPresent || isAltKeyPresent || isShiftKeyPresent;
  2010. };
  2011. /** Constructs a raw "keydown"-facing handler, given a binding entry. This
  2012. * checks whether the key event genuinely triggers the event and forwards it
  2013. * to any "activateHandler" registered in the binding.
  2014. */
  2015. var makeActivationHandler = function(binding) {
  2016. return function(evt) {
  2017. var target = evt.target;
  2018. if (!fluid.enabled(evt.target)) {
  2019. return;
  2020. }
  2021. // The following 'if' clause works in the real world, but there's a bug in the jQuery simulation
  2022. // that causes keyboard simulation to fail in Safari, causing our tests to fail:
  2023. // http://ui.jquery.com/bugs/ticket/3229
  2024. // The replacement 'if' clause works around this bug.
  2025. // When this issue is resolved, we should revert to the original clause.
  2026. // if (evt.which === binding.key && binding.activateHandler && checkForModifier(binding, evt)) {
  2027. var code = evt.which? evt.which : evt.keyCode;
  2028. if (code === binding.key && binding.activateHandler && checkForModifier(binding, evt)) {
  2029. var event = $.Event("fluid-activate");
  2030. $(evt.target).trigger(event, [binding.activateHandler]);
  2031. if (event.isDefaultPrevented()) {
  2032. evt.preventDefault();
  2033. }
  2034. }
  2035. };
  2036. };
  2037. var makeElementsActivatable = function(elements, onActivateHandler, defaultKeys, options) {
  2038. // Create bindings for each default key.
  2039. var bindings = [];
  2040. $(defaultKeys).each(function(index, key) {
  2041. bindings.push({
  2042. modifier: null,
  2043. key: key,
  2044. activateHandler: onActivateHandler
  2045. });
  2046. });
  2047. // Merge with any additional key bindings.
  2048. if (options && options.additionalBindings) {
  2049. bindings = bindings.concat(options.additionalBindings);
  2050. }
  2051. setData(elements, ENABLEMENT_KEY, true);
  2052. // Add listeners for each key binding.
  2053. for (var i = 0; i < bindings.length; ++ i) {
  2054. var binding = bindings[i];
  2055. elements.keydown(makeActivationHandler(binding));
  2056. }
  2057. elements.bind("fluid-activate", function(evt, handler) {
  2058. handler = handler || onActivateHandler;
  2059. return handler? handler(evt): null;
  2060. });
  2061. };
  2062. /**
  2063. * Makes all matched elements activatable with the Space and Enter keys.
  2064. * Provide your own handler function for custom behaviour.
  2065. * Options allow you to provide a list of additionalActivationKeys.
  2066. */
  2067. fluid.activatable = function(target, fn, options) {
  2068. target = $(target);
  2069. makeElementsActivatable(target, fn, fluid.activatable.defaults.keys, options);
  2070. };
  2071. /**
  2072. * Activates the specified element.
  2073. */
  2074. fluid.activate = function(target) {
  2075. $(target).trigger("fluid-activate");
  2076. };
  2077. // Public Defaults.
  2078. fluid.activatable.defaults = {
  2079. keys: [$.ui.keyCode.ENTER, $.ui.keyCode.SPACE]
  2080. };
  2081. })(jQuery, fluid_1_2);
  2082. /*! SWFObject v2.1 <http://code.google.com/p/swfobject/>
  2083. Copyright (c) 2007-2008 Geoff Stearns, Michael Williams, and Bobby van der Sluis
  2084. This software is released under the MIT License <http://www.opensource.org/licenses/mit-license.php>
  2085. */
  2086. var swfobject = function() {
  2087. var UNDEF = "undefined",
  2088. OBJECT = "object",
  2089. SHOCKWAVE_FLASH = "Shockwave Flash",
  2090. SHOCKWAVE_FLASH_AX = "ShockwaveFlash.ShockwaveFlash",
  2091. FLASH_MIME_TYPE = "application/x-shockwave-flash",
  2092. EXPRESS_INSTALL_ID = "SWFObjectExprInst",
  2093. win = window,
  2094. doc = document,
  2095. nav = navigator,
  2096. domLoadFnArr = [],
  2097. regObjArr = [],
  2098. objIdArr = [],
  2099. listenersArr = [],
  2100. script,
  2101. timer = null,
  2102. storedAltContent = null,
  2103. storedAltContentId = null,
  2104. isDomLoaded = false,
  2105. isExpressInstallActive = false;
  2106. /* Centralized function for browser feature detection
  2107. - Proprietary feature detection (conditional compiling) is used to detect Internet Explorer's features
  2108. - User agent string detection is only used when no alternative is possible
  2109. - Is executed directly for optimal performance
  2110. */
  2111. var ua = function() {
  2112. var w3cdom = typeof doc.getElementById != UNDEF && typeof doc.getElementsByTagName != UNDEF && typeof doc.createElement != UNDEF,
  2113. playerVersion = [0,0,0],
  2114. d = null;
  2115. if (typeof nav.plugins != UNDEF && typeof nav.plugins[SHOCKWAVE_FLASH] == OBJECT) {
  2116. d = nav.plugins[SHOCKWAVE_FLASH].description;
  2117. if (d && !(typeof nav.mimeTypes != UNDEF && nav.mimeTypes[FLASH_MIME_TYPE] && !nav.mimeTypes[FLASH_MIME_TYPE].enabledPlugin)) { // navigator.mimeTypes["application/x-shockwave-flash"].enabledPlugin indicates whether plug-ins are enabled or disabled in Safari 3+
  2118. d = d.replace(/^.*\s+(\S+\s+\S+$)/, "$1");
  2119. playerVersion[0] = parseInt(d.replace(/^(.*)\..*$/, "$1"), 10);
  2120. playerVersion[1] = parseInt(d.replace(/^.*\.(.*)\s.*$/, "$1"), 10);
  2121. playerVersion[2] = /r/.test(d) ? parseInt(d.replace(/^.*r(.*)$/, "$1"), 10) : 0;
  2122. }
  2123. }
  2124. else if (typeof win.ActiveXObject != UNDEF) {
  2125. var a = null, fp6Crash = false;
  2126. try {
  2127. a = new ActiveXObject(SHOCKWAVE_FLASH_AX + ".7");
  2128. }
  2129. catch(e) {
  2130. try {
  2131. a = new ActiveXObject(SHOCKWAVE_FLASH_AX + ".6");
  2132. playerVersion = [6,0,21];
  2133. a.AllowScriptAccess = "always"; // Introduced in fp6.0.47
  2134. }
  2135. catch(e) {
  2136. if (playerVersion[0] == 6) {
  2137. fp6Crash = true;
  2138. }
  2139. }
  2140. if (!fp6Crash) {
  2141. try {
  2142. a = new ActiveXObject(SHOCKWAVE_FLASH_AX);
  2143. }
  2144. catch(e) {}
  2145. }
  2146. }
  2147. if (!fp6Crash && a) { // a will return null when ActiveX is disabled
  2148. try {
  2149. d = a.GetVariable("$version"); // Will crash fp6.0.21/23/29
  2150. if (d) {
  2151. d = d.split(" ")[1].split(",");
  2152. playerVersion = [parseInt(d[0], 10), parseInt(d[1], 10), parseInt(d[2], 10)];
  2153. }
  2154. }
  2155. catch(e) {}
  2156. }
  2157. }
  2158. var u = nav.userAgent.toLowerCase(),
  2159. p = nav.platform.toLowerCase(),
  2160. webkit = /webkit/.test(u) ? parseFloat(u.replace(/^.*webkit\/(\d+(\.\d+)?).*$/, "$1")) : false, // returns either the webkit version or false if not webkit
  2161. ie = false,
  2162. windows = p ? /win/.test(p) : /win/.test(u),
  2163. mac = p ? /mac/.test(p) : /mac/.test(u);
  2164. /*@cc_on
  2165. ie = true;
  2166. @if (@_win32)
  2167. windows = true;
  2168. @elif (@_mac)
  2169. mac = true;
  2170. @end
  2171. @*/
  2172. return { w3cdom:w3cdom, pv:playerVersion, webkit:webkit, ie:ie, win:windows, mac:mac };
  2173. }();
  2174. /* Cross-browser onDomLoad
  2175. - Based on Dean Edwards' solution: http://dean.edwards.name/weblog/2006/06/again/
  2176. - Will fire an event as soon as the DOM of a page is loaded (supported by Gecko based browsers - like Firefox -, IE, Opera9+, Safari)
  2177. */
  2178. var onDomLoad = function() {
  2179. if (!ua.w3cdom) {
  2180. return;
  2181. }
  2182. addDomLoadEvent(main);
  2183. if (ua.ie && ua.win) {
  2184. try { // Avoid a possible Operation Aborted error
  2185. doc.write("<scr" + "ipt id=__ie_ondomload defer=true src=//:></scr" + "ipt>"); // String is split into pieces to avoid Norton AV to add code that can cause errors
  2186. script = getElementById("__ie_ondomload");
  2187. if (script) {
  2188. addListener(script, "onreadystatechange", checkReadyState);
  2189. }
  2190. }
  2191. catch(e) {}
  2192. }
  2193. if (ua.webkit && typeof doc.readyState != UNDEF) {
  2194. timer = setInterval(function() { if (/loaded|complete/.test(doc.readyState)) { callDomLoadFunctions(); }}, 10);
  2195. }
  2196. if (typeof doc.addEventListener != UNDEF) {
  2197. doc.addEventListener("DOMContentLoaded", callDomLoadFunctions, null);
  2198. }
  2199. addLoadEvent(callDomLoadFunctions);
  2200. }();
  2201. function checkReadyState() {
  2202. if (script.readyState == "complete") {
  2203. script.parentNode.removeChild(script);
  2204. callDomLoadFunctions();
  2205. }
  2206. }
  2207. function callDomLoadFunctions() {
  2208. if (isDomLoaded) {
  2209. return;
  2210. }
  2211. if (ua.ie && ua.win) { // Test if we can really add elements to the DOM; we don't want to fire it too early
  2212. var s = createElement("span");
  2213. try { // Avoid a possible Operation Aborted error
  2214. var t = doc.getElementsByTagName("body")[0].appendChild(s);
  2215. t.parentNode.removeChild(t);
  2216. }
  2217. catch (e) {
  2218. return;
  2219. }
  2220. }
  2221. isDomLoaded = true;
  2222. if (timer) {
  2223. clearInterval(timer);
  2224. timer = null;
  2225. }
  2226. var dl = domLoadFnArr.length;
  2227. for (var i = 0; i < dl; i++) {
  2228. domLoadFnArr[i]();
  2229. }
  2230. }
  2231. function addDomLoadEvent(fn) {
  2232. if (isDomLoaded) {
  2233. fn();
  2234. }
  2235. else {
  2236. domLoadFnArr[domLoadFnArr.length] = fn; // Array.push() is only available in IE5.5+
  2237. }
  2238. }
  2239. /* Cross-browser onload
  2240. - Based on James Edwards' solution: http://brothercake.com/site/resources/scripts/onload/
  2241. - Will fire an event as soon as a web page including all of its assets are loaded
  2242. */
  2243. function addLoadEvent(fn) {
  2244. if (typeof win.addEventListener != UNDEF) {
  2245. win.addEventListener("load", fn, false);
  2246. }
  2247. else if (typeof doc.addEventListener != UNDEF) {
  2248. doc.addEventListener("load", fn, false);
  2249. }
  2250. else if (typeof win.attachEvent != UNDEF) {
  2251. addListener(win, "onload", fn);
  2252. }
  2253. else if (typeof win.onload == "function") {
  2254. var fnOld = win.onload;
  2255. win.onload = function() {
  2256. fnOld();
  2257. fn();
  2258. };
  2259. }
  2260. else {
  2261. win.onload = fn;
  2262. }
  2263. }
  2264. /* Main function
  2265. - Will preferably execute onDomLoad, otherwise onload (as a fallback)
  2266. */
  2267. function main() { // Static publishing only
  2268. var rl = regObjArr.length;
  2269. for (var i = 0; i < rl; i++) { // For each registered object element
  2270. var id = regObjArr[i].id;
  2271. if (ua.pv[0] > 0) {
  2272. var obj = getElementById(id);
  2273. if (obj) {
  2274. regObjArr[i].width = obj.getAttribute("width") ? obj.getAttribute("width") : "0";
  2275. regObjArr[i].height = obj.getAttribute("height") ? obj.getAttribute("height") : "0";
  2276. if (hasPlayerVersion(regObjArr[i].swfVersion)) { // Flash plug-in version >= Flash content version: Houston, we have a match!
  2277. if (ua.webkit && ua.webkit < 312) { // Older webkit engines ignore the object element's nested param elements
  2278. fixParams(obj);
  2279. }
  2280. setVisibility(id, true);
  2281. }
  2282. else if (regObjArr[i].expressInstall && !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac)) { // Show the Adobe Express Install dialog if set by the web page author and if supported (fp6.0.65+ on Win/Mac OS only)
  2283. showExpressInstall(regObjArr[i]);
  2284. }
  2285. else { // Flash plug-in and Flash content version mismatch: display alternative content instead of Flash content
  2286. displayAltContent(obj);
  2287. }
  2288. }
  2289. }
  2290. else { // If no fp is installed, we let the object element do its job (show alternative content)
  2291. setVisibility(id, true);
  2292. }
  2293. }
  2294. }
  2295. /* Fix nested param elements, which are ignored by older webkit engines
  2296. - This includes Safari up to and including version 1.2.2 on Mac OS 10.3
  2297. - Fall back to the proprietary embed element
  2298. */
  2299. function fixParams(obj) {
  2300. var nestedObj = obj.getElementsByTagName(OBJECT)[0];
  2301. if (nestedObj) {
  2302. var e = createElement("embed"), a = nestedObj.attributes;
  2303. if (a) {
  2304. var al = a.length;
  2305. for (var i = 0; i < al; i++) {
  2306. if (a[i].nodeName == "DATA") {
  2307. e.setAttribute("src", a[i].nodeValue);
  2308. }
  2309. else {
  2310. e.setAttribute(a[i].nodeName, a[i].nodeValue);
  2311. }
  2312. }
  2313. }
  2314. var c = nestedObj.childNodes;
  2315. if (c) {
  2316. var cl = c.length;
  2317. for (var j = 0; j < cl; j++) {
  2318. if (c[j].nodeType == 1 && c[j].nodeName == "PARAM") {
  2319. e.setAttribute(c[j].getAttribute("name"), c[j].getAttribute("value"));
  2320. }
  2321. }
  2322. }
  2323. obj.parentNode.replaceChild(e, obj);
  2324. }
  2325. }
  2326. /* Show the Adobe Express Install dialog
  2327. - Reference: http://www.adobe.com/cfusion/knowledgebase/index.cfm?id=6a253b75
  2328. */
  2329. function showExpressInstall(regObj) {
  2330. isExpressInstallActive = true;
  2331. var obj = getElementById(regObj.id);
  2332. if (obj) {
  2333. if (regObj.altContentId) {
  2334. var ac = getElementById(regObj.altContentId);
  2335. if (ac) {
  2336. storedAltContent = ac;
  2337. storedAltContentId = regObj.altContentId;
  2338. }
  2339. }
  2340. else {
  2341. storedAltContent = abstractAltContent(obj);
  2342. }
  2343. if (!(/%$/.test(regObj.width)) && parseInt(regObj.width, 10) < 310) {
  2344. regObj.width = "310";
  2345. }
  2346. if (!(/%$/.test(regObj.height)) && parseInt(regObj.height, 10) < 137) {
  2347. regObj.height = "137";
  2348. }
  2349. doc.title = doc.title.slice(0, 47) + " - Flash Player Installation";
  2350. var pt = ua.ie && ua.win ? "ActiveX" : "PlugIn",
  2351. dt = doc.title,
  2352. fv = "MMredirectURL=" + win.location + "&MMplayerType=" + pt + "&MMdoctitle=" + dt,
  2353. replaceId = regObj.id;
  2354. // For IE when a SWF is loading (AND: not available in cache) wait for the onload event to fire to remove the original object element
  2355. // In IE you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
  2356. if (ua.ie && ua.win && obj.readyState != 4) {
  2357. var newObj = createElement("div");
  2358. replaceId += "SWFObjectNew";
  2359. newObj.setAttribute("id", replaceId);
  2360. obj.parentNode.insertBefore(newObj, obj); // Insert placeholder div that will be replaced by the object element that loads expressinstall.swf
  2361. obj.style.display = "none";
  2362. var fn = function() {
  2363. obj.parentNode.removeChild(obj);
  2364. };
  2365. addListener(win, "onload", fn);
  2366. }
  2367. createSWF({ data:regObj.expressInstall, id:EXPRESS_INSTALL_ID, width:regObj.width, height:regObj.height }, { flashvars:fv }, replaceId);
  2368. }
  2369. }
  2370. /* Functions to abstract and display alternative content
  2371. */
  2372. function displayAltContent(obj) {
  2373. if (ua.ie && ua.win && obj.readyState != 4) {
  2374. // For IE when a SWF is loading (AND: not available in cache) wait for the onload event to fire to remove the original object element
  2375. // In IE you cannot properly cancel a loading SWF file without breaking browser load references, also obj.onreadystatechange doesn't work
  2376. var el = createElement("div");
  2377. obj.parentNode.insertBefore(el, obj); // Insert placeholder div that will be replaced by the alternative content
  2378. el.parentNode.replaceChild(abstractAltContent(obj), el);
  2379. obj.style.display = "none";
  2380. var fn = function() {
  2381. obj.parentNode.removeChild(obj);
  2382. };
  2383. addListener(win, "onload", fn);
  2384. }
  2385. else {
  2386. obj.parentNode.replaceChild(abstractAltContent(obj), obj);
  2387. }
  2388. }
  2389. function abstractAltContent(obj) {
  2390. var ac = createElement("div");
  2391. if (ua.win && ua.ie) {
  2392. ac.innerHTML = obj.innerHTML;
  2393. }
  2394. else {
  2395. var nestedObj = obj.getElementsByTagName(OBJECT)[0];
  2396. if (nestedObj) {
  2397. var c = nestedObj.childNodes;
  2398. if (c) {
  2399. var cl = c.length;
  2400. for (var i = 0; i < cl; i++) {
  2401. if (!(c[i].nodeType == 1 && c[i].nodeName == "PARAM") && !(c[i].nodeType == 8)) {
  2402. ac.appendChild(c[i].cloneNode(true));
  2403. }
  2404. }
  2405. }
  2406. }
  2407. }
  2408. return ac;
  2409. }
  2410. /* Cross-browser dynamic SWF creation
  2411. */
  2412. function createSWF(attObj, parObj, id) {
  2413. var r, el = getElementById(id);
  2414. if (el) {
  2415. if (typeof attObj.id == UNDEF) { // if no 'id' is defined for the object element, it will inherit the 'id' from the alternative content
  2416. attObj.id = id;
  2417. }
  2418. if (ua.ie && ua.win) { // IE, the object element and W3C DOM methods do not combine: fall back to outerHTML
  2419. var att = "";
  2420. for (var i in attObj) {
  2421. if (attObj[i] != Object.prototype[i]) { // Filter out prototype additions from other potential libraries, like Object.prototype.toJSONString = function() {}
  2422. if (i.toLowerCase() == "data") {
  2423. parObj.movie = attObj[i];
  2424. }
  2425. else if (i.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
  2426. att += ' class="' + attObj[i] + '"';
  2427. }
  2428. else if (i.toLowerCase() != "classid") {
  2429. att += ' ' + i + '="' + attObj[i] + '"';
  2430. }
  2431. }
  2432. }
  2433. var par = "";
  2434. for (var j in parObj) {
  2435. if (parObj[j] != Object.prototype[j]) { // Filter out prototype additions from other potential libraries
  2436. par += '<param name="' + j + '" value="' + parObj[j] + '" />';
  2437. }
  2438. }
  2439. el.outerHTML = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"' + att + '>' + par + '</object>';
  2440. objIdArr[objIdArr.length] = attObj.id; // Stored to fix object 'leaks' on unload (dynamic publishing only)
  2441. r = getElementById(attObj.id);
  2442. }
  2443. else if (ua.webkit && ua.webkit < 312) { // Older webkit engines ignore the object element's nested param elements: fall back to the proprietary embed element
  2444. var e = createElement("embed");
  2445. e.setAttribute("type", FLASH_MIME_TYPE);
  2446. for (var k in attObj) {
  2447. if (attObj[k] != Object.prototype[k]) { // Filter out prototype additions from other potential libraries
  2448. if (k.toLowerCase() == "data") {
  2449. e.setAttribute("src", attObj[k]);
  2450. }
  2451. else if (k.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
  2452. e.setAttribute("class", attObj[k]);
  2453. }
  2454. else if (k.toLowerCase() != "classid") { // Filter out IE specific attribute
  2455. e.setAttribute(k, attObj[k]);
  2456. }
  2457. }
  2458. }
  2459. for (var l in parObj) {
  2460. if (parObj[l] != Object.prototype[l]) { // Filter out prototype additions from other potential libraries
  2461. if (l.toLowerCase() != "movie") { // Filter out IE specific param element
  2462. e.setAttribute(l, parObj[l]);
  2463. }
  2464. }
  2465. }
  2466. el.parentNode.replaceChild(e, el);
  2467. r = e;
  2468. }
  2469. else { // Well-behaving browsers
  2470. var o = createElement(OBJECT);
  2471. o.setAttribute("type", FLASH_MIME_TYPE);
  2472. for (var m in attObj) {
  2473. if (attObj[m] != Object.prototype[m]) { // Filter out prototype additions from other potential libraries
  2474. if (m.toLowerCase() == "styleclass") { // 'class' is an ECMA4 reserved keyword
  2475. o.setAttribute("class", attObj[m]);
  2476. }
  2477. else if (m.toLowerCase() != "classid") { // Filter out IE specific attribute
  2478. o.setAttribute(m, attObj[m]);
  2479. }
  2480. }
  2481. }
  2482. for (var n in parObj) {
  2483. if (parObj[n] != Object.prototype[n] && n.toLowerCase() != "movie") { // Filter out prototype additions from other potential libraries and IE specific param element
  2484. createObjParam(o, n, parObj[n]);
  2485. }
  2486. }
  2487. el.parentNode.replaceChild(o, el);
  2488. r = o;
  2489. }
  2490. }
  2491. return r;
  2492. }
  2493. function createObjParam(el, pName, pValue) {
  2494. var p = createElement("param");
  2495. p.setAttribute("name", pName);
  2496. p.setAttribute("value", pValue);
  2497. el.appendChild(p);
  2498. }
  2499. /* Cross-browser SWF removal
  2500. - Especially needed to safely and completely remove a SWF in Internet Explorer
  2501. */
  2502. function removeSWF(id) {
  2503. var obj = getElementById(id);
  2504. if (obj && (obj.nodeName == "OBJECT" || obj.nodeName == "EMBED")) {
  2505. if (ua.ie && ua.win) {
  2506. if (obj.readyState == 4) {
  2507. removeObjectInIE(id);
  2508. }
  2509. else {
  2510. win.attachEvent("onload", function() {
  2511. removeObjectInIE(id);
  2512. });
  2513. }
  2514. }
  2515. else {
  2516. obj.parentNode.removeChild(obj);
  2517. }
  2518. }
  2519. }
  2520. function removeObjectInIE(id) {
  2521. var obj = getElementById(id);
  2522. if (obj) {
  2523. for (var i in obj) {
  2524. if (typeof obj[i] == "function") {
  2525. obj[i] = null;
  2526. }
  2527. }
  2528. obj.parentNode.removeChild(obj);
  2529. }
  2530. }
  2531. /* Functions to optimize JavaScript compression
  2532. */
  2533. function getElementById(id) {
  2534. var el = null;
  2535. try {
  2536. el = doc.getElementById(id);
  2537. }
  2538. catch (e) {}
  2539. return el;
  2540. }
  2541. function createElement(el) {
  2542. return doc.createElement(el);
  2543. }
  2544. /* Updated attachEvent function for Internet Explorer
  2545. - Stores attachEvent information in an Array, so on unload the detachEvent functions can be called to avoid memory leaks
  2546. */
  2547. function addListener(target, eventType, fn) {
  2548. target.attachEvent(eventType, fn);
  2549. listenersArr[listenersArr.length] = [target, eventType, fn];
  2550. }
  2551. /* Flash Player and SWF content version matching
  2552. */
  2553. function hasPlayerVersion(rv) {
  2554. var pv = ua.pv, v = rv.split(".");
  2555. v[0] = parseInt(v[0], 10);
  2556. v[1] = parseInt(v[1], 10) || 0; // supports short notation, e.g. "9" instead of "9.0.0"
  2557. v[2] = parseInt(v[2], 10) || 0;
  2558. return (pv[0] > v[0] || (pv[0] == v[0] && pv[1] > v[1]) || (pv[0] == v[0] && pv[1] == v[1] && pv[2] >= v[2])) ? true : false;
  2559. }
  2560. /* Cross-browser dynamic CSS creation
  2561. - Based on Bobby van der Sluis' solution: http://www.bobbyvandersluis.com/articles/dynamicCSS.php
  2562. */
  2563. function createCSS(sel, decl) {
  2564. if (ua.ie && ua.mac) {
  2565. return;
  2566. }
  2567. var h = doc.getElementsByTagName("head")[0], s = createElement("style");
  2568. s.setAttribute("type", "text/css");
  2569. s.setAttribute("media", "screen");
  2570. if (!(ua.ie && ua.win) && typeof doc.createTextNode != UNDEF) {
  2571. s.appendChild(doc.createTextNode(sel + " {" + decl + "}"));
  2572. }
  2573. h.appendChild(s);
  2574. if (ua.ie && ua.win && typeof doc.styleSheets != UNDEF && doc.styleSheets.length > 0) {
  2575. var ls = doc.styleSheets[doc.styleSheets.length - 1];
  2576. if (typeof ls.addRule == OBJECT) {
  2577. ls.addRule(sel, decl);
  2578. }
  2579. }
  2580. }
  2581. function setVisibility(id, isVisible) {
  2582. var v = isVisible ? "visible" : "hidden";
  2583. if (isDomLoaded && getElementById(id)) {
  2584. getElementById(id).style.visibility = v;
  2585. }
  2586. else {
  2587. createCSS("#" + id, "visibility:" + v);
  2588. }
  2589. }
  2590. /* Filter to avoid XSS attacks
  2591. */
  2592. function urlEncodeIfNecessary(s) {
  2593. var regex = /[\\\"<>\.;]/;
  2594. var hasBadChars = regex.exec(s) != null;
  2595. return hasBadChars ? encodeURIComponent(s) : s;
  2596. }
  2597. /* Release memory to avoid memory leaks caused by closures, fix hanging audio/video threads and force open sockets/NetConnections to disconnect (Internet Explorer only)
  2598. */
  2599. var cleanup = function() {
  2600. if (ua.ie && ua.win) {
  2601. window.attachEvent("onunload", function() {
  2602. // remove listeners to avoid memory leaks
  2603. var ll = listenersArr.length;
  2604. for (var i = 0; i < ll; i++) {
  2605. listenersArr[i][0].detachEvent(listenersArr[i][1], listenersArr[i][2]);
  2606. }
  2607. // cleanup dynamically embedded objects to fix audio/video threads and force open sockets and NetConnections to disconnect
  2608. var il = objIdArr.length;
  2609. for (var j = 0; j < il; j++) {
  2610. removeSWF(objIdArr[j]);
  2611. }
  2612. // cleanup library's main closures to avoid memory leaks
  2613. for (var k in ua) {
  2614. ua[k] = null;
  2615. }
  2616. ua = null;
  2617. for (var l in swfobject) {
  2618. swfobject[l] = null;
  2619. }
  2620. swfobject = null;
  2621. });
  2622. }
  2623. }();
  2624. return {
  2625. /* Public API
  2626. - Reference: http://code.google.com/p/swfobject/wiki/SWFObject_2_0_documentation
  2627. */
  2628. registerObject: function(objectIdStr, swfVersionStr, xiSwfUrlStr) {
  2629. if (!ua.w3cdom || !objectIdStr || !swfVersionStr) {
  2630. return;
  2631. }
  2632. var regObj = {};
  2633. regObj.id = objectIdStr;
  2634. regObj.swfVersion = swfVersionStr;
  2635. regObj.expressInstall = xiSwfUrlStr ? xiSwfUrlStr : false;
  2636. regObjArr[regObjArr.length] = regObj;
  2637. setVisibility(objectIdStr, false);
  2638. },
  2639. getObjectById: function(objectIdStr) {
  2640. var r = null;
  2641. if (ua.w3cdom) {
  2642. var o = getElementById(objectIdStr);
  2643. if (o) {
  2644. var n = o.getElementsByTagName(OBJECT)[0];
  2645. if (!n || (n && typeof o.SetVariable != UNDEF)) {
  2646. r = o;
  2647. }
  2648. else if (typeof n.SetVariable != UNDEF) {
  2649. r = n;
  2650. }
  2651. }
  2652. }
  2653. return r;
  2654. },
  2655. embedSWF: function(swfUrlStr, replaceElemIdStr, widthStr, heightStr, swfVersionStr, xiSwfUrlStr, flashvarsObj, parObj, attObj) {
  2656. if (!ua.w3cdom || !swfUrlStr || !replaceElemIdStr || !widthStr || !heightStr || !swfVersionStr) {
  2657. return;
  2658. }
  2659. widthStr += ""; // Auto-convert to string
  2660. heightStr += "";
  2661. if (hasPlayerVersion(swfVersionStr)) {
  2662. setVisibility(replaceElemIdStr, false);
  2663. var att = {};
  2664. if (attObj && typeof attObj === OBJECT) {
  2665. for (var i in attObj) {
  2666. if (attObj[i] != Object.prototype[i]) { // Filter out prototype additions from other potential libraries
  2667. att[i] = attObj[i];
  2668. }
  2669. }
  2670. }
  2671. att.data = swfUrlStr;
  2672. att.width = widthStr;
  2673. att.height = heightStr;
  2674. var par = {};
  2675. if (parObj && typeof parObj === OBJECT) {
  2676. for (var j in parObj) {
  2677. if (parObj[j] != Object.prototype[j]) { // Filter out prototype additions from other potential libraries
  2678. par[j] = parObj[j];
  2679. }
  2680. }
  2681. }
  2682. if (flashvarsObj && typeof flashvarsObj === OBJECT) {
  2683. for (var k in flashvarsObj) {
  2684. if (flashvarsObj[k] != Object.prototype[k]) { // Filter out prototype additions from other potential libraries
  2685. if (typeof par.flashvars != UNDEF) {
  2686. par.flashvars += "&" + k + "=" + flashvarsObj[k];
  2687. }
  2688. else {
  2689. par.flashvars = k + "=" + flashvarsObj[k];
  2690. }
  2691. }
  2692. }
  2693. }
  2694. addDomLoadEvent(function() {
  2695. createSWF(att, par, replaceElemIdStr);
  2696. if (att.id == replaceElemIdStr) {
  2697. setVisibility(replaceElemIdStr, true);
  2698. }
  2699. });
  2700. }
  2701. else if (xiSwfUrlStr && !isExpressInstallActive && hasPlayerVersion("6.0.65") && (ua.win || ua.mac)) {
  2702. isExpressInstallActive = true; // deferred execution
  2703. setVisibility(replaceElemIdStr, false);
  2704. addDomLoadEvent(function() {
  2705. var regObj = {};
  2706. regObj.id = regObj.altContentId = replaceElemIdStr;
  2707. regObj.width = widthStr;
  2708. regObj.height = heightStr;
  2709. regObj.expressInstall = xiSwfUrlStr;
  2710. showExpressInstall(regObj);
  2711. });
  2712. }
  2713. },
  2714. getFlashPlayerVersion: function() {
  2715. return { major:ua.pv[0], minor:ua.pv[1], release:ua.pv[2] };
  2716. },
  2717. hasFlashPlayerVersion: hasPlayerVersion,
  2718. createSWF: function(attObj, parObj, replaceElemIdStr) {
  2719. if (ua.w3cdom) {
  2720. return createSWF(attObj, parObj, replaceElemIdStr);
  2721. }
  2722. else {
  2723. return undefined;
  2724. }
  2725. },
  2726. removeSWF: function(objElemIdStr) {
  2727. if (ua.w3cdom) {
  2728. removeSWF(objElemIdStr);
  2729. }
  2730. },
  2731. createCSS: function(sel, decl) {
  2732. if (ua.w3cdom) {
  2733. createCSS(sel, decl);
  2734. }
  2735. },
  2736. addDomLoadEvent: addDomLoadEvent,
  2737. addLoadEvent: addLoadEvent,
  2738. getQueryParamValue: function(param) {
  2739. var q = doc.location.search || doc.location.hash;
  2740. if (param == null) {
  2741. return urlEncodeIfNecessary(q);
  2742. }
  2743. if (q) {
  2744. var pairs = q.substring(1).split("&");
  2745. for (var i = 0; i < pairs.length; i++) {
  2746. if (pairs[i].substring(0, pairs[i].indexOf("=")) == param) {
  2747. return urlEncodeIfNecessary(pairs[i].substring((pairs[i].indexOf("=") + 1)));
  2748. }
  2749. }
  2750. }
  2751. return "";
  2752. },
  2753. // For internal usage only
  2754. expressInstallCallback: function() {
  2755. if (isExpressInstallActive && storedAltContent) {
  2756. var obj = getElementById(EXPRESS_INSTALL_ID);
  2757. if (obj) {
  2758. obj.parentNode.replaceChild(storedAltContent, obj);
  2759. if (storedAltContentId) {
  2760. setVisibility(storedAltContentId, true);
  2761. if (ua.ie && ua.win) {
  2762. storedAltContent.style.display = "block";
  2763. }
  2764. }
  2765. storedAltContent = null;
  2766. storedAltContentId = null;
  2767. isExpressInstallActive = false;
  2768. }
  2769. }
  2770. }
  2771. };
  2772. }();
  2773. /**
  2774. * SWFUpload: http://www.swfupload.org, http://swfupload.googlecode.com
  2775. *
  2776. * mmSWFUpload 1.0: Flash upload dialog - http://profandesign.se/swfupload/, http://www.vinterwebb.se/
  2777. *
  2778. * SWFUpload is (c) 2006-2007 Lars Huring, Olov Nilz�n and Mammon Media and is released under the MIT License:
  2779. * http://www.opensource.org/licenses/mit-license.php
  2780. *
  2781. * SWFUpload 2 is (c) 2007-2008 Jake Roberts and is released under the MIT License:
  2782. * http://www.opensource.org/licenses/mit-license.php
  2783. *
  2784. */
  2785. /* ******************* */
  2786. /* Constructor & Init */
  2787. /* ******************* */
  2788. var SWFUpload;
  2789. if (SWFUpload == undefined) {
  2790. SWFUpload = function (settings) {
  2791. this.initSWFUpload(settings);
  2792. };
  2793. }
  2794. SWFUpload.prototype.initSWFUpload = function (settings) {
  2795. try {
  2796. this.customSettings = {}; // A container where developers can place their own settings associated with this instance.
  2797. this.settings = settings;
  2798. this.eventQueue = [];
  2799. this.movieName = "SWFUpload_" + SWFUpload.movieCount++;
  2800. this.movieElement = null;
  2801. // Setup global control tracking
  2802. SWFUpload.instances[this.movieName] = this;
  2803. // Load the settings. Load the Flash movie.
  2804. this.initSettings();
  2805. this.loadFlash();
  2806. this.displayDebugInfo();
  2807. } catch (ex) {
  2808. delete SWFUpload.instances[this.movieName];
  2809. throw ex;
  2810. }
  2811. };
  2812. /* *************** */
  2813. /* Static Members */
  2814. /* *************** */
  2815. SWFUpload.instances = {};
  2816. SWFUpload.movieCount = 0;
  2817. SWFUpload.version = "2.2.0 2009-03-25";
  2818. SWFUpload.QUEUE_ERROR = {
  2819. QUEUE_LIMIT_EXCEEDED : -100,
  2820. FILE_EXCEEDS_SIZE_LIMIT : -110,
  2821. ZERO_BYTE_FILE : -120,
  2822. INVALID_FILETYPE : -130
  2823. };
  2824. SWFUpload.UPLOAD_ERROR = {
  2825. HTTP_ERROR : -200,
  2826. MISSING_UPLOAD_URL : -210,
  2827. IO_ERROR : -220,
  2828. SECURITY_ERROR : -230,
  2829. UPLOAD_LIMIT_EXCEEDED : -240,
  2830. UPLOAD_FAILED : -250,
  2831. SPECIFIED_FILE_ID_NOT_FOUND : -260,
  2832. FILE_VALIDATION_FAILED : -270,
  2833. FILE_CANCELLED : -280,
  2834. UPLOAD_STOPPED : -290
  2835. };
  2836. SWFUpload.FILE_STATUS = {
  2837. QUEUED : -1,
  2838. IN_PROGRESS : -2,
  2839. ERROR : -3,
  2840. COMPLETE : -4,
  2841. CANCELLED : -5
  2842. };
  2843. SWFUpload.BUTTON_ACTION = {
  2844. SELECT_FILE : -100,
  2845. SELECT_FILES : -110,
  2846. START_UPLOAD : -120
  2847. };
  2848. SWFUpload.CURSOR = {
  2849. ARROW : -1,
  2850. HAND : -2
  2851. };
  2852. SWFUpload.WINDOW_MODE = {
  2853. WINDOW : "window",
  2854. TRANSPARENT : "transparent",
  2855. OPAQUE : "opaque"
  2856. };
  2857. // Private: takes a URL, determines if it is relative and converts to an absolute URL
  2858. // using the current site. Only processes the URL if it can, otherwise returns the URL untouched
  2859. SWFUpload.completeURL = function(url) {
  2860. if (typeof(url) !== "string" || url.match(/^https?:\/\//i) || url.match(/^\//)) {
  2861. return url;
  2862. }
  2863. var currentURL = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port : "");
  2864. var indexSlash = window.location.pathname.lastIndexOf("/");
  2865. if (indexSlash <= 0) {
  2866. path = "/";
  2867. } else {
  2868. path = window.location.pathname.substr(0, indexSlash) + "/";
  2869. }
  2870. return /*currentURL +*/ path + url;
  2871. };
  2872. /* ******************** */
  2873. /* Instance Members */
  2874. /* ******************** */
  2875. // Private: initSettings ensures that all the
  2876. // settings are set, getting a default value if one was not assigned.
  2877. SWFUpload.prototype.initSettings = function () {
  2878. this.ensureDefault = function (settingName, defaultValue) {
  2879. this.settings[settingName] = (this.settings[settingName] == undefined) ? defaultValue : this.settings[settingName];
  2880. };
  2881. // Upload backend settings
  2882. this.ensureDefault("upload_url", "");
  2883. this.ensureDefault("preserve_relative_urls", false);
  2884. this.ensureDefault("file_post_name", "Filedata");
  2885. this.ensureDefault("post_params", {});
  2886. this.ensureDefault("use_query_string", false);
  2887. this.ensureDefault("requeue_on_error", false);
  2888. this.ensureDefault("http_success", []);
  2889. this.ensureDefault("assume_success_timeout", 0);
  2890. // File Settings
  2891. this.ensureDefault("file_types", "*.*");
  2892. this.ensureDefault("file_types_description", "All Files");
  2893. this.ensureDefault("file_size_limit", 0); // Default zero means "unlimited"
  2894. this.ensureDefault("file_upload_limit", 0);
  2895. this.ensureDefault("file_queue_limit", 0);
  2896. // Flash Settings
  2897. this.ensureDefault("flash_url", "swfupload.swf");
  2898. this.ensureDefault("prevent_swf_caching", true);
  2899. // Button Settings
  2900. this.ensureDefault("button_image_url", "");
  2901. this.ensureDefault("button_width", 1);
  2902. this.ensureDefault("button_height", 1);
  2903. this.ensureDefault("button_text", "");
  2904. this.ensureDefault("button_text_style", "color: #000000; font-size: 16pt;");
  2905. this.ensureDefault("button_text_top_padding", 0);
  2906. this.ensureDefault("button_text_left_padding", 0);
  2907. this.ensureDefault("button_action", SWFUpload.BUTTON_ACTION.SELECT_FILES);
  2908. this.ensureDefault("button_disabled", false);
  2909. this.ensureDefault("button_placeholder_id", "");
  2910. this.ensureDefault("button_placeholder", null);
  2911. this.ensureDefault("button_cursor", SWFUpload.CURSOR.ARROW);
  2912. this.ensureDefault("button_window_mode", SWFUpload.WINDOW_MODE.WINDOW);
  2913. // Debug Settings
  2914. this.ensureDefault("debug", false);
  2915. this.settings.debug_enabled = this.settings.debug; // Here to maintain v2 API
  2916. // Event Handlers
  2917. this.settings.return_upload_start_handler = this.returnUploadStart;
  2918. this.ensureDefault("swfupload_loaded_handler", null);
  2919. this.ensureDefault("file_dialog_start_handler", null);
  2920. this.ensureDefault("file_queued_handler", null);
  2921. this.ensureDefault("file_queue_error_handler", null);
  2922. this.ensureDefault("file_dialog_complete_handler", null);
  2923. this.ensureDefault("upload_start_handler", null);
  2924. this.ensureDefault("upload_progress_handler", null);
  2925. this.ensureDefault("upload_error_handler", null);
  2926. this.ensureDefault("upload_success_handler", null);
  2927. this.ensureDefault("upload_complete_handler", null);
  2928. this.ensureDefault("debug_handler", this.debugMessage);
  2929. this.ensureDefault("custom_settings", {});
  2930. // Other settings
  2931. this.customSettings = this.settings.custom_settings;
  2932. // Update the flash url if needed
  2933. if (!!this.settings.prevent_swf_caching) {
  2934. this.settings.flash_url = this.settings.flash_url + (this.settings.flash_url.indexOf("?") < 0 ? "?" : "&") + "preventswfcaching=" + new Date().getTime();
  2935. }
  2936. if (!this.settings.preserve_relative_urls) {
  2937. //this.settings.flash_url = SWFUpload.completeURL(this.settings.flash_url); // Don't need to do this one since flash doesn't look at it
  2938. this.settings.upload_url = SWFUpload.completeURL(this.settings.upload_url);
  2939. this.settings.button_image_url = SWFUpload.completeURL(this.settings.button_image_url);
  2940. }
  2941. delete this.ensureDefault;
  2942. };
  2943. // Private: loadFlash replaces the button_placeholder element with the flash movie.
  2944. SWFUpload.prototype.loadFlash = function () {
  2945. var targetElement, tempParent;
  2946. // Make sure an element with the ID we are going to use doesn't already exist
  2947. if (document.getElementById(this.movieName) !== null) {
  2948. throw "ID " + this.movieName + " is already in use. The Flash Object could not be added";
  2949. }
  2950. // Get the element where we will be placing the flash movie
  2951. targetElement = document.getElementById(this.settings.button_placeholder_id) || this.settings.button_placeholder;
  2952. if (targetElement == undefined) {
  2953. throw "Could not find the placeholder element: " + this.settings.button_placeholder_id;
  2954. }
  2955. // Append the container and load the flash
  2956. tempParent = document.createElement("div");
  2957. tempParent.innerHTML = this.getFlashHTML(); // Using innerHTML is non-standard but the only sensible way to dynamically add Flash in IE (and maybe other browsers)
  2958. targetElement.parentNode.replaceChild(tempParent.firstChild, targetElement);
  2959. // Fix IE Flash/Form bug
  2960. if (window[this.movieName] == undefined) {
  2961. window[this.movieName] = this.getMovieElement();
  2962. }
  2963. };
  2964. // Private: getFlashHTML generates the object tag needed to embed the flash in to the document
  2965. SWFUpload.prototype.getFlashHTML = function () {
  2966. // Flash Satay object syntax: http://www.alistapart.com/articles/flashsatay
  2967. return ['<object id="', this.movieName, '" type="application/x-shockwave-flash" data="', this.settings.flash_url, '" width="', this.settings.button_width, '" height="', this.settings.button_height, '" class="swfupload">',
  2968. '<param name="wmode" value="', this.settings.button_window_mode, '" />',
  2969. '<param name="movie" value="', this.settings.flash_url, '" />',
  2970. '<param name="quality" value="high" />',
  2971. '<param name="menu" value="false" />',
  2972. '<param name="allowScriptAccess" value="always" />',
  2973. '<param name="flashvars" value="' + this.getFlashVars() + '" />',
  2974. '</object>'].join("");
  2975. };
  2976. // Private: getFlashVars builds the parameter string that will be passed
  2977. // to flash in the flashvars param.
  2978. SWFUpload.prototype.getFlashVars = function () {
  2979. // Build a string from the post param object
  2980. var paramString = this.buildParamString();
  2981. var httpSuccessString = this.settings.http_success.join(",");
  2982. // Build the parameter string
  2983. return ["movieName=", encodeURIComponent(this.movieName),
  2984. "&amp;uploadURL=", encodeURIComponent(this.settings.upload_url),
  2985. "&amp;useQueryString=", encodeURIComponent(this.settings.use_query_string),
  2986. "&amp;requeueOnError=", encodeURIComponent(this.settings.requeue_on_error),
  2987. "&amp;httpSuccess=", encodeURIComponent(httpSuccessString),
  2988. "&amp;assumeSuccessTimeout=", encodeURIComponent(this.settings.assume_success_timeout),
  2989. "&amp;params=", encodeURIComponent(paramString),
  2990. "&amp;filePostName=", encodeURIComponent(this.settings.file_post_name),
  2991. "&amp;fileTypes=", encodeURIComponent(this.settings.file_types),
  2992. "&amp;fileTypesDescription=", encodeURIComponent(this.settings.file_types_description),
  2993. "&amp;fileSizeLimit=", encodeURIComponent(this.settings.file_size_limit),
  2994. "&amp;fileUploadLimit=", encodeURIComponent(this.settings.file_upload_limit),
  2995. "&amp;fileQueueLimit=", encodeURIComponent(this.settings.file_queue_limit),
  2996. "&amp;debugEnabled=", encodeURIComponent(this.settings.debug_enabled),
  2997. "&amp;buttonImageURL=", encodeURIComponent(this.settings.button_image_url),
  2998. "&amp;buttonWidth=", encodeURIComponent(this.settings.button_width),
  2999. "&amp;buttonHeight=", encodeURIComponent(this.settings.button_height),
  3000. "&amp;buttonText=", encodeURIComponent(this.settings.button_text),
  3001. "&amp;buttonTextTopPadding=", encodeURIComponent(this.settings.button_text_top_padding),
  3002. "&amp;buttonTextLeftPadding=", encodeURIComponent(this.settings.button_text_left_padding),
  3003. "&amp;buttonTextStyle=", encodeURIComponent(this.settings.button_text_style),
  3004. "&amp;buttonAction=", encodeURIComponent(this.settings.button_action),
  3005. "&amp;buttonDisabled=", encodeURIComponent(this.settings.button_disabled),
  3006. "&amp;buttonCursor=", encodeURIComponent(this.settings.button_cursor)
  3007. ].join("");
  3008. };
  3009. // Public: getMovieElement retrieves the DOM reference to the Flash element added by SWFUpload
  3010. // The element is cached after the first lookup
  3011. SWFUpload.prototype.getMovieElement = function () {
  3012. if (this.movieElement == undefined) {
  3013. this.movieElement = document.getElementById(this.movieName);
  3014. }
  3015. if (this.movieElement === null) {
  3016. throw "Could not find Flash element";
  3017. }
  3018. return this.movieElement;
  3019. };
  3020. // Private: buildParamString takes the name/value pairs in the post_params setting object
  3021. // and joins them up in to a string formatted "name=value&amp;name=value"
  3022. SWFUpload.prototype.buildParamString = function () {
  3023. var postParams = this.settings.post_params;
  3024. var paramStringPairs = [];
  3025. if (typeof(postParams) === "object") {
  3026. for (var name in postParams) {
  3027. if (postParams.hasOwnProperty(name)) {
  3028. paramStringPairs.push(encodeURIComponent(name.toString()) + "=" + encodeURIComponent(postParams[name].toString()));
  3029. }
  3030. }
  3031. }
  3032. return paramStringPairs.join("&amp;");
  3033. };
  3034. // Public: Used to remove a SWFUpload instance from the page. This method strives to remove
  3035. // all references to the SWF, and other objects so memory is properly freed.
  3036. // Returns true if everything was destroyed. Returns a false if a failure occurs leaving SWFUpload in an inconsistant state.
  3037. // Credits: Major improvements provided by steffen
  3038. SWFUpload.prototype.destroy = function () {
  3039. try {
  3040. // Make sure Flash is done before we try to remove it
  3041. this.cancelUpload(null, false);
  3042. // Remove the SWFUpload DOM nodes
  3043. var movieElement = null;
  3044. movieElement = this.getMovieElement();
  3045. if (movieElement && typeof(movieElement.CallFunction) === "unknown") { // We only want to do this in IE
  3046. // Loop through all the movie's properties and remove all function references (DOM/JS IE 6/7 memory leak workaround)
  3047. for (var i in movieElement) {
  3048. try {
  3049. if (typeof(movieElement[i]) === "function") {
  3050. movieElement[i] = null;
  3051. }
  3052. } catch (ex1) {}
  3053. }
  3054. // Remove the Movie Element from the page
  3055. try {
  3056. movieElement.parentNode.removeChild(movieElement);
  3057. } catch (ex) {}
  3058. }
  3059. // Remove IE form fix reference
  3060. window[this.movieName] = null;
  3061. // Destroy other references
  3062. SWFUpload.instances[this.movieName] = null;
  3063. delete SWFUpload.instances[this.movieName];
  3064. this.movieElement = null;
  3065. this.settings = null;
  3066. this.customSettings = null;
  3067. this.eventQueue = null;
  3068. this.movieName = null;
  3069. return true;
  3070. } catch (ex2) {
  3071. return false;
  3072. }
  3073. };
  3074. // Public: displayDebugInfo prints out settings and configuration
  3075. // information about this SWFUpload instance.
  3076. // This function (and any references to it) can be deleted when placing
  3077. // SWFUpload in production.
  3078. SWFUpload.prototype.displayDebugInfo = function () {
  3079. this.debug(
  3080. [
  3081. "---SWFUpload Instance Info---\n",
  3082. "Version: ", SWFUpload.version, "\n",
  3083. "Movie Name: ", this.movieName, "\n",
  3084. "Settings:\n",
  3085. "\t", "upload_url: ", this.settings.upload_url, "\n",
  3086. "\t", "flash_url: ", this.settings.flash_url, "\n",
  3087. "\t", "use_query_string: ", this.settings.use_query_string.toString(), "\n",
  3088. "\t", "requeue_on_error: ", this.settings.requeue_on_error.toString(), "\n",
  3089. "\t", "http_success: ", this.settings.http_success.join(", "), "\n",
  3090. "\t", "assume_success_timeout: ", this.settings.assume_success_timeout, "\n",
  3091. "\t", "file_post_name: ", this.settings.file_post_name, "\n",
  3092. "\t", "post_params: ", this.settings.post_params.toString(), "\n",
  3093. "\t", "file_types: ", this.settings.file_types, "\n",
  3094. "\t", "file_types_description: ", this.settings.file_types_description, "\n",
  3095. "\t", "file_size_limit: ", this.settings.file_size_limit, "\n",
  3096. "\t", "file_upload_limit: ", this.settings.file_upload_limit, "\n",
  3097. "\t", "file_queue_limit: ", this.settings.file_queue_limit, "\n",
  3098. "\t", "debug: ", this.settings.debug.toString(), "\n",
  3099. "\t", "prevent_swf_caching: ", this.settings.prevent_swf_caching.toString(), "\n",
  3100. "\t", "button_placeholder_id: ", this.settings.button_placeholder_id.toString(), "\n",
  3101. "\t", "button_placeholder: ", (this.settings.button_placeholder ? "Set" : "Not Set"), "\n",
  3102. "\t", "button_image_url: ", this.settings.button_image_url.toString(), "\n",
  3103. "\t", "button_width: ", this.settings.button_width.toString(), "\n",
  3104. "\t", "button_height: ", this.settings.button_height.toString(), "\n",
  3105. "\t", "button_text: ", this.settings.button_text.toString(), "\n",
  3106. "\t", "button_text_style: ", this.settings.button_text_style.toString(), "\n",
  3107. "\t", "button_text_top_padding: ", this.settings.button_text_top_padding.toString(), "\n",
  3108. "\t", "button_text_left_padding: ", this.settings.button_text_left_padding.toString(), "\n",
  3109. "\t", "button_action: ", this.settings.button_action.toString(), "\n",
  3110. "\t", "button_disabled: ", this.settings.button_disabled.toString(), "\n",
  3111. "\t", "custom_settings: ", this.settings.custom_settings.toString(), "\n",
  3112. "Event Handlers:\n",
  3113. "\t", "swfupload_loaded_handler assigned: ", (typeof this.settings.swfupload_loaded_handler === "function").toString(), "\n",
  3114. "\t", "file_dialog_start_handler assigned: ", (typeof this.settings.file_dialog_start_handler === "function").toString(), "\n",
  3115. "\t", "file_queued_handler assigned: ", (typeof this.settings.file_queued_handler === "function").toString(), "\n",
  3116. "\t", "file_queue_error_handler assigned: ", (typeof this.settings.file_queue_error_handler === "function").toString(), "\n",
  3117. "\t", "upload_start_handler assigned: ", (typeof this.settings.upload_start_handler === "function").toString(), "\n",
  3118. "\t", "upload_progress_handler assigned: ", (typeof this.settings.upload_progress_handler === "function").toString(), "\n",
  3119. "\t", "upload_error_handler assigned: ", (typeof this.settings.upload_error_handler === "function").toString(), "\n",
  3120. "\t", "upload_success_handler assigned: ", (typeof this.settings.upload_success_handler === "function").toString(), "\n",
  3121. "\t", "upload_complete_handler assigned: ", (typeof this.settings.upload_complete_handler === "function").toString(), "\n",
  3122. "\t", "debug_handler assigned: ", (typeof this.settings.debug_handler === "function").toString(), "\n"
  3123. ].join("")
  3124. );
  3125. };
  3126. /* Note: addSetting and getSetting are no longer used by SWFUpload but are included
  3127. the maintain v2 API compatibility
  3128. */
  3129. // Public: (Deprecated) addSetting adds a setting value. If the value given is undefined or null then the default_value is used.
  3130. SWFUpload.prototype.addSetting = function (name, value, default_value) {
  3131. if (value == undefined) {
  3132. return (this.settings[name] = default_value);
  3133. } else {
  3134. return (this.settings[name] = value);
  3135. }
  3136. };
  3137. // Public: (Deprecated) getSetting gets a setting. Returns an empty string if the setting was not found.
  3138. SWFUpload.prototype.getSetting = function (name) {
  3139. if (this.settings[name] != undefined) {
  3140. return this.settings[name];
  3141. }
  3142. return "";
  3143. };
  3144. // Private: callFlash handles function calls made to the Flash element.
  3145. // Calls are made with a setTimeout for some functions to work around
  3146. // bugs in the ExternalInterface library.
  3147. SWFUpload.prototype.callFlash = function (functionName, argumentArray) {
  3148. argumentArray = argumentArray || [];
  3149. var movieElement = this.getMovieElement();
  3150. var returnValue, returnString;
  3151. // Flash's method if calling ExternalInterface methods (code adapted from MooTools).
  3152. try {
  3153. returnString = movieElement.CallFunction('<invoke name="' + functionName + '" returntype="javascript">' + __flash__argumentsToXML(argumentArray, 0) + '</invoke>');
  3154. returnValue = eval(returnString);
  3155. } catch (ex) {
  3156. throw "Call to " + functionName + " failed";
  3157. }
  3158. // Unescape file post param values
  3159. if (returnValue != undefined && typeof returnValue.post === "object") {
  3160. returnValue = this.unescapeFilePostParams(returnValue);
  3161. }
  3162. return returnValue;
  3163. };
  3164. /* *****************************
  3165. -- Flash control methods --
  3166. Your UI should use these
  3167. to operate SWFUpload
  3168. ***************************** */
  3169. // WARNING: this function does not work in Flash Player 10
  3170. // Public: selectFile causes a File Selection Dialog window to appear. This
  3171. // dialog only allows 1 file to be selected.
  3172. SWFUpload.prototype.selectFile = function () {
  3173. this.callFlash("SelectFile");
  3174. };
  3175. // WARNING: this function does not work in Flash Player 10
  3176. // Public: selectFiles causes a File Selection Dialog window to appear/ This
  3177. // dialog allows the user to select any number of files
  3178. // Flash Bug Warning: Flash limits the number of selectable files based on the combined length of the file names.
  3179. // If the selection name length is too long the dialog will fail in an unpredictable manner. There is no work-around
  3180. // for this bug.
  3181. SWFUpload.prototype.selectFiles = function () {
  3182. this.callFlash("SelectFiles");
  3183. };
  3184. // Public: startUpload starts uploading the first file in the queue unless
  3185. // the optional parameter 'fileID' specifies the ID
  3186. SWFUpload.prototype.startUpload = function (fileID) {
  3187. this.callFlash("StartUpload", [fileID]);
  3188. };
  3189. // Public: cancelUpload cancels any queued file. The fileID parameter may be the file ID or index.
  3190. // If you do not specify a fileID the current uploading file or first file in the queue is cancelled.
  3191. // If you do not want the uploadError event to trigger you can specify false for the triggerErrorEvent parameter.
  3192. SWFUpload.prototype.cancelUpload = function (fileID, triggerErrorEvent) {
  3193. if (triggerErrorEvent !== false) {
  3194. triggerErrorEvent = true;
  3195. }
  3196. this.callFlash("CancelUpload", [fileID, triggerErrorEvent]);
  3197. };
  3198. // Public: stopUpload stops the current upload and requeues the file at the beginning of the queue.
  3199. // If nothing is currently uploading then nothing happens.
  3200. SWFUpload.prototype.stopUpload = function () {
  3201. this.callFlash("StopUpload");
  3202. };
  3203. /* ************************
  3204. * Settings methods
  3205. * These methods change the SWFUpload settings.
  3206. * SWFUpload settings should not be changed directly on the settings object
  3207. * since many of the settings need to be passed to Flash in order to take
  3208. * effect.
  3209. * *********************** */
  3210. // Public: getStats gets the file statistics object.
  3211. SWFUpload.prototype.getStats = function () {
  3212. return this.callFlash("GetStats");
  3213. };
  3214. // Public: setStats changes the SWFUpload statistics. You shouldn't need to
  3215. // change the statistics but you can. Changing the statistics does not
  3216. // affect SWFUpload accept for the successful_uploads count which is used
  3217. // by the upload_limit setting to determine how many files the user may upload.
  3218. SWFUpload.prototype.setStats = function (statsObject) {
  3219. this.callFlash("SetStats", [statsObject]);
  3220. };
  3221. // Public: getFile retrieves a File object by ID or Index. If the file is
  3222. // not found then 'null' is returned.
  3223. SWFUpload.prototype.getFile = function (fileID) {
  3224. if (typeof(fileID) === "number") {
  3225. return this.callFlash("GetFileByIndex", [fileID]);
  3226. } else {
  3227. return this.callFlash("GetFile", [fileID]);
  3228. }
  3229. };
  3230. // Public: addFileParam sets a name/value pair that will be posted with the
  3231. // file specified by the Files ID. If the name already exists then the
  3232. // exiting value will be overwritten.
  3233. SWFUpload.prototype.addFileParam = function (fileID, name, value) {
  3234. return this.callFlash("AddFileParam", [fileID, name, value]);
  3235. };
  3236. // Public: removeFileParam removes a previously set (by addFileParam) name/value
  3237. // pair from the specified file.
  3238. SWFUpload.prototype.removeFileParam = function (fileID, name) {
  3239. this.callFlash("RemoveFileParam", [fileID, name]);
  3240. };
  3241. // Public: setUploadUrl changes the upload_url setting.
  3242. SWFUpload.prototype.setUploadURL = function (url) {
  3243. this.settings.upload_url = url.toString();
  3244. this.callFlash("SetUploadURL", [url]);
  3245. };
  3246. // Public: setPostParams changes the post_params setting
  3247. SWFUpload.prototype.setPostParams = function (paramsObject) {
  3248. this.settings.post_params = paramsObject;
  3249. this.callFlash("SetPostParams", [paramsObject]);
  3250. };
  3251. // Public: addPostParam adds post name/value pair. Each name can have only one value.
  3252. SWFUpload.prototype.addPostParam = function (name, value) {
  3253. this.settings.post_params[name] = value;
  3254. this.callFlash("SetPostParams", [this.settings.post_params]);
  3255. };
  3256. // Public: removePostParam deletes post name/value pair.
  3257. SWFUpload.prototype.removePostParam = function (name) {
  3258. delete this.settings.post_params[name];
  3259. this.callFlash("SetPostParams", [this.settings.post_params]);
  3260. };
  3261. // Public: setFileTypes changes the file_types setting and the file_types_description setting
  3262. SWFUpload.prototype.setFileTypes = function (types, description) {
  3263. this.settings.file_types = types;
  3264. this.settings.file_types_description = description;
  3265. this.callFlash("SetFileTypes", [types, description]);
  3266. };
  3267. // Public: setFileSizeLimit changes the file_size_limit setting
  3268. SWFUpload.prototype.setFileSizeLimit = function (fileSizeLimit) {
  3269. this.settings.file_size_limit = fileSizeLimit;
  3270. this.callFlash("SetFileSizeLimit", [fileSizeLimit]);
  3271. };
  3272. // Public: setFileUploadLimit changes the file_upload_limit setting
  3273. SWFUpload.prototype.setFileUploadLimit = function (fileUploadLimit) {
  3274. this.settings.file_upload_limit = fileUploadLimit;
  3275. this.callFlash("SetFileUploadLimit", [fileUploadLimit]);
  3276. };
  3277. // Public: setFileQueueLimit changes the file_queue_limit setting
  3278. SWFUpload.prototype.setFileQueueLimit = function (fileQueueLimit) {
  3279. this.settings.file_queue_limit = fileQueueLimit;
  3280. this.callFlash("SetFileQueueLimit", [fileQueueLimit]);
  3281. };
  3282. // Public: setFilePostName changes the file_post_name setting
  3283. SWFUpload.prototype.setFilePostName = function (filePostName) {
  3284. this.settings.file_post_name = filePostName;
  3285. this.callFlash("SetFilePostName", [filePostName]);
  3286. };
  3287. // Public: setUseQueryString changes the use_query_string setting
  3288. SWFUpload.prototype.setUseQueryString = function (useQueryString) {
  3289. this.settings.use_query_string = useQueryString;
  3290. this.callFlash("SetUseQueryString", [useQueryString]);
  3291. };
  3292. // Public: setRequeueOnError changes the requeue_on_error setting
  3293. SWFUpload.prototype.setRequeueOnError = function (requeueOnError) {
  3294. this.settings.requeue_on_error = requeueOnError;
  3295. this.callFlash("SetRequeueOnError", [requeueOnError]);
  3296. };
  3297. // Public: setHTTPSuccess changes the http_success setting
  3298. SWFUpload.prototype.setHTTPSuccess = function (http_status_codes) {
  3299. if (typeof http_status_codes === "string") {
  3300. http_status_codes = http_status_codes.replace(" ", "").split(",");
  3301. }
  3302. this.settings.http_success = http_status_codes;
  3303. this.callFlash("SetHTTPSuccess", [http_status_codes]);
  3304. };
  3305. // Public: setHTTPSuccess changes the http_success setting
  3306. SWFUpload.prototype.setAssumeSuccessTimeout = function (timeout_seconds) {
  3307. this.settings.assume_success_timeout = timeout_seconds;
  3308. this.callFlash("SetAssumeSuccessTimeout", [timeout_seconds]);
  3309. };
  3310. // Public: setDebugEnabled changes the debug_enabled setting
  3311. SWFUpload.prototype.setDebugEnabled = function (debugEnabled) {
  3312. this.settings.debug_enabled = debugEnabled;
  3313. this.callFlash("SetDebugEnabled", [debugEnabled]);
  3314. };
  3315. // Public: setButtonImageURL loads a button image sprite
  3316. SWFUpload.prototype.setButtonImageURL = function (buttonImageURL) {
  3317. if (buttonImageURL == undefined) {
  3318. buttonImageURL = "";
  3319. }
  3320. this.settings.button_image_url = buttonImageURL;
  3321. this.callFlash("SetButtonImageURL", [buttonImageURL]);
  3322. };
  3323. // Public: setButtonDimensions resizes the Flash Movie and button
  3324. SWFUpload.prototype.setButtonDimensions = function (width, height) {
  3325. this.settings.button_width = width;
  3326. this.settings.button_height = height;
  3327. var movie = this.getMovieElement();
  3328. if (movie != undefined) {
  3329. movie.style.width = width + "px";
  3330. movie.style.height = height + "px";
  3331. }
  3332. this.callFlash("SetButtonDimensions", [width, height]);
  3333. };
  3334. // Public: setButtonText Changes the text overlaid on the button
  3335. SWFUpload.prototype.setButtonText = function (html) {
  3336. this.settings.button_text = html;
  3337. this.callFlash("SetButtonText", [html]);
  3338. };
  3339. // Public: setButtonTextPadding changes the top and left padding of the text overlay
  3340. SWFUpload.prototype.setButtonTextPadding = function (left, top) {
  3341. this.settings.button_text_top_padding = top;
  3342. this.settings.button_text_left_padding = left;
  3343. this.callFlash("SetButtonTextPadding", [left, top]);
  3344. };
  3345. // Public: setButtonTextStyle changes the CSS used to style the HTML/Text overlaid on the button
  3346. SWFUpload.prototype.setButtonTextStyle = function (css) {
  3347. this.settings.button_text_style = css;
  3348. this.callFlash("SetButtonTextStyle", [css]);
  3349. };
  3350. // Public: setButtonDisabled disables/enables the button
  3351. SWFUpload.prototype.setButtonDisabled = function (isDisabled) {
  3352. this.settings.button_disabled = isDisabled;
  3353. this.callFlash("SetButtonDisabled", [isDisabled]);
  3354. };
  3355. // Public: setButtonAction sets the action that occurs when the button is clicked
  3356. SWFUpload.prototype.setButtonAction = function (buttonAction) {
  3357. this.settings.button_action = buttonAction;
  3358. this.callFlash("SetButtonAction", [buttonAction]);
  3359. };
  3360. // Public: setButtonCursor changes the mouse cursor displayed when hovering over the button
  3361. SWFUpload.prototype.setButtonCursor = function (cursor) {
  3362. this.settings.button_cursor = cursor;
  3363. this.callFlash("SetButtonCursor", [cursor]);
  3364. };
  3365. /* *******************************
  3366. Flash Event Interfaces
  3367. These functions are used by Flash to trigger the various
  3368. events.
  3369. All these functions a Private.
  3370. Because the ExternalInterface library is buggy the event calls
  3371. are added to a queue and the queue then executed by a setTimeout.
  3372. This ensures that events are executed in a determinate order and that
  3373. the ExternalInterface bugs are avoided.
  3374. ******************************* */
  3375. SWFUpload.prototype.queueEvent = function (handlerName, argumentArray) {
  3376. // Warning: Don't call this.debug inside here or you'll create an infinite loop
  3377. if (argumentArray == undefined) {
  3378. argumentArray = [];
  3379. } else if (!(argumentArray instanceof Array)) {
  3380. argumentArray = [argumentArray];
  3381. }
  3382. var self = this;
  3383. if (typeof this.settings[handlerName] === "function") {
  3384. // Queue the event
  3385. this.eventQueue.push(function () {
  3386. this.settings[handlerName].apply(this, argumentArray);
  3387. });
  3388. // Execute the next queued event
  3389. setTimeout(function () {
  3390. self.executeNextEvent();
  3391. }, 0);
  3392. } else if (this.settings[handlerName] !== null) {
  3393. throw "Event handler " + handlerName + " is unknown or is not a function";
  3394. }
  3395. };
  3396. // Private: Causes the next event in the queue to be executed. Since events are queued using a setTimeout
  3397. // we must queue them in order to garentee that they are executed in order.
  3398. SWFUpload.prototype.executeNextEvent = function () {
  3399. // Warning: Don't call this.debug inside here or you'll create an infinite loop
  3400. var f = this.eventQueue ? this.eventQueue.shift() : null;
  3401. if (typeof(f) === "function") {
  3402. f.apply(this);
  3403. }
  3404. };
  3405. // Private: unescapeFileParams is part of a workaround for a flash bug where objects passed through ExternalInterface cannot have
  3406. // properties that contain characters that are not valid for JavaScript identifiers. To work around this
  3407. // the Flash Component escapes the parameter names and we must unescape again before passing them along.
  3408. SWFUpload.prototype.unescapeFilePostParams = function (file) {
  3409. var reg = /[$]([0-9a-f]{4})/i;
  3410. var unescapedPost = {};
  3411. var uk;
  3412. if (file != undefined) {
  3413. for (var k in file.post) {
  3414. if (file.post.hasOwnProperty(k)) {
  3415. uk = k;
  3416. var match;
  3417. while ((match = reg.exec(uk)) !== null) {
  3418. uk = uk.replace(match[0], String.fromCharCode(parseInt("0x" + match[1], 16)));
  3419. }
  3420. unescapedPost[uk] = file.post[k];
  3421. }
  3422. }
  3423. file.post = unescapedPost;
  3424. }
  3425. return file;
  3426. };
  3427. // Private: Called by Flash to see if JS can call in to Flash (test if External Interface is working)
  3428. SWFUpload.prototype.testExternalInterface = function () {
  3429. try {
  3430. return this.callFlash("TestExternalInterface");
  3431. } catch (ex) {
  3432. return false;
  3433. }
  3434. };
  3435. // Private: This event is called by Flash when it has finished loading. Don't modify this.
  3436. // Use the swfupload_loaded_handler event setting to execute custom code when SWFUpload has loaded.
  3437. SWFUpload.prototype.flashReady = function () {
  3438. // Check that the movie element is loaded correctly with its ExternalInterface methods defined
  3439. var movieElement = this.getMovieElement();
  3440. if (!movieElement) {
  3441. this.debug("Flash called back ready but the flash movie can't be found.");
  3442. return;
  3443. }
  3444. this.cleanUp(movieElement);
  3445. this.queueEvent("swfupload_loaded_handler");
  3446. };
  3447. // Private: removes Flash added fuctions to the DOM node to prevent memory leaks in IE.
  3448. // This function is called by Flash each time the ExternalInterface functions are created.
  3449. SWFUpload.prototype.cleanUp = function (movieElement) {
  3450. // Pro-actively unhook all the Flash functions
  3451. try {
  3452. if (this.movieElement && typeof(movieElement.CallFunction) === "unknown") { // We only want to do this in IE
  3453. this.debug("Removing Flash functions hooks (this should only run in IE and should prevent memory leaks)");
  3454. for (var key in movieElement) {
  3455. try {
  3456. if (typeof(movieElement[key]) === "function") {
  3457. movieElement[key] = null;
  3458. }
  3459. } catch (ex) {
  3460. }
  3461. }
  3462. }
  3463. } catch (ex1) {
  3464. }
  3465. // Fix Flashes own cleanup code so if the SWFMovie was removed from the page
  3466. // it doesn't display errors.
  3467. window["__flash__removeCallback"] = function (instance, name) {
  3468. try {
  3469. if (instance) {
  3470. instance[name] = null;
  3471. }
  3472. } catch (flashEx) {
  3473. }
  3474. };
  3475. };
  3476. /* This is a chance to do something before the browse window opens */
  3477. SWFUpload.prototype.fileDialogStart = function () {
  3478. this.queueEvent("file_dialog_start_handler");
  3479. };
  3480. /* Called when a file is successfully added to the queue. */
  3481. SWFUpload.prototype.fileQueued = function (file) {
  3482. file = this.unescapeFilePostParams(file);
  3483. this.queueEvent("file_queued_handler", file);
  3484. };
  3485. /* Handle errors that occur when an attempt to queue a file fails. */
  3486. SWFUpload.prototype.fileQueueError = function (file, errorCode, message) {
  3487. file = this.unescapeFilePostParams(file);
  3488. this.queueEvent("file_queue_error_handler", [file, errorCode, message]);
  3489. };
  3490. /* Called after the file dialog has closed and the selected files have been queued.
  3491. You could call startUpload here if you want the queued files to begin uploading immediately. */
  3492. SWFUpload.prototype.fileDialogComplete = function (numFilesSelected, numFilesQueued, numFilesInQueue) {
  3493. this.queueEvent("file_dialog_complete_handler", [numFilesSelected, numFilesQueued, numFilesInQueue]);
  3494. };
  3495. SWFUpload.prototype.uploadStart = function (file) {
  3496. file = this.unescapeFilePostParams(file);
  3497. this.queueEvent("return_upload_start_handler", file);
  3498. };
  3499. SWFUpload.prototype.returnUploadStart = function (file) {
  3500. var returnValue;
  3501. if (typeof this.settings.upload_start_handler === "function") {
  3502. file = this.unescapeFilePostParams(file);
  3503. returnValue = this.settings.upload_start_handler.call(this, file);
  3504. } else if (this.settings.upload_start_handler != undefined) {
  3505. throw "upload_start_handler must be a function";
  3506. }
  3507. // Convert undefined to true so if nothing is returned from the upload_start_handler it is
  3508. // interpretted as 'true'.
  3509. if (returnValue === undefined) {
  3510. returnValue = true;
  3511. }
  3512. returnValue = !!returnValue;
  3513. this.callFlash("ReturnUploadStart", [returnValue]);
  3514. };
  3515. SWFUpload.prototype.uploadProgress = function (file, bytesComplete, bytesTotal) {
  3516. file = this.unescapeFilePostParams(file);
  3517. this.queueEvent("upload_progress_handler", [file, bytesComplete, bytesTotal]);
  3518. };
  3519. SWFUpload.prototype.uploadError = function (file, errorCode, message) {
  3520. file = this.unescapeFilePostParams(file);
  3521. this.queueEvent("upload_error_handler", [file, errorCode, message]);
  3522. };
  3523. SWFUpload.prototype.uploadSuccess = function (file, serverData, responseReceived) {
  3524. file = this.unescapeFilePostParams(file);
  3525. this.queueEvent("upload_success_handler", [file, serverData, responseReceived]);
  3526. };
  3527. SWFUpload.prototype.uploadComplete = function (file) {
  3528. file = this.unescapeFilePostParams(file);
  3529. this.queueEvent("upload_complete_handler", file);
  3530. };
  3531. /* Called by SWFUpload JavaScript and Flash functions when debug is enabled. By default it writes messages to the
  3532. internal debug console. You can override this event and have messages written where you want. */
  3533. SWFUpload.prototype.debug = function (message) {
  3534. this.queueEvent("debug_handler", message);
  3535. };
  3536. /* **********************************
  3537. Debug Console
  3538. The debug console is a self contained, in page location
  3539. for debug message to be sent. The Debug Console adds
  3540. itself to the body if necessary.
  3541. The console is automatically scrolled as messages appear.
  3542. If you are using your own debug handler or when you deploy to production and
  3543. have debug disabled you can remove these functions to reduce the file size
  3544. and complexity.
  3545. ********************************** */
  3546. // Private: debugMessage is the default debug_handler. If you want to print debug messages
  3547. // call the debug() function. When overriding the function your own function should
  3548. // check to see if the debug setting is true before outputting debug information.
  3549. SWFUpload.prototype.debugMessage = function (message) {
  3550. if (this.settings.debug) {
  3551. var exceptionMessage, exceptionValues = [];
  3552. // Check for an exception object and print it nicely
  3553. if (typeof message === "object" && typeof message.name === "string" && typeof message.message === "string") {
  3554. for (var key in message) {
  3555. if (message.hasOwnProperty(key)) {
  3556. exceptionValues.push(key + ": " + message[key]);
  3557. }
  3558. }
  3559. exceptionMessage = exceptionValues.join("\n") || "";
  3560. exceptionValues = exceptionMessage.split("\n");
  3561. exceptionMessage = "EXCEPTION: " + exceptionValues.join("\nEXCEPTION: ");
  3562. SWFUpload.Console.writeLine(exceptionMessage);
  3563. } else {
  3564. SWFUpload.Console.writeLine(message);
  3565. }
  3566. }
  3567. };
  3568. SWFUpload.Console = {};
  3569. SWFUpload.Console.writeLine = function (message) {
  3570. var console, documentForm;
  3571. try {
  3572. console = document.getElementById("SWFUpload_Console");
  3573. if (!console) {
  3574. documentForm = document.createElement("form");
  3575. document.getElementsByTagName("body")[0].appendChild(documentForm);
  3576. console = document.createElement("textarea");
  3577. console.id = "SWFUpload_Console";
  3578. console.style.fontFamily = "monospace";
  3579. console.setAttribute("wrap", "off");
  3580. console.wrap = "off";
  3581. console.style.overflow = "auto";
  3582. console.style.width = "700px";
  3583. console.style.height = "350px";
  3584. console.style.margin = "5px";
  3585. documentForm.appendChild(console);
  3586. }
  3587. console.value += message + "\n";
  3588. console.scrollTop = console.scrollHeight - console.clientHeight;
  3589. } catch (ex) {
  3590. alert("Exception: " + ex.name + " Message: " + ex.message);
  3591. }
  3592. };
  3593. /*
  3594. Copyright 2008-2009 University of Toronto
  3595. Copyright 2008-2009 University of California, Berkeley
  3596. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  3597. BSD license. You may not use this file except in compliance with one these
  3598. Licenses.
  3599. You may obtain a copy of the ECL 2.0 License and BSD License at
  3600. https://source.fluidproject.org/svn/LICENSE.txt
  3601. */
  3602. /*global jQuery*/
  3603. /*global fluid_1_2*/
  3604. fluid_1_2 = fluid_1_2 || {};
  3605. (function ($, fluid) {
  3606. var animateDisplay = function (elm, animation, defaultAnimation) {
  3607. animation = (animation) ? animation : defaultAnimation;
  3608. elm.animate(animation.params, animation.duration, animation.callback);
  3609. };
  3610. var animateProgress = function (elm, width, speed) {
  3611. // de-queue any left over animations
  3612. elm.queue("fx", []);
  3613. elm.animate({
  3614. width: width,
  3615. queue: false
  3616. },
  3617. speed);
  3618. };
  3619. var showProgress = function (that, animation) {
  3620. if (animation === false) {
  3621. that.displayElement.show();
  3622. } else {
  3623. animateDisplay(that.displayElement, animation, that.options.showAnimation);
  3624. }
  3625. };
  3626. var hideProgress = function (that, delay, animation) {
  3627. delay = (delay === null || isNaN(delay)) ? that.options.delay : delay;
  3628. if (delay) {
  3629. // use a setTimeout to delay the hide for n millies, note use of recursion
  3630. var timeOut = setTimeout(function () {
  3631. hideProgress(that, 0, animation);
  3632. }, delay);
  3633. } else {
  3634. if (animation === false) {
  3635. that.displayElement.hide();
  3636. } else {
  3637. animateDisplay(that.displayElement, animation, that.options.hideAnimation);
  3638. }
  3639. }
  3640. };
  3641. var updateWidth = function (that, newWidth, dontAnimate) {
  3642. dontAnimate = dontAnimate || false;
  3643. var currWidth = that.indicator.width();
  3644. var direction = that.options.animate;
  3645. if ((newWidth > currWidth) && (direction === "both" || direction === "forward") && !dontAnimate) {
  3646. animateProgress(that.indicator, newWidth, that.options.speed);
  3647. } else if ((newWidth < currWidth) && (direction === "both" || direction === "backward") && !dontAnimate) {
  3648. animateProgress(that.indicator, newWidth, that.options.speed);
  3649. } else {
  3650. that.indicator.width(newWidth);
  3651. }
  3652. };
  3653. var percentToPixels = function (that, percent) {
  3654. // progress does not support percents over 100, also all numbers are rounded to integers
  3655. return Math.round((Math.min(percent, 100) * that.progressBar.width()) / 100);
  3656. };
  3657. var refreshRelativeWidth = function (that) {
  3658. var pixels = Math.max(percentToPixels(that, parseFloat(that.storedPercent)), that.options.minWidth);
  3659. updateWidth(that, pixels, true);
  3660. };
  3661. var initARIA = function (ariaElement) {
  3662. ariaElement.attr("role", "progressbar");
  3663. ariaElement.attr("aria-valuemin", "0");
  3664. ariaElement.attr("aria-valuemax", "100");
  3665. ariaElement.attr("aria-valuenow", "0");
  3666. ariaElement.attr("aria-valuetext", "");
  3667. ariaElement.attr("aria-busy", "false");
  3668. };
  3669. var updateARIA = function (that, percent) {
  3670. var busy = percent < 100 && percent > 0;
  3671. that.ariaElement.attr("aria-busy", busy);
  3672. that.ariaElement.attr("aria-valuenow", percent);
  3673. if (busy) {
  3674. var busyString = fluid.stringTemplate(that.options.ariaBusyText, {percentComplete : percent});
  3675. that.ariaElement.attr("aria-valuetext", busyString);
  3676. } else if (percent === 100) {
  3677. // FLUID-2936: JAWS doesn't currently read the "Progress is complete" message to the user, even though we set it here.
  3678. that.ariaElement.attr("aria-valuetext", that.options.ariaDoneText);
  3679. }
  3680. };
  3681. var updateText = function (label, value) {
  3682. label.html(value);
  3683. };
  3684. var repositionIndicator = function (that) {
  3685. that.indicator.css("top", that.progressBar.position().top)
  3686. .css("left", 0)
  3687. .height(that.progressBar.height());
  3688. refreshRelativeWidth(that);
  3689. };
  3690. var updateProgress = function (that, percent, labelText, animationForShow) {
  3691. // show progress before updating, jQuery will handle the case if the object is already displayed
  3692. showProgress(that, animationForShow);
  3693. // do not update if the value of percent is falsey
  3694. if (percent !== null) {
  3695. that.storedPercent = percent;
  3696. var pixels = Math.max(percentToPixels(that, parseFloat(percent)), that.options.minWidth);
  3697. updateWidth(that, pixels);
  3698. }
  3699. if (labelText !== null) {
  3700. updateText(that.label, labelText);
  3701. }
  3702. // update ARIA
  3703. if (that.ariaElement) {
  3704. updateARIA(that, percent);
  3705. }
  3706. };
  3707. var setupProgress = function (that) {
  3708. that.displayElement = that.locate("displayElement");
  3709. // hide file progress in case it is showing
  3710. if (that.options.initiallyHidden) {
  3711. that.displayElement.hide();
  3712. }
  3713. that.progressBar = that.locate("progressBar");
  3714. that.label = that.locate("label");
  3715. that.indicator = that.locate("indicator");
  3716. that.ariaElement = that.locate("ariaElement");
  3717. that.indicator.width(that.options.minWidth);
  3718. that.storedPercent = 0;
  3719. // initialize ARIA
  3720. if (that.ariaElement) {
  3721. initARIA(that.ariaElement);
  3722. }
  3723. };
  3724. /**
  3725. * Instantiates a new Progress component.
  3726. *
  3727. * @param {jQuery|Selector|Element} container the DOM element in which the Uploader lives
  3728. * @param {Object} options configuration options for the component.
  3729. */
  3730. fluid.progress = function (container, options) {
  3731. var that = fluid.initView("fluid.progress", container, options);
  3732. setupProgress(that);
  3733. /**
  3734. * Shows the progress bar if is currently hidden.
  3735. *
  3736. * @param {Object} animation a custom animation used when showing the progress bar
  3737. */
  3738. that.show = function (animation) {
  3739. showProgress(that, animation);
  3740. };
  3741. /**
  3742. * Hides the progress bar if it is visible.
  3743. *
  3744. * @param {Number} delay the amount of time to wait before hiding
  3745. * @param {Object} animation a custom animation used when hiding the progress bar
  3746. */
  3747. that.hide = function (delay, animation) {
  3748. hideProgress(that, delay, animation);
  3749. };
  3750. /**
  3751. * Updates the state of the progress bar.
  3752. * This will automatically show the progress bar if it is currently hidden.
  3753. * Percentage is specified as a decimal value, but will be automatically converted if needed.
  3754. *
  3755. *
  3756. * @param {Number|String} percentage the current percentage, specified as a "float-ish" value
  3757. * @param {String} labelValue the value to set for the label; this can be an HTML string
  3758. * @param {Object} animationForShow the animation to use when showing the progress bar if it is hidden
  3759. */
  3760. that.update = function (percentage, labelValue, animationForShow) {
  3761. updateProgress(that, percentage, labelValue, animationForShow);
  3762. };
  3763. that.refreshView = function () {
  3764. repositionIndicator(that);
  3765. };
  3766. return that;
  3767. };
  3768. fluid.defaults("fluid.progress", {
  3769. selectors: {
  3770. displayElement: ".flc-progress", // required, the element that gets displayed when progress is displayed, could be the indicator or bar or some larger outer wrapper as in an overlay effect
  3771. progressBar: ".flc-progress-bar", //required
  3772. indicator: ".flc-progress-indicator", //required
  3773. label: ".flc-progress-label", //optional
  3774. ariaElement: ".flc-progress-bar" // usually required, except in cases where there are more than one progressor for the same data such as a total and a sub-total
  3775. },
  3776. // progress display and hide animations, use the jQuery animation primatives, set to false to use no animation
  3777. // animations must be symetrical (if you hide with width, you'd better show with width) or you get odd effects
  3778. // see jQuery docs about animations to customize
  3779. showAnimation: {
  3780. params: {
  3781. opacity: "show"
  3782. },
  3783. duration: "slow",
  3784. callback: null
  3785. }, // equivalent of $().fadeIn("slow")
  3786. hideAnimation: {
  3787. params: {
  3788. opacity: "hide"
  3789. },
  3790. duration: "slow",
  3791. callback: null
  3792. }, // equivalent of $().fadeOut("slow")
  3793. minWidth: 5, // 0 length indicators can look broken if there is a long pause between updates
  3794. delay: 0, // the amount to delay the fade out of the progress
  3795. speed: 200, // default speed for animations, pretty fast
  3796. animate: "forward", // suppport "forward", "backward", and "both", any other value is no animation either way
  3797. initiallyHidden: true, // supports progress indicators which may always be present
  3798. updatePosition: false,
  3799. ariaBusyText: "Progress is %percentComplete percent complete",
  3800. ariaDoneText: "Progress is complete."
  3801. });
  3802. })(jQuery, fluid_1_2);
  3803. /*
  3804. Copyright 2009 University of Toronto
  3805. Copyright 2009 University of California, Berkeley
  3806. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  3807. BSD license. You may not use this file except in compliance with one these
  3808. Licenses.
  3809. You may obtain a copy of the ECL 2.0 License and BSD License at
  3810. https://source.fluidproject.org/svn/LICENSE.txt
  3811. */
  3812. /*global jQuery*/
  3813. /*global fluid_1_2*/
  3814. fluid_1_2 = fluid_1_2 || {};
  3815. /***********************
  3816. * Demo Upload Manager *
  3817. ***********************/
  3818. (function ($, fluid) {
  3819. var updateProgress = function (file, events, demoState, isUploading) {
  3820. if (!isUploading) {
  3821. return;
  3822. }
  3823. var chunk = Math.min(demoState.chunkSize, file.size);
  3824. demoState.bytesUploaded = Math.min(demoState.bytesUploaded + chunk, file.size);
  3825. events.onFileProgress.fire(file, demoState.bytesUploaded, file.size);
  3826. };
  3827. var fireAfterFileComplete = function (that, file) {
  3828. // this is a horrible hack that needs to be addressed.
  3829. if (that.swfUploadSettings) {
  3830. that.swfUploadSettings.upload_complete_handler(file);
  3831. } else {
  3832. that.events.afterFileComplete.fire(file);
  3833. }
  3834. };
  3835. var finishAndContinueOrCleanup = function (that, file) {
  3836. that.queueManager.finishFile(file);
  3837. if (that.queueManager.shouldUploadNextFile()) {
  3838. startUploading(that);
  3839. } else {
  3840. that.queueManager.complete();
  3841. }
  3842. };
  3843. var finishUploading = function (that) {
  3844. if (!that.queue.isUploading) {
  3845. return;
  3846. }
  3847. var file = that.demoState.currentFile;
  3848. file.filestatus = fluid.uploader.fileStatusConstants.COMPLETE;
  3849. that.events.onFileSuccess.fire(file);
  3850. that.demoState.fileIdx++;
  3851. finishAndContinueOrCleanup(that, file);
  3852. };
  3853. var simulateUpload = function (that) {
  3854. if (!that.queue.isUploading) {
  3855. return;
  3856. }
  3857. var file = that.demoState.currentFile;
  3858. if (that.demoState.bytesUploaded < file.size) {
  3859. that.invokeAfterRandomDelay(function () {
  3860. updateProgress(file, that.events, that.demoState, that.queue.isUploading);
  3861. simulateUpload(that);
  3862. });
  3863. } else {
  3864. finishUploading(that);
  3865. }
  3866. };
  3867. var startUploading = function (that) {
  3868. // Reset our upload stats for each new file.
  3869. that.demoState.currentFile = that.queue.files[that.demoState.fileIdx];
  3870. that.demoState.chunksForCurrentFile = Math.ceil(that.demoState.currentFile / that.demoState.chunkSize);
  3871. that.demoState.bytesUploaded = 0;
  3872. that.queue.isUploading = true;
  3873. that.events.onFileStart.fire(that.demoState.currentFile);
  3874. that.demoState.currentFile.filestatus = fluid.uploader.fileStatusConstants.IN_PROGRESS;
  3875. simulateUpload(that);
  3876. };
  3877. var stopDemo = function (that) {
  3878. var file = that.demoState.currentFile;
  3879. file.filestatus = fluid.uploader.fileStatusConstants.CANCELLED;
  3880. that.queue.shouldStop = true;
  3881. // In SWFUpload's world, pausing is a combinination of an UPLOAD_STOPPED error and a complete.
  3882. that.events.onFileError.fire(file,
  3883. fluid.uploader.errorConstants.UPLOAD_STOPPED,
  3884. "The demo upload was paused by the user.");
  3885. finishAndContinueOrCleanup(that, file);
  3886. that.events.onUploadStop.fire();
  3887. };
  3888. var setupDemoUploadManager = function (that) {
  3889. if (that.options.simulateDelay === undefined || that.options.simulateDelay === null) {
  3890. that.options.simulateDelay = true;
  3891. }
  3892. // Initialize state for our upload simulation.
  3893. that.demoState = {
  3894. fileIdx: 0,
  3895. chunkSize: 200000
  3896. };
  3897. return that;
  3898. };
  3899. /**
  3900. * The Demo Upload Manager wraps a standard upload manager and simulates the upload process.
  3901. *
  3902. * @param {UploadManager} uploadManager the upload manager to wrap
  3903. */
  3904. fluid.demoUploadManager = function (uploadManager) {
  3905. var that = uploadManager;
  3906. that.start = function () {
  3907. that.queueManager.start();
  3908. startUploading(that);
  3909. };
  3910. /**
  3911. * Cancels a simulated upload.
  3912. * This method overrides the default behaviour in SWFUploadManager.
  3913. */
  3914. that.stop = function () {
  3915. stopDemo(that);
  3916. };
  3917. /**
  3918. * Invokes a function after a random delay by using setTimeout.
  3919. * If the simulateDelay option is false, the function is invoked immediately.
  3920. *
  3921. * @param {Object} fn the function to invoke
  3922. */
  3923. that.invokeAfterRandomDelay = function (fn) {
  3924. var delay;
  3925. if (that.options.simulateDelay) {
  3926. delay = Math.floor(Math.random() * 1000 + 100);
  3927. setTimeout(fn, delay);
  3928. } else {
  3929. fn();
  3930. }
  3931. };
  3932. setupDemoUploadManager(that);
  3933. return that;
  3934. };
  3935. })(jQuery, fluid_1_2);
  3936. /*
  3937. Copyright 2008-2009 University of Toronto
  3938. Copyright 2008-2009 University of California, Berkeley
  3939. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  3940. BSD license. You may not use this file except in compliance with one these
  3941. Licenses.
  3942. You may obtain a copy of the ECL 2.0 License and BSD License at
  3943. https://source.fluidproject.org/svn/LICENSE.txt
  3944. */
  3945. /*global SWFUpload*/
  3946. /*global jQuery*/
  3947. /*global fluid_1_2*/
  3948. fluid_1_2 = fluid_1_2 || {};
  3949. (function ($, fluid) {
  3950. var filterFiles = function (files, filterFn) {
  3951. var filteredFiles = [];
  3952. for (var i = 0; i < files.length; i++) {
  3953. var file = files[i];
  3954. if (filterFn(file) === true) {
  3955. filteredFiles.push(file);
  3956. }
  3957. }
  3958. return filteredFiles;
  3959. };
  3960. var getUploadedFiles = function (that) {
  3961. return filterFiles(that.files, function (file) {
  3962. return (file.filestatus === fluid.uploader.fileStatusConstants.COMPLETE);
  3963. });
  3964. };
  3965. var getReadyFiles = function (that) {
  3966. return filterFiles(that.files, function (file) {
  3967. return (file.filestatus === fluid.uploader.fileStatusConstants.QUEUED || file.filestatus === fluid.uploader.fileStatusConstants.CANCELLED);
  3968. });
  3969. };
  3970. var getErroredFiles = function (that) {
  3971. return filterFiles(that.files, function (file) {
  3972. return (file.filestatus === fluid.uploader.fileStatusConstants.ERROR);
  3973. });
  3974. };
  3975. var removeFile = function (that, file) {
  3976. // Remove the file from the collection and tell the world about it.
  3977. var idx = $.inArray(file, that.files);
  3978. that.files.splice(idx, 1);
  3979. };
  3980. var clearCurrentBatch = function (that) {
  3981. that.currentBatch = {
  3982. fileIdx: -1,
  3983. files: [],
  3984. totalBytes: 0,
  3985. numFilesCompleted: 0,
  3986. numFilesErrored: 0,
  3987. bytesUploadedForFile: 0,
  3988. previousBytesUploadedForFile: 0,
  3989. totalBytesUploaded: 0
  3990. };
  3991. };
  3992. var updateCurrentBatch = function (that) {
  3993. var readyFiles = that.getReadyFiles();
  3994. that.currentBatch.files = readyFiles;
  3995. that.currentBatch.totalBytes = fluid.fileQueue.sizeOfFiles(readyFiles);
  3996. };
  3997. var setupCurrentBatch = function (that) {
  3998. clearCurrentBatch(that);
  3999. updateCurrentBatch(that);
  4000. };
  4001. fluid.fileQueue = function () {
  4002. var that = {};
  4003. that.files = [];
  4004. that.isUploading = false;
  4005. that.addFile = function (file) {
  4006. that.files.push(file);
  4007. };
  4008. that.removeFile = function (file) {
  4009. removeFile(that, file);
  4010. };
  4011. that.totalBytes = function () {
  4012. return fluid.fileQueue.sizeOfFiles(that.files);
  4013. };
  4014. that.getReadyFiles = function () {
  4015. return getReadyFiles(that);
  4016. };
  4017. that.getErroredFiles = function () {
  4018. return getErroredFiles(that);
  4019. };
  4020. that.sizeOfReadyFiles = function () {
  4021. return fluid.fileQueue.sizeOfFiles(that.getReadyFiles());
  4022. };
  4023. that.getUploadedFiles = function () {
  4024. return getUploadedFiles(that);
  4025. };
  4026. that.sizeOfUploadedFiles = function () {
  4027. return fluid.fileQueue.sizeOfFiles(that.getUploadedFiles());
  4028. };
  4029. that.setupCurrentBatch = function () {
  4030. setupCurrentBatch(that);
  4031. };
  4032. that.clearCurrentBatch = function () {
  4033. clearCurrentBatch(that);
  4034. };
  4035. that.updateCurrentBatch = function () {
  4036. updateCurrentBatch(that);
  4037. };
  4038. return that;
  4039. };
  4040. fluid.fileQueue.sizeOfFiles = function (files) {
  4041. var totalBytes = 0;
  4042. for (var i = 0; i < files.length; i++) {
  4043. var file = files[i];
  4044. totalBytes += file.size;
  4045. }
  4046. return totalBytes;
  4047. };
  4048. fluid.fileQueue.manager = function (queue, events) {
  4049. var that = {};
  4050. that.queue = queue;
  4051. that.events = events;
  4052. that.start = function () {
  4053. that.queue.setupCurrentBatch();
  4054. that.queue.isUploading = true;
  4055. that.queue.shouldStop = false;
  4056. that.events.onUploadStart.fire(that.queue.currentBatch.files);
  4057. };
  4058. that.startFile = function () {
  4059. that.queue.currentBatch.fileIdx++;
  4060. that.queue.currentBatch.bytesUploadedForFile = 0;
  4061. that.queue.currentBatch.previousBytesUploadedForFile = 0;
  4062. };
  4063. that.finishFile = function (file) {
  4064. var batch = that.queue.currentBatch;
  4065. batch.numFilesCompleted++;
  4066. that.events.afterFileComplete.fire(file);
  4067. };
  4068. that.shouldUploadNextFile = function () {
  4069. return !that.queue.shouldStop && that.queue.isUploading && that.queue.currentBatch.numFilesCompleted < that.queue.currentBatch.files.length;
  4070. };
  4071. that.complete = function () {
  4072. that.events.afterUploadComplete.fire(that.queue.currentBatch.files);
  4073. that.queue.clearCurrentBatch();
  4074. };
  4075. return that;
  4076. };
  4077. })(jQuery, fluid_1_2);
  4078. /*
  4079. Copyright 2008-2009 University of Toronto
  4080. Copyright 2008-2009 University of California, Berkeley
  4081. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  4082. BSD license. You may not use this file except in compliance with one these
  4083. Licenses.
  4084. You may obtain a copy of the ECL 2.0 License and BSD License at
  4085. https://source.fluidproject.org/svn/LICENSE.txt
  4086. */
  4087. /*global jQuery*/
  4088. /*global fluid_1_2*/
  4089. fluid_1_2 = fluid_1_2 || {};
  4090. (function ($, fluid) {
  4091. var refreshView = function (that) {
  4092. var maxHeight = that.options.maxHeight;
  4093. var isOverMaxHeight = (that.scrollingElm.children().eq(0).height() > maxHeight);
  4094. var setHeight = (isOverMaxHeight) ? maxHeight : "";
  4095. that.scrollingElm.height(setHeight);
  4096. };
  4097. var scrollBottom = function (that) {
  4098. that.scrollingElm[0].scrollTop = that.scrollingElm[0].scrollHeight;
  4099. };
  4100. var scrollTo = function (that, element) {
  4101. if (!element || element.length < 1) {
  4102. return;
  4103. }
  4104. var padTop = 0;
  4105. var padBottom = 0;
  4106. var elmPosTop = element[0].offsetTop;
  4107. var elmHeight = element.height();
  4108. var containerScrollTop = that.scrollingElm[0].scrollTop;
  4109. var containerHeight = that.scrollingElm.height();
  4110. if (that.options.padScroll) {
  4111. // if the combined height of the elements is greater than the
  4112. // viewport then then scrollTo element would not be in view
  4113. var prevElmHeight = element.prev().height();
  4114. padTop = (prevElmHeight + elmHeight <= containerHeight) ? prevElmHeight : 0;
  4115. var nextElmHeight = element.next().height();
  4116. padBottom = (nextElmHeight + elmHeight <= containerHeight) ? nextElmHeight : 0;
  4117. }
  4118. // if the top of the row is ABOVE the view port move the row into position
  4119. if ((elmPosTop - padTop) < containerScrollTop) {
  4120. that.scrollingElm[0].scrollTop = elmPosTop - padTop;
  4121. }
  4122. // if the bottom of the row is BELOW the viewport then scroll it into position
  4123. if (((elmPosTop + elmHeight) + padBottom) > (containerScrollTop + containerHeight)) {
  4124. elmHeight = (elmHeight < containerHeight) ? elmHeight : containerHeight;
  4125. that.scrollingElm[0].scrollTop = (elmPosTop - containerHeight + elmHeight + padBottom);
  4126. }
  4127. };
  4128. var setupScroller = function (that) {
  4129. that.scrollingElm = that.container.parents(that.options.selectors.wrapper);
  4130. // We should render our own sensible default if the scrolling element is missing.
  4131. if (!that.scrollingElm.length) {
  4132. fluid.fail({
  4133. name: "Missing Scroller",
  4134. message: "The scroller wrapper element was not found."
  4135. });
  4136. }
  4137. // set the height of the scroller unless this is IE6
  4138. if (!$.browser.msie || $.browser.version > 6) {
  4139. that.scrollingElm.css("max-height", that.options.maxHeight);
  4140. }
  4141. };
  4142. /**
  4143. * Creates a new Scroller component.
  4144. *
  4145. * @param {Object} container the element containing the collection of things to make scrollable
  4146. * @param {Object} options configuration options for the component
  4147. */
  4148. fluid.scroller = function (container, options) {
  4149. var that = fluid.initView("fluid.scroller", container, options);
  4150. setupScroller(that);
  4151. /**
  4152. * Scrolls the specified element into view
  4153. *
  4154. * @param {jQuery} element the element to scroll into view
  4155. */
  4156. that.scrollTo = function (element) {
  4157. scrollTo(that, element);
  4158. };
  4159. /**
  4160. * Scrolls to the bottom of the view.
  4161. */
  4162. that.scrollBottom = function () {
  4163. scrollBottom(that);
  4164. };
  4165. /**
  4166. * Refreshes the scroller's appearance based on any changes to the document.
  4167. */
  4168. that.refreshView = function () {
  4169. if ($.browser.msie && $.browser.version < 7) {
  4170. refreshView(that);
  4171. }
  4172. };
  4173. that.refreshView();
  4174. return that;
  4175. };
  4176. fluid.defaults("fluid.scroller", {
  4177. selectors: {
  4178. wrapper: ".flc-scroller"
  4179. },
  4180. maxHeight: 180,
  4181. padScroll: true
  4182. });
  4183. })(jQuery, fluid_1_2);
  4184. /*
  4185. Copyright 2008-2009 University of Toronto
  4186. Copyright 2008-2009 University of California, Berkeley
  4187. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  4188. BSD license. You may not use this file except in compliance with one these
  4189. Licenses.
  4190. You may obtain a copy of the ECL 2.0 License and BSD License at
  4191. https://source.fluidproject.org/svn/LICENSE.txt
  4192. */
  4193. /*global fluid_1_2,SWFUpload,swfobject,jQuery*/
  4194. fluid_1_2 = fluid_1_2 || {};
  4195. (function ($, fluid) {
  4196. /*****************************
  4197. * SWFUpload Setup Decorator *
  4198. *****************************/
  4199. var unbindSelectFiles = function () {
  4200. // There's a bug in SWFUpload 2.2.0b3 that causes the entire browser to crash
  4201. // if selectFile() or selectFiles() is invoked. Remove them so no one will accidently crash their browser.
  4202. var emptyFunction = function () {};
  4203. SWFUpload.prototype.selectFile = emptyFunction;
  4204. SWFUpload.prototype.selectFiles = emptyFunction;
  4205. };
  4206. var prepareUpstreamOptions = function (that, uploader) {
  4207. that.returnedOptions = {
  4208. uploadManager: {
  4209. type: uploader.options.uploadManager.type || uploader.options.uploadManager
  4210. }
  4211. };
  4212. };
  4213. var createFlash9MovieContainer = function (that) {
  4214. var container = $("<div><span></span></div>");
  4215. container.addClass(that.options.styles.flash9Container);
  4216. $("body").append(container);
  4217. return container;
  4218. };
  4219. var setupForFlash9 = function (that) {
  4220. var flashContainer = createFlash9MovieContainer(that);
  4221. that.returnedOptions.uploadManager.options = {
  4222. flashURL: that.options.flash9URL || undefined,
  4223. flashButtonPeerId: fluid.allocateSimpleId(flashContainer.children().eq(0))
  4224. };
  4225. };
  4226. var createFlash10MovieContainer = function (that, uploaderContainer) {
  4227. // Wrap the whole uploader first.
  4228. uploaderContainer.wrap("<div class='" + that.options.styles.uploaderWrapperFlash10 + "'></div>");
  4229. // Then create a container and placeholder for the Flash movie as a sibling to the uploader.
  4230. var flashContainer = $("<div><span></span></div>");
  4231. flashContainer.addClass(that.options.styles.browseButtonOverlay);
  4232. uploaderContainer.after(flashContainer);
  4233. unbindSelectFiles();
  4234. return flashContainer;
  4235. };
  4236. var setupForFlash10 = function (that, uploader) {
  4237. var o = that.options,
  4238. flashContainer = createFlash10MovieContainer(that, uploader.container),
  4239. browseButton = uploader.locate("browseButton");
  4240. fluid.tabindex(browseButton, -1);
  4241. that.isTransparent = o.flashButtonAlwaysVisible ? false : (!$.browser.msie || o.transparentEvenInIE);
  4242. that.returnedOptions.uploadManager.options = {
  4243. flashURL: o.flash10URL || undefined,
  4244. flashButtonImageURL: that.isTransparent ? undefined : o.flashButtonImageURL,
  4245. flashButtonPeerId: fluid.allocateSimpleId(flashContainer.children().eq(0)),
  4246. flashButtonHeight: o.flashButtonHeight || browseButton.outerHeight(),
  4247. flashButtonWidth: o.flashButtonWidth || browseButton.outerWidth(),
  4248. flashButtonWindowMode: that.isTransparent ? SWFUpload.WINDOW_MODE.TRANSPARENT : SWFUpload.WINDOW_MODE.OPAQUE,
  4249. flashButtonCursorEffect: SWFUpload.CURSOR.HAND,
  4250. listeners: {
  4251. onUploadStart: function () {
  4252. uploader.uploadManager.swfUploader.setButtonDisabled(true);
  4253. },
  4254. afterUploadComplete: function () {
  4255. uploader.uploadManager.swfUploader.setButtonDisabled(false);
  4256. }
  4257. }
  4258. };
  4259. };
  4260. /**
  4261. * SWFUploadSetupDecorator is a decorator designed to setup the DOM correctly for SWFUpload and configure
  4262. * the Uploader component according to the version of Flash and browser currently running.
  4263. *
  4264. * @param {Uploader} uploader the Uploader component to decorate
  4265. * @param {options} options configuration options for the decorator
  4266. */
  4267. fluid.swfUploadSetupDecorator = function (uploader, options) {
  4268. var that = {};
  4269. fluid.mergeComponentOptions(that, "fluid.swfUploadSetupDecorator", options);
  4270. that.flashVersion = swfobject.getFlashPlayerVersion().major;
  4271. prepareUpstreamOptions(that, uploader);
  4272. if (that.flashVersion === 9) {
  4273. setupForFlash9(that, uploader);
  4274. } else {
  4275. setupForFlash10(that, uploader);
  4276. }
  4277. return that;
  4278. };
  4279. fluid.defaults("fluid.swfUploadSetupDecorator", {
  4280. // The flash9URL and flash10URLs are now deprecated in favour of the flashURL option in upload manager.
  4281. flashButtonAlwaysVisible: false,
  4282. transparentEvenInIE: true,
  4283. // Used only when the Flash movie is visible.
  4284. flashButtonImageURL: "../images/browse.png",
  4285. styles: {
  4286. browseButtonOverlay: "fl-uploader-browse-overlay",
  4287. flash9Container: "fl-uploader-flash9-container",
  4288. uploaderWrapperFlash10: "fl-uploader-flash10-wrapper"
  4289. }
  4290. });
  4291. /***********************
  4292. * SWF Upload Manager *
  4293. ***********************/
  4294. // Maps SWFUpload's setting names to our component's setting names.
  4295. var swfUploadOptionsMap = {
  4296. uploadURL: "upload_url",
  4297. flashURL: "flash_url",
  4298. postParams: "post_params",
  4299. fileSizeLimit: "file_size_limit",
  4300. fileTypes: "file_types",
  4301. fileTypesDescription: "file_types_description",
  4302. fileUploadLimit: "file_upload_limit",
  4303. fileQueueLimit: "file_queue_limit",
  4304. flashButtonPeerId: "button_placeholder_id",
  4305. flashButtonImageURL: "button_image_url",
  4306. flashButtonHeight: "button_height",
  4307. flashButtonWidth: "button_width",
  4308. flashButtonWindowMode: "button_window_mode",
  4309. flashButtonCursorEffect: "button_cursor",
  4310. debug: "debug"
  4311. };
  4312. // Maps SWFUpload's callback names to our component's callback names.
  4313. var swfUploadEventMap = {
  4314. afterReady: "swfupload_loaded_handler",
  4315. onFileDialog: "file_dialog_start_handler",
  4316. afterFileQueued: "file_queued_handler",
  4317. onQueueError: "file_queue_error_handler",
  4318. afterFileDialog: "file_dialog_complete_handler",
  4319. onFileStart: "upload_start_handler",
  4320. onFileProgress: "upload_progress_handler",
  4321. onFileError: "upload_error_handler",
  4322. onFileSuccess: "upload_success_handler"
  4323. };
  4324. var mapNames = function (nameMap, source, target) {
  4325. var result = target || {};
  4326. for (var key in source) {
  4327. var mappedKey = nameMap[key];
  4328. if (mappedKey) {
  4329. result[mappedKey] = source[key];
  4330. }
  4331. }
  4332. return result;
  4333. };
  4334. // For each event type, hand the fire function to SWFUpload so it can fire the event at the right time for us.
  4335. var mapEvents = function (that, nameMap, target) {
  4336. var result = target || {};
  4337. for (var eventType in that.events) {
  4338. var fireFn = that.events[eventType].fire;
  4339. var mappedName = nameMap[eventType];
  4340. if (mappedName) {
  4341. result[mappedName] = fireFn;
  4342. }
  4343. }
  4344. result.upload_complete_handler = function (file) {
  4345. that.queueManager.finishFile(file);
  4346. if (that.queueManager.shouldUploadNextFile()) {
  4347. that.swfUploader.startUpload();
  4348. } else {
  4349. if (that.queueManager.queue.shouldStop) {
  4350. that.swfUploader.stopUpload();
  4351. }
  4352. that.queueManager.complete();
  4353. }
  4354. };
  4355. return result;
  4356. };
  4357. // Invokes the OS browse files dialog, allowing either single or multiple select based on the options.
  4358. var browse = function (that) {
  4359. if (that.queue.isUploading) {
  4360. return;
  4361. }
  4362. if (that.options.fileQueueLimit === 1) {
  4363. that.swfUploader.selectFile();
  4364. } else {
  4365. that.swfUploader.selectFiles();
  4366. }
  4367. };
  4368. /* FLUID-822: while stopping the upload cycle while a file is in mid-upload should be possible
  4369. * in practice, it sets up a state where when the upload cycle is restarted SWFUpload will get stuck
  4370. * therefor we only stop the upload after a file has completed but before the next file begins.
  4371. */
  4372. var stopUpload = function (that) {
  4373. that.queue.shouldStop = true;
  4374. that.events.onUploadStop.fire();
  4375. };
  4376. var bindEvents = function (that) {
  4377. var fileStatusUpdater = function (file) {
  4378. fluid.find(that.queue.files, function (potentialMatch) {
  4379. if (potentialMatch.id === file.id) {
  4380. potentialMatch.filestatus = file.filestatus;
  4381. return true;
  4382. }
  4383. });
  4384. };
  4385. // Add a listener that will keep our file queue model in sync with SWFUpload.
  4386. that.events.afterFileQueued.addListener(function (file) {
  4387. that.queue.addFile(file);
  4388. });
  4389. that.events.onFileStart.addListener(function (file) {
  4390. that.queueManager.startFile();
  4391. fileStatusUpdater(file);
  4392. });
  4393. that.events.onFileProgress.addListener(function (file, currentBytes, totalBytes) {
  4394. var currentBatch = that.queue.currentBatch;
  4395. var byteIncrement = currentBytes - currentBatch.previousBytesUploadedForFile;
  4396. currentBatch.totalBytesUploaded += byteIncrement;
  4397. currentBatch.bytesUploadedForFile += byteIncrement;
  4398. currentBatch.previousBytesUploadedForFile = currentBytes;
  4399. fileStatusUpdater(file);
  4400. });
  4401. that.events.onFileError.addListener(function (file, error) {
  4402. if (error === fluid.uploader.errorConstants.UPLOAD_STOPPED) {
  4403. that.queue.isUploading = false;
  4404. } else if (that.queue.isUploading) {
  4405. that.queue.currentBatch.totalBytesUploaded += file.size;
  4406. that.queue.currentBatch.numFilesErrored++;
  4407. }
  4408. fileStatusUpdater(file);
  4409. });
  4410. that.events.onFileSuccess.addListener(function (file) {
  4411. if (that.queue.currentBatch.bytesUploadedForFile === 0) {
  4412. that.queue.currentBatch.totalBytesUploaded += file.size;
  4413. }
  4414. fileStatusUpdater(file);
  4415. });
  4416. that.events.afterUploadComplete.addListener(function () {
  4417. that.queue.isUploading = false;
  4418. });
  4419. };
  4420. var removeFile = function (that, file) {
  4421. that.queue.removeFile(file);
  4422. that.swfUploader.cancelUpload(file.id);
  4423. that.events.afterFileRemoved.fire(file);
  4424. };
  4425. // Instantiates a new SWFUploader instance and attaches it the upload manager.
  4426. var setupSwfUploadManager = function (that, events) {
  4427. that.events = events;
  4428. that.queue = fluid.fileQueue();
  4429. that.queueManager = fluid.fileQueue.manager(that.queue, that.events);
  4430. // Map the event and settings names to SWFUpload's expectations.
  4431. that.swfUploadSettings = mapNames(swfUploadOptionsMap, that.options);
  4432. mapEvents(that, swfUploadEventMap, that.swfUploadSettings);
  4433. // Setup the instance.
  4434. that.swfUploader = new SWFUpload(that.swfUploadSettings);
  4435. bindEvents(that);
  4436. };
  4437. /**
  4438. * Server Upload Manager is responsible for coordinating with the Flash-based SWFUploader library,
  4439. * providing a simple way to start, pause, and cancel the uploading process. It requires a working
  4440. * server to respond to the upload POST requests.
  4441. *
  4442. * @param {Object} eventBindings an object containing upload lifecycle callbacks
  4443. * @param {Object} options configuration options for the upload manager
  4444. */
  4445. fluid.swfUploadManager = function (events, options) {
  4446. var that = {};
  4447. // This needs to be refactored!
  4448. fluid.mergeComponentOptions(that, "fluid.swfUploadManager", options);
  4449. fluid.mergeListeners(events, that.options.listeners);
  4450. /**
  4451. * Opens the native OS browse file dialog.
  4452. */
  4453. that.browseForFiles = function () {
  4454. browse(that);
  4455. };
  4456. /**
  4457. * Removes the specified file from the upload queue.
  4458. *
  4459. * @param {File} file the file to remove
  4460. */
  4461. that.removeFile = function (file) {
  4462. removeFile(that, file);
  4463. };
  4464. /**
  4465. * Starts uploading all queued files to the server.
  4466. */
  4467. that.start = function () {
  4468. that.queueManager.start();
  4469. that.swfUploader.startUpload();
  4470. };
  4471. /**
  4472. * Cancels an in-progress upload.
  4473. */
  4474. that.stop = function () {
  4475. stopUpload(that);
  4476. };
  4477. setupSwfUploadManager(that, events);
  4478. return that;
  4479. };
  4480. fluid.defaults("fluid.swfUploadManager", {
  4481. uploadURL: "",
  4482. flashURL: "../../../lib/swfupload/flash/swfupload.swf",
  4483. flashButtonPeerId: "",
  4484. postParams: {},
  4485. fileSizeLimit: "20480",
  4486. fileTypes: "*",
  4487. fileTypesDescription: null,
  4488. fileUploadLimit: 0,
  4489. fileQueueLimit: 0,
  4490. debug: false
  4491. });
  4492. })(jQuery, fluid_1_2);
  4493. /*
  4494. Copyright 2008-2009 University of Toronto
  4495. Copyright 2008-2009 University of California, Berkeley
  4496. Copyright 2008-2009 University of Cambridge
  4497. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  4498. BSD license. You may not use this file except in compliance with one these
  4499. Licenses.
  4500. You may obtain a copy of the ECL 2.0 License and BSD License at
  4501. https://source.fluidproject.org/svn/LICENSE.txt
  4502. */
  4503. /*global SWFUpload*/
  4504. /*global swfobject*/
  4505. /*global jQuery*/
  4506. /*global fluid_1_2*/
  4507. fluid_1_2 = fluid_1_2 || {};
  4508. /*******************
  4509. * File Queue View *
  4510. *******************/
  4511. (function ($, fluid) {
  4512. // Real data binding would be nice to replace these two pairs.
  4513. var rowForFile = function (that, file) {
  4514. return that.locate("fileQueue").find("#" + file.id);
  4515. };
  4516. var errorRowForFile = function (that, file) {
  4517. return $("#" + file.id + "_error", that.container);
  4518. };
  4519. var fileForRow = function (that, row) {
  4520. var files = that.uploadManager.queue.files;
  4521. for (var i = 0; i < files.length; i++) {
  4522. var file = files[i];
  4523. if (file.id.toString() === row.attr("id")) {
  4524. return file;
  4525. }
  4526. }
  4527. return null;
  4528. };
  4529. var progressorForFile = function (that, file) {
  4530. var progressId = file.id + "_progress";
  4531. return that.fileProgressors[progressId];
  4532. };
  4533. var startFileProgress = function (that, file) {
  4534. var fileRowElm = rowForFile(that, file);
  4535. that.scroller.scrollTo(fileRowElm);
  4536. // update the progressor and make sure that it's in position
  4537. var fileProgressor = progressorForFile(that, file);
  4538. fileProgressor.refreshView();
  4539. fileProgressor.show();
  4540. };
  4541. var updateFileProgress = function (that, file, fileBytesComplete, fileTotalBytes) {
  4542. var filePercent = fluid.uploader.derivePercent(fileBytesComplete, fileTotalBytes);
  4543. var filePercentStr = filePercent + "%";
  4544. progressorForFile(that, file).update(filePercent, filePercentStr);
  4545. };
  4546. var hideFileProgress = function (that, file) {
  4547. var fileRowElm = rowForFile(that, file);
  4548. progressorForFile(that, file).hide();
  4549. if (file.filestatus === fluid.uploader.fileStatusConstants.COMPLETE) {
  4550. that.locate("fileIconBtn", fileRowElm).removeClass(that.options.styles.dim);
  4551. }
  4552. };
  4553. var removeFileProgress = function (that, file) {
  4554. var fileProgressor = progressorForFile(that, file);
  4555. if (!fileProgressor) {
  4556. return;
  4557. }
  4558. var rowProgressor = fileProgressor.displayElement;
  4559. rowProgressor.remove();
  4560. };
  4561. var animateRowRemoval = function (that, row) {
  4562. row.fadeOut("fast", function () {
  4563. row.remove();
  4564. that.refreshView();
  4565. });
  4566. };
  4567. var removeFileErrorRow = function (that, file) {
  4568. if (file.filestatus === fluid.uploader.fileStatusConstants.ERROR) {
  4569. animateRowRemoval(that, errorRowForFile(that, file));
  4570. }
  4571. };
  4572. var removeFileAndRow = function (that, file, row) {
  4573. // Clean up the stuff associated with a file row.
  4574. removeFileProgress(that, file);
  4575. removeFileErrorRow(that, file);
  4576. // Remove the file itself.
  4577. that.uploadManager.removeFile(file);
  4578. animateRowRemoval(that, row);
  4579. };
  4580. var removeFileForRow = function (that, row) {
  4581. var file = fileForRow(that, row);
  4582. if (!file || file.filestatus === fluid.uploader.fileStatusConstants.COMPLETE) {
  4583. return;
  4584. }
  4585. removeFileAndRow(that, file, row);
  4586. };
  4587. var removeRowForFile = function (that, file) {
  4588. var row = rowForFile(that, file);
  4589. removeFileAndRow(that, file, row);
  4590. };
  4591. var bindHover = function (row, styles) {
  4592. var over = function () {
  4593. if (row.hasClass(styles.ready) && !row.hasClass(styles.uploading)) {
  4594. row.addClass(styles.hover);
  4595. }
  4596. };
  4597. var out = function () {
  4598. if (row.hasClass(styles.ready) && !row.hasClass(styles.uploading)) {
  4599. row.removeClass(styles.hover);
  4600. }
  4601. };
  4602. row.hover(over, out);
  4603. };
  4604. var bindDeleteKey = function (that, row) {
  4605. var deleteHandler = function () {
  4606. removeFileForRow(that, row);
  4607. };
  4608. fluid.activatable(row, null, {
  4609. additionalBindings: [{
  4610. key: $.ui.keyCode.DELETE,
  4611. activateHandler: deleteHandler
  4612. }]
  4613. });
  4614. };
  4615. var bindRowHandlers = function (that, row) {
  4616. if ($.browser.msie && $.browser.version < 7) {
  4617. bindHover(row, that.options.styles);
  4618. }
  4619. that.locate("fileIconBtn", row).click(function () {
  4620. removeFileForRow(that, row);
  4621. });
  4622. bindDeleteKey(that, row);
  4623. };
  4624. var renderRowFromTemplate = function (that, file) {
  4625. var row = that.rowTemplate.clone();
  4626. row.removeClass(that.options.styles.hiddenTemplate);
  4627. that.locate("fileName", row).text(file.name);
  4628. that.locate("fileSize", row).text(fluid.uploader.formatFileSize(file.size));
  4629. that.locate("fileIconBtn", row).addClass(that.options.styles.remove);
  4630. row.attr("id", file.id);
  4631. row.addClass(that.options.styles.ready);
  4632. bindRowHandlers(that, row);
  4633. return row;
  4634. };
  4635. var createProgressorFromTemplate = function (that, row) {
  4636. // create a new progress bar for the row and position it
  4637. var rowProgressor = that.rowProgressorTemplate.clone();
  4638. var rowId = row.attr("id");
  4639. var progressId = rowId + "_progress";
  4640. rowProgressor.attr("id", progressId);
  4641. rowProgressor.css("top", row.position().top);
  4642. rowProgressor.height(row.height()).width(5);
  4643. that.container.after(rowProgressor);
  4644. that.fileProgressors[progressId] = fluid.progress(that.uploadContainer, {
  4645. selectors: {
  4646. progressBar: "#" + rowId,
  4647. displayElement: "#" + progressId,
  4648. label: "#" + progressId + " .fl-uploader-file-progress-text",
  4649. indicator: "#" + progressId
  4650. }
  4651. });
  4652. };
  4653. var addFile = function (that, file) {
  4654. var row = renderRowFromTemplate(that, file);
  4655. /* FLUID-2720 - do not hide the row under IE8 */
  4656. if (!($.browser.msie && ($.browser.version >= 8))) {
  4657. row.hide();
  4658. }
  4659. that.container.append(row);
  4660. row.fadeIn("slow");
  4661. that.scroller.scrollBottom();
  4662. createProgressorFromTemplate(that, row);
  4663. that.refreshView();
  4664. };
  4665. var prepareForUpload = function (that) {
  4666. var rowButtons = that.locate("fileIconBtn", that.locate("fileRows"));
  4667. rowButtons.attr("disabled", "disabled");
  4668. rowButtons.addClass(that.options.styles.dim);
  4669. };
  4670. var refreshAfterUpload = function (that) {
  4671. var rowButtons = that.locate("fileIconBtn", that.locate("fileRows"));
  4672. rowButtons.removeAttr("disabled");
  4673. rowButtons.removeClass(that.options.styles.dim);
  4674. };
  4675. var changeRowState = function (that, row, newState) {
  4676. row.removeClass(that.options.styles.ready).removeClass(that.options.styles.error).addClass(newState);
  4677. };
  4678. var markRowAsComplete = function (that, file) {
  4679. // update styles and keyboard bindings for the file row
  4680. var row = rowForFile(that, file);
  4681. changeRowState(that, row, that.options.styles.uploaded);
  4682. row.attr("title", that.options.strings.status.success);
  4683. fluid.enabled(row, false);
  4684. // update the click event and the styling for the file delete button
  4685. var removeRowBtn = that.locate("fileIconBtn", row);
  4686. removeRowBtn.unbind("click");
  4687. removeRowBtn.removeClass(that.options.styles.remove);
  4688. removeRowBtn.attr("title", that.options.strings.status.success);
  4689. };
  4690. var renderErrorInfoRowFromTemplate = function (that, fileRow, error) {
  4691. // Render the row by cloning the template and binding its id to the file.
  4692. var errorRow = that.errorInfoRowTemplate.clone();
  4693. errorRow.attr("id", fileRow.attr("id") + "_error");
  4694. // Look up the error message and render it.
  4695. var errorType = fluid.keyForValue(fluid.uploader.errorConstants, error);
  4696. var errorMsg = that.options.strings.errors[errorType];
  4697. that.locate("errorText", errorRow).text(errorMsg);
  4698. fileRow.after(errorRow);
  4699. that.scroller.scrollTo(errorRow);
  4700. };
  4701. var showErrorForFile = function (that, file, error) {
  4702. hideFileProgress(that, file);
  4703. if (file.filestatus === fluid.uploader.fileStatusConstants.ERROR) {
  4704. var fileRowElm = rowForFile(that, file);
  4705. changeRowState(that, fileRowElm, that.options.styles.error);
  4706. renderErrorInfoRowFromTemplate(that, fileRowElm, error);
  4707. }
  4708. };
  4709. var bindModelEvents = function (that) {
  4710. that.returnedOptions = {
  4711. listeners: {
  4712. afterFileQueued: that.addFile,
  4713. onUploadStart: that.prepareForUpload,
  4714. onFileStart: that.showFileProgress,
  4715. onFileProgress: that.updateFileProgress,
  4716. onFileSuccess: that.markFileComplete,
  4717. onFileError: that.showErrorForFile,
  4718. afterFileComplete: that.hideFileProgress,
  4719. afterUploadComplete: that.refreshAfterUpload
  4720. }
  4721. };
  4722. };
  4723. var addKeyboardNavigation = function (that) {
  4724. fluid.tabbable(that.container);
  4725. that.selectableContext = fluid.selectable(that.container, {
  4726. selectableSelector: that.options.selectors.fileRows,
  4727. onSelect: function (itemToSelect) {
  4728. $(itemToSelect).addClass(that.options.styles.selected);
  4729. },
  4730. onUnselect: function (selectedItem) {
  4731. $(selectedItem).removeClass(that.options.styles.selected);
  4732. }
  4733. });
  4734. };
  4735. var prepareTemplateElements = function (that) {
  4736. // Grab our template elements out of the DOM.
  4737. that.rowTemplate = that.locate("rowTemplate").remove();
  4738. that.errorInfoRowTemplate = that.locate("errorInfoRowTemplate").remove();
  4739. that.errorInfoRowTemplate.removeClass(that.options.styles.hiddenTemplate);
  4740. that.rowProgressorTemplate = that.locate("rowProgressorTemplate", that.uploadContainer).remove();
  4741. };
  4742. var setupFileQueue = function (that, uploadManager) {
  4743. that.uploadManager = uploadManager;
  4744. that.scroller = fluid.scroller(that.container);
  4745. prepareTemplateElements(that);
  4746. addKeyboardNavigation(that);
  4747. bindModelEvents(that);
  4748. };
  4749. /**
  4750. * Creates a new File Queue view.
  4751. *
  4752. * @param {jQuery|selector} container the file queue's container DOM element
  4753. * @param {UploadManager} uploadManager an upload manager model instance
  4754. * @param {Object} options configuration options for the view
  4755. */
  4756. fluid.fileQueueView = function (container, parentContainer, uploadManager, options) {
  4757. var that = fluid.initView("fluid.fileQueueView", container, options);
  4758. that.uploadContainer = parentContainer;
  4759. that.fileProgressors = {};
  4760. that.addFile = function (file) {
  4761. addFile(that, file);
  4762. };
  4763. that.removeFile = function (file) {
  4764. removeRowForFile(that, file);
  4765. };
  4766. that.prepareForUpload = function () {
  4767. prepareForUpload(that);
  4768. };
  4769. that.refreshAfterUpload = function () {
  4770. refreshAfterUpload(that);
  4771. };
  4772. that.showFileProgress = function (file) {
  4773. startFileProgress(that, file);
  4774. };
  4775. that.updateFileProgress = function (file, fileBytesComplete, fileTotalBytes) {
  4776. updateFileProgress(that, file, fileBytesComplete, fileTotalBytes);
  4777. };
  4778. that.markFileComplete = function (file) {
  4779. progressorForFile(that, file).update(100, "100%");
  4780. markRowAsComplete(that, file);
  4781. };
  4782. that.showErrorForFile = function (file, error) {
  4783. showErrorForFile(that, file, error);
  4784. };
  4785. that.hideFileProgress = function (file) {
  4786. hideFileProgress(that, file);
  4787. };
  4788. that.refreshView = function () {
  4789. that.scroller.refreshView();
  4790. that.selectableContext.refresh();
  4791. };
  4792. setupFileQueue(that, uploadManager);
  4793. return that;
  4794. };
  4795. fluid.defaults("fluid.fileQueueView", {
  4796. selectors: {
  4797. fileRows: ".flc-uploader-file",
  4798. fileName: ".flc-uploader-file-name",
  4799. fileSize: ".flc-uploader-file-size",
  4800. fileIconBtn: ".flc-uploader-file-action",
  4801. errorText: ".flc-uploader-file-error",
  4802. rowTemplate: ".flc-uploader-file-tmplt",
  4803. errorInfoRowTemplate: ".flc-uploader-file-error-tmplt",
  4804. rowProgressorTemplate: ".flc-uploader-file-progressor-tmplt"
  4805. },
  4806. styles: {
  4807. hover: "fl-uploader-file-hover",
  4808. selected: "fl-uploader-file-focus",
  4809. ready: "fl-uploader-file-state-ready",
  4810. uploading: "fl-uploader-file-state-uploading",
  4811. uploaded: "fl-uploader-file-state-uploaded",
  4812. error: "fl-uploader-file-state-error",
  4813. remove: "fl-uploader-file-action-remove",
  4814. dim: "fl-uploader-dim",
  4815. hiddenTemplate: "fl-uploader-hidden-templates"
  4816. },
  4817. strings: {
  4818. progress: {
  4819. toUploadLabel: "To upload: %fileCount %fileLabel (%totalBytes)",
  4820. singleFile: "file",
  4821. pluralFiles: "files"
  4822. },
  4823. status: {
  4824. success: "File Uploaded",
  4825. error: "File Upload Error"
  4826. },
  4827. errors: {
  4828. HTTP_ERROR: "File upload error: a network error occured or the file was rejected (reason unknown).",
  4829. IO_ERROR: "File upload error: a network error occured.",
  4830. UPLOAD_LIMIT_EXCEEDED: "File upload error: you have uploaded as many files as you are allowed during this session",
  4831. UPLOAD_FAILED: "File upload error: the upload failed for an unknown reason.",
  4832. QUEUE_LIMIT_EXCEEDED: "You have as many files in the queue as can be added at one time. Removing files from the queue may allow you to add different files.",
  4833. FILE_EXCEEDS_SIZE_LIMIT: "One or more of the files that you attempted to add to the queue exceeded the limit of %fileSizeLimit.",
  4834. ZERO_BYTE_FILE: "One or more of the files that you attempted to add contained no data.",
  4835. INVALID_FILETYPE: "One or more files were not added to the queue because they were of the wrong type."
  4836. }
  4837. }
  4838. });
  4839. })(jQuery, fluid_1_2);
  4840. /************
  4841. * Uploader *
  4842. ************/
  4843. (function ($, fluid) {
  4844. var fileOrFiles = function (that, numFiles) {
  4845. return (numFiles === 1) ? that.options.strings.progress.singleFile :
  4846. that.options.strings.progress.pluralFiles;
  4847. };
  4848. var enableElement = function (that, elm) {
  4849. elm.removeAttr("disabled");
  4850. elm.removeClass(that.options.styles.dim);
  4851. };
  4852. var disableElement = function (that, elm) {
  4853. elm.attr("disabled", "disabled");
  4854. elm.addClass(that.options.styles.dim);
  4855. };
  4856. var showElement = function (that, elm) {
  4857. elm.removeClass(that.options.styles.hidden);
  4858. };
  4859. var hideElement = function (that, elm) {
  4860. elm.addClass(that.options.styles.hidden);
  4861. };
  4862. var setTotalProgressStyle = function (that, didError) {
  4863. didError = didError || false;
  4864. var indicator = that.totalProgress.indicator;
  4865. indicator.toggleClass(that.options.styles.totalProgress, !didError);
  4866. indicator.toggleClass(that.options.styles.totalProgressError, didError);
  4867. };
  4868. var setStateEmpty = function (that) {
  4869. disableElement(that, that.locate("uploadButton"));
  4870. // If the queue is totally empty, treat it specially.
  4871. if (that.uploadManager.queue.files.length === 0) {
  4872. that.locate("browseButton").text(that.options.strings.buttons.browse);
  4873. showElement(that, that.locate("instructions"));
  4874. }
  4875. };
  4876. var setStateDone = function (that) {
  4877. disableElement(that, that.locate("uploadButton"));
  4878. enableElement(that, that.locate("browseButton"));
  4879. hideElement(that, that.locate("pauseButton"));
  4880. showElement(that, that.locate("uploadButton"));
  4881. };
  4882. var setStateLoaded = function (that) {
  4883. that.locate("browseButton").text(that.options.strings.buttons.addMore);
  4884. hideElement(that, that.locate("pauseButton"));
  4885. showElement(that, that.locate("uploadButton"));
  4886. enableElement(that, that.locate("uploadButton"));
  4887. enableElement(that, that.locate("browseButton"));
  4888. hideElement(that, that.locate("instructions"));
  4889. that.totalProgress.hide();
  4890. };
  4891. var setStateUploading = function (that) {
  4892. that.totalProgress.hide(false, false);
  4893. setTotalProgressStyle(that);
  4894. hideElement(that, that.locate("uploadButton"));
  4895. disableElement(that, that.locate("browseButton"));
  4896. enableElement(that, that.locate("pauseButton"));
  4897. showElement(that, that.locate("pauseButton"));
  4898. that.locate(that.options.focusWithEvent.afterUploadStart).focus();
  4899. };
  4900. var renderUploadTotalMessage = function (that) {
  4901. // Render template for the total file status message.
  4902. var numReadyFiles = that.uploadManager.queue.getReadyFiles().length;
  4903. var bytesReadyFiles = that.uploadManager.queue.sizeOfReadyFiles();
  4904. var fileLabelStr = fileOrFiles(that, numReadyFiles);
  4905. var totalStateStr = fluid.stringTemplate(that.options.strings.progress.toUploadLabel, {
  4906. fileCount: numReadyFiles,
  4907. fileLabel: fileLabelStr,
  4908. totalBytes: fluid.uploader.formatFileSize(bytesReadyFiles)
  4909. });
  4910. that.locate("totalFileStatusText").html(totalStateStr);
  4911. };
  4912. var updateTotalProgress = function (that) {
  4913. var batch = that.uploadManager.queue.currentBatch;
  4914. var totalPercent = fluid.uploader.derivePercent(batch.totalBytesUploaded, batch.totalBytes);
  4915. var numFilesInBatch = batch.files.length;
  4916. var fileLabelStr = fileOrFiles(that, numFilesInBatch);
  4917. var totalProgressStr = fluid.stringTemplate(that.options.strings.progress.totalProgressLabel, {
  4918. curFileN: batch.fileIdx + 1,
  4919. totalFilesN: numFilesInBatch,
  4920. fileLabel: fileLabelStr,
  4921. currBytes: fluid.uploader.formatFileSize(batch.totalBytesUploaded),
  4922. totalBytes: fluid.uploader.formatFileSize(batch.totalBytes)
  4923. });
  4924. that.totalProgress.update(totalPercent, totalProgressStr);
  4925. };
  4926. var updateTotalAtCompletion = function (that) {
  4927. var numErroredFiles = that.uploadManager.queue.getErroredFiles().length;
  4928. var numTotalFiles = that.uploadManager.queue.files.length;
  4929. var fileLabelStr = fileOrFiles(that, numTotalFiles);
  4930. var errorStr = "";
  4931. // if there are errors then change the total progress bar
  4932. // and set up the errorStr so that we can use it in the totalProgressStr
  4933. if (numErroredFiles > 0) {
  4934. var errorLabelString = (numErroredFiles === 1) ? that.options.strings.progress.singleError :
  4935. that.options.strings.progress.pluralErrors;
  4936. setTotalProgressStyle(that, true);
  4937. errorStr = fluid.stringTemplate(that.options.strings.progress.numberOfErrors, {
  4938. errorsN: numErroredFiles,
  4939. errorLabel: errorLabelString
  4940. });
  4941. }
  4942. var totalProgressStr = fluid.stringTemplate(that.options.strings.progress.completedLabel, {
  4943. curFileN: that.uploadManager.queue.getUploadedFiles().length,
  4944. totalFilesN: numTotalFiles,
  4945. errorString: errorStr,
  4946. fileLabel: fileLabelStr,
  4947. totalCurrBytes: fluid.uploader.formatFileSize(that.uploadManager.queue.sizeOfUploadedFiles())
  4948. });
  4949. that.totalProgress.update(100, totalProgressStr);
  4950. };
  4951. var bindDOMEvents = function (that) {
  4952. that.locate("browseButton").click(function (evnt) {
  4953. that.uploadManager.browseForFiles();
  4954. evnt.preventDefault();
  4955. });
  4956. that.locate("uploadButton").click(function () {
  4957. that.uploadManager.start();
  4958. });
  4959. that.locate("pauseButton").click(function () {
  4960. that.uploadManager.stop();
  4961. });
  4962. };
  4963. var updateStateAfterFileDialog = function (that) {
  4964. if (that.uploadManager.queue.getReadyFiles().length > 0) {
  4965. setStateLoaded(that);
  4966. renderUploadTotalMessage(that);
  4967. that.locate(that.options.focusWithEvent.afterFileDialog).focus();
  4968. }
  4969. };
  4970. var updateStateAfterFileRemoval = function (that) {
  4971. if (that.uploadManager.queue.getReadyFiles().length === 0) {
  4972. setStateEmpty(that);
  4973. }
  4974. renderUploadTotalMessage(that);
  4975. };
  4976. var updateStateAfterPause = function (that) {
  4977. // do nothing, moved to afterUploadComplete
  4978. };
  4979. var updateStateAfterCompletion = function (that) {
  4980. var userPaused = that.uploadManager.queue.shouldStop;
  4981. if (that.uploadManager.queue.getReadyFiles().length === 0) {
  4982. setStateDone(that);
  4983. } else {
  4984. setStateLoaded(that);
  4985. }
  4986. updateTotalAtCompletion(that);
  4987. };
  4988. var bindModelEvents = function (that) {
  4989. that.events.afterFileDialog.addListener(function () {
  4990. updateStateAfterFileDialog(that);
  4991. });
  4992. that.events.afterFileRemoved.addListener(function () {
  4993. updateStateAfterFileRemoval(that);
  4994. });
  4995. that.events.onUploadStart.addListener(function () {
  4996. setStateUploading(that);
  4997. });
  4998. that.events.onUploadStop.addListener(function () {
  4999. that.locate(that.options.focusWithEvent.afterUploadStop).focus();
  5000. });
  5001. that.events.onFileProgress.addListener(function () {
  5002. updateTotalProgress(that);
  5003. });
  5004. that.events.onFileSuccess.addListener(function () {
  5005. updateTotalProgress(that);
  5006. });
  5007. that.events.onFileError.addListener(function (file, error, message) {
  5008. if (error === fluid.uploader.errorConstants.UPLOAD_STOPPED) {
  5009. updateStateAfterPause(that);
  5010. }
  5011. });
  5012. that.events.afterUploadComplete.addListener(function () {
  5013. updateStateAfterCompletion(that);
  5014. });
  5015. };
  5016. var initUploadManager = function (that) {
  5017. var manager = fluid.initSubcomponent(that,
  5018. "uploadManager",
  5019. [that.events, fluid.COMPONENT_OPTIONS]);
  5020. return that.options.demo ? fluid.demoUploadManager(manager) : manager;
  5021. };
  5022. var setupUploader = function (that) {
  5023. // Instantiate the upload manager, file queue view, and total file progress bar,
  5024. // passing them smaller chunks of the overall options for the uploader.
  5025. that.decorators = fluid.initSubcomponents(that, "decorators", [that, fluid.COMPONENT_OPTIONS]);
  5026. that.uploadManager = initUploadManager(that);
  5027. that.fileQueueView = fluid.initSubcomponent(that,
  5028. "fileQueueView",
  5029. [that.locate("fileQueue"),
  5030. that.container,
  5031. that.uploadManager,
  5032. fluid.COMPONENT_OPTIONS]);
  5033. that.totalProgress = fluid.initSubcomponent(that,
  5034. "totalProgressBar",
  5035. [that.container, fluid.COMPONENT_OPTIONS]);
  5036. // Upload button should not be enabled until there are files to upload
  5037. disableElement(that, that.locate("uploadButton"));
  5038. bindDOMEvents(that);
  5039. bindModelEvents(that);
  5040. };
  5041. /**
  5042. * Instantiates a new Uploader component.
  5043. *
  5044. * @param {Object} container the DOM element in which the Uploader lives
  5045. * @param {Object} options configuration options for the component.
  5046. */
  5047. fluid.uploader = function (container, options) {
  5048. var that = fluid.initView("fluid.uploader", container, options);
  5049. setupUploader(that);
  5050. return that;
  5051. };
  5052. /**
  5053. * Instantiates a new Uploader component in the progressive enhancement style.
  5054. * This mode requires another DOM element to be present, the element that is to be enhanced.
  5055. * This method checks to see if the correct version of Flash is present, and will only
  5056. * create the Uploader component if so.
  5057. *
  5058. * @param {Object} container the DOM element in which the Uploader component lives
  5059. * @param {Object} enhanceable the DOM element to show if the system requirements aren't met
  5060. * @param {Object} options configuration options for the component
  5061. */
  5062. fluid.progressiveEnhanceableUploader = function (container, enhanceable, options) {
  5063. enhanceable = fluid.container(enhanceable);
  5064. container = fluid.container(container);
  5065. if (swfobject.getFlashPlayerVersion().major < 9) {
  5066. // Degrade gracefully.
  5067. enhanceable.show();
  5068. } else {
  5069. // Instantiate the component.
  5070. container.show();
  5071. return fluid.uploader(container, options);
  5072. }
  5073. };
  5074. /**
  5075. * Pretty prints a file's size, converting from bytes to kilobytes or megabytes.
  5076. *
  5077. * @param {Number} bytes the files size, specified as in number bytes.
  5078. */
  5079. fluid.uploader.formatFileSize = function (bytes) {
  5080. if (typeof bytes === "number") {
  5081. if (bytes === 0) {
  5082. return "0.0 KB";
  5083. } else if (bytes > 0) {
  5084. if (bytes < 1048576) {
  5085. return (Math.ceil(bytes / 1024 * 10) / 10).toFixed(1) + " KB";
  5086. }
  5087. else {
  5088. return (Math.ceil(bytes / 1048576 * 10) / 10).toFixed(1) + " MB";
  5089. }
  5090. }
  5091. }
  5092. return "";
  5093. };
  5094. fluid.uploader.derivePercent = function (num, total) {
  5095. return Math.round((num * 100) / total);
  5096. };
  5097. fluid.defaults("fluid.uploader", {
  5098. demo: false,
  5099. decorators: [{
  5100. type: "fluid.swfUploadSetupDecorator"
  5101. }, {
  5102. type: "fluid.manuallyDegrade",
  5103. options: {
  5104. selectors: {
  5105. enhanceable: ".fl-uploader.fl-progEnhance-basic"
  5106. }
  5107. }
  5108. }],
  5109. uploadManager: {
  5110. type: "fluid.swfUploadManager"
  5111. },
  5112. fileQueueView: {
  5113. type: "fluid.fileQueueView"
  5114. },
  5115. totalProgressBar: {
  5116. type: "fluid.progress",
  5117. options: {
  5118. selectors: {
  5119. progressBar: ".flc-uploader-queue-footer",
  5120. displayElement: ".flc-uploader-total-progress",
  5121. label: ".flc-uploader-total-progress-text",
  5122. indicator: ".flc-uploader-total-progress",
  5123. ariaElement: ".flc-uploader-total-progress"
  5124. }
  5125. }
  5126. },
  5127. selectors: {
  5128. fileQueue: ".flc-uploader-queue",
  5129. browseButton: ".flc-uploader-button-browse",
  5130. uploadButton: ".flc-uploader-button-upload",
  5131. pauseButton: ".flc-uploader-button-pause",
  5132. totalFileStatusText: ".flc-uploader-total-progress-text",
  5133. instructions: ".flc-uploader-browse-instructions"
  5134. },
  5135. // Event listeners must already be implemented to use these options.
  5136. // At the moment, the following events are supported:
  5137. // afterFileDialog, afterUploadStart, and afterUploadStop.
  5138. focusWithEvent: {
  5139. afterFileDialog: "uploadButton",
  5140. afterUploadStart: "pauseButton",
  5141. afterUploadStop: "uploadButton"
  5142. },
  5143. styles: {
  5144. disabled: "fl-uploader-disabled",
  5145. hidden: "fl-uploader-hidden",
  5146. dim: "fl-uploader-dim",
  5147. totalProgress: "fl-uploader-total-progress-okay",
  5148. totalProgressError: "fl-uploader-total-progress-errored"
  5149. },
  5150. events: {
  5151. afterReady: null,
  5152. onFileDialog: null,
  5153. afterFileQueued: null,
  5154. afterFileRemoved: null,
  5155. onQueueError: null,
  5156. afterFileDialog: null,
  5157. onUploadStart: null,
  5158. onUploadStop: null,
  5159. onFileStart: null,
  5160. onFileProgress: null,
  5161. onFileError: null,
  5162. onFileSuccess: null,
  5163. afterFileComplete: null,
  5164. afterUploadComplete: null
  5165. },
  5166. strings: {
  5167. progress: {
  5168. toUploadLabel: "To upload: %fileCount %fileLabel (%totalBytes)",
  5169. totalProgressLabel: "Uploading: %curFileN of %totalFilesN %fileLabel (%currBytes of %totalBytes)",
  5170. completedLabel: "Uploaded: %curFileN of %totalFilesN %fileLabel (%totalCurrBytes)%errorString",
  5171. numberOfErrors: ", %errorsN %errorLabel",
  5172. singleFile: "file",
  5173. pluralFiles: "files",
  5174. singleError: "error",
  5175. pluralErrors: "errors"
  5176. },
  5177. buttons: {
  5178. browse: "Browse Files",
  5179. addMore: "Add More",
  5180. stopUpload: "Stop Upload",
  5181. cancelRemaning: "Cancel remaining Uploads",
  5182. resumeUpload: "Resume Upload"
  5183. }
  5184. }
  5185. });
  5186. fluid.uploader.errorConstants = {
  5187. HTTP_ERROR: -200,
  5188. MISSING_UPLOAD_URL: -210,
  5189. IO_ERROR: -220,
  5190. SECURITY_ERROR: -230,
  5191. UPLOAD_LIMIT_EXCEEDED: -240,
  5192. UPLOAD_FAILED: -250,
  5193. SPECIFIED_FILE_ID_NOT_FOUND: -260,
  5194. FILE_VALIDATION_FAILED: -270,
  5195. FILE_CANCELLED: -280,
  5196. UPLOAD_STOPPED: -290
  5197. };
  5198. fluid.uploader.fileStatusConstants = {
  5199. QUEUED: -1,
  5200. IN_PROGRESS: -2,
  5201. ERROR: -3,
  5202. COMPLETE: -4,
  5203. CANCELLED: -5
  5204. };
  5205. /*******************
  5206. * ManuallyDegrade *
  5207. *******************/
  5208. var renderLink = function (renderLocation, text, classes, appendBeside) {
  5209. var link = $("<a href='#'>" + text + "</a>");
  5210. link.addClass(classes);
  5211. if (renderLocation === "before") {
  5212. appendBeside.before(link);
  5213. } else {
  5214. appendBeside.after(link);
  5215. }
  5216. return link;
  5217. };
  5218. var toggleVisibility = function (toShow, toHide) {
  5219. // For FLUID-2789: hide() doesn't work in Opera, so this check
  5220. // uses a style to hide if the browser is Opera
  5221. if (window.opera) {
  5222. toShow.show().removeClass("hideUploaderForOpera");
  5223. toHide.show().addClass("hideUploaderForOpera");
  5224. } else {
  5225. toShow.show();
  5226. toHide.hide();
  5227. }
  5228. };
  5229. var defaultControlRenderer = function (that) {
  5230. var degradeLink = renderLink(that.options.defaultRenderLocation,
  5231. that.options.strings.degradeLinkText,
  5232. that.options.styles.degradeLinkClass,
  5233. that.enhancedContainer);
  5234. degradeLink.addClass("flc-manuallyDegrade-degrade");
  5235. var enhanceLink = renderLink(that.options.defaultRenderLocation,
  5236. that.options.strings.enhanceLinkText,
  5237. that.options.styles.enhanceLinkClass,
  5238. that.degradedContainer);
  5239. enhanceLink.addClass("flc-manuallyDegrade-enhance");
  5240. };
  5241. var fetchControls = function (that) {
  5242. that.degradeControl = that.locate("degradeControl");
  5243. that.enhanceControl = that.locate("enhanceControl");
  5244. };
  5245. var setupManuallyDegrade = function (that) {
  5246. // If we don't have anything to degrade to, stop right here.
  5247. if (!that.degradedContainer.length) {
  5248. return;
  5249. }
  5250. // Render the controls if they're not already there.
  5251. fetchControls(that);
  5252. if (!that.degradeControl.length && !that.enhanceControl.length) {
  5253. that.options.controlRenderer(that);
  5254. fetchControls(that);
  5255. }
  5256. // Bind click handlers to them.
  5257. that.degradeControl.click(that.degrade);
  5258. that.enhanceControl.click(that.enhance);
  5259. // Hide the enhance link to start.
  5260. that.enhanceControl.hide();
  5261. };
  5262. var determineContainer = function (options) {
  5263. var defaults = fluid.defaults("fluid.manuallyDegrade");
  5264. return (options && options.container) ? options.container : defaults.container;
  5265. };
  5266. fluid.manuallyDegrade = function (component, options) {
  5267. var container = determineContainer(options);
  5268. var that = fluid.initView("fluid.manuallyDegrade", container, options);
  5269. var isDegraded = false;
  5270. that.enhancedContainer = component.container;
  5271. that.degradedContainer = that.locate("enhanceable");
  5272. that.degrade = function () {
  5273. toggleVisibility(that.enhanceControl, that.degradeControl);
  5274. toggleVisibility(that.degradedContainer, that.enhancedContainer);
  5275. isDegraded = true;
  5276. };
  5277. that.enhance = function () {
  5278. toggleVisibility(that.degradeControl, that.enhanceControl);
  5279. toggleVisibility(that.enhancedContainer, that.degradedContainer);
  5280. isDegraded = false;
  5281. };
  5282. that.isDegraded = function () {
  5283. return isDegraded;
  5284. };
  5285. setupManuallyDegrade(that);
  5286. return that;
  5287. };
  5288. fluid.defaults("fluid.manuallyDegrade", {
  5289. container: "body",
  5290. controlRenderer: defaultControlRenderer,
  5291. defaultRenderLocation: "before",
  5292. strings: {
  5293. degradeLinkText: "Switch to the standard single-file Uploader",
  5294. enhanceLinkText: "Switch to the Flash-based multi-file Uploader"
  5295. },
  5296. selectors: {
  5297. enhanceable: ".fl-ProgEnhance-basic",
  5298. degradeControl: ".flc-manuallyDegrade-degrade",
  5299. enhanceControl: ".flc-manuallyDegrade-enhance"
  5300. },
  5301. styles: {
  5302. degradeLinkClass: "fl-uploader-manually-degrade",
  5303. enhanceLinkClass: "fl-uploader-manually-enhance"
  5304. }
  5305. });
  5306. })(jQuery, fluid_1_2);
  5307. /*
  5308. json2.js
  5309. 2007-11-06
  5310. Public Domain
  5311. No warranty expressed or implied. Use at your own risk.
  5312. See http://www.JSON.org/js.html
  5313. This file creates a global JSON object containing two methods:
  5314. JSON.stringify(value, whitelist)
  5315. value any JavaScript value, usually an object or array.
  5316. whitelist an optional that determines how object values are
  5317. stringified.
  5318. This method produces a JSON text from a JavaScript value.
  5319. There are three possible ways to stringify an object, depending
  5320. on the optional whitelist parameter.
  5321. If an object has a toJSON method, then the toJSON() method will be
  5322. called. The value returned from the toJSON method will be
  5323. stringified.
  5324. Otherwise, if the optional whitelist parameter is an array, then
  5325. the elements of the array will be used to select members of the
  5326. object for stringification.
  5327. Otherwise, if there is no whitelist parameter, then all of the
  5328. members of the object will be stringified.
  5329. Values that do not have JSON representaions, such as undefined or
  5330. functions, will not be serialized. Such values in objects will be
  5331. dropped, in arrays will be replaced with null. JSON.stringify()
  5332. returns undefined. Dates will be stringified as quoted ISO dates.
  5333. Example:
  5334. var text = JSON.stringify(['e', {pluribus: 'unum'}]);
  5335. // text is '["e",{"pluribus":"unum"}]'
  5336. JSON.parse(text, filter)
  5337. This method parses a JSON text to produce an object or
  5338. array. It can throw a SyntaxError exception.
  5339. The optional filter parameter is a function that can filter and
  5340. transform the results. It receives each of the keys and values, and
  5341. its return value is used instead of the original value. If it
  5342. returns what it received, then structure is not modified. If it
  5343. returns undefined then the member is deleted.
  5344. Example:
  5345. // Parse the text. If a key contains the string 'date' then
  5346. // convert the value to a date.
  5347. myData = JSON.parse(text, function (key, value) {
  5348. return key.indexOf('date') >= 0 ? new Date(value) : value;
  5349. });
  5350. This is a reference implementation. You are free to copy, modify, or
  5351. redistribute.
  5352. Use your own copy. It is extremely unwise to load third party
  5353. code into your pages.
  5354. */
  5355. /*jslint evil: true */
  5356. /*extern JSON */
  5357. if (!this.JSON) {
  5358. JSON = function () {
  5359. function f(n) { // Format integers to have at least two digits.
  5360. return n < 10 ? '0' + n : n;
  5361. }
  5362. Date.prototype.toJSON = function () {
  5363. // Eventually, this method will be based on the date.toISOString method.
  5364. return this.getUTCFullYear() + '-' +
  5365. f(this.getUTCMonth() + 1) + '-' +
  5366. f(this.getUTCDate()) + 'T' +
  5367. f(this.getUTCHours()) + ':' +
  5368. f(this.getUTCMinutes()) + ':' +
  5369. f(this.getUTCSeconds()) + 'Z';
  5370. };
  5371. var m = { // table of character substitutions
  5372. '\b': '\\b',
  5373. '\t': '\\t',
  5374. '\n': '\\n',
  5375. '\f': '\\f',
  5376. '\r': '\\r',
  5377. '"' : '\\"',
  5378. '\\': '\\\\'
  5379. };
  5380. function stringify(value, whitelist) {
  5381. var a, // The array holding the partial texts.
  5382. i, // The loop counter.
  5383. k, // The member key.
  5384. l, // Length.
  5385. r = /["\\\x00-\x1f\x7f-\x9f]/g,
  5386. v; // The member value.
  5387. switch (typeof value) {
  5388. case 'string':
  5389. // If the string contains no control characters, no quote characters, and no
  5390. // backslash characters, then we can safely slap some quotes around it.
  5391. // Otherwise we must also replace the offending characters with safe sequences.
  5392. return r.test(value) ?
  5393. '"' + value.replace(r, function (a) {
  5394. var c = m[a];
  5395. if (c) {
  5396. return c;
  5397. }
  5398. c = a.charCodeAt();
  5399. return '\\u00' + Math.floor(c / 16).toString(16) +
  5400. (c % 16).toString(16);
  5401. }) + '"' :
  5402. '"' + value + '"';
  5403. case 'number':
  5404. // JSON numbers must be finite. Encode non-finite numbers as null.
  5405. return isFinite(value) ? String(value) : 'null';
  5406. case 'boolean':
  5407. case 'null':
  5408. return String(value);
  5409. case 'object':
  5410. // Due to a specification blunder in ECMAScript,
  5411. // typeof null is 'object', so watch out for that case.
  5412. if (!value) {
  5413. return 'null';
  5414. }
  5415. // If the object has a toJSON method, call it, and stringify the result.
  5416. if (typeof value.toJSON === 'function') {
  5417. return stringify(value.toJSON());
  5418. }
  5419. a = [];
  5420. if (typeof value.length === 'number' &&
  5421. !(value.propertyIsEnumerable('length'))) {
  5422. // The object is an array. Stringify every element. Use null as a placeholder
  5423. // for non-JSON values.
  5424. l = value.length;
  5425. for (i = 0; i < l; i += 1) {
  5426. a.push(stringify(value[i], whitelist) || 'null');
  5427. }
  5428. // Join all of the elements together and wrap them in brackets.
  5429. return '[' + a.join(',') + ']';
  5430. }
  5431. if (whitelist) {
  5432. // If a whitelist (array of keys) is provided, use it to select the components
  5433. // of the object.
  5434. l = whitelist.length;
  5435. for (i = 0; i < l; i += 1) {
  5436. k = whitelist[i];
  5437. if (typeof k === 'string') {
  5438. v = stringify(value[k], whitelist);
  5439. if (v) {
  5440. a.push(stringify(k) + ':' + v);
  5441. }
  5442. }
  5443. }
  5444. } else {
  5445. // Otherwise, iterate through all of the keys in the object.
  5446. for (k in value) {
  5447. if (typeof k === 'string') {
  5448. v = stringify(value[k], whitelist);
  5449. if (v) {
  5450. a.push(stringify(k) + ':' + v);
  5451. }
  5452. }
  5453. }
  5454. }
  5455. // Join all of the member texts together and wrap them in braces.
  5456. return '{' + a.join(',') + '}';
  5457. }
  5458. }
  5459. return {
  5460. stringify: stringify,
  5461. parse: function (text, filter) {
  5462. var j;
  5463. function walk(k, v) {
  5464. var i, n;
  5465. if (v && typeof v === 'object') {
  5466. for (i in v) {
  5467. if (Object.prototype.hasOwnProperty.apply(v, [i])) {
  5468. n = walk(i, v[i]);
  5469. if (n !== undefined) {
  5470. v[i] = n;
  5471. }
  5472. }
  5473. }
  5474. }
  5475. return filter(k, v);
  5476. }
  5477. // Parsing happens in three stages. In the first stage, we run the text against
  5478. // regular expressions that look for non-JSON patterns. We are especially
  5479. // concerned with '()' and 'new' because they can cause invocation, and '='
  5480. // because it can cause mutation. But just to be safe, we want to reject all
  5481. // unexpected forms.
  5482. // We split the first stage into 4 regexp operations in order to work around
  5483. // crippling inefficiencies in IE's and Safari's regexp engines. First we
  5484. // replace all backslash pairs with '@' (a non-JSON character). Second, we
  5485. // replace all simple value tokens with ']' characters. Third, we delete all
  5486. // open brackets that follow a colon or comma or that begin the text. Finally,
  5487. // we look to see that the remaining characters are only whitespace or ']' or
  5488. // ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.
  5489. if (/^[\],:{}\s]*$/.test(text.replace(/\\./g, '@').
  5490. replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g, ']').
  5491. replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
  5492. // In the second stage we use the eval function to compile the text into a
  5493. // JavaScript structure. The '{' operator is subject to a syntactic ambiguity
  5494. // in JavaScript: it can begin a block or an object literal. We wrap the text
  5495. // in parens to eliminate the ambiguity.
  5496. j = eval('(' + text + ')');
  5497. // In the optional third stage, we recursively walk the new structure, passing
  5498. // each name/value pair to a filter function for possible transformation.
  5499. return typeof filter === 'function' ? walk('', j) : j;
  5500. }
  5501. // If the text is not JSON parseable, then a SyntaxError is thrown.
  5502. throw new SyntaxError('parseJSON');
  5503. }
  5504. };
  5505. }();
  5506. }
  5507. /*
  5508. Copyright 2008-2010 University of Cambridge
  5509. Copyright 2008-2010 University of Toronto
  5510. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  5511. BSD license. You may not use this file except in compliance with one these
  5512. Licenses.
  5513. You may obtain a copy of the ECL 2.0 License and BSD License at
  5514. https://source.fluidproject.org/svn/LICENSE.txt
  5515. */
  5516. // Declare dependencies.
  5517. /*global jQuery*/
  5518. /*global fluid_1_2*/
  5519. var fluid_1_2 = fluid_1_2 || {};
  5520. (function ($, fluid) {
  5521. /**
  5522. * Returns the absolute position of a supplied DOM node in pixels.
  5523. * Implementation taken from quirksmode http://www.quirksmode.org/js/findpos.html
  5524. */
  5525. fluid.dom.computeAbsolutePosition = function (element) {
  5526. var curleft = 0, curtop = 0;
  5527. if (element.offsetParent) {
  5528. do {
  5529. curleft += element.offsetLeft;
  5530. curtop += element.offsetTop;
  5531. element = element.offsetParent;
  5532. } while (element);
  5533. return [curleft, curtop];
  5534. }
  5535. };
  5536. /**
  5537. * Cleanse the children of a DOM node by removing all <script> tags.
  5538. * This is necessary to prevent the possibility that these blocks are
  5539. * reevaluated if the node were reattached to the document.
  5540. */
  5541. fluid.dom.cleanseScripts = function (element) {
  5542. var cleansed = $.data(element, fluid.dom.cleanseScripts.MARKER);
  5543. if (!cleansed) {
  5544. fluid.dom.iterateDom(element, function (node) {
  5545. return node.tagName.toLowerCase() === "script"? "delete" : null;
  5546. });
  5547. $.data(element, fluid.dom.cleanseScripts.MARKER, true);
  5548. }
  5549. };
  5550. fluid.dom.cleanseScripts.MARKER = "fluid-scripts-cleansed";
  5551. /**
  5552. * Inserts newChild as the next sibling of refChild.
  5553. * @param {Object} newChild
  5554. * @param {Object} refChild
  5555. */
  5556. fluid.dom.insertAfter = function (newChild, refChild) {
  5557. var nextSib = refChild.nextSibling;
  5558. if (!nextSib) {
  5559. refChild.parentNode.appendChild(newChild);
  5560. }
  5561. else {
  5562. refChild.parentNode.insertBefore(newChild, nextSib);
  5563. }
  5564. };
  5565. // The following two functions taken from http://developer.mozilla.org/En/Whitespace_in_the_DOM
  5566. /**
  5567. * Determine whether a node's text content is entirely whitespace.
  5568. *
  5569. * @param node A node implementing the |CharacterData| interface (i.e.,
  5570. * a |Text|, |Comment|, or |CDATASection| node
  5571. * @return True if all of the text content of |nod| is whitespace,
  5572. * otherwise false.
  5573. */
  5574. fluid.dom.isWhitespaceNode = function (node) {
  5575. // Use ECMA-262 Edition 3 String and RegExp features
  5576. return !(/[^\t\n\r ]/.test(node.data));
  5577. };
  5578. /**
  5579. * Determine if a node should be ignored by the iterator functions.
  5580. *
  5581. * @param nod An object implementing the DOM1 |Node| interface.
  5582. * @return true if the node is:
  5583. * 1) A |Text| node that is all whitespace
  5584. * 2) A |Comment| node
  5585. * and otherwise false.
  5586. */
  5587. fluid.dom.isIgnorableNode = function (node) {
  5588. return (node.nodeType === 8) || // A comment node
  5589. ((node.nodeType === 3) && fluid.dom.isWhitespaceNode(node)); // a text node, all ws
  5590. };
  5591. })(jQuery, fluid_1_2);
  5592. /*
  5593. Copyright 2008-2010 University of Cambridge
  5594. Copyright 2008-2010 University of Toronto
  5595. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  5596. BSD license. You may not use this file except in compliance with one these
  5597. Licenses.
  5598. You may obtain a copy of the ECL 2.0 License and BSD License at
  5599. https://source.fluidproject.org/svn/LICENSE.txt
  5600. */
  5601. // Declare dependencies.
  5602. /*global jQuery*/
  5603. /*global fluid_1_2*/
  5604. var fluid_1_2 = fluid_1_2 || {};
  5605. (function ($, fluid) {
  5606. fluid.orientation = {
  5607. HORIZONTAL: 4,
  5608. VERTICAL: 1
  5609. };
  5610. fluid.rectSides = {
  5611. // agree with fluid.orientation
  5612. 4: ["left", "right"],
  5613. 1: ["top", "bottom"],
  5614. // agree with fluid.direction
  5615. 8: "top",
  5616. 12: "bottom",
  5617. 2: "left",
  5618. 3: "right"
  5619. };
  5620. /**
  5621. * This is the position, relative to a given drop target, that a dragged item should be dropped.
  5622. */
  5623. fluid.position = {
  5624. BEFORE: -1,
  5625. AFTER: 1,
  5626. INSIDE: 2,
  5627. REPLACE: 3
  5628. };
  5629. /**
  5630. * For incrementing/decrementing a count or index, or moving in a rectilinear direction.
  5631. */
  5632. fluid.direction = {
  5633. NEXT: 1,
  5634. PREVIOUS: -1,
  5635. UP: 8,
  5636. DOWN: 12,
  5637. LEFT: 2,
  5638. RIGHT: 3
  5639. };
  5640. fluid.directionSign = function (direction) {
  5641. return direction === fluid.direction.UP || direction === fluid.direction.LEFT?
  5642. fluid.direction.PREVIOUS : fluid.direction.NEXT;
  5643. };
  5644. fluid.directionAxis = function (direction) {
  5645. return direction === fluid.direction.LEFT || direction === fluid.direction.RIGHT?
  5646. 0 : 1;
  5647. };
  5648. fluid.directionOrientation = function (direction) {
  5649. return fluid.directionAxis(direction)? fluid.orientation.VERTICAL : fluid.orientation.HORIZONTAL;
  5650. };
  5651. fluid.keycodeDirection = {
  5652. up: fluid.direction.UP,
  5653. down: fluid.direction.DOWN,
  5654. left: fluid.direction.LEFT,
  5655. right: fluid.direction.RIGHT
  5656. };
  5657. // moves a single node in the DOM to a new position relative to another
  5658. fluid.moveDom = function (source, target, position) {
  5659. source = fluid.unwrap(source);
  5660. target = fluid.unwrap(target);
  5661. var scan;
  5662. // fluid.log("moveDom source " + fluid.dumpEl(source) + " target " + fluid.dumpEl(target) + " position " + position);
  5663. if (position === fluid.position.INSIDE) {
  5664. target.appendChild(source);
  5665. }
  5666. else if (position === fluid.position.BEFORE) {
  5667. for (scan = target.previousSibling; ; scan = scan.previousSibling) {
  5668. if (!scan || !fluid.dom.isIgnorableNode(scan)) {
  5669. if (scan !== source) {
  5670. fluid.dom.cleanseScripts(source);
  5671. target.parentNode.insertBefore(source, target);
  5672. }
  5673. break;
  5674. }
  5675. }
  5676. }
  5677. else if (position === fluid.position.AFTER) {
  5678. for (scan = target.nextSibling; ; scan = scan.nextSibling) {
  5679. if (!scan || !fluid.dom.isIgnorableNode(scan)) {
  5680. if (scan !== source) {
  5681. fluid.dom.cleanseScripts(source);
  5682. fluid.dom.insertAfter(source, target);
  5683. }
  5684. break;
  5685. }
  5686. }
  5687. }
  5688. else {
  5689. fluid.fail("Unrecognised position supplied to fluid.moveDom: " + position);
  5690. }
  5691. };
  5692. fluid.normalisePosition = function (position, samespan, targeti, sourcei) {
  5693. // convert a REPLACE into a primitive BEFORE/AFTER
  5694. if (position === fluid.position.REPLACE) {
  5695. position = samespan && targeti >= sourcei? fluid.position.AFTER: fluid.position.BEFORE;
  5696. }
  5697. return position;
  5698. };
  5699. fluid.permuteDom = function (element, target, position, sourceelements, targetelements) {
  5700. element = fluid.unwrap(element);
  5701. target = fluid.unwrap(target);
  5702. var sourcei = $.inArray(element, sourceelements);
  5703. if (sourcei === -1) {
  5704. fluid.fail("Error in permuteDom: source element " + fluid.dumpEl(element)
  5705. + " not found in source list " + fluid.dumpEl(sourceelements));
  5706. }
  5707. var targeti = $.inArray(target, targetelements);
  5708. if (targeti === -1) {
  5709. fluid.fail("Error in permuteDom: target element " + fluid.dumpEl(target)
  5710. + " not found in source list " + fluid.dumpEl(targetelements));
  5711. }
  5712. var samespan = sourceelements === targetelements;
  5713. position = fluid.normalisePosition(position, samespan, targeti, sourcei);
  5714. //fluid.log("permuteDom sourcei " + sourcei + " targeti " + targeti);
  5715. // cache the old neighbourhood of the element for the final move
  5716. var oldn = {};
  5717. oldn[fluid.position.AFTER] = element.nextSibling;
  5718. oldn[fluid.position.BEFORE] = element.previousSibling;
  5719. fluid.moveDom(sourceelements[sourcei], targetelements[targeti], position);
  5720. // perform the leftward-moving, AFTER shift
  5721. var frontlimit = samespan? targeti - 1: sourceelements.length - 2;
  5722. var i;
  5723. if (position === fluid.position.BEFORE && samespan) {
  5724. // we cannot do skip processing if the element was "fused against the grain"
  5725. frontlimit--;
  5726. }
  5727. if (!samespan || targeti > sourcei) {
  5728. for (i = frontlimit; i > sourcei; -- i) {
  5729. fluid.moveDom(sourceelements[i + 1], sourceelements[i], fluid.position.AFTER);
  5730. }
  5731. if (sourcei + 1 < sourceelements.length) {
  5732. fluid.moveDom(sourceelements[sourcei + 1], oldn[fluid.position.AFTER], fluid.position.BEFORE);
  5733. }
  5734. }
  5735. // perform the rightward-moving, BEFORE shift
  5736. var backlimit = samespan? sourcei - 1: targetelements.length - 1;
  5737. if (position === fluid.position.AFTER) {
  5738. // we cannot do skip processing if the element was "fused against the grain"
  5739. targeti++;
  5740. }
  5741. if (!samespan || targeti < sourcei) {
  5742. for (i = targeti; i < backlimit; ++ i) {
  5743. fluid.moveDom(targetelements[i], targetelements[i + 1], fluid.position.BEFORE);
  5744. }
  5745. if (backlimit >= 0 && backlimit < targetelements.length - 1) {
  5746. fluid.moveDom(targetelements[backlimit], oldn[fluid.position.BEFORE], fluid.position.AFTER);
  5747. }
  5748. }
  5749. };
  5750. var curCss = function (a, name) {
  5751. return window.getComputedStyle? window.getComputedStyle(a, null).getPropertyValue(name) :
  5752. a.currentStyle[name];
  5753. };
  5754. var isAttached = function (node) {
  5755. while (node && node.nodeName) {
  5756. if (node.nodeName === "BODY") {
  5757. return true;
  5758. }
  5759. node = node.parentNode;
  5760. }
  5761. return false;
  5762. };
  5763. var generalHidden = function (a) {
  5764. return "hidden" === a.type || curCss(a, "display") === "none" || curCss(a, "visibility") === "hidden" || !isAttached(a);
  5765. };
  5766. var computeGeometry = function (element, orientation, disposition) {
  5767. var elem = {};
  5768. elem.element = element;
  5769. elem.orientation = orientation;
  5770. if (disposition === fluid.position.INSIDE) {
  5771. elem.position = disposition;
  5772. }
  5773. if (generalHidden(element)) {
  5774. elem.clazz = "hidden";
  5775. }
  5776. var pos = fluid.dom.computeAbsolutePosition(element) || [0, 0];
  5777. var width = element.offsetWidth;
  5778. var height = element.offsetHeight;
  5779. elem.rect = {left: pos[0], top: pos[1]};
  5780. elem.rect.right = pos[0] + width;
  5781. elem.rect.bottom = pos[1] + height;
  5782. return elem;
  5783. };
  5784. // A "suitable large" value for the sentinel blocks at the ends of spans
  5785. var SENTINEL_DIMENSION = 10000;
  5786. function dumprect(rect) {
  5787. return "Rect top: " + rect.top +
  5788. " left: " + rect.left +
  5789. " bottom: " + rect.bottom +
  5790. " right: " + rect.right;
  5791. }
  5792. function dumpelem(cacheelem) {
  5793. if (!cacheelem || !cacheelem.rect) {
  5794. return "null";
  5795. } else {
  5796. return dumprect(cacheelem.rect) + " position: " +
  5797. cacheelem.position +
  5798. " for " +
  5799. fluid.dumpEl(cacheelem.element);
  5800. }
  5801. }
  5802. fluid.dropManager = function () {
  5803. var targets = [];
  5804. var cache = {};
  5805. var that = {};
  5806. var lastClosest;
  5807. function cacheKey(element) {
  5808. return fluid.allocateSimpleId(element);
  5809. }
  5810. function sentinelizeElement(targets, sides, cacheelem, fc, disposition, clazz) {
  5811. var elemCopy = $.extend(true, {}, cacheelem);
  5812. elemCopy.rect[sides[fc]] = elemCopy.rect[sides[1 - fc]] + (fc? 1: -1);
  5813. elemCopy.rect[sides[1 - fc]] = (fc? -1 : 1) * SENTINEL_DIMENSION;
  5814. elemCopy.position = disposition === fluid.position.INSIDE?
  5815. disposition : (fc? fluid.position.BEFORE : fluid.position.AFTER);
  5816. elemCopy.clazz = clazz;
  5817. targets[targets.length] = elemCopy;
  5818. }
  5819. function splitElement(targets, sides, cacheelem, disposition, clazz1, clazz2) {
  5820. var elem1 = $.extend(true, {}, cacheelem);
  5821. var elem2 = $.extend(true, {}, cacheelem);
  5822. var midpoint = (elem1.rect[sides[0]] + elem1.rect[sides[1]]) / 2;
  5823. elem1.rect[sides[1]] = midpoint;
  5824. elem1.position = fluid.position.BEFORE;
  5825. elem2.rect[sides[0]] = midpoint;
  5826. elem2.position = fluid.position.AFTER;
  5827. elem1.clazz = clazz1;
  5828. elem2.clazz = clazz2;
  5829. targets[targets.length] = elem1;
  5830. targets[targets.length] = elem2;
  5831. }
  5832. // Expand this configuration point if we ever go back to a full "permissions" model
  5833. function getRelativeClass(thisElements, index, relative, thisclazz, mapper) {
  5834. index += relative;
  5835. if (index < 0 && thisclazz === "locked") {
  5836. return "locked";
  5837. }
  5838. if (index >= thisElements.length || mapper === null) {
  5839. return null;
  5840. } else {
  5841. relative = thisElements[index];
  5842. return mapper(relative) === "locked" && thisclazz === "locked" ? "locked" : null;
  5843. }
  5844. }
  5845. var lastGeometry;
  5846. var displacementX, displacementY;
  5847. that.updateGeometry = function (geometricInfo) {
  5848. lastGeometry = geometricInfo;
  5849. targets = [];
  5850. cache = {};
  5851. var mapper = geometricInfo.elementMapper;
  5852. for (var i = 0; i < geometricInfo.extents.length; ++ i) {
  5853. var thisInfo = geometricInfo.extents[i];
  5854. var orientation = thisInfo.orientation;
  5855. var sides = fluid.rectSides[orientation];
  5856. var processElement = function (element, sentB, sentF, disposition, j) {
  5857. var cacheelem = computeGeometry(element, orientation, disposition);
  5858. cacheelem.owner = thisInfo;
  5859. if (cacheelem.clazz !== "hidden" && mapper) {
  5860. cacheelem.clazz = mapper(element);
  5861. }
  5862. cache[cacheKey(element)] = cacheelem;
  5863. var backClass = getRelativeClass(thisInfo.elements, j, fluid.position.BEFORE, cacheelem.clazz, mapper);
  5864. var frontClass = getRelativeClass(thisInfo.elements, j, fluid.position.AFTER, cacheelem.clazz, mapper);
  5865. if (disposition === fluid.position.INSIDE) {
  5866. targets[targets.length] = cacheelem;
  5867. }
  5868. else {
  5869. splitElement(targets, sides, cacheelem, disposition, backClass, frontClass);
  5870. }
  5871. // deal with sentinel blocks by creating near-copies of the end elements
  5872. if (sentB && geometricInfo.sentinelize) {
  5873. sentinelizeElement(targets, sides, cacheelem, 1, disposition, backClass);
  5874. }
  5875. if (sentF && geometricInfo.sentinelize) {
  5876. sentinelizeElement(targets, sides, cacheelem, 0, disposition, frontClass);
  5877. }
  5878. //fluid.log(dumpelem(cacheelem));
  5879. return cacheelem;
  5880. };
  5881. var allHidden = true;
  5882. for (var j = 0; j < thisInfo.elements.length; ++ j) {
  5883. var element = thisInfo.elements[j];
  5884. var cacheelem = processElement(element, j === 0, j === thisInfo.elements.length - 1,
  5885. fluid.position.INTERLEAVED, j);
  5886. if (cacheelem.clazz !== "hidden") {
  5887. allHidden = false;
  5888. }
  5889. }
  5890. if (allHidden && thisInfo.parentElement) {
  5891. processElement(thisInfo.parentElement, true, true,
  5892. fluid.position.INSIDE);
  5893. }
  5894. }
  5895. };
  5896. that.startDrag = function (event, handlePos, handleWidth, handleHeight) {
  5897. var handleMidX = handlePos[0] + handleWidth / 2;
  5898. var handleMidY = handlePos[1] + handleHeight / 2;
  5899. var dX = handleMidX - event.pageX;
  5900. var dY = handleMidY - event.pageY;
  5901. that.updateGeometry(lastGeometry);
  5902. lastClosest = null;
  5903. displacementX = dX;
  5904. displacementY = dY;
  5905. $("body").bind("mousemove.fluid-dropManager", that.mouseMove);
  5906. };
  5907. that.lastPosition = function () {
  5908. return lastClosest;
  5909. };
  5910. that.endDrag = function () {
  5911. $("body").unbind("mousemove.fluid-dropManager");
  5912. };
  5913. that.mouseMove = function (evt) {
  5914. var x = evt.pageX + displacementX;
  5915. var y = evt.pageY + displacementY;
  5916. //fluid.log("Mouse x " + x + " y " + y );
  5917. var closestTarget = that.closestTarget(x, y, lastClosest);
  5918. if (closestTarget && closestTarget !== fluid.dropManager.NO_CHANGE) {
  5919. lastClosest = closestTarget;
  5920. that.dropChangeFirer.fire(closestTarget);
  5921. }
  5922. };
  5923. that.dropChangeFirer = fluid.event.getEventFirer();
  5924. var blankHolder = {
  5925. element: null
  5926. };
  5927. that.closestTarget = function (x, y, lastClosest) {
  5928. var mindistance = Number.MAX_VALUE;
  5929. var minelem = blankHolder;
  5930. var minlockeddistance = Number.MAX_VALUE;
  5931. var minlockedelem = blankHolder;
  5932. for (var i = 0; i < targets.length; ++ i) {
  5933. var cacheelem = targets[i];
  5934. if (cacheelem.clazz === "hidden") {
  5935. continue;
  5936. }
  5937. var distance = fluid.geom.minPointRectangle(x, y, cacheelem.rect);
  5938. if (cacheelem.clazz === "locked") {
  5939. if (distance < minlockeddistance) {
  5940. minlockeddistance = distance;
  5941. minlockedelem = cacheelem;
  5942. }
  5943. } else {
  5944. if (distance < mindistance) {
  5945. mindistance = distance;
  5946. minelem = cacheelem;
  5947. }
  5948. if (distance === 0) {
  5949. break;
  5950. }
  5951. }
  5952. }
  5953. if (!minelem) {
  5954. return minelem;
  5955. }
  5956. if (minlockeddistance >= mindistance) {
  5957. minlockedelem = blankHolder;
  5958. }
  5959. //fluid.log("PRE: mindistance " + mindistance + " element " +
  5960. // fluid.dumpEl(minelem.element) + " minlockeddistance " + minlockeddistance
  5961. // + " locked elem " + dumpelem(minlockedelem));
  5962. if (lastClosest && lastClosest.position === minelem.position &&
  5963. fluid.unwrap(lastClosest.element) === fluid.unwrap(minelem.element) &&
  5964. fluid.unwrap(lastClosest.lockedelem) === fluid.unwrap(minlockedelem.element)
  5965. ) {
  5966. return fluid.dropManager.NO_CHANGE;
  5967. }
  5968. //fluid.log("mindistance " + mindistance + " minlockeddistance " + minlockeddistance);
  5969. return {
  5970. position: minelem.position,
  5971. element: minelem.element,
  5972. lockedelem: minlockedelem.element
  5973. };
  5974. };
  5975. that.shuffleProjectFrom = function (element, direction, includeLocked) {
  5976. var togo = that.projectFrom(element, direction, includeLocked);
  5977. if (togo) {
  5978. togo.position = fluid.position.REPLACE;
  5979. }
  5980. return togo;
  5981. };
  5982. that.projectFrom = function (element, direction, includeLocked) {
  5983. that.updateGeometry(lastGeometry);
  5984. var cacheelem = cache[cacheKey(element)];
  5985. var projected = fluid.geom.projectFrom(cacheelem.rect, direction, targets, includeLocked);
  5986. if (!projected.cacheelem) {
  5987. return null;
  5988. }
  5989. var retpos = projected.cacheelem.position;
  5990. return {element: projected.cacheelem.element,
  5991. position: retpos? retpos : fluid.position.BEFORE
  5992. };
  5993. };
  5994. function getRelativeElement(element, direction, elements) {
  5995. var folded = fluid.directionSign(direction);
  5996. var index = $(elements).index(element) + folded;
  5997. if (index < 0) {
  5998. index += elements.length;
  5999. }
  6000. index %= elements.length;
  6001. return elements[index];
  6002. }
  6003. that.logicalFrom = function (element, direction, includeLocked) {
  6004. var orderables = that.getOwningSpan(element, fluid.position.INTERLEAVED, includeLocked);
  6005. return {element: getRelativeElement(element, direction, orderables),
  6006. position: fluid.position.REPLACE};
  6007. };
  6008. that.lockedWrapFrom = function (element, direction, includeLocked) {
  6009. var base = that.logicalFrom(element, direction, includeLocked);
  6010. var selectables = that.getOwningSpan(element, fluid.position.INTERLEAVED, includeLocked);
  6011. var allElements = cache[cacheKey(element)].owner.elements;
  6012. if (includeLocked || selectables[0] === allElements[0]) {
  6013. return base;
  6014. }
  6015. var directElement = getRelativeElement(element, direction, allElements);
  6016. if (lastGeometry.elementMapper(directElement) === "locked") {
  6017. base.element = null;
  6018. base.clazz = "locked";
  6019. }
  6020. return base;
  6021. };
  6022. that.getOwningSpan = function (element, position, includeLocked) {
  6023. var owner = cache[cacheKey(element)].owner;
  6024. var elements = position === fluid.position.INSIDE? [owner.parentElement] : owner.elements;
  6025. if (!includeLocked && lastGeometry.elementMapper) {
  6026. elements = $.makeArray(elements);
  6027. fluid.remove_if(elements, function (element) {
  6028. return lastGeometry.elementMapper(element) === "locked";
  6029. });
  6030. }
  6031. return elements;
  6032. };
  6033. that.geometricMove = function (element, target, position) {
  6034. var sourceElements = that.getOwningSpan(element, null, true);
  6035. var targetElements = that.getOwningSpan(target, position, true);
  6036. fluid.permuteDom(element, target, position, sourceElements, targetElements);
  6037. };
  6038. return that;
  6039. };
  6040. fluid.dropManager.NO_CHANGE = "no change";
  6041. fluid.geom = fluid.geom || {};
  6042. // These distance algorithms have been taken from
  6043. // http://www.cs.mcgill.ca/~cs644/Godfried/2005/Fall/fzamal/concepts.htm
  6044. /** Returns the minimum squared distance between a point and a rectangle **/
  6045. fluid.geom.minPointRectangle = function (x, y, rectangle) {
  6046. var dx = x < rectangle.left? (rectangle.left - x) :
  6047. (x > rectangle.right? (x - rectangle.right) : 0);
  6048. var dy = y < rectangle.top? (rectangle.top - y) :
  6049. (y > rectangle.bottom? (y - rectangle.bottom) : 0);
  6050. return dx * dx + dy * dy;
  6051. };
  6052. /** Returns the minimum squared distance between two rectangles **/
  6053. fluid.geom.minRectRect = function (rect1, rect2) {
  6054. var dx = rect1.right < rect2.left? rect2.left - rect1.right :
  6055. rect2.right < rect1.left? rect1.left - rect2.right :0;
  6056. var dy = rect1.bottom < rect2.top? rect2.top - rect1.bottom :
  6057. rect2.bottom < rect1.top? rect1.top - rect2.bottom :0;
  6058. return dx * dx + dy * dy;
  6059. };
  6060. var makePenCollect = function () {
  6061. return {
  6062. mindist: Number.MAX_VALUE,
  6063. minrdist: Number.MAX_VALUE
  6064. };
  6065. };
  6066. /** Determine the one amongst a set of rectangle targets which is the "best fit"
  6067. * for an axial motion from a "base rectangle" (commonly arising from the case
  6068. * of cursor key navigation).
  6069. * @param {Rectangle} baserect The base rectangl from which the motion is to be referred
  6070. * @param {fluid.direction} direction The direction of motion
  6071. * @param {Array of Rectangle holders} targets An array of objects "cache elements"
  6072. * for which the member <code>rect</code> is the holder of the rectangle to be tested.
  6073. * @return The cache element which is the most appropriate for the requested motion.
  6074. */
  6075. fluid.geom.projectFrom = function (baserect, direction, targets, forSelection) {
  6076. var axis = fluid.directionAxis(direction);
  6077. var frontSide = fluid.rectSides[direction];
  6078. var backSide = fluid.rectSides[axis * 15 + 5 - direction];
  6079. var dirSign = fluid.directionSign(direction);
  6080. var penrect = {left: (7 * baserect.left + 1 * baserect.right) / 8,
  6081. right: (5 * baserect.left + 3 * baserect.right) / 8,
  6082. top: (7 * baserect.top + 1 * baserect.bottom) / 8,
  6083. bottom: (5 * baserect.top + 3 * baserect.bottom) / 8};
  6084. penrect[frontSide] = dirSign * SENTINEL_DIMENSION;
  6085. penrect[backSide] = -penrect[frontSide];
  6086. function accPen(collect, cacheelem, backSign) {
  6087. var thisrect = cacheelem.rect;
  6088. var pdist = fluid.geom.minRectRect(penrect, thisrect);
  6089. var rdist = -dirSign * backSign * (baserect[backSign === 1 ? frontSide:backSide]
  6090. - thisrect[backSign === 1 ? backSide:frontSide]);
  6091. // fluid.log("pdist: " + pdist + " rdist: " + rdist);
  6092. // the oddity in the rdist comparison is intended to express "half-open"-ness of rectangles
  6093. // (backSign === 1? 0 : 1) - this is now gone - must be possible to move to perpendicularly abutting regions
  6094. if (pdist <= collect.mindist && rdist >= 0) {
  6095. if (pdist === collect.mindist && rdist * backSign > collect.minrdist) {
  6096. return;
  6097. }
  6098. collect.minrdist = rdist * backSign;
  6099. collect.mindist = pdist;
  6100. collect.minelem = cacheelem;
  6101. }
  6102. }
  6103. var collect = makePenCollect();
  6104. var backcollect = makePenCollect();
  6105. var lockedcollect = makePenCollect();
  6106. for (var i = 0; i < targets.length; ++ i) {
  6107. var elem = targets[i];
  6108. var isPure = elem.owner && elem.element === elem.owner.parentElement;
  6109. if (elem.clazz === "hidden" || forSelection && isPure) {
  6110. continue;
  6111. }
  6112. else if (!forSelection && elem.clazz === "locked") {
  6113. accPen(lockedcollect, elem, 1);
  6114. }
  6115. else {
  6116. accPen(collect, elem, 1);
  6117. accPen(backcollect, elem, -1);
  6118. }
  6119. //fluid.log("Element " + i + " " + dumpelem(elem) + " mindist " + collect.mindist);
  6120. }
  6121. var wrap = !collect.minelem || backcollect.mindist < collect.mindist;
  6122. var mincollect = wrap? backcollect: collect;
  6123. var togo = {
  6124. wrapped: wrap,
  6125. cacheelem: mincollect.minelem
  6126. };
  6127. if (lockedcollect.mindist < mincollect.mindist) {
  6128. togo.lockedelem = lockedcollect.minelem;
  6129. }
  6130. return togo;
  6131. };
  6132. })(jQuery, fluid_1_2);
  6133. /*
  6134. Copyright 2007-2009 University of Toronto
  6135. Copyright 2007-2010 University of Cambridge
  6136. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  6137. BSD license. You may not use this file except in compliance with one these
  6138. Licenses.
  6139. You may obtain a copy of the ECL 2.0 License and BSD License at
  6140. https://source.fluidproject.org/svn/LICENSE.txt
  6141. */
  6142. // Declare dependencies.
  6143. /*global jQuery, fluid_1_2, document*/
  6144. fluid_1_2 = fluid_1_2 || {};
  6145. (function ($, fluid) {
  6146. var defaultAvatarCreator = function (item, cssClass, dropWarning) {
  6147. fluid.dom.cleanseScripts(fluid.unwrap(item));
  6148. var avatar = $(item).clone();
  6149. fluid.dom.iterateDom(avatar.get(0), function (node) {
  6150. node.removeAttribute("id");
  6151. if (node.tagName.toLowerCase() === "input") {
  6152. node.setAttribute("disabled", "disabled");
  6153. }
  6154. });
  6155. avatar.removeAttr("id");
  6156. avatar.removeClass("ui-droppable");
  6157. avatar.addClass(cssClass);
  6158. if (dropWarning) {
  6159. // Will a 'div' always be valid in this position?
  6160. var avatarContainer = $(document.createElement("div"));
  6161. avatarContainer.append(avatar);
  6162. avatarContainer.append(dropWarning);
  6163. avatar = avatarContainer;
  6164. }
  6165. $("body").append(avatar);
  6166. if (!$.browser.safari) {
  6167. // FLUID-1597: Safari appears incapable of correctly determining the dimensions of elements
  6168. avatar.css("display", "block").width(item.offsetWidth).height(item.offsetHeight);
  6169. }
  6170. if ($.browser.opera) { // FLUID-1490. Without this detect, curCSS explodes on the avatar on Firefox.
  6171. avatar.hide();
  6172. }
  6173. return avatar;
  6174. };
  6175. function bindHandlersToContainer(container, keyDownHandler, keyUpHandler, mouseMoveHandler) {
  6176. var actualKeyDown = keyDownHandler;
  6177. var advancedPrevention = false;
  6178. // FLUID-1598 and others: Opera will refuse to honour a "preventDefault" on a keydown.
  6179. // http://forums.devshed.com/javascript-development-115/onkeydown-preventdefault-opera-485371.html
  6180. if ($.browser.opera) {
  6181. container.keypress(function (evt) {
  6182. if (advancedPrevention) {
  6183. advancedPrevention = false;
  6184. evt.preventDefault();
  6185. return false;
  6186. }
  6187. });
  6188. actualKeyDown = function (evt) {
  6189. var oldret = keyDownHandler(evt);
  6190. if (oldret === false) {
  6191. advancedPrevention = true;
  6192. }
  6193. };
  6194. }
  6195. container.keydown(actualKeyDown);
  6196. container.keyup(keyUpHandler);
  6197. }
  6198. function addRolesToContainer(that) {
  6199. that.container.attr("role", that.options.containerRole.container);
  6200. that.container.attr("aria-multiselectable", "false");
  6201. that.container.attr("aria-readonly", "false");
  6202. that.container.attr("aria-disabled", "false");
  6203. }
  6204. function createAvatarId(parentId) {
  6205. // Generating the avatar's id to be containerId_avatar
  6206. // This is safe since there is only a single avatar at a time
  6207. return parentId + "_avatar";
  6208. }
  6209. var adaptKeysets = function (options) {
  6210. if (options.keysets && !(options.keysets instanceof Array)) {
  6211. options.keysets = [options.keysets];
  6212. }
  6213. };
  6214. /**
  6215. * @param container - A jQueryable designator for the root node of the reorderer (a selector, a DOM node, or a jQuery instance)
  6216. * @param options - an object containing any of the available options:
  6217. * containerRole - indicates the role, or general use, for this instance of the Reorderer
  6218. * keysets - an object containing sets of keycodes to use for directional navigation. Must contain:
  6219. * modifier - a function that returns a boolean, indicating whether or not the required modifier(s) are activated
  6220. * up
  6221. * down
  6222. * right
  6223. * left
  6224. * styles - an object containing class names for styling the Reorderer
  6225. * defaultStyle
  6226. * selected
  6227. * dragging
  6228. * hover
  6229. * dropMarker
  6230. * mouseDrag
  6231. * avatar
  6232. * avatarCreator - a function that returns a valid DOM node to be used as the dragging avatar
  6233. */
  6234. fluid.reorderer = function (container, options) {
  6235. if (!container) {
  6236. fluid.fail("Reorderer initialised with no container");
  6237. }
  6238. var thatReorderer = fluid.initView("fluid.reorderer", container, options);
  6239. options = thatReorderer.options;
  6240. var dropManager = fluid.dropManager();
  6241. thatReorderer.layoutHandler = fluid.initSubcomponent(thatReorderer,
  6242. "layoutHandler", [thatReorderer.container, options, dropManager, thatReorderer.dom]);
  6243. thatReorderer.activeItem = undefined;
  6244. adaptKeysets(options);
  6245. var kbDropWarning = thatReorderer.locate("dropWarning");
  6246. var mouseDropWarning;
  6247. if (kbDropWarning) {
  6248. mouseDropWarning = kbDropWarning.clone();
  6249. }
  6250. var isMove = function (evt) {
  6251. var keysets = options.keysets;
  6252. for (var i = 0; i < keysets.length; i++) {
  6253. if (keysets[i].modifier(evt)) {
  6254. return true;
  6255. }
  6256. }
  6257. return false;
  6258. };
  6259. var isActiveItemMovable = function () {
  6260. return $.inArray(thatReorderer.activeItem, thatReorderer.dom.fastLocate("movables")) >= 0;
  6261. };
  6262. var setDropEffects = function (value) {
  6263. thatReorderer.dom.fastLocate("dropTargets").attr("aria-dropeffect", value);
  6264. };
  6265. var styles = options.styles;
  6266. var noModifier = function (evt) {
  6267. return (!evt.ctrlKey && !evt.altKey && !evt.shiftKey && !evt.metaKey);
  6268. };
  6269. var handleDirectionKeyDown = function (evt) {
  6270. var item = thatReorderer.activeItem;
  6271. if (!item) {
  6272. return true;
  6273. }
  6274. var keysets = options.keysets;
  6275. for (var i = 0; i < keysets.length; i++) {
  6276. var keyset = keysets[i];
  6277. var keydir = fluid.keyForValue(keyset, evt.keyCode);
  6278. if (!keydir) {
  6279. continue;
  6280. }
  6281. var isMovement = keyset.modifier(evt);
  6282. var dirnum = fluid.keycodeDirection[keydir];
  6283. var relativeItem = thatReorderer.layoutHandler.getRelativePosition(item, dirnum, !isMovement);
  6284. if (!relativeItem) {
  6285. continue;
  6286. }
  6287. if (isMovement) {
  6288. var prevent = thatReorderer.events.onBeginMove.fire(item);
  6289. if (prevent === false) {
  6290. return false;
  6291. }
  6292. if (kbDropWarning.length > 0) {
  6293. if (relativeItem.clazz === "locked") {
  6294. thatReorderer.events.onShowKeyboardDropWarning.fire(item, kbDropWarning);
  6295. kbDropWarning.show();
  6296. }
  6297. else {
  6298. kbDropWarning.hide();
  6299. }
  6300. }
  6301. if (relativeItem.element) {
  6302. thatReorderer.requestMovement(relativeItem, item);
  6303. }
  6304. } else if (noModifier(evt)) {
  6305. item.blur();
  6306. $(relativeItem.element).focus();
  6307. }
  6308. return false;
  6309. }
  6310. return true;
  6311. };
  6312. thatReorderer.handleKeyDown = function (evt) {
  6313. if (!thatReorderer.activeItem || thatReorderer.activeItem !== evt.target) {
  6314. return true;
  6315. }
  6316. // If the key pressed is ctrl, and the active item is movable we want to restyle the active item.
  6317. var jActiveItem = $(thatReorderer.activeItem);
  6318. if (!jActiveItem.hasClass(styles.dragging) && isMove(evt)) {
  6319. // Don't treat the active item as dragging unless it is a movable.
  6320. if (isActiveItemMovable()) {
  6321. jActiveItem.removeClass(styles.selected);
  6322. jActiveItem.addClass(styles.dragging);
  6323. jActiveItem.attr("aria-grabbed", "true");
  6324. setDropEffects("move");
  6325. }
  6326. return false;
  6327. }
  6328. // The only other keys we listen for are the arrows.
  6329. return handleDirectionKeyDown(evt);
  6330. };
  6331. thatReorderer.handleKeyUp = function (evt) {
  6332. if (!thatReorderer.activeItem || thatReorderer.activeItem !== evt.target) {
  6333. return true;
  6334. }
  6335. var jActiveItem = $(thatReorderer.activeItem);
  6336. // Handle a key up event for the modifier
  6337. if (jActiveItem.hasClass(styles.dragging) && !isMove(evt)) {
  6338. if (kbDropWarning) {
  6339. kbDropWarning.hide();
  6340. }
  6341. jActiveItem.removeClass(styles.dragging);
  6342. jActiveItem.addClass(styles.selected);
  6343. jActiveItem.attr("aria-grabbed", "false");
  6344. setDropEffects("none");
  6345. return false;
  6346. }
  6347. return false;
  6348. };
  6349. var dropMarker;
  6350. var createDropMarker = function (tagName) {
  6351. var dropMarker = $(document.createElement(tagName));
  6352. dropMarker.addClass(options.styles.dropMarker);
  6353. dropMarker.hide();
  6354. return dropMarker;
  6355. };
  6356. fluid.logEnabled = true;
  6357. thatReorderer.requestMovement = function (requestedPosition, item) {
  6358. // Temporary censoring to get around ModuleLayout inability to update relative to self.
  6359. if (!requestedPosition || fluid.unwrap(requestedPosition.element) === fluid.unwrap(item)) {
  6360. return;
  6361. }
  6362. thatReorderer.events.onMove.fire(item, requestedPosition);
  6363. dropManager.geometricMove(item, requestedPosition.element, requestedPosition.position);
  6364. //$(thatReorderer.activeItem).removeClass(options.styles.selected);
  6365. // refocus on the active item because moving places focus on the body
  6366. $(thatReorderer.activeItem).focus();
  6367. thatReorderer.refresh();
  6368. dropManager.updateGeometry(thatReorderer.layoutHandler.getGeometricInfo());
  6369. thatReorderer.events.afterMove.fire(item, requestedPosition, thatReorderer.dom.fastLocate("movables"));
  6370. };
  6371. var hoverStyleHandler = function (item, state) {
  6372. thatReorderer.dom.fastLocate("grabHandle", item)[state?"addClass":"removeClass"](styles.hover);
  6373. };
  6374. /**
  6375. * Takes a $ object and adds 'movable' functionality to it
  6376. */
  6377. function initMovable(item) {
  6378. var styles = options.styles;
  6379. item.attr("aria-grabbed", "false");
  6380. item.mouseover(
  6381. function () {
  6382. thatReorderer.events.onHover.fire(item, true);
  6383. }
  6384. );
  6385. item.mouseout(
  6386. function () {
  6387. thatReorderer.events.onHover.fire(item, false);
  6388. }
  6389. );
  6390. var avatar;
  6391. thatReorderer.dom.fastLocate("grabHandle", item).draggable({
  6392. refreshPositions: false,
  6393. scroll: true,
  6394. helper: function () {
  6395. var dropWarningEl;
  6396. if (mouseDropWarning) {
  6397. dropWarningEl = mouseDropWarning[0];
  6398. }
  6399. avatar = $(options.avatarCreator(item[0], styles.avatar, dropWarningEl));
  6400. avatar.attr("id", createAvatarId(thatReorderer.container.id));
  6401. return avatar;
  6402. },
  6403. start: function (e, ui) {
  6404. var prevent = thatReorderer.events.onBeginMove.fire(item);
  6405. if (prevent === false) {
  6406. return false;
  6407. }
  6408. var handle = thatReorderer.dom.fastLocate("grabHandle", item)[0];
  6409. var handlePos = fluid.dom.computeAbsolutePosition(handle);
  6410. var handleWidth = handle.offsetWidth;
  6411. var handleHeight = handle.offsetHeight;
  6412. item.focus();
  6413. item.removeClass(options.styles.selected);
  6414. item.addClass(options.styles.mouseDrag);
  6415. item.attr("aria-grabbed", "true");
  6416. setDropEffects("move");
  6417. dropManager.startDrag(e, handlePos, handleWidth, handleHeight);
  6418. avatar.show();
  6419. },
  6420. stop: function (e, ui) {
  6421. item.removeClass(options.styles.mouseDrag);
  6422. item.addClass(options.styles.selected);
  6423. $(thatReorderer.activeItem).attr("aria-grabbed", "false");
  6424. var markerNode = fluid.unwrap(dropMarker);
  6425. if (markerNode.parentNode) {
  6426. markerNode.parentNode.removeChild(markerNode);
  6427. }
  6428. avatar.hide();
  6429. ui.helper = null;
  6430. setDropEffects("none");
  6431. dropManager.endDrag();
  6432. thatReorderer.requestMovement(dropManager.lastPosition(), item);
  6433. // refocus on the active item because moving places focus on the body
  6434. thatReorderer.activeItem.focus();
  6435. },
  6436. handle: thatReorderer.dom.fastLocate("grabHandle", item)
  6437. });
  6438. }
  6439. function changeSelectedToDefault(jItem, styles) {
  6440. jItem.removeClass(styles.selected);
  6441. jItem.removeClass(styles.dragging);
  6442. jItem.addClass(styles.defaultStyle);
  6443. jItem.attr("aria-selected", "false");
  6444. }
  6445. var selectItem = function (anItem) {
  6446. thatReorderer.events.onSelect.fire(anItem);
  6447. var styles = options.styles;
  6448. // Set the previous active item back to its default state.
  6449. if (thatReorderer.activeItem && thatReorderer.activeItem !== anItem) {
  6450. changeSelectedToDefault($(thatReorderer.activeItem), styles);
  6451. }
  6452. // Then select the new item.
  6453. thatReorderer.activeItem = anItem;
  6454. var jItem = $(anItem);
  6455. jItem.removeClass(styles.defaultStyle);
  6456. jItem.addClass(styles.selected);
  6457. jItem.attr("aria-selected", "true");
  6458. };
  6459. var initSelectables = function () {
  6460. var handleBlur = function (evt) {
  6461. changeSelectedToDefault($(this), options.styles);
  6462. return evt.stopPropagation();
  6463. };
  6464. var handleFocus = function (evt) {
  6465. selectItem(this);
  6466. return evt.stopPropagation();
  6467. };
  6468. var selectables = thatReorderer.dom.fastLocate("selectables");
  6469. for (var i = 0; i < selectables.length; ++ i) {
  6470. var selectable = $(selectables[i]);
  6471. if (!$.data(selectable[0], "fluid.reorderer.selectable-initialised")) {
  6472. selectable.addClass(styles.defaultStyle);
  6473. selectable.blur(handleBlur);
  6474. selectable.focus(handleFocus);
  6475. selectable.click(function (evt) {
  6476. var handle = fluid.unwrap(thatReorderer.dom.fastLocate("grabHandle", this));
  6477. if (fluid.dom.isContainer(handle, evt.target)) {
  6478. $(this).focus();
  6479. }
  6480. });
  6481. selectable.attr("role", options.containerRole.item);
  6482. selectable.attr("aria-selected", "false");
  6483. selectable.attr("aria-disabled", "false");
  6484. $.data(selectable[0], "fluid.reorderer.selectable-initialised", true);
  6485. }
  6486. }
  6487. if (!thatReorderer.selectableContext) {
  6488. thatReorderer.selectableContext = fluid.selectable(thatReorderer.container, {
  6489. selectableElements: selectables,
  6490. selectablesTabindex: thatReorderer.options.selectablesTabindex,
  6491. direction: null
  6492. });
  6493. }
  6494. };
  6495. var dropChangeListener = function (dropTarget) {
  6496. fluid.moveDom(dropMarker, dropTarget.element, dropTarget.position);
  6497. dropMarker.css("display", "");
  6498. if (mouseDropWarning) {
  6499. if (dropTarget.lockedelem) {
  6500. mouseDropWarning.show();
  6501. }
  6502. else {
  6503. mouseDropWarning.hide();
  6504. }
  6505. }
  6506. };
  6507. var initItems = function () {
  6508. var movables = thatReorderer.dom.fastLocate("movables");
  6509. var dropTargets = thatReorderer.dom.fastLocate("dropTargets");
  6510. initSelectables();
  6511. // Setup movables
  6512. for (var i = 0; i < movables.length; i++) {
  6513. var item = movables[i];
  6514. if (!$.data(item, "fluid.reorderer.movable-initialised")) {
  6515. initMovable($(item));
  6516. $.data(item, "fluid.reorderer.movable-initialised", true);
  6517. }
  6518. }
  6519. // In order to create valid html, the drop marker is the same type as the node being dragged.
  6520. // This creates a confusing UI in cases such as an ordered list.
  6521. // drop marker functionality should be made pluggable.
  6522. if (movables.length > 0 && !dropMarker) {
  6523. dropMarker = createDropMarker(movables[0].tagName);
  6524. }
  6525. dropManager.updateGeometry(thatReorderer.layoutHandler.getGeometricInfo());
  6526. dropManager.dropChangeFirer.addListener(dropChangeListener, "fluid.Reorderer");
  6527. // Setup dropTargets
  6528. dropTargets.attr("aria-dropeffect", "none");
  6529. };
  6530. // Final initialization of the Reorderer at the end of the construction process
  6531. if (thatReorderer.container) {
  6532. bindHandlersToContainer(thatReorderer.container,
  6533. thatReorderer.handleKeyDown,
  6534. thatReorderer.handleKeyUp);
  6535. addRolesToContainer(thatReorderer);
  6536. fluid.tabbable(thatReorderer.container);
  6537. initItems();
  6538. }
  6539. if (options.afterMoveCallbackUrl) {
  6540. thatReorderer.events.afterMove.addListener(function () {
  6541. var layoutHandler = thatReorderer.layoutHandler;
  6542. var model = layoutHandler.getModel? layoutHandler.getModel():
  6543. options.acquireModel(thatReorderer);
  6544. $.post(options.afterMoveCallbackUrl, JSON.stringify(model));
  6545. }, "postModel");
  6546. }
  6547. thatReorderer.events.onHover.addListener(hoverStyleHandler, "style");
  6548. thatReorderer.refresh = function () {
  6549. thatReorderer.dom.refresh("movables");
  6550. thatReorderer.dom.refresh("selectables");
  6551. thatReorderer.dom.refresh("grabHandle", thatReorderer.dom.fastLocate("movables"));
  6552. thatReorderer.dom.refresh("stylisticOffset", thatReorderer.dom.fastLocate("movables"));
  6553. thatReorderer.dom.refresh("dropTargets");
  6554. thatReorderer.events.onRefresh.fire();
  6555. initItems();
  6556. thatReorderer.selectableContext.selectables = thatReorderer.dom.fastLocate("selectables");
  6557. thatReorderer.selectableContext.selectablesUpdated(thatReorderer.activeItem);
  6558. };
  6559. thatReorderer.refresh();
  6560. return thatReorderer;
  6561. };
  6562. /**
  6563. * Constants for key codes in events.
  6564. */
  6565. fluid.reorderer.keys = {
  6566. TAB: 9,
  6567. ENTER: 13,
  6568. SHIFT: 16,
  6569. CTRL: 17,
  6570. ALT: 18,
  6571. META: 19,
  6572. SPACE: 32,
  6573. LEFT: 37,
  6574. UP: 38,
  6575. RIGHT: 39,
  6576. DOWN: 40,
  6577. i: 73,
  6578. j: 74,
  6579. k: 75,
  6580. m: 77
  6581. };
  6582. /**
  6583. * The default key sets for the Reorderer. Should be moved into the proper component defaults.
  6584. */
  6585. fluid.reorderer.defaultKeysets = [{
  6586. modifier : function (evt) {
  6587. return evt.ctrlKey;
  6588. },
  6589. up : fluid.reorderer.keys.UP,
  6590. down : fluid.reorderer.keys.DOWN,
  6591. right : fluid.reorderer.keys.RIGHT,
  6592. left : fluid.reorderer.keys.LEFT
  6593. },
  6594. {
  6595. modifier : function (evt) {
  6596. return evt.ctrlKey;
  6597. },
  6598. up : fluid.reorderer.keys.i,
  6599. down : fluid.reorderer.keys.m,
  6600. right : fluid.reorderer.keys.k,
  6601. left : fluid.reorderer.keys.j
  6602. }];
  6603. /**
  6604. * These roles are used to add ARIA roles to orderable items. This list can be extended as needed,
  6605. * but the values of the container and item roles must match ARIA-specified roles.
  6606. */
  6607. fluid.reorderer.roles = {
  6608. GRID: { container: "grid", item: "gridcell" },
  6609. LIST: { container: "list", item: "listitem" },
  6610. REGIONS: { container: "main", item: "article" }
  6611. };
  6612. // Simplified API for reordering lists and grids.
  6613. var simpleInit = function (container, layoutHandler, options) {
  6614. options = options || {};
  6615. options.layoutHandler = layoutHandler;
  6616. return fluid.reorderer(container, options);
  6617. };
  6618. fluid.reorderList = function (container, options) {
  6619. return simpleInit(container, "fluid.listLayoutHandler", options);
  6620. };
  6621. fluid.reorderGrid = function (container, options) {
  6622. return simpleInit(container, "fluid.gridLayoutHandler", options);
  6623. };
  6624. fluid.reorderer.SHUFFLE_GEOMETRIC_STRATEGY = "shuffleProjectFrom";
  6625. fluid.reorderer.GEOMETRIC_STRATEGY = "projectFrom";
  6626. fluid.reorderer.LOGICAL_STRATEGY = "logicalFrom";
  6627. fluid.reorderer.WRAP_LOCKED_STRATEGY = "lockedWrapFrom";
  6628. fluid.reorderer.NO_STRATEGY = null;
  6629. fluid.reorderer.relativeInfoGetter = function (orientation, coStrategy, contraStrategy, dropManager, dom) {
  6630. return function (item, direction, forSelection) {
  6631. var dirorient = fluid.directionOrientation(direction);
  6632. var strategy = dirorient === orientation? coStrategy: contraStrategy;
  6633. return strategy !== null? dropManager[strategy](item, direction, forSelection) : null;
  6634. };
  6635. };
  6636. fluid.defaults("fluid.reorderer", {
  6637. styles: {
  6638. defaultStyle: "fl-reorderer-movable-default",
  6639. selected: "fl-reorderer-movable-selected",
  6640. dragging: "fl-reorderer-movable-dragging",
  6641. mouseDrag: "fl-reorderer-movable-dragging",
  6642. hover: "fl-reorderer-movable-hover",
  6643. dropMarker: "fl-reorderer-dropMarker",
  6644. avatar: "fl-reorderer-avatar"
  6645. },
  6646. selectors: {
  6647. dropWarning: ".flc-reorderer-dropWarning",
  6648. movables: ".flc-reorderer-movable",
  6649. grabHandle: "",
  6650. stylisticOffset: ""
  6651. },
  6652. avatarCreator: defaultAvatarCreator,
  6653. keysets: fluid.reorderer.defaultKeysets,
  6654. layoutHandler: {
  6655. type: "fluid.listLayoutHandler"
  6656. },
  6657. events: {
  6658. onShowKeyboardDropWarning: null,
  6659. onSelect: null,
  6660. onBeginMove: "preventable",
  6661. onMove: null,
  6662. afterMove: null,
  6663. onHover: null,
  6664. onRefresh: null
  6665. },
  6666. mergePolicy: {
  6667. keysets: "replace",
  6668. "selectors.selectables": "selectors.movables",
  6669. "selectors.dropTargets": "selectors.movables"
  6670. }
  6671. });
  6672. /*******************
  6673. * Layout Handlers *
  6674. *******************/
  6675. function geometricInfoGetter(orientation, sentinelize, dom) {
  6676. return function () {
  6677. return {
  6678. sentinelize: sentinelize,
  6679. extents: [{
  6680. orientation: orientation,
  6681. elements: dom.fastLocate("dropTargets")
  6682. }],
  6683. elementMapper: function (element) {
  6684. return $.inArray(element, dom.fastLocate("movables")) === -1? "locked": null;
  6685. }
  6686. };
  6687. };
  6688. }
  6689. fluid.defaults(true, "fluid.listLayoutHandler",
  6690. {orientation: fluid.orientation.VERTICAL,
  6691. containerRole: fluid.reorderer.roles.LIST,
  6692. selectablesTabindex: -1,
  6693. sentinelize: true
  6694. });
  6695. // Public layout handlers.
  6696. fluid.listLayoutHandler = function (container, options, dropManager, dom) {
  6697. var that = {};
  6698. that.getRelativePosition =
  6699. fluid.reorderer.relativeInfoGetter(options.orientation,
  6700. fluid.reorderer.LOGICAL_STRATEGY, null, dropManager, dom);
  6701. that.getGeometricInfo = geometricInfoGetter(options.orientation, options.sentinelize, dom);
  6702. return that;
  6703. }; // End ListLayoutHandler
  6704. fluid.defaults(true, "fluid.gridLayoutHandler",
  6705. {orientation: fluid.orientation.HORIZONTAL,
  6706. containerRole: fluid.reorderer.roles.GRID,
  6707. selectablesTabindex: -1,
  6708. sentinelize: false
  6709. });
  6710. /*
  6711. * Items in the Lightbox are stored in a list, but they are visually presented as a grid that
  6712. * changes dimensions when the window changes size. As a result, when the user presses the up or
  6713. * down arrow key, what lies above or below depends on the current window size.
  6714. *
  6715. * The GridLayoutHandler is responsible for handling changes to this virtual 'grid' of items
  6716. * in the window, and of informing the Lightbox of which items surround a given item.
  6717. */
  6718. fluid.gridLayoutHandler = function (container, options, dropManager, dom) {
  6719. var that = {};
  6720. that.getRelativePosition =
  6721. fluid.reorderer.relativeInfoGetter(options.orientation,
  6722. fluid.reorderer.LOGICAL_STRATEGY, fluid.reorderer.SHUFFLE_GEOMETRIC_STRATEGY,
  6723. dropManager, dom);
  6724. that.getGeometricInfo = geometricInfoGetter(options.orientation, options.sentinelize, dom);
  6725. return that;
  6726. }; // End of GridLayoutHandler
  6727. })(jQuery, fluid_1_2);
  6728. /*
  6729. Copyright 2008-2009 University of Cambridge
  6730. Copyright 2008-2009 University of Toronto
  6731. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  6732. BSD license. You may not use this file except in compliance with one these
  6733. Licenses.
  6734. You may obtain a copy of the ECL 2.0 License and BSD License at
  6735. https://source.fluidproject.org/svn/LICENSE.txt
  6736. */
  6737. /*global jQuery*/
  6738. /*global fluid_1_2*/
  6739. fluid_1_2 = fluid_1_2 || {};
  6740. (function ($, fluid) {
  6741. var deriveLightboxCellBase = function (namebase, index) {
  6742. return namebase + "lightbox-cell:" + index + ":";
  6743. };
  6744. var addThumbnailActivateHandler = function (container) {
  6745. var enterKeyHandler = function (evt) {
  6746. if (evt.which === fluid.reorderer.keys.ENTER) {
  6747. var thumbnailAnchors = $("a", evt.target);
  6748. document.location = thumbnailAnchors.attr("href");
  6749. }
  6750. };
  6751. container.keypress(enterKeyHandler);
  6752. };
  6753. // Custom query method seeks all tags descended from a given root with a
  6754. // particular tag name, whose id matches a regex.
  6755. var seekNodesById = function (rootnode, tagname, idmatch) {
  6756. var inputs = rootnode.getElementsByTagName(tagname);
  6757. var togo = [];
  6758. for (var i = 0; i < inputs.length; i += 1) {
  6759. var input = inputs[i];
  6760. var id = input.id;
  6761. if (id && id.match(idmatch)) {
  6762. togo.push(input);
  6763. }
  6764. }
  6765. return togo;
  6766. };
  6767. var createImageCellFinder = function (parentNode, containerId) {
  6768. parentNode = fluid.unwrap(parentNode);
  6769. var lightboxCellNamePattern = "^" + deriveLightboxCellBase(containerId, "[0-9]+") + "$";
  6770. return function () {
  6771. // This orderable finder assumes that the lightbox thumbnails are 'div' elements
  6772. return seekNodesById(parentNode, "div", lightboxCellNamePattern);
  6773. };
  6774. };
  6775. var seekForm = function (container) {
  6776. return fluid.findAncestor(container, function (element) {
  6777. return $(element).is("form");
  6778. });
  6779. };
  6780. var seekInputs = function (container, reorderform) {
  6781. return seekNodesById(reorderform,
  6782. "input",
  6783. "^" + deriveLightboxCellBase(container.attr("id"), "[^:]*") + "reorder-index$");
  6784. };
  6785. var mapIdsToNames = function (container, reorderform) {
  6786. var inputs = seekInputs(container, reorderform);
  6787. for (var i = 0; i < inputs.length; i++) {
  6788. var input = inputs[i];
  6789. var name = input.name;
  6790. input.name = name || input.id;
  6791. }
  6792. };
  6793. /**
  6794. * Returns a default afterMove listener using the id-based, form-driven scheme for communicating with the server.
  6795. * It is implemented by nesting hidden form fields inside each thumbnail container. The value of these form elements
  6796. * represent the order for each image. This default listener submits the form's default
  6797. * action via AJAX.
  6798. *
  6799. * @param {jQueryable} container the Image Reorderer's container element
  6800. */
  6801. var createIDAfterMoveListener = function (container) {
  6802. var reorderform = seekForm(container);
  6803. mapIdsToNames(container, reorderform);
  6804. return function () {
  6805. var inputs, i;
  6806. inputs = seekInputs(container, reorderform);
  6807. for (i = 0; i < inputs.length; i += 1) {
  6808. inputs[i].value = i;
  6809. }
  6810. if (reorderform && reorderform.action) {
  6811. var order = $(reorderform).serialize();
  6812. $.post(reorderform.action,
  6813. order,
  6814. function (type, data, evt) { /* No-op response */ });
  6815. }
  6816. };
  6817. };
  6818. var setDefaultValue = function (target, path, value) {
  6819. var previousValue = fluid.model.getBeanValue(target, path);
  6820. var valueToSet = previousValue || value;
  6821. fluid.model.setBeanValue(target, path, valueToSet);
  6822. };
  6823. // Public Lightbox API
  6824. /**
  6825. * Creates a new Lightbox instance from the specified parameters, providing full control over how
  6826. * the Lightbox is configured.
  6827. *
  6828. * @param {Object} container
  6829. * @param {Object} options
  6830. */
  6831. fluid.reorderImages = function (container, options) {
  6832. // Instantiate a mini-Image Reorderer component, then feed its options to the real Reorderer.
  6833. var that = fluid.initView("fluid.reorderImages", container, options);
  6834. // If the user didn't specify their own afterMove or movables options,
  6835. // set up defaults for them using the old id-based scheme.
  6836. // Backwards API compatiblity. Remove references to afterMoveCallback by Infusion 1.5.
  6837. setDefaultValue(that, "options.listeners.afterMove",
  6838. that.options.afterMoveCallback || createIDAfterMoveListener(that.container));
  6839. setDefaultValue(that, "options.selectors.movables",
  6840. createImageCellFinder(that.container, that.container.attr("id")));
  6841. var reorderer = fluid.reorderer(that.container, that.options);
  6842. // Add accessibility support, including ARIA and keyboard navigation.
  6843. fluid.transform(reorderer.locate("movables"), function (cell) {
  6844. fluid.reorderImages.addAriaRoles(that.options.selectors.imageTitle, cell);
  6845. });
  6846. fluid.tabindex($("a", that.container), -1);
  6847. addThumbnailActivateHandler(that.container);
  6848. return reorderer;
  6849. };
  6850. fluid.reorderImages.addAriaRoles = function (imageTitle, cell) {
  6851. cell = $(cell);
  6852. cell.attr("role", "img");
  6853. var title = $(imageTitle, cell);
  6854. if (title[0] === cell[0] || title[0] === document) {
  6855. fluid.fail("Could not locate cell title using selector " + imageTitle + " in context " + fluid.dumpEl(cell));
  6856. }
  6857. var titleId = fluid.allocateSimpleId(title);
  6858. cell.attr("aria-labelledby", titleId);
  6859. var image = $("img", cell);
  6860. image.attr("role", "presentation");
  6861. image.attr("alt", "");
  6862. };
  6863. // This function now deprecated. Please use fluid.reorderImages() instead.
  6864. fluid.lightbox = fluid.reorderImages;
  6865. fluid.defaults("fluid.reorderImages", {
  6866. layoutHandler: "fluid.gridLayoutHandler",
  6867. selectors: {
  6868. imageTitle: ".flc-reorderer-imageTitle"
  6869. }
  6870. });
  6871. })(jQuery, fluid_1_2);
  6872. /*
  6873. Copyright 2008-2009 University of Cambridge
  6874. Copyright 2008-2009 University of Toronto
  6875. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  6876. BSD license. You may not use this file except in compliance with one these
  6877. Licenses.
  6878. You may obtain a copy of the ECL 2.0 License and BSD License at
  6879. https://source.fluidproject.org/svn/LICENSE.txt
  6880. */
  6881. // Declare dependencies.
  6882. /*global jQuery*/
  6883. /*global fluid, fluid_1_2*/
  6884. fluid_1_2 = fluid_1_2 || {};
  6885. (function ($, fluid) {
  6886. fluid.moduleLayout = fluid.moduleLayout || {};
  6887. /**
  6888. * Calculate the location of the item and the column in which it resides.
  6889. * @return An object with column index and item index (within that column) properties.
  6890. * These indices are -1 if the item does not exist in the grid.
  6891. */
  6892. var findColumnAndItemIndices = function (item, layout) {
  6893. return fluid.find(layout.columns,
  6894. function (column, colIndex) {
  6895. var index = $.inArray(item, column.elements);
  6896. return index === -1? null : {columnIndex: colIndex, itemIndex: index};
  6897. }, {columnIndex: -1, itemIndex: -1});
  6898. };
  6899. var findColIndex = function (item, layout) {
  6900. return fluid.find(layout.columns,
  6901. function (column, colIndex) {
  6902. return item === column.container? colIndex : null;
  6903. }, -1);
  6904. };
  6905. /**
  6906. * Move an item within the layout object.
  6907. */
  6908. fluid.moduleLayout.updateLayout = function (item, target, position, layout) {
  6909. item = fluid.unwrap(item);
  6910. target = fluid.unwrap(target);
  6911. var itemIndices = findColumnAndItemIndices(item, layout);
  6912. layout.columns[itemIndices.columnIndex].elements.splice(itemIndices.itemIndex, 1);
  6913. var targetCol;
  6914. if (position === fluid.position.INSIDE) {
  6915. targetCol = layout.columns[findColIndex(target, layout)].elements;
  6916. targetCol.splice(targetCol.length, 0, item);
  6917. } else {
  6918. var relativeItemIndices = findColumnAndItemIndices(target, layout);
  6919. targetCol = layout.columns[relativeItemIndices.columnIndex].elements;
  6920. position = fluid.normalisePosition(position,
  6921. itemIndices.columnIndex === relativeItemIndices.columnIndex,
  6922. relativeItemIndices.itemIndex, itemIndices.itemIndex);
  6923. var relative = position === fluid.position.BEFORE? 0 : 1;
  6924. targetCol.splice(relativeItemIndices.itemIndex + relative, 0, item);
  6925. }
  6926. };
  6927. /**
  6928. * Builds a layout object from a set of columns and modules.
  6929. * @param {jQuery} container
  6930. * @param {jQuery} columns
  6931. * @param {jQuery} portlets
  6932. */
  6933. fluid.moduleLayout.layoutFromFlat = function (container, columns, portlets) {
  6934. var layout = {};
  6935. layout.container = container;
  6936. layout.columns = fluid.transform(columns,
  6937. function (column) {
  6938. return {
  6939. container: column,
  6940. elements: $.makeArray(portlets.filter(function () {
  6941. // is this a bug in filter? would have expected "this" to be 1st arg
  6942. return fluid.dom.isContainer(column, this);
  6943. }))
  6944. };
  6945. });
  6946. return layout;
  6947. };
  6948. /**
  6949. * Builds a layout object from a serialisable "layout" object consisting of id lists
  6950. */
  6951. fluid.moduleLayout.layoutFromIds = function (idLayout) {
  6952. return {
  6953. container: fluid.byId(idLayout.id),
  6954. columns: fluid.transform(idLayout.columns,
  6955. function (column) {
  6956. return {
  6957. container: fluid.byId(column.id),
  6958. elements: fluid.transform(column.children, fluid.byId)
  6959. };
  6960. })
  6961. };
  6962. };
  6963. /**
  6964. * Serializes the current layout into a structure of ids
  6965. */
  6966. fluid.moduleLayout.layoutToIds = function (idLayout) {
  6967. return {
  6968. id: fluid.getId(idLayout.container),
  6969. columns: fluid.transform(idLayout.columns,
  6970. function (column) {
  6971. return {
  6972. id: fluid.getId(column.container),
  6973. children: fluid.transform(column.elements, fluid.getId)
  6974. };
  6975. })
  6976. };
  6977. };
  6978. var defaultOnShowKeyboardDropWarning = function (item, dropWarning) {
  6979. if (dropWarning) {
  6980. var offset = $(item).offset();
  6981. dropWarning = $(dropWarning);
  6982. dropWarning.css("position", "absolute");
  6983. dropWarning.css("top", offset.top);
  6984. dropWarning.css("left", offset.left);
  6985. }
  6986. };
  6987. fluid.defaults(true, "fluid.moduleLayoutHandler",
  6988. {orientation: fluid.orientation.VERTICAL,
  6989. containerRole: fluid.reorderer.roles.REGIONS,
  6990. selectablesTabindex: 0,
  6991. sentinelize: true
  6992. });
  6993. /**
  6994. * Module Layout Handler for reordering content modules.
  6995. *
  6996. * General movement guidelines:
  6997. *
  6998. * - Arrowing sideways will always go to the top (moveable) module in the column
  6999. * - Moving sideways will always move to the top available drop target in the column
  7000. * - Wrapping is not necessary at this first pass, but is ok
  7001. */
  7002. fluid.moduleLayoutHandler = function (container, options, dropManager, dom) {
  7003. var that = {};
  7004. function computeLayout() {
  7005. var togo;
  7006. if (options.selectors.modules) {
  7007. togo = fluid.moduleLayout.layoutFromFlat(container, dom.locate("columns"), dom.locate("modules"));
  7008. }
  7009. if (!togo) {
  7010. var idLayout = fluid.model.getBeanValue(options, "moduleLayout.layout");
  7011. fluid.moduleLayout.layoutFromIds(idLayout);
  7012. }
  7013. return togo;
  7014. }
  7015. var layout = computeLayout();
  7016. that.layout = layout;
  7017. function isLocked(item) {
  7018. var lockedModules = options.selectors.lockedModules? dom.fastLocate("lockedModules") : [];
  7019. return $.inArray(item, lockedModules) !== -1;
  7020. }
  7021. that.getRelativePosition =
  7022. fluid.reorderer.relativeInfoGetter(options.orientation,
  7023. fluid.reorderer.WRAP_LOCKED_STRATEGY, fluid.reorderer.GEOMETRIC_STRATEGY,
  7024. dropManager, dom);
  7025. that.getGeometricInfo = function () {
  7026. var extents = [];
  7027. var togo = {extents: extents,
  7028. sentinelize: options.sentinelize};
  7029. togo.elementMapper = function (element) {
  7030. return isLocked(element)? "locked" : null;
  7031. };
  7032. for (var col = 0; col < layout.columns.length; col++) {
  7033. var column = layout.columns[col];
  7034. var thisEls = {
  7035. orientation: options.orientation,
  7036. elements: $.makeArray(column.elements),
  7037. parentElement: column.container
  7038. };
  7039. // fluid.log("Geometry col " + col + " elements " + fluid.dumpEl(thisEls.elements) + " isLocked [" +
  7040. // fluid.transform(thisEls.elements, togo.elementMapper).join(", ") + "]");
  7041. extents.push(thisEls);
  7042. }
  7043. return togo;
  7044. };
  7045. function computeModules(all) {
  7046. return function () {
  7047. var modules = fluid.accumulate(layout.columns, function (column, list) {
  7048. return list.concat(column.elements); // note that concat will not work on a jQuery
  7049. }, []);
  7050. if (!all) {
  7051. fluid.remove_if(modules, isLocked);
  7052. }
  7053. return modules;
  7054. };
  7055. }
  7056. that.returnedOptions = {
  7057. selectors: {
  7058. movables: computeModules(false),
  7059. dropTargets: computeModules(false),
  7060. selectables: computeModules(true)
  7061. },
  7062. listeners: {
  7063. onMove: function (item, requestedPosition) {
  7064. fluid.moduleLayout.updateLayout(item, requestedPosition.element, requestedPosition.position, layout);
  7065. },
  7066. onRefresh: function () {
  7067. layout = computeLayout();
  7068. that.layout = layout;
  7069. },
  7070. "onShowKeyboardDropWarning.setPosition": defaultOnShowKeyboardDropWarning
  7071. }
  7072. };
  7073. that.getModel = function () {
  7074. return fluid.moduleLayout.layoutToIds(layout);
  7075. };
  7076. return that;
  7077. };
  7078. })(jQuery, fluid_1_2);
  7079. /*
  7080. Copyright 2008-2009 University of Cambridge
  7081. Copyright 2008-2009 University of Toronto
  7082. Licensed under the Educational Community License (ECL), Version 2.0 or the New
  7083. BSD license. You may not use this file except in compliance with one these
  7084. Licenses.
  7085. You may obtain a copy of the ECL 2.0 License and BSD License at
  7086. https://source.fluidproject.org/svn/LICENSE.txt
  7087. */
  7088. /*global jQuery*/
  7089. /*global fluid_1_2*/
  7090. fluid_1_2 = fluid_1_2 || {};
  7091. (function ($, fluid) {
  7092. /**
  7093. * Simple way to create a layout reorderer.
  7094. * @param {selector} a jQueryable (selector, element, jQuery) for the layout container
  7095. * @param {Object} a map of selectors for columns and modules within the layout
  7096. * @param {Function} a function to be called when the order changes
  7097. * @param {Object} additional configuration options
  7098. */
  7099. fluid.reorderLayout = function (container, userOptions) {
  7100. var assembleOptions = {
  7101. layoutHandler: "fluid.moduleLayoutHandler",
  7102. selectors: {
  7103. columns: ".flc-reorderer-column",
  7104. modules: ".flc-reorderer-module"
  7105. }
  7106. };
  7107. var options = $.extend(true, assembleOptions, userOptions);
  7108. return fluid.reorderer(container, options);
  7109. };
  7110. })(jQuery, fluid_1_2);