PageSourceSearch

https://blueprintnhpatlas.org/assets/manifests/microarray/microarr…anifest-77b0da9ce123405d95d93359e7a8b0ad.js

js blueprintnhpatlas.org collected 2026-09-26 00:37:22 UTC 2,606,832 bytes, 77,201 lines download raw bytes

vendor: 11,242 bytes, lines 1-432
1/*!
2 * jQuery JavaScript Library v1.10.2
3 * http://jquery.com/
4 *
5 * Includes Sizzle.js
6 * http://sizzlejs.com/
7 *
8 * Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors
9 * Released under the MIT license
10 * http://jquery.org/license
11 *
12 * Date: 2013-07-03T13:48Z
13 */
14
15(function( window, undefined ) {
16
17// Can't do this because several apps including ASP.NET trace
18// the stack via arguments.caller.callee and Firefox dies if
19// you try to trace through "use strict" call chains. (#13335)
20// Support: Firefox 18+
21//"use strict";
22var
23	// The deferred used on DOM ready
24	readyList,
25
26	// A central reference to the root jQuery(document)
27	rootjQuery,
28
29	// Support: IE<10
30	// For `typeof xmlNode.method` instead of `xmlNode.method !== undefined`
31	core_strundefined = typeof undefined,
32
33	// Use the correct document accordingly with window argument (sandbox)
34	location = window.location,
35	document = window.document,
36	docElem = document.documentElement,
37
38	// Map over jQuery in case of overwrite
39	_jQuery = window.jQuery,
40
41	// Map over the $ in case of overwrite
42	_$ = window.$,
43
44	// [[Class]] -> type pairs
45	class2type = {},
46
47	// List of deleted data cache ids, so we can reuse them
48	core_deletedIds = [],
49
50	core_version = "1.10.2",
51
52	// Save a reference to some core methods
53	core_concat = core_deletedIds.concat,
54	core_push = core_deletedIds.push,
55	core_slice = core_deletedIds.slice,
56	core_indexOf = core_deletedIds.indexOf,
57	core_toString = class2type.toString,
58	core_hasOwn = class2type.hasOwnProperty,
59	core_trim = core_version.trim,
60
61	// Define a local copy of jQuery
62	jQuery = function( selector, context ) {
63		// The jQuery object is actually just the init constructor 'enhanced'
64		return new jQuery.fn.init( selector, context, rootjQuery );
65	},
66
67	// Used for matching numbers
68	core_pnum = /[+-]?(?:\d*\.|)\d+(?:[eE][+-]?\d+|)/.source,
69
70	// Used for splitting on whitespace
71	core_rnotwhite = /\S+/g,
72
73	// Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE)
74	rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,
75
76	// A simple way to check for HTML strings
77	// Prioritize #id over <tag> to avoid XSS via location.hash (#9521)
78	// Strict HTML recognition (#11290: must start with <)
79	rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]*))$/,
80
81	// Match a standalone tag
82	rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>|)$/,
83
84	// JSON RegExp
85	rvalidchars = /^[\],:{}\s]*$/,
86	rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
87	rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
88	rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g,
89
90	// Matches dashed string for camelizing
91	rmsPrefix = /^-ms-/,
92	rdashAlpha = /-([\da-z])/gi,
93
94	// Used by jQuery.camelCase as callback to replace()
95	fcamelCase = function( all, letter ) {
96		return letter.toUpperCase();
97	},
98
99	// The ready event handler
100	completed = function( event ) {
101
102		// readyState === "complete" is good enough for us to call the dom ready in oldIE
103		if ( document.addEventListener || event.type === "load" || document.readyState === "complete" ) {
104			detach();
105			jQuery.ready();
106		}
107	},
108	// Clean-up method for dom ready events
109	detach = function() {
110		if ( document.addEventListener ) {
111			document.removeEventListener( "DOMContentLoaded", completed, false );
112			window.removeEventListener( "load", completed, false );
113
114		} else {
115			document.detachEvent( "onreadystatechange", completed );
116			window.detachEvent( "onload", completed );
117		}
118	};
119
120jQuery.fn = jQuery.prototype = {
121	// The current version of jQuery being used
122	jquery: core_version,
123
124	constructor: jQuery,
125	init: function( selector, context, rootjQuery ) {
126		var match, elem;
127
128		// HANDLE: $(""), $(null), $(undefined), $(false)
129		if ( !selector ) {
130			return this;
131		}
132
133		// Handle HTML strings
134		if ( typeof selector === "string" ) {
135			if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) {
136				// Assume that strings that start and end with <> are HTML and skip the regex check
137				match = [ null, selector, null ];
138
139			} else {
140				match = rquickExpr.exec( selector );
141			}
142
143			// Match html or make sure no context is specified for #id
144			if ( match && (match[1] || !context) ) {
145
146				// HANDLE: $(html) -> $(array)
147				if ( match[1] ) {
148					context = context instanceof jQuery ? context[0] : context;
149
150					// scripts is true for back-compat
151					jQuery.merge( this, jQuery.parseHTML(
152						match[1],
153						context && context.nodeType ? context.ownerDocument || context : document,
154						true
155					) );
156
157					// HANDLE: $(html, props)
158					if ( rsingleTag.test( match[1] ) && jQuery.isPlainObject( context ) ) {
159						for ( match in context ) {
160							// Properties of context are called as methods if possible
161							if ( jQuery.isFunction( this[ match ] ) ) {
162								this[ match ]( context[ match ] );
163
164							// ...and otherwise set as attributes
165							} else {
166								this.attr( match, context[ match ] );
167							}
168						}
169					}
170
171					return this;
172
173				// HANDLE: $(#id)
174				} else {
175					elem = document.getElementById( match[2] );
176
177					// Check parentNode to catch when Blackberry 4.6 returns
178					// nodes that are no longer in the document #6963
179					if ( elem && elem.parentNode ) {
180						// Handle the case where IE and Opera return items
181						// by name instead of ID
182						if ( elem.id !== match[2] ) {
183							return rootjQuery.find( selector );
184						}
185
186						// Otherwise, we inject the element directly into the jQuery object
187						this.length = 1;
188						this[0] = elem;
189					}
190
191					this.context = document;
192					this.selector = selector;
193					return this;
194				}
195
196			// HANDLE: $(expr, $(...))
197			} else if ( !context || context.jquery ) {
198				return ( context || rootjQuery ).find( selector );
199
200			// HANDLE: $(expr, context)
201			// (which is just equivalent to: $(context).find(expr)
202			} else {
203				return this.constructor( context ).find( selector );
204			}
205
206		// HANDLE: $(DOMElement)
207		} else if ( selector.nodeType ) {
208			this.context = this[0] = selector;
209			this.length = 1;
210			return this;
211
212		// HANDLE: $(function)
213		// Shortcut for document ready
214		} else if ( jQuery.isFunction( selector ) ) {
215			return rootjQuery.ready( selector );
216		}
217
218		if ( selector.selector !== undefined ) {
219			this.selector = selector.selector;
220			this.context = selector.context;
221		}
222
223		return jQuery.makeArray( selector, this );
224	},
225
226	// Start with an empty selector
227	selector: "",
228
229	// The default length of a jQuery object is 0
230	length: 0,
231
232	toArray: function() {
233		return core_slice.call( this );
234	},
235
236	// Get the Nth element in the matched element set OR
237	// Get the whole matched element set as a clean array
238	get: function( num ) {
239		return num == null ?
240
241			// Return a 'clean' array
242			this.toArray() :
243
244			// Return just the object
245			( num < 0 ? this[ this.length + num ] : this[ num ] );
246	},
247
248	// Take an array of elements and push it onto the stack
249	// (returning the new matched element set)
250	pushStack: function( elems ) {
251
252		// Build a new jQuery matched element set
253		var ret = jQuery.merge( this.constructor(), elems );
254
255		// Add the old object onto the stack (as a reference)
256		ret.prevObject = this;
257		ret.context = this.context;
258
259		// Return the newly-formed element set
260		return ret;
261	},
262
263	// Execute a callback for every element in the matched set.
264	// (You can seed the arguments with an array of args, but this is
265	// only used internally.)
266	each: function( callback, args ) {
267		return jQuery.each( this, callback, args );
268	},
269
270	ready: function( fn ) {
271		// Add the callback
272		jQuery.ready.promise().done( fn );
273
274		return this;
275	},
276
277	slice: function() {
278		return this.pushStack( core_slice.apply( this, arguments ) );
279	},
280
281	first: function() {
282		return this.eq( 0 );
283	},
284
285	last: function() {
286		return this.eq( -1 );
287	},
288
289	eq: function( i ) {
290		var len = this.length,
291			j = +i + ( i < 0 ? len : 0 );
292		return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] );
293	},
294
295	map: function( callback ) {
296		return this.pushStack( jQuery.map(this, function( elem, i ) {
297			return callback.call( elem, i, elem );
298		}));
299	},
300
301	end: function() {
302		return this.prevObject || this.constructor(null);
303	},
304
305	// For internal use only.
306	// Behaves like an Array's method, not like a jQuery method.
307	push: core_push,
308	sort: [].sort,
309	splice: [].splice
310};
311
312// Give the init function the jQuery prototype for later instantiation
313jQuery.fn.init.prototype = jQuery.fn;
314
315jQuery.extend = jQuery.fn.extend = function() {
316	var src, copyIsArray, copy, name, options, clone,
317		target = arguments[0] || {},
318		i = 1,
319		length = arguments.length,
320		deep = false;
321
322	// Handle a deep copy situation
323	if ( typeof target === "boolean" ) {
324		deep = target;
325		target = arguments[1] || {};
326		// skip the boolean and the target
327		i = 2;
328	}
329
330	// Handle case when target is a string or something (possible in deep copy)
331	if ( typeof target !== "object" && !jQuery.isFunction(target) ) {
332		target = {};
333	}
334
335	// extend jQuery itself if only one argument is passed
336	if ( length === i ) {
337		target = this;
338		--i;
339	}
340
341	for ( ; i < length; i++ ) {
342		// Only deal with non-null/undefined values
343		if ( (options = arguments[ i ]) != null ) {
344			// Extend the base object
345			for ( name in options ) {
346				src = target[ name ];
347				copy = options[ name ];
348
349				// Prevent never-ending loop
350				if ( target === copy ) {
351					continue;
352				}
353
354				// Recurse if we're merging plain objects or arrays
355				if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) {
356					if ( copyIsArray ) {
357						copyIsArray = false;
358						clone = src && jQuery.isArray(src) ? src : [];
359
360					} else {
361						clone = src && jQuery.isPlainObject(src) ? src : {};
362					}
363
364					// Never move original objects, clone them
365					target[ name ] = jQuery.extend( deep, clone, copy );
366
367				// Don't bring in undefined values
368				} else if ( copy !== undefined ) {
369					target[ name ] = copy;
370				}
371			}
372		}
373	}
374
375	// Return the modified object
376	return target;
377};
378
379jQuery.extend({
380	// Unique for each copy of jQuery on the page
381	// Non-digits removed to match rinlinejQuery
382	expando: "jQuery" + ( core_version + Math.random() ).replace( /\D/g, "" ),
383
384	noConflict: function( deep ) {
385		if ( window.$ === jQuery ) {
386			window.$ = _$;
387		}
388
389		if ( deep && window.jQuery === jQuery ) {
390			window.jQuery = _jQuery;
391		}
392
393		return jQuery;
394	},
395
396	// Is the DOM ready to be used? Set to true once it occurs.
397	isReady: false,
398
399	// A counter to track how many items to wait for before
400	// the ready event fires. See #6781
401	readyWait: 1,
402
403	// Hold (or release) the ready event
404	holdReady: function( hold ) {
405		if ( hold ) {
406			jQuery.readyWait++;
407		} else {
408			jQuery.ready( true );
409		}
410	},
411
412	// Handle when the DOM is ready
413	ready: function( wait ) {
414
415		// Abort if there are pending holds or we're already ready
416		if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
417			return;
418		}
419
420		// Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443).
421		if ( !document.body ) {
422			return setTimeout( jQuery.ready );
423		}
424
425		// Remember that the DOM is ready
426		jQuery.isReady = true;
427
428		// If a normal DOM Ready event fired, decrement, and wait if need be
429		if ( wait !== true && --jQuery.readyWait > 0 ) {
430			return;
431		}
432
433		// If there are functions bound, to execute
434		readyList.resolveWith( document, [ jQuery ] );
435
436		// Trigger any bound ready events
437		if ( jQuery.fn.trigger ) {
438			jQuery( document ).trigger("ready").off("ready");
439		}
440	},
441
442	// See test/unit/core.js for details concerning isFunction.
443	// Since version 1.3, DOM methods and functions like alert
444	// aren't supported. They return false on IE (#2968).
445	isFunction: function( obj ) {
446		return jQuery.type(obj) === "function";
447	},
448
449	isArray: Array.isArray || function( obj ) {
450		return jQuery.type(obj) === "array";
451	},
452
453	isWindow: function( obj ) {
454		/* jshint eqeqeq: false */
455		return obj != null && obj == obj.window;
456	},
457
458	isNumeric: function( obj ) {
459		return !isNaN( parseFloat(obj) ) && isFinite( obj );
460	},
461
462	type: function( obj ) {
463		if ( obj == null ) {
464			return String( obj );
465		}
466		return typeof obj === "object" || typeof obj === "function" ?
467			class2type[ core_toString.call(obj) ] || "object" :
468			typeof obj;
469	},
470
471	isPlainObject: function( obj ) {
472		var key;
473
474		// Must be an Object.
475		// Because of IE, we also have to check the presence of the constructor property.
476		// Make sure that DOM nodes and window objects don't pass through, as well
477		if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) {
478			return false;
479		}
480
481		try {
482			// Not own constructor property must be Object
483			if ( obj.constructor &&
484				!core_hasOwn.call(obj, "constructor") &&
485				!core_hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) {
486				return false;
487			}
488		} catch ( e ) {
489			// IE8,9 Will throw exceptions on certain host objects #9897
490			return false;
491		}
492
493		// Support: IE<9
494		// Handle iteration over inherited properties before own properties.
495		if ( jQuery.support.ownLast ) {
496			for ( key in obj ) {
497				return core_hasOwn.call( obj, key );
498			}
499		}
500
501		// Own properties are enumerated firstly, so to speed up,
502		// if last one is own, then all properties are own.
503		for ( key in obj ) {}
504
505		return key === undefined || core_hasOwn.call( obj, key );
506	},
507
508	isEmptyObject: function( obj ) {
509		var name;
510		for ( name in obj ) {
511			return false;
512		}
513		return true;
514	},
515
516	error: function( msg ) {
517		throw new Error( msg );
518	},
519
520	// data: string of html
521	// context (optional): If specified, the fragment will be created in this context, defaults to document
522	// keepScripts (optional): If true, will include scripts passed in the html string
523	parseHTML: function( data, context, keepScripts ) {
524		if ( !data || typeof data !== "string" ) {
525			return null;
526		}
527		if ( typeof context === "boolean" ) {
528			keepScripts = context;
529			context = false;
530		}
531		context = context || document;
532
533		var parsed = rsingleTag.exec( data ),
534			scripts = !keepScripts && [];
535
536		// Single tag
537		if ( parsed ) {
538			return [ context.createElement( parsed[1] ) ];
539		}
540
541		parsed = jQuery.buildFragment( [ data ], context, scripts );
542		if ( scripts ) {
543			jQuery( scripts ).remove();
544		}
545		return jQuery.merge( [], parsed.childNodes );
546	},
547
548	parseJSON: function( data ) {
549		// Attempt to parse using the native JSON parser first
550		if ( window.JSON && window.JSON.parse ) {
551			return window.JSON.parse( data );
552		}
553
554		if ( data === null ) {
555			return data;
556		}
557
558		if ( typeof data === "string" ) {
559
560			// Make sure leading/trailing whitespace is removed (IE can't handle it)
561			data = jQuery.trim( data );
562
563			if ( data ) {
564				// Make sure the incoming data is actual JSON
565				// Logic borrowed from http://json.org/json2.js
566				if ( rvalidchars.test( data.replace( rvalidescape, "@" )
567					.replace( rvalidtokens, "]" )
568					.replace( rvalidbraces, "")) ) {
569
570					return ( new Function( "return " + data ) )();
571				}
572			}
573		}
574
575		jQuery.error( "Invalid JSON: " + data );
576	},
577
578	// Cross-browser xml parsing
579	parseXML: function( data ) {
580		var xml, tmp;
581		if ( !data || typeof data !== "string" ) {
582			return null;
583		}
584		try {
585			if ( window.DOMParser ) { // Standard
586				tmp = new DOMParser();
587				xml = tmp.parseFromString( data , "text/xml" );
588			} else { // IE
589				xml = new ActiveXObject( "Microsoft.XMLDOM" );
590				xml.async = "false";
591				xml.loadXML( data );
592			}
593		} catch( e ) {
594			xml = undefined;
595		}
596		if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) {
597			jQuery.error( "Invalid XML: " + data );
598		}
599		return xml;
600	},
601
602	noop: function() {},
603
604	// Evaluates a script in a global context
605	// Workarounds based on findings by Jim Driscoll
606	// http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context
607	globalEval: function( data ) {
608		if ( data && jQuery.trim( data ) ) {
609			// We use execScript on Internet Explorer
610			// We use an anonymous function so that context is window
611			// rather than jQuery in Firefox
612			( window.execScript || function( data ) {
613				window[ "eval" ].call( window, data );
614			} )( data );
615		}
616	},
617
618	// Convert dashed to camelCase; used by the css and data modules
619	// Microsoft forgot to hump their vendor prefix (#9572)
620	camelCase: function( string ) {
621		return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase );
622	},
623
624	nodeName: function( elem, name ) {
625		return elem.nodeName && elem.nodeName.toLowerCase() === name.toLowerCase();
626	},
627
628	// args is for internal usage only
629	each: function( obj, callback, args ) {
630		var value,
631			i = 0,
632			length = obj.length,
633			isArray = isArraylike( obj );
634
635		if ( args ) {
636			if ( isArray ) {
637				for ( ; i < length; i++ ) {
638					value = callback.apply( obj[ i ], args );
639
640					if ( value === false ) {
641						break;
642					}
643				}
644			} else {
645				for ( i in obj ) {
646					value = callback.apply( obj[ i ], args );
647
648					if ( value === false ) {
649						break;
650					}
651				}
652			}
653
654		// A special, fast, case for the most common use of each
655		} else {
656			if ( isArray ) {
657				for ( ; i < length; i++ ) {
658					value = callback.call( obj[ i ], i, obj[ i ] );
659
660					if ( value === false ) {
661						break;
662					}
663				}
664			} else {
665				for ( i in obj ) {
666					value = callback.call( obj[ i ], i, obj[ i ] );
667
668					if ( value === false ) {
669						break;
670					}
671				}
672			}
673		}
674
675		return obj;
676	},
677
678	// Use native String.trim function wherever possible
679	trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
680		function( text ) {
681			return text == null ?
682				"" :
683				core_trim.call( text );
684		} :
685
686		// Otherwise use our own trimming functionality
687		function( text ) {
688			return text == null ?
689				"" :
690				( text + "" ).replace( rtrim, "" );
691		},
692
693	// results is for internal usage only
694	makeArray: function( arr, results ) {
695		var ret = results || [];
696
697		if ( arr != null ) {
698			if ( isArraylike( Object(arr) ) ) {
699				jQuery.merge( ret,
700					typeof arr === "string" ?
701					[ arr ] : arr
702				);
703			} else {
704				core_push.call( ret, arr );
705			}
706		}
707
708		return ret;
709	},
710
711	inArray: function( elem, arr, i ) {
712		var len;
713
714		if ( arr ) {
715			if ( core_indexOf ) {
716				return core_indexOf.call( arr, elem, i );
717			}
718
719			len = arr.length;
720			i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0;
721
722			for ( ; i < len; i++ ) {
723				// Skip accessing in sparse arrays
724				if ( i in arr && arr[ i ] === elem ) {
725					return i;
726				}
727			}
728		}
729
730		return -1;
731	},
732
733	merge: function( first, second ) {
734		var l = second.length,
735			i = first.length,
736			j = 0;
737
738		if ( typeof l === "number" ) {
739			for ( ; j < l; j++ ) {
740				first[ i++ ] = second[ j ];
741			}
742		} else {
743			while ( second[j] !== undefined ) {
744				first[ i++ ] = second[ j++ ];
745			}
746		}
747
748		first.length = i;
749
750		return first;
751	},
752
753	grep: function( elems, callback, inv ) {
754		var retVal,
755			ret = [],
756			i = 0,
757			length = elems.length;
758		inv = !!inv;
759
760		// Go through the array, only saving the items
761		// that pass the validator function
762		for ( ; i < length; i++ ) {
763			retVal = !!callback( elems[ i ], i );
764			if ( inv !== retVal ) {
765				ret.push( elems[ i ] );
766			}
767		}
768
769		return ret;
770	},
771
772	// arg is for internal usage only
773	map: function( elems, callback, arg ) {
774		var value,
775			i = 0,
776			length = elems.length,
777			isArray = isArraylike( elems ),
778			ret = [];
779
780		// Go through the array, translating each of the items to their
781		if ( isArray ) {
782			for ( ; i < length; i++ ) {
783				value = callback( elems[ i ], i, arg );
784
785				if ( value != null ) {
786					ret[ ret.length ] = value;
787				}
788			}
789
790		// Go through every key on the object,
791		} else {
792			for ( i in elems ) {
793				value = callback( elems[ i ], i, arg );
794
795				if ( value != null ) {
796					ret[ ret.length ] = value;
797				}
798			}
799		}
800
801		// Flatten any nested arrays
802		return core_concat.apply( [], ret );
803	},
804
805	// A global GUID counter for objects
806	guid: 1,
807
808	// Bind a function to a context, optionally partially applying any
809	// arguments.
810	proxy: function( fn, context ) {
811		var args, proxy, tmp;
812
813		if ( typeof context === "string" ) {
814			tmp = fn[ context ];
815			context = fn;
816			fn = tmp;
817		}
818
819		// Quick check to determine if target is callable, in the spec
820		// this throws a TypeError, but we will just return undefined.
821		if ( !jQuery.isFunction( fn ) ) {
822			return undefined;
823		}
824
825		// Simulated bind
826		args = core_slice.call( arguments, 2 );
827		proxy = function() {
828			return fn.apply( context || this, args.concat( core_slice.call( arguments ) ) );
829		};
830
831		// Set the guid of unique handler to the same of original handler, so it can be removed
832		proxy.guid = fn.guid = fn.guid || jQuery.guid++;
833
834		return proxy;
835	},
836
837	// Multifunctional method to get and set values of a collection
838	// The value/s can optionally be executed if it's a function
839	access: function( elems, fn, key, value, chainable, emptyGet, raw ) {
840		var i = 0,
841			length = elems.length,
842			bulk = key == null;
843
844		// Sets many values
845		if ( jQuery.type( key ) === "object" ) {
846			chainable = true;
847			for ( i in key ) {
848				jQuery.access( elems, fn, i, key[i], true, emptyGet, raw );
849			}
850
851		// Sets one value
852		} else if ( value !== undefined ) {
853			chainable = true;
854
855			if ( !jQuery.isFunction( value ) ) {
856				raw = true;
857			}
858
859			if ( bulk ) {
860				// Bulk operations run against the entire set
861				if ( raw ) {
862					fn.call( elems, value );
863					fn = null;
864
865				// ...except when executing function values
866				} else {
867					bulk = fn;
868					fn = function( elem, key, value ) {
869						return bulk.call( jQuery( elem ), value );
870					};
871				}
872			}
873
874			if ( fn ) {
875				for ( ; i < length; i++ ) {
876					fn( elems[i], key, raw ? value : value.call( elems[i], i, fn( elems[i], key ) ) );
877				}
878			}
879		}
880
881		return chainable ?
882			elems :
883
884			// Gets
885			bulk ?
886				fn.call( elems ) :
887				length ? fn( elems[0], key ) : emptyGet;
888	},
889
890	now: function() {
891		return ( new Date() ).getTime();
892	},
893
894	// A method for quickly swapping in/out CSS properties to get correct calculations.
895	// Note: this method belongs to the css module but it's needed here for the support module.
896	// If support gets modularized, this method should be moved back to the css module.
897	swap: function( elem, options, callback, args ) {
898		var ret, name,
899			old = {};
900
901		// Remember the old values, and insert the new ones
902		for ( name in options ) {
903			old[ name ] = elem.style[ name ];
904			elem.style[ name ] = options[ name ];
905		}
906
907		ret = callback.apply( elem, args || [] );
908
909		// Revert the old values
910		for ( name in options ) {
911			elem.style[ name ] = old[ name ];
912		}
913
914		return ret;
915	}
916});
917
918jQuery.ready.promise = function( obj ) {
919	if ( !readyList ) {
920
921		readyList = jQuery.Deferred();
922
923		// Catch cases where $(document).ready() is called after the browser event has already occurred.
924		// we once tried to use readyState "interactive" here, but it caused issues like the one
925		// discovered by ChrisS here: http://bugs.jquery.com/ticket/12282#comment:15
926		if ( document.readyState === "complete" ) {
927			// Handle it asynchronously to allow scripts the opportunity to delay ready
928			setTimeout( jQuery.ready );
929
930		// Standards-based browsers support DOMContentLoaded
931		} else if ( document.addEventListener ) {
932			// Use the handy event callback
933			document.addEventListener( "DOMContentLoaded", completed, false );
934
935			// A fallback to window.onload, that will always work
936			window.addEventListener( "load", completed, false );
937
938		// If IE event model is used
939		} else {
940			// Ensure firing before onload, maybe late but safe also for iframes
941			document.attachEvent( "onreadystatechange", completed );
942
943			// A fallback to window.onload, that will always work
944			window.attachEvent( "onload", completed );
945
946			// If IE and not a frame
947			// continually check to see if the document is ready
948			var top = false;
949
950			try {
951				top = window.frameElement == null && document.documentElement;
952			} catch(e) {}
953
954			if ( top && top.doScroll ) {
955				(function doScrollCheck() {
956					if ( !jQuery.isReady ) {
957
958						try {
959							// Use the trick by Diego Perini
960							// http://javascript.nwbox.com/IEContentLoaded/
961							top.doScroll("left");
962						} catch(e) {
963							return setTimeout( doScrollCheck, 50 );
964						}
965
966						// detach all dom ready events
967						detach();
968
969						// and execute any waiting functions
970						jQuery.ready();
971					}
972				})();
973			}
974		}
975	}
976	return readyList.promise( obj );
977};
978
979// Populate the class2type map
980jQuery.each("Boolean Number String Function Array Date RegExp Object Error".split(" "), function(i, name) {
981	class2type[ "[object " + name + "]" ] = name.toLowerCase();
982});
983
984function isArraylike( obj ) {
985	var length = obj.length,
986		type = jQuery.type( obj );
987
988	if ( jQuery.isWindow( obj ) ) {
989		return false;
990	}
991
992	if ( obj.nodeType === 1 && length ) {
993		return true;
994	}
995
996	return type === "array" || type !== "function" &&
997		( length === 0 ||
998		typeof length === "number" && length > 0 && ( length - 1 ) in obj );
999}
1000
1001// All jQuery objects should point back to these
1002rootjQuery = jQuery(document);
1003/*!
1004 * Sizzle CSS Selector Engine v1.10.2
1005 * http://sizzlejs.com/
1006 *
1007 * Copyright 2013 jQuery Foundation, Inc. and other contributors
1008 * Released under the MIT license
1009 * http://jquery.org/license
1010 *
1011 * Date: 2013-07-03
1012 */
1013(function( window, undefined ) {
1014
1015var i,
1016	support,
1017	cachedruns,
1018	Expr,
1019	getText,
1020	isXML,
1021	compile,
1022	outermostContext,
1023	sortInput,
1024
1025	// Local document vars
1026	setDocument,
1027	document,
1028	docElem,
1029	documentIsHTML,
1030	rbuggyQSA,
1031	rbuggyMatches,
1032	matches,
1033	contains,
1034
1035	// Instance-specific data
1036	expando = "sizzle" + -(new Date()),
1037	preferredDoc = window.document,
1038	dirruns = 0,
1039	done = 0,
1040	classCache = createCache(),
1041	tokenCache = createCache(),
1042	compilerCache = createCache(),
1043	hasDuplicate = false,
1044	sortOrder = function( a, b ) {
1045		if ( a === b ) {
1046			hasDuplicate = true;
1047			return 0;
1048		}
1049		return 0;
1050	},
1051
1052	// General-purpose constants
1053	strundefined = typeof undefined,
1054	MAX_NEGATIVE = 1 << 31,
1055
1056	// Instance methods
1057	hasOwn = ({}).hasOwnProperty,
1058	arr = [],
1059	pop = arr.pop,
1060	push_native = arr.push,
1061	push = arr.push,
1062	slice = arr.slice,
1063	// Use a stripped-down indexOf if we can't use a native one
1064	indexOf = arr.indexOf || function( elem ) {
1065		var i = 0,
1066			len = this.length;
1067		for ( ; i < len; i++ ) {
1068			if ( this[i] === elem ) {
1069				return i;
1070			}
1071		}
1072		return -1;
1073	},
1074
1075	booleans = "checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped",
1076
1077	// Regular expressions
1078
1079	// Whitespace characters http://www.w3.org/TR/css3-selectors/#whitespace
1080	whitespace = "[\\x20\\t\\r\\n\\f]",
1081	// http://www.w3.org/TR/css3-syntax/#characters
1082	characterEncoding = "(?:\\\\.|[\\w-]|[^\\x00-\\xa0])+",
1083
1084	// Loosely modeled on CSS identifier characters
1085	// An unquoted value should be a CSS identifier http://www.w3.org/TR/css3-selectors/#attribute-selectors
1086	// Proper syntax: http://www.w3.org/TR/CSS21/syndata.html#value-def-identifier
1087	identifier = characterEncoding.replace( "w", "w#" ),
1088
1089	// Acceptable operators http://www.w3.org/TR/selectors/#attribute-selectors
1090	attributes = "\\[" + whitespace + "*(" + characterEncoding + ")" + whitespace +
1091		"*(?:([*^$|!~]?=)" + whitespace + "*(?:(['\"])((?:\\\\.|[^\\\\])*?)\\3|(" + identifier + ")|)|)" + whitespace + "*\\]",
1092
1093	// Prefer arguments quoted,
1094	//   then not containing pseudos/brackets,
1095	//   then attribute selectors/non-parenthetical expressions,
1096	//   then anything else
1097	// These preferences are here to reduce the number of selectors
1098	//   needing tokenize in the PSEUDO preFilter
1099	pseudos = ":(" + characterEncoding + ")(?:\\(((['\"])((?:\\\\.|[^\\\\])*?)\\3|((?:\\\\.|[^\\\\()[\\]]|" + attributes.replace( 3, 8 ) + ")*)|.*)\\)|)",
1100
1101	// Leading and non-escaped trailing whitespace, capturing some non-whitespace characters preceding the latter
1102	rtrim = new RegExp( "^" + whitespace + "+|((?:^|[^\\\\])(?:\\\\.)*)" + whitespace + "+$", "g" ),
1103
1104	rcomma = new RegExp( "^" + whitespace + "*," + whitespace + "*" ),
1105	rcombinators = new RegExp( "^" + whitespace + "*([>+~]|" + whitespace + ")" + whitespace + "*" ),
1106
1107	rsibling = new RegExp( whitespace + "*[+~]" ),
1108	rattributeQuotes = new RegExp( "=" + whitespace + "*([^\\]'\"]*)" + whitespace + "*\\]", "g" ),
1109
1110	rpseudo = new RegExp( pseudos ),
1111	ridentifier = new RegExp( "^" + identifier + "$" ),
1112
1113	matchExpr = {
1114		"ID": new RegExp( "^#(" + characterEncoding + ")" ),
1115		"CLASS": new RegExp( "^\\.(" + characterEncoding + ")" ),
1116		"TAG": new RegExp( "^(" + characterEncoding.replace( "w", "w*" ) + ")" ),
1117		"ATTR": new RegExp( "^" + attributes ),
1118		"PSEUDO": new RegExp( "^" + pseudos ),
1119		"CHILD": new RegExp( "^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\(" + whitespace +
1120			"*(even|odd|(([+-]|)(\\d*)n|)" + whitespace + "*(?:([+-]|)" + whitespace +
1121			"*(\\d+)|))" + whitespace + "*\\)|)", "i" ),
1122		"bool": new RegExp( "^(?:" + booleans + ")$", "i" ),
1123		// For use in libraries implementing .is()
1124		// We use this for POS matching in `select`
1125		"needsContext": new RegExp( "^" + whitespace + "*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\(" +
1126			whitespace + "*((?:-\\d)?\\d*)" + whitespace + "*\\)|)(?=[^-]|$)", "i" )
1127	},
1128
1129	rnative = /^[^{]+\{\s*\[native \w/,
1130
vendor: 21,121 bytes, lines 1131-1895
1131	// Easily-parseable/retrievable ID or TAG or CLASS selectors
1132	rquickExpr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/,
1133
1134	rinputs = /^(?:input|select|textarea|button)$/i,
1135	rheader = /^h\d$/i,
1136
1137	rescape = /'|\\/g,
1138
1139	// CSS escapes http://www.w3.org/TR/CSS21/syndata.html#escaped-characters
1140	runescape = new RegExp( "\\\\([\\da-f]{1,6}" + whitespace + "?|(" + whitespace + ")|.)", "ig" ),
1141	funescape = function( _, escaped, escapedWhitespace ) {
1142		var high = "0x" + escaped - 0x10000;
1143		// NaN means non-codepoint
1144		// Support: Firefox
1145		// Workaround erroneous numeric interpretation of +"0x"
1146		return high !== high || escapedWhitespace ?
1147			escaped :
1148			// BMP codepoint
1149			high < 0 ?
1150				String.fromCharCode( high + 0x10000 ) :
1151				// Supplemental Plane codepoint (surrogate pair)
1152				String.fromCharCode( high >> 10 | 0xD800, high & 0x3FF | 0xDC00 );
1153	};
1154
1155// Optimize for push.apply( _, NodeList )
1156try {
1157	push.apply(
1158		(arr = slice.call( preferredDoc.childNodes )),
1159		preferredDoc.childNodes
1160	);
1161	// Support: Android<4.0
1162	// Detect silently failing push.apply
1163	arr[ preferredDoc.childNodes.length ].nodeType;
1164} catch ( e ) {
1165	push = { apply: arr.length ?
1166
1167		// Leverage slice if possible
1168		function( target, els ) {
1169			push_native.apply( target, slice.call(els) );
1170		} :
1171
1172		// Support: IE<9
1173		// Otherwise append directly
1174		function( target, els ) {
1175			var j = target.length,
1176				i = 0;
1177			// Can't trust NodeList.length
1178			while ( (target[j++] = els[i++]) ) {}
1179			target.length = j - 1;
1180		}
1181	};
1182}
1183
1184function Sizzle( selector, context, results, seed ) {
1185	var match, elem, m, nodeType,
1186		// QSA vars
1187		i, groups, old, nid, newContext, newSelector;
1188
1189	if ( ( context ? context.ownerDocument || context : preferredDoc ) !== document ) {
1190		setDocument( context );
1191	}
1192
1193	context = context || document;
1194	results = results || [];
1195
1196	if ( !selector || typeof selector !== "string" ) {
1197		return results;
1198	}
1199
1200	if ( (nodeType = context.nodeType) !== 1 && nodeType !== 9 ) {
1201		return [];
1202	}
1203
1204	if ( documentIsHTML && !seed ) {
1205
1206		// Shortcuts
1207		if ( (match = rquickExpr.exec( selector )) ) {
1208			// Speed-up: Sizzle("#ID")
1209			if ( (m = match[1]) ) {
1210				if ( nodeType === 9 ) {
1211					elem = context.getElementById( m );
1212					// Check parentNode to catch when Blackberry 4.6 returns
1213					// nodes that are no longer in the document #6963
1214					if ( elem && elem.parentNode ) {
1215						// Handle the case where IE, Opera, and Webkit return items
1216						// by name instead of ID
1217						if ( elem.id === m ) {
1218							results.push( elem );
1219							return results;
1220						}
1221					} else {
1222						return results;
1223					}
1224				} else {
1225					// Context is not a document
1226					if ( context.ownerDocument && (elem = context.ownerDocument.getElementById( m )) &&
1227						contains( context, elem ) && elem.id === m ) {
1228						results.push( elem );
1229						return results;
1230					}
1231				}
1232
1233			// Speed-up: Sizzle("TAG")
1234			} else if ( match[2] ) {
1235				push.apply( results, context.getElementsByTagName( selector ) );
1236				return results;
1237
1238			// Speed-up: Sizzle(".CLASS")
1239			} else if ( (m = match[3]) && support.getElementsByClassName && context.getElementsByClassName ) {
1240				push.apply( results, context.getElementsByClassName( m ) );
1241				return results;
1242			}
1243		}
1244
1245		// QSA path
1246		if ( support.qsa && (!rbuggyQSA || !rbuggyQSA.test( selector )) ) {
1247			nid = old = expando;
1248			newContext = context;
1249			newSelector = nodeType === 9 && selector;
1250
1251			// qSA works strangely on Element-rooted queries
1252			// We can work around this by specifying an extra ID on the root
1253			// and working up from there (Thanks to Andrew Dupont for the technique)
1254			// IE 8 doesn't work on object elements
1255			if ( nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
1256				groups = tokenize( selector );
1257
1258				if ( (old = context.getAttribute("id")) ) {
1259					nid = old.replace( rescape, "\\$&" );
1260				} else {
1261					context.setAttribute( "id", nid );
1262				}
1263				nid = "[id='" + nid + "'] ";
1264
1265				i = groups.length;
1266				while ( i-- ) {
1267					groups[i] = nid + toSelector( groups[i] );
1268				}
1269				newContext = rsibling.test( selector ) && context.parentNode || context;
1270				newSelector = groups.join(",");
1271			}
1272
1273			if ( newSelector ) {
1274				try {
1275					push.apply( results,
1276						newContext.querySelectorAll( newSelector )
1277					);
1278					return results;
1279				} catch(qsaError) {
1280				} finally {
1281					if ( !old ) {
1282						context.removeAttribute("id");
1283					}
1284				}
1285			}
1286		}
1287	}
1288
1289	// All others
1290	return select( selector.replace( rtrim, "$1" ), context, results, seed );
1291}
1292
1293/**
1294 * Create key-value caches of limited size
1295 * @returns {Function(string, Object)} Returns the Object data after storing it on itself with
1296 *	property name the (space-suffixed) string and (if the cache is larger than Expr.cacheLength)
1297 *	deleting the oldest entry
1298 */
1299function createCache() {
1300	var keys = [];
1301
1302	function cache( key, value ) {
1303		// Use (key + " ") to avoid collision with native prototype properties (see Issue #157)
1304		if ( keys.push( key += " " ) > Expr.cacheLength ) {
1305			// Only keep the most recent entries
1306			delete cache[ keys.shift() ];
1307		}
1308		return (cache[ key ] = value);
1309	}
1310	return cache;
1311}
1312
1313/**
1314 * Mark a function for special use by Sizzle
1315 * @param {Function} fn The function to mark
1316 */
1317function markFunction( fn ) {
1318	fn[ expando ] = true;
1319	return fn;
1320}
1321
1322/**
1323 * Support testing using an element
1324 * @param {Function} fn Passed the created div and expects a boolean result
1325 */
1326function assert( fn ) {
1327	var div = document.createElement("div");
1328
1329	try {
1330		return !!fn( div );
1331	} catch (e) {
1332		return false;
1333	} finally {
1334		// Remove from its parent by default
1335		if ( div.parentNode ) {
1336			div.parentNode.removeChild( div );
1337		}
1338		// release memory in IE
1339		div = null;
1340	}
1341}
1342
1343/**
1344 * Adds the same handler for all of the specified attrs
1345 * @param {String} attrs Pipe-separated list of attributes
1346 * @param {Function} handler The method that will be applied
1347 */
1348function addHandle( attrs, handler ) {
1349	var arr = attrs.split("|"),
1350		i = attrs.length;
1351
1352	while ( i-- ) {
1353		Expr.attrHandle[ arr[i] ] = handler;
1354	}
1355}
1356
1357/**
1358 * Checks document order of two siblings
1359 * @param {Element} a
1360 * @param {Element} b
1361 * @returns {Number} Returns less than 0 if a precedes b, greater than 0 if a follows b
1362 */
1363function siblingCheck( a, b ) {
1364	var cur = b && a,
1365		diff = cur && a.nodeType === 1 && b.nodeType === 1 &&
1366			( ~b.sourceIndex || MAX_NEGATIVE ) -
1367			( ~a.sourceIndex || MAX_NEGATIVE );
1368
1369	// Use IE sourceIndex if available on both nodes
1370	if ( diff ) {
1371		return diff;
1372	}
1373
1374	// Check if b follows a
1375	if ( cur ) {
1376		while ( (cur = cur.nextSibling) ) {
1377			if ( cur === b ) {
1378				return -1;
1379			}
1380		}
1381	}
1382
1383	return a ? 1 : -1;
1384}
1385
1386/**
1387 * Returns a function to use in pseudos for input types
1388 * @param {String} type
1389 */
1390function createInputPseudo( type ) {
1391	return function( elem ) {
1392		var name = elem.nodeName.toLowerCase();
1393		return name === "input" && elem.type === type;
1394	};
1395}
1396
1397/**
1398 * Returns a function to use in pseudos for buttons
1399 * @param {String} type
1400 */
1401function createButtonPseudo( type ) {
1402	return function( elem ) {
1403		var name = elem.nodeName.toLowerCase();
1404		return (name === "input" || name === "button") && elem.type === type;
1405	};
1406}
1407
1408/**
1409 * Returns a function to use in pseudos for positionals
1410 * @param {Function} fn
1411 */
1412function createPositionalPseudo( fn ) {
1413	return markFunction(function( argument ) {
1414		argument = +argument;
1415		return markFunction(function( seed, matches ) {
1416			var j,
1417				matchIndexes = fn( [], seed.length, argument ),
1418				i = matchIndexes.length;
1419
1420			// Match elements found at the specified indexes
1421			while ( i-- ) {
1422				if ( seed[ (j = matchIndexes[i]) ] ) {
1423					seed[j] = !(matches[j] = seed[j]);
1424				}
1425			}
1426		});
1427	});
1428}
1429
1430/**
1431 * Detect xml
1432 * @param {Element|Object} elem An element or a document
1433 */
1434isXML = Sizzle.isXML = function( elem ) {
1435	// documentElement is verified for cases where it doesn't yet exist
1436	// (such as loading iframes in IE - #4833)
1437	var documentElement = elem && (elem.ownerDocument || elem).documentElement;
1438	return documentElement ? documentElement.nodeName !== "HTML" : false;
1439};
1440
1441// Expose support vars for convenience
1442support = Sizzle.support = {};
1443
1444/**
1445 * Sets document-related variables once based on the current document
1446 * @param {Element|Object} [doc] An element or document object to use to set the document
1447 * @returns {Object} Returns the current document
1448 */
1449setDocument = Sizzle.setDocument = function( node ) {
1450	var doc = node ? node.ownerDocument || node : preferredDoc,
1451		parent = doc.defaultView;
1452
1453	// If no document and documentElement is available, return
1454	if ( doc === document || doc.nodeType !== 9 || !doc.documentElement ) {
1455		return document;
1456	}
1457
1458	// Set our document
1459	document = doc;
1460	docElem = doc.documentElement;
1461
1462	// Support tests
1463	documentIsHTML = !isXML( doc );
1464
1465	// Support: IE>8
1466	// If iframe document is assigned to "document" variable and if iframe has been reloaded,
1467	// IE will throw "permission denied" error when accessing "document" variable, see jQuery #13936
1468	// IE6-8 do not support the defaultView property so parent will be undefined
1469	if ( parent && parent.attachEvent && parent !== parent.top ) {
1470		parent.attachEvent( "onbeforeunload", function() {
1471			setDocument();
1472		});
1473	}
1474
1475	/* Attributes
1476	---------------------------------------------------------------------- */
1477
1478	// Support: IE<8
1479	// Verify that getAttribute really returns attributes and not properties (excepting IE8 booleans)
1480	support.attributes = assert(function( div ) {
1481		div.className = "i";
1482		return !div.getAttribute("className");
1483	});
1484
1485	/* getElement(s)By*
1486	---------------------------------------------------------------------- */
1487
1488	// Check if getElementsByTagName("*") returns only elements
1489	support.getElementsByTagName = assert(function( div ) {
1490		div.appendChild( doc.createComment("") );
1491		return !div.getElementsByTagName("*").length;
1492	});
1493
1494	// Check if getElementsByClassName can be trusted
1495	support.getElementsByClassName = assert(function( div ) {
1496		div.innerHTML = "<div class='a'></div><div class='a i'></div>";
1497
1498		// Support: Safari<4
1499		// Catch class over-caching
1500		div.firstChild.className = "i";
1501		// Support: Opera<10
1502		// Catch gEBCN failure to find non-leading classes
1503		return div.getElementsByClassName("i").length === 2;
1504	});
1505
1506	// Support: IE<10
1507	// Check if getElementById returns elements by name
1508	// The broken getElementById methods don't pick up programatically-set names,
1509	// so use a roundabout getElementsByName test
1510	support.getById = assert(function( div ) {
1511		docElem.appendChild( div ).id = expando;
1512		return !doc.getElementsByName || !doc.getElementsByName( expando ).length;
1513	});
1514
1515	// ID find and filter
1516	if ( support.getById ) {
1517		Expr.find["ID"] = function( id, context ) {
1518			if ( typeof context.getElementById !== strundefined && documentIsHTML ) {
1519				var m = context.getElementById( id );
1520				// Check parentNode to catch when Blackberry 4.6 returns
1521				// nodes that are no longer in the document #6963
1522				return m && m.parentNode ? [m] : [];
1523			}
1524		};
1525		Expr.filter["ID"] = function( id ) {
1526			var attrId = id.replace( runescape, funescape );
1527			return function( elem ) {
1528				return elem.getAttribute("id") === attrId;
1529			};
1530		};
1531	} else {
1532		// Support: IE6/7
1533		// getElementById is not reliable as a find shortcut
1534		delete Expr.find["ID"];
1535
1536		Expr.filter["ID"] =  function( id ) {
1537			var attrId = id.replace( runescape, funescape );
1538			return function( elem ) {
1539				var node = typeof elem.getAttributeNode !== strundefined && elem.getAttributeNode("id");
1540				return node && node.value === attrId;
1541			};
1542		};
1543	}
1544
1545	// Tag
1546	Expr.find["TAG"] = support.getElementsByTagName ?
1547		function( tag, context ) {
1548			if ( typeof context.getElementsByTagName !== strundefined ) {
1549				return context.getElementsByTagName( tag );
1550			}
1551		} :
1552		function( tag, context ) {
1553			var elem,
1554				tmp = [],
1555				i = 0,
1556				results = context.getElementsByTagName( tag );
1557
1558			// Filter out possible comments
1559			if ( tag === "*" ) {
1560				while ( (elem = results[i++]) ) {
1561					if ( elem.nodeType === 1 ) {
1562						tmp.push( elem );
1563					}
1564				}
1565
1566				return tmp;
1567			}
1568			return results;
1569		};
1570
1571	// Class
1572	Expr.find["CLASS"] = support.getElementsByClassName && function( className, context ) {
1573		if ( typeof context.getElementsByClassName !== strundefined && documentIsHTML ) {
1574			return context.getElementsByClassName( className );
1575		}
1576	};
1577
1578	/* QSA/matchesSelector
1579	---------------------------------------------------------------------- */
1580
1581	// QSA and matchesSelector support
1582
1583	// matchesSelector(:active) reports false when true (IE9/Opera 11.5)
1584	rbuggyMatches = [];
1585
1586	// qSa(:focus) reports false when true (Chrome 21)
1587	// We allow this because of a bug in IE8/9 that throws an error
1588	// whenever `document.activeElement` is accessed on an iframe
1589	// So, we allow :focus to pass through QSA all the time to avoid the IE error
1590	// See http://bugs.jquery.com/ticket/13378
1591	rbuggyQSA = [];
1592
1593	if ( (support.qsa = rnative.test( doc.querySelectorAll )) ) {
1594		// Build QSA regex
1595		// Regex strategy adopted from Diego Perini
1596		assert(function( div ) {
1597			// Select is set to empty string on purpose
1598			// This is to test IE's treatment of not explicitly
1599			// setting a boolean content attribute,
1600			// since its presence should be enough
1601			// http://bugs.jquery.com/ticket/12359
1602			div.innerHTML = "<select><option selected=''></option></select>";
1603
1604			// Support: IE8
1605			// Boolean attributes and "value" are not treated correctly
1606			if ( !div.querySelectorAll("[selected]").length ) {
1607				rbuggyQSA.push( "\\[" + whitespace + "*(?:value|" + booleans + ")" );
1608			}
1609
1610			// Webkit/Opera - :checked should return selected option elements
1611			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
1612			// IE8 throws error here and will not see later tests
1613			if ( !div.querySelectorAll(":checked").length ) {
1614				rbuggyQSA.push(":checked");
1615			}
1616		});
1617
1618		assert(function( div ) {
1619
1620			// Support: Opera 10-12/IE8
1621			// ^= $= *= and empty values
1622			// Should not select anything
1623			// Support: Windows 8 Native Apps
1624			// The type attribute is restricted during .innerHTML assignment
1625			var input = doc.createElement("input");
1626			input.setAttribute( "type", "hidden" );
1627			div.appendChild( input ).setAttribute( "t", "" );
1628
1629			if ( div.querySelectorAll("[t^='']").length ) {
1630				rbuggyQSA.push( "[*^$]=" + whitespace + "*(?:''|\"\")" );
1631			}
1632
1633			// FF 3.5 - :enabled/:disabled and hidden elements (hidden elements are still enabled)
1634			// IE8 throws error here and will not see later tests
1635			if ( !div.querySelectorAll(":enabled").length ) {
1636				rbuggyQSA.push( ":enabled", ":disabled" );
1637			}
1638
1639			// Opera 10-11 does not throw on post-comma invalid pseudos
1640			div.querySelectorAll("*,:x");
1641			rbuggyQSA.push(",.*:");
1642		});
1643	}
1644
1645	if ( (support.matchesSelector = rnative.test( (matches = docElem.webkitMatchesSelector ||
1646		docElem.mozMatchesSelector ||
1647		docElem.oMatchesSelector ||
1648		docElem.msMatchesSelector) )) ) {
1649
1650		assert(function( div ) {
1651			// Check to see if it's possible to do matchesSelector
1652			// on a disconnected node (IE 9)
1653			support.disconnectedMatch = matches.call( div, "div" );
1654
1655			// This should fail with an exception
1656			// Gecko does not error, returns false instead
1657			matches.call( div, "[s!='']:x" );
1658			rbuggyMatches.push( "!=", pseudos );
1659		});
1660	}
1661
1662	rbuggyQSA = rbuggyQSA.length && new RegExp( rbuggyQSA.join("|") );
1663	rbuggyMatches = rbuggyMatches.length && new RegExp( rbuggyMatches.join("|") );
1664
1665	/* Contains
1666	---------------------------------------------------------------------- */
1667
1668	// Element contains another
1669	// Purposefully does not implement inclusive descendent
1670	// As in, an element does not contain itself
1671	contains = rnative.test( docElem.contains ) || docElem.compareDocumentPosition ?
1672		function( a, b ) {
1673			var adown = a.nodeType === 9 ? a.documentElement : a,
1674				bup = b && b.parentNode;
1675			return a === bup || !!( bup && bup.nodeType === 1 && (
1676				adown.contains ?
1677					adown.contains( bup ) :
1678					a.compareDocumentPosition && a.compareDocumentPosition( bup ) & 16
1679			));
1680		} :
1681		function( a, b ) {
1682			if ( b ) {
1683				while ( (b = b.parentNode) ) {
1684					if ( b === a ) {
1685						return true;
1686					}
1687				}
1688			}
1689			return false;
1690		};
1691
1692	/* Sorting
1693	---------------------------------------------------------------------- */
1694
1695	// Document order sorting
1696	sortOrder = docElem.compareDocumentPosition ?
1697	function( a, b ) {
1698
1699		// Flag for duplicate removal
1700		if ( a === b ) {
1701			hasDuplicate = true;
1702			return 0;
1703		}
1704
1705		var compare = b.compareDocumentPosition && a.compareDocumentPosition && a.compareDocumentPosition( b );
1706
1707		if ( compare ) {
1708			// Disconnected nodes
1709			if ( compare & 1 ||
1710				(!support.sortDetached && b.compareDocumentPosition( a ) === compare) ) {
1711
1712				// Choose the first element that is related to our preferred document
1713				if ( a === doc || contains(preferredDoc, a) ) {
1714					return -1;
1715				}
1716				if ( b === doc || contains(preferredDoc, b) ) {
1717					return 1;
1718				}
1719
1720				// Maintain original order
1721				return sortInput ?
1722					( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
1723					0;
1724			}
1725
1726			return compare & 4 ? -1 : 1;
1727		}
1728
1729		// Not directly comparable, sort on existence of method
1730		return a.compareDocumentPosition ? -1 : 1;
1731	} :
1732	function( a, b ) {
1733		var cur,
1734			i = 0,
1735			aup = a.parentNode,
1736			bup = b.parentNode,
1737			ap = [ a ],
1738			bp = [ b ];
1739
1740		// Exit early if the nodes are identical
1741		if ( a === b ) {
1742			hasDuplicate = true;
1743			return 0;
1744
1745		// Parentless nodes are either documents or disconnected
1746		} else if ( !aup || !bup ) {
1747			return a === doc ? -1 :
1748				b === doc ? 1 :
1749				aup ? -1 :
1750				bup ? 1 :
1751				sortInput ?
1752				( indexOf.call( sortInput, a ) - indexOf.call( sortInput, b ) ) :
1753				0;
1754
1755		// If the nodes are siblings, we can do a quick check
1756		} else if ( aup === bup ) {
1757			return siblingCheck( a, b );
1758		}
1759
1760		// Otherwise we need full lists of their ancestors for comparison
1761		cur = a;
1762		while ( (cur = cur.parentNode) ) {
1763			ap.unshift( cur );
1764		}
1765		cur = b;
1766		while ( (cur = cur.parentNode) ) {
1767			bp.unshift( cur );
1768		}
1769
1770		// Walk down the tree looking for a discrepancy
1771		while ( ap[i] === bp[i] ) {
1772			i++;
1773		}
1774
1775		return i ?
1776			// Do a sibling check if the nodes have a common ancestor
1777			siblingCheck( ap[i], bp[i] ) :
1778
1779			// Otherwise nodes in our document sort first
1780			ap[i] === preferredDoc ? -1 :
1781			bp[i] === preferredDoc ? 1 :
1782			0;
1783	};
1784
1785	return doc;
1786};
1787
1788Sizzle.matches = function( expr, elements ) {
1789	return Sizzle( expr, null, null, elements );
1790};
1791
1792Sizzle.matchesSelector = function( elem, expr ) {
1793	// Set document vars if needed
1794	if ( ( elem.ownerDocument || elem ) !== document ) {
1795		setDocument( elem );
1796	}
1797
1798	// Make sure that attribute selectors are quoted
1799	expr = expr.replace( rattributeQuotes, "='$1']" );
1800
1801	if ( support.matchesSelector && documentIsHTML &&
1802		( !rbuggyMatches || !rbuggyMatches.test( expr ) ) &&
1803		( !rbuggyQSA     || !rbuggyQSA.test( expr ) ) ) {
1804
1805		try {
1806			var ret = matches.call( elem, expr );
1807
1808			// IE 9's matchesSelector returns false on disconnected nodes
1809			if ( ret || support.disconnectedMatch ||
1810					// As well, disconnected nodes are said to be in a document
1811					// fragment in IE 9
1812					elem.document && elem.document.nodeType !== 11 ) {
1813				return ret;
1814			}
1815		} catch(e) {}
1816	}
1817
1818	return Sizzle( expr, document, null, [elem] ).length > 0;
1819};
1820
1821Sizzle.contains = function( context, elem ) {
1822	// Set document vars if needed
1823	if ( ( context.ownerDocument || context ) !== document ) {
1824		setDocument( context );
1825	}
1826	return contains( context, elem );
1827};
1828
1829Sizzle.attr = function( elem, name ) {
1830	// Set document vars if needed
1831	if ( ( elem.ownerDocument || elem ) !== document ) {
1832		setDocument( elem );
1833	}
1834
1835	var fn = Expr.attrHandle[ name.toLowerCase() ],
1836		// Don't get fooled by Object.prototype properties (jQuery #13807)
1837		val = fn && hasOwn.call( Expr.attrHandle, name.toLowerCase() ) ?
1838			fn( elem, name, !documentIsHTML ) :
1839			undefined;
1840
1841	return val === undefined ?
1842		support.attributes || !documentIsHTML ?
1843			elem.getAttribute( name ) :
1844			(val = elem.getAttributeNode(name)) && val.specified ?
1845				val.value :
1846				null :
1847		val;
1848};
1849
1850Sizzle.error = function( msg ) {
1851	throw new Error( "Syntax error, unrecognized expression: " + msg );
1852};
1853
1854/**
1855 * Document sorting and removing duplicates
1856 * @param {ArrayLike} results
1857 */
1858Sizzle.uniqueSort = function( results ) {
1859	var elem,
1860		duplicates = [],
1861		j = 0,
1862		i = 0;
1863
1864	// Unless we *know* we can detect duplicates, assume their presence
1865	hasDuplicate = !support.detectDuplicates;
1866	sortInput = !support.sortStable && results.slice( 0 );
1867	results.sort( sortOrder );
1868
1869	if ( hasDuplicate ) {
1870		while ( (elem = results[i++]) ) {
1871			if ( elem === results[ i ] ) {
1872				j = duplicates.push( i );
1873			}
1874		}
1875		while ( j-- ) {
1876			results.splice( duplicates[ j ], 1 );
1877		}
1878	}
1879
1880	return results;
1881};
1882
1883/**
1884 * Utility function for retrieving the text value of an array of DOM nodes
1885 * @param {Array|Element} elem
1886 */
1887getText = Sizzle.getText = function( elem ) {
1888	var node,
1889		ret = "",
1890		i = 0,
1891		nodeType = elem.nodeType;
1892
1893	if ( !nodeType ) {
1894		// If no nodeType, this is expected to be an array
1895		for ( ; (node = elem[i]); i++ ) {
vendor: 2,485 bytes, lines 1896-1994
1896			// Do not traverse comment nodes
1897			ret += getText( node );
1898		}
1899	} else if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) {
1900		// Use textContent for elements
1901		// innerText usage removed for consistency of new lines (see #11153)
1902		if ( typeof elem.textContent === "string" ) {
1903			return elem.textContent;
1904		} else {
1905			// Traverse its children
1906			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
1907				ret += getText( elem );
1908			}
1909		}
1910	} else if ( nodeType === 3 || nodeType === 4 ) {
1911		return elem.nodeValue;
1912	}
1913	// Do not include comment or processing instruction nodes
1914
1915	return ret;
1916};
1917
1918Expr = Sizzle.selectors = {
1919
1920	// Can be adjusted by the user
1921	cacheLength: 50,
1922
1923	createPseudo: markFunction,
1924
1925	match: matchExpr,
1926
1927	attrHandle: {},
1928
1929	find: {},
1930
1931	relative: {
1932		">": { dir: "parentNode", first: true },
1933		" ": { dir: "parentNode" },
1934		"+": { dir: "previousSibling", first: true },
1935		"~": { dir: "previousSibling" }
1936	},
1937
1938	preFilter: {
1939		"ATTR": function( match ) {
1940			match[1] = match[1].replace( runescape, funescape );
1941
1942			// Move the given value to match[3] whether quoted or unquoted
1943			match[3] = ( match[4] || match[5] || "" ).replace( runescape, funescape );
1944
1945			if ( match[2] === "~=" ) {
1946				match[3] = " " + match[3] + " ";
1947			}
1948
1949			return match.slice( 0, 4 );
1950		},
1951
1952		"CHILD": function( match ) {
1953			/* matches from matchExpr["CHILD"]
1954				1 type (only|nth|...)
1955				2 what (child|of-type)
1956				3 argument (even|odd|\d*|\d*n([+-]\d+)?|...)
1957				4 xn-component of xn+y argument ([+-]?\d*n|)
1958				5 sign of xn-component
1959				6 x of xn-component
1960				7 sign of y-component
1961				8 y of y-component
1962			*/
1963			match[1] = match[1].toLowerCase();
1964
1965			if ( match[1].slice( 0, 3 ) === "nth" ) {
1966				// nth-* requires argument
1967				if ( !match[3] ) {
1968					Sizzle.error( match[0] );
1969				}
1970
1971				// numeric x and y parameters for Expr.filter.CHILD
1972				// remember that false/true cast respectively to 0/1
1973				match[4] = +( match[4] ? match[5] + (match[6] || 1) : 2 * ( match[3] === "even" || match[3] === "odd" ) );
1974				match[5] = +( ( match[7] + match[8] ) || match[3] === "odd" );
1975
1976			// other types prohibit arguments
1977			} else if ( match[3] ) {
1978				Sizzle.error( match[0] );
1979			}
1980
1981			return match;
1982		},
1983
1984		"PSEUDO": function( match ) {
1985			var excess,
1986				unquoted = !match[5] && match[2];
1987
1988			if ( matchExpr["CHILD"].test( match[0] ) ) {
1989				return null;
1990			}
1991
1992			// Accept quoted arguments as-is
1993			if ( match[3] && match[4] !== undefined ) {
1994				match[2] = match[4]
vendor: 9,637 bytes, lines 1994-2298
1994;
1995
1996			// Strip excess characters from unquoted arguments
1997			} else if ( unquoted && rpseudo.test( unquoted ) &&
1998				// Get excess from tokenize (recursively)
1999				(excess = tokenize( unquoted, true )) &&
2000				// advance to the next closing parenthesis
2001				(excess = unquoted.indexOf( ")", unquoted.length - excess ) - unquoted.length) ) {
2002
2003				// excess is a negative index
2004				match[0] = match[0].slice( 0, excess );
2005				match[2] = unquoted.slice( 0, excess );
2006			}
2007
2008			// Return only captures needed by the pseudo filter method (type and argument)
2009			return match.slice( 0, 3 );
2010		}
2011	},
2012
2013	filter: {
2014
2015		"TAG": function( nodeNameSelector ) {
2016			var nodeName = nodeNameSelector.replace( runescape, funescape ).toLowerCase();
2017			return nodeNameSelector === "*" ?
2018				function() { return true; } :
2019				function( elem ) {
2020					return elem.nodeName && elem.nodeName.toLowerCase() === nodeName;
2021				};
2022		},
2023
2024		"CLASS": function( className ) {
2025			var pattern = classCache[ className + " " ];
2026
2027			return pattern ||
2028				(pattern = new RegExp( "(^|" + whitespace + ")" + className + "(" + whitespace + "|$)" )) &&
2029				classCache( className, function( elem ) {
2030					return pattern.test( typeof elem.className === "string" && elem.className || typeof elem.getAttribute !== strundefined && elem.getAttribute("class") || "" );
2031				});
2032		},
2033
2034		"ATTR": function( name, operator, check ) {
2035			return function( elem ) {
2036				var result = Sizzle.attr( elem, name );
2037
2038				if ( result == null ) {
2039					return operator === "!=";
2040				}
2041				if ( !operator ) {
2042					return true;
2043				}
2044
2045				result += "";
2046
2047				return operator === "=" ? result === check :
2048					operator === "!=" ? result !== check :
2049					operator === "^=" ? check && result.indexOf( check ) === 0 :
2050					operator === "*=" ? check && result.indexOf( check ) > -1 :
2051					operator === "$=" ? check && result.slice( -check.length ) === check :
2052					operator === "~=" ? ( " " + result + " " ).indexOf( check ) > -1 :
2053					operator === "|=" ? result === check || result.slice( 0, check.length + 1 ) === check + "-" :
2054					false;
2055			};
2056		},
2057
2058		"CHILD": function( type, what, argument, first, last ) {
2059			var simple = type.slice( 0, 3 ) !== "nth",
2060				forward = type.slice( -4 ) !== "last",
2061				ofType = what === "of-type";
2062
2063			return first === 1 && last === 0 ?
2064
2065				// Shortcut for :nth-*(n)
2066				function( elem ) {
2067					return !!elem.parentNode;
2068				} :
2069
2070				function( elem, context, xml ) {
2071					var cache, outerCache, node, diff, nodeIndex, start,
2072						dir = simple !== forward ? "nextSibling" : "previousSibling",
2073						parent = elem.parentNode,
2074						name = ofType && elem.nodeName.toLowerCase(),
2075						useCache = !xml && !ofType;
2076
2077					if ( parent ) {
2078
2079						// :(first|last|only)-(child|of-type)
2080						if ( simple ) {
2081							while ( dir ) {
2082								node = elem;
2083								while ( (node = node[ dir ]) ) {
2084									if ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) {
2085										return false;
2086									}
2087								}
2088								// Reverse direction for :only-* (if we haven't yet done so)
2089								start = dir = type === "only" && !start && "nextSibling";
2090							}
2091							return true;
2092						}
2093
2094						start = [ forward ? parent.firstChild : parent.lastChild ];
2095
2096						// non-xml :nth-child(...) stores cache data on `parent`
2097						if ( forward && useCache ) {
2098							// Seek `elem` from a previously-cached index
2099							outerCache = parent[ expando ] || (parent[ expando ] = {});
2100							cache = outerCache[ type ] || [];
2101							nodeIndex = cache[0] === dirruns && cache[1];
2102							diff = cache[0] === dirruns && cache[2];
2103							node = nodeIndex && parent.childNodes[ nodeIndex ];
2104
2105							while ( (node = ++nodeIndex && node && node[ dir ] ||
2106
2107								// Fallback to seeking `elem` from the start
2108								(diff = nodeIndex = 0) || start.pop()) ) {
2109
2110								// When found, cache indexes on `parent` and break
2111								if ( node.nodeType === 1 && ++diff && node === elem ) {
2112									outerCache[ type ] = [ dirruns, nodeIndex, diff ];
2113									break;
2114								}
2115							}
2116
2117						// Use previously-cached element index if available
2118						} else if ( useCache && (cache = (elem[ expando ] || (elem[ expando ] = {}))[ type ]) && cache[0] === dirruns ) {
2119							diff = cache[1];
2120
2121						// xml :nth-child(...) or :nth-last-child(...) or :nth(-last)?-of-type(...)
2122						} else {
2123							// Use the same loop as above to seek `elem` from the start
2124							while ( (node = ++nodeIndex && node && node[ dir ] ||
2125								(diff = nodeIndex = 0) || start.pop()) ) {
2126
2127								if ( ( ofType ? node.nodeName.toLowerCase() === name : node.nodeType === 1 ) && ++diff ) {
2128									// Cache the index of each encountered element
2129									if ( useCache ) {
2130										(node[ expando ] || (node[ expando ] = {}))[ type ] = [ dirruns, diff ];
2131									}
2132
2133									if ( node === elem ) {
2134										break;
2135									}
2136								}
2137							}
2138						}
2139
2140						// Incorporate the offset, then check against cycle size
2141						diff -= last;
2142						return diff === first || ( diff % first === 0 && diff / first >= 0 );
2143					}
2144				};
2145		},
2146
2147		"PSEUDO": function( pseudo, argument ) {
2148			// pseudo-class names are case-insensitive
2149			// http://www.w3.org/TR/selectors/#pseudo-classes
2150			// Prioritize by case sensitivity in case custom pseudos are added with uppercase letters
2151			// Remember that setFilters inherits from pseudos
2152			var args,
2153				fn = Expr.pseudos[ pseudo ] || Expr.setFilters[ pseudo.toLowerCase() ] ||
2154					Sizzle.error( "unsupported pseudo: " + pseudo );
2155
2156			// The user may use createPseudo to indicate that
2157			// arguments are needed to create the filter function
2158			// just as Sizzle does
2159			if ( fn[ expando ] ) {
2160				return fn( argument );
2161			}
2162
2163			// But maintain support for old signatures
2164			if ( fn.length > 1 ) {
2165				args = [ pseudo, pseudo, "", argument ];
2166				return Expr.setFilters.hasOwnProperty( pseudo.toLowerCase() ) ?
2167					markFunction(function( seed, matches ) {
2168						var idx,
2169							matched = fn( seed, argument ),
2170							i = matched.length;
2171						while ( i-- ) {
2172							idx = indexOf.call( seed, matched[i] );
2173							seed[ idx ] = !( matches[ idx ] = matched[i] );
2174						}
2175					}) :
2176					function( elem ) {
2177						return fn( elem, 0, args );
2178					};
2179			}
2180
2181			return fn;
2182		}
2183	},
2184
2185	pseudos: {
2186		// Potentially complex pseudos
2187		"not": markFunction(function( selector ) {
2188			// Trim the selector passed to compile
2189			// to avoid treating leading and trailing
2190			// spaces as combinators
2191			var input = [],
2192				results = [],
2193				matcher = compile( selector.replace( rtrim, "$1" ) );
2194
2195			return matcher[ expando ] ?
2196				markFunction(function( seed, matches, context, xml ) {
2197					var elem,
2198						unmatched = matcher( seed, null, xml, [] ),
2199						i = seed.length;
2200
2201					// Match elements unmatched by `matcher`
2202					while ( i-- ) {
2203						if ( (elem = unmatched[i]) ) {
2204							seed[i] = !(matches[i] = elem);
2205						}
2206					}
2207				}) :
2208				function( elem, context, xml ) {
2209					input[0] = elem;
2210					matcher( input, null, xml, results );
2211					return !results.pop();
2212				};
2213		}),
2214
2215		"has": markFunction(function( selector ) {
2216			return function( elem ) {
2217				return Sizzle( selector, elem ).length > 0;
2218			};
2219		}),
2220
2221		"contains": markFunction(function( text ) {
2222			return function( elem ) {
2223				return ( elem.textContent || elem.innerText || getText( elem ) ).indexOf( text ) > -1;
2224			};
2225		}),
2226
2227		// "Whether an element is represented by a :lang() selector
2228		// is based solely on the element's language value
2229		// being equal to the identifier C,
2230		// or beginning with the identifier C immediately followed by "-".
2231		// The matching of C against the element's language value is performed case-insensitively.
2232		// The identifier C does not have to be a valid language name."
2233		// http://www.w3.org/TR/selectors/#lang-pseudo
2234		"lang": markFunction( function( lang ) {
2235			// lang value must be a valid identifier
2236			if ( !ridentifier.test(lang || "") ) {
2237				Sizzle.error( "unsupported lang: " + lang );
2238			}
2239			lang = lang.replace( runescape, funescape ).toLowerCase();
2240			return function( elem ) {
2241				var elemLang;
2242				do {
2243					if ( (elemLang = documentIsHTML ?
2244						elem.lang :
2245						elem.getAttribute("xml:lang") || elem.getAttribute("lang")) ) {
2246
2247						elemLang = elemLang.toLowerCase();
2248						return elemLang === lang || elemLang.indexOf( lang + "-" ) === 0;
2249					}
2250				} while ( (elem = elem.parentNode) && elem.nodeType === 1 );
2251				return false;
2252			};
2253		}),
2254
2255		// Miscellaneous
2256		"target": function( elem ) {
2257			var hash = window.location && window.location.hash;
2258			return hash && hash.slice( 1 ) === elem.id;
2259		},
2260
2261		"root": function( elem ) {
2262			return elem === docElem;
2263		},
2264
2265		"focus": function( elem ) {
2266			return elem === document.activeElement && (!document.hasFocus || document.hasFocus()) && !!(elem.type || elem.href || ~elem.tabIndex);
2267		},
2268
2269		// Boolean properties
2270		"enabled": function( elem ) {
2271			return elem.disabled === false;
2272		},
2273
2274		"disabled": function( elem ) {
2275			return elem.disabled === true;
2276		},
2277
2278		"checked": function( elem ) {
2279			// In CSS3, :checked should return both checked and selected elements
2280			// http://www.w3.org/TR/2011/REC-css3-selectors-20110929/#checked
2281			var nodeName = elem.nodeName.toLowerCase();
2282			return (nodeName === "input" && !!elem.checked) || (nodeName === "option" && !!elem.selected);
2283		},
2284
2285		"selected": function( elem ) {
2286			// Accessing this property makes selected-by-default
2287			// options in Safari work properly
2288			if ( elem.parentNode ) {
2289				elem.parentNode.selectedIndex;
2290			}
2291
2292			return elem.selected === true;
2293		},
2294
2295		// Contents
2296		"empty": function( elem ) {
2297			// http://www.w3.org/TR/selectors/#empty-pseudo
2298			// :empty is 
vendor: 3,404 bytes, lines 2298-2423
2298only affected by element nodes and content nodes(including text(3), cdata(4)),
2299			//   not comment, processing instructions, or others
2300			// Thanks to Diego Perini for the nodeName shortcut
2301			//   Greater than "@" means alpha characters (specifically not starting with "#" or "?")
2302			for ( elem = elem.firstChild; elem; elem = elem.nextSibling ) {
2303				if ( elem.nodeName > "@" || elem.nodeType === 3 || elem.nodeType === 4 ) {
2304					return false;
2305				}
2306			}
2307			return true;
2308		},
2309
2310		"parent": function( elem ) {
2311			return !Expr.pseudos["empty"]( elem );
2312		},
2313
2314		// Element/input types
2315		"header": function( elem ) {
2316			return rheader.test( elem.nodeName );
2317		},
2318
2319		"input": function( elem ) {
2320			return rinputs.test( elem.nodeName );
2321		},
2322
2323		"button": function( elem ) {
2324			var name = elem.nodeName.toLowerCase();
2325			return name === "input" && elem.type === "button" || name === "button";
2326		},
2327
2328		"text": function( elem ) {
2329			var attr;
2330			// IE6 and 7 will map elem.type to 'text' for new HTML5 types (search, etc)
2331			// use getAttribute instead to test this case
2332			return elem.nodeName.toLowerCase() === "input" &&
2333				elem.type === "text" &&
2334				( (attr = elem.getAttribute("type")) == null || attr.toLowerCase() === elem.type );
2335		},
2336
2337		// Position-in-collection
2338		"first": createPositionalPseudo(function() {
2339			return [ 0 ];
2340		}),
2341
2342		"last": createPositionalPseudo(function( matchIndexes, length ) {
2343			return [ length - 1 ];
2344		}),
2345
2346		"eq": createPositionalPseudo(function( matchIndexes, length, argument ) {
2347			return [ argument < 0 ? argument + length : argument ];
2348		}),
2349
2350		"even": createPositionalPseudo(function( matchIndexes, length ) {
2351			var i = 0;
2352			for ( ; i < length; i += 2 ) {
2353				matchIndexes.push( i );
2354			}
2355			return matchIndexes;
2356		}),
2357
2358		"odd": createPositionalPseudo(function( matchIndexes, length ) {
2359			var i = 1;
2360			for ( ; i < length; i += 2 ) {
2361				matchIndexes.push( i );
2362			}
2363			return matchIndexes;
2364		}),
2365
2366		"lt": createPositionalPseudo(function( matchIndexes, length, argument ) {
2367			var i = argument < 0 ? argument + length : argument;
2368			for ( ; --i >= 0; ) {
2369				matchIndexes.push( i );
2370			}
2371			return matchIndexes;
2372		}),
2373
2374		"gt": createPositionalPseudo(function( matchIndexes, length, argument ) {
2375			var i = argument < 0 ? argument + length : argument;
2376			for ( ; ++i < length; ) {
2377				matchIndexes.push( i );
2378			}
2379			return matchIndexes;
2380		})
2381	}
2382};
2383
2384Expr.pseudos["nth"] = Expr.pseudos["eq"];
2385
2386// Add button/input type pseudos
2387for ( i in { radio: true, checkbox: true, file: true, password: true, image: true } ) {
2388	Expr.pseudos[ i ] = createInputPseudo( i );
2389}
2390for ( i in { submit: true, reset: true } ) {
2391	Expr.pseudos[ i ] = createButtonPseudo( i );
2392}
2393
2394// Easy API for creating new setFilters
2395function setFilters() {}
2396setFilters.prototype = Expr.filters = Expr.pseudos;
2397Expr.setFilters = new setFilters();
2398
2399function tokenize( selector, parseOnly ) {
2400	var matched, match, tokens, type,
2401		soFar, groups, preFilters,
2402		cached = tokenCache[ selector + " " ];
2403
2404	if ( cached ) {
2405		return parseOnly ? 0 : cached.slice( 0 );
2406	}
2407
2408	soFar = selector;
2409	groups = [];
2410	preFilters = Expr.preFilter;
2411
2412	while ( soFar ) {
2413
2414		// Comma and first run
2415		if ( !matched || (match = rcomma.exec( soFar )) ) {
2416			if ( match ) {
2417				// Don't consume trailing commas as valid
2418				soFar = soFar.slice( match[0].length ) || soFar;
2419			}
2420			groups.push( tokens = [] );
2421		}
2422
2423		matched = false;
vendor: 25,111 bytes, lines 2424-3313
2424
2425		// Combinators
2426		if ( (match = rcombinators.exec( soFar )) ) {
2427			matched = match.shift();
2428			tokens.push({
2429				value: matched,
2430				// Cast descendant combinators to space
2431				type: match[0].replace( rtrim, " " )
2432			});
2433			soFar = soFar.slice( matched.length );
2434		}
2435
2436		// Filters
2437		for ( type in Expr.filter ) {
2438			if ( (match = matchExpr[ type ].exec( soFar )) && (!preFilters[ type ] ||
2439				(match = preFilters[ type ]( match ))) ) {
2440				matched = match.shift();
2441				tokens.push({
2442					value: matched,
2443					type: type,
2444					matches: match
2445				});
2446				soFar = soFar.slice( matched.length );
2447			}
2448		}
2449
2450		if ( !matched ) {
2451			break;
2452		}
2453	}
2454
2455	// Return the length of the invalid excess
2456	// if we're just parsing
2457	// Otherwise, throw an error or return tokens
2458	return parseOnly ?
2459		soFar.length :
2460		soFar ?
2461			Sizzle.error( selector ) :
2462			// Cache the tokens
2463			tokenCache( selector, groups ).slice( 0 );
2464}
2465
2466function toSelector( tokens ) {
2467	var i = 0,
2468		len = tokens.length,
2469		selector = "";
2470	for ( ; i < len; i++ ) {
2471		selector += tokens[i].value;
2472	}
2473	return selector;
2474}
2475
2476function addCombinator( matcher, combinator, base ) {
2477	var dir = combinator.dir,
2478		checkNonElements = base && dir === "parentNode",
2479		doneName = done++;
2480
2481	return combinator.first ?
2482		// Check against closest ancestor/preceding element
2483		function( elem, context, xml ) {
2484			while ( (elem = elem[ dir ]) ) {
2485				if ( elem.nodeType === 1 || checkNonElements ) {
2486					return matcher( elem, context, xml );
2487				}
2488			}
2489		} :
2490
2491		// Check against all ancestor/preceding elements
2492		function( elem, context, xml ) {
2493			var data, cache, outerCache,
2494				dirkey = dirruns + " " + doneName;
2495
2496			// We can't set arbitrary data on XML nodes, so they don't benefit from dir caching
2497			if ( xml ) {
2498				while ( (elem = elem[ dir ]) ) {
2499					if ( elem.nodeType === 1 || checkNonElements ) {
2500						if ( matcher( elem, context, xml ) ) {
2501							return true;
2502						}
2503					}
2504				}
2505			} else {
2506				while ( (elem = elem[ dir ]) ) {
2507					if ( elem.nodeType === 1 || checkNonElements ) {
2508						outerCache = elem[ expando ] || (elem[ expando ] = {});
2509						if ( (cache = outerCache[ dir ]) && cache[0] === dirkey ) {
2510							if ( (data = cache[1]) === true || data === cachedruns ) {
2511								return data === true;
2512							}
2513						} else {
2514							cache = outerCache[ dir ] = [ dirkey ];
2515							cache[1] = matcher( elem, context, xml ) || cachedruns;
2516							if ( cache[1] === true ) {
2517								return true;
2518							}
2519						}
2520					}
2521				}
2522			}
2523		};
2524}
2525
2526function elementMatcher( matchers ) {
2527	return matchers.length > 1 ?
2528		function( elem, context, xml ) {
2529			var i = matchers.length;
2530			while ( i-- ) {
2531				if ( !matchers[i]( elem, context, xml ) ) {
2532					return false;
2533				}
2534			}
2535			return true;
2536		} :
2537		matchers[0];
2538}
2539
2540function condense( unmatched, map, filter, context, xml ) {
2541	var elem,
2542		newUnmatched = [],
2543		i = 0,
2544		len = unmatched.length,
2545		mapped = map != null;
2546
2547	for ( ; i < len; i++ ) {
2548		if ( (elem = unmatched[i]) ) {
2549			if ( !filter || filter( elem, context, xml ) ) {
2550				newUnmatched.push( elem );
2551				if ( mapped ) {
2552					map.push( i );
2553				}
2554			}
2555		}
2556	}
2557
2558	return newUnmatched;
2559}
2560
2561function setMatcher( preFilter, selector, matcher, postFilter, postFinder, postSelector ) {
2562	if ( postFilter && !postFilter[ expando ] ) {
2563		postFilter = setMatcher( postFilter );
2564	}
2565	if ( postFinder && !postFinder[ expando ] ) {
2566		postFinder = setMatcher( postFinder, postSelector );
2567	}
2568	return markFunction(function( seed, results, context, xml ) {
2569		var temp, i, elem,
2570			preMap = [],
2571			postMap = [],
2572			preexisting = results.length,
2573
2574			// Get initial elements from seed or context
2575			elems = seed || multipleContexts( selector || "*", context.nodeType ? [ context ] : context, [] ),
2576
2577			// Prefilter to get matcher input, preserving a map for seed-results synchronization
2578			matcherIn = preFilter && ( seed || !selector ) ?
2579				condense( elems, preMap, preFilter, context, xml ) :
2580				elems,
2581
2582			matcherOut = matcher ?
2583				// If we have a postFinder, or filtered seed, or non-seed postFilter or preexisting results,
2584				postFinder || ( seed ? preFilter : preexisting || postFilter ) ?
2585
2586					// ...intermediate processing is necessary
2587					[] :
2588
2589					// ...otherwise use results directly
2590					results :
2591				matcherIn;
2592
2593		// Find primary matches
2594		if ( matcher ) {
2595			matcher( matcherIn, matcherOut, context, xml );
2596		}
2597
2598		// Apply postFilter
2599		if ( postFilter ) {
2600			temp = condense( matcherOut, postMap );
2601			postFilter( temp, [], context, xml );
2602
2603			// Un-match failing elements by moving them back to matcherIn
2604			i = temp.length;
2605			while ( i-- ) {
2606				if ( (elem = temp[i]) ) {
2607					matcherOut[ postMap[i] ] = !(matcherIn[ postMap[i] ] = elem);
2608				}
2609			}
2610		}
2611
2612		if ( seed ) {
2613			if ( postFinder || preFilter ) {
2614				if ( postFinder ) {
2615					// Get the final matcherOut by condensing this intermediate into postFinder contexts
2616					temp = [];
2617					i = matcherOut.length;
2618					while ( i-- ) {
2619						if ( (elem = matcherOut[i]) ) {
2620							// Restore matcherIn since elem is not yet a final match
2621							temp.push( (matcherIn[i] = elem) );
2622						}
2623					}
2624					postFinder( null, (matcherOut = []), temp, xml );
2625				}
2626
2627				// Move matched elements from seed to results to keep them synchronized
2628				i = matcherOut.length;
2629				while ( i-- ) {
2630					if ( (elem = matcherOut[i]) &&
2631						(temp = postFinder ? indexOf.call( seed, elem ) : preMap[i]) > -1 ) {
2632
2633						seed[temp] = !(results[temp] = elem);
2634					}
2635				}
2636			}
2637
2638		// Add elements to results, through postFinder if defined
2639		} else {
2640			matcherOut = condense(
2641				matcherOut === results ?
2642					matcherOut.splice( preexisting, matcherOut.length ) :
2643					matcherOut
2644			);
2645			if ( postFinder ) {
2646				postFinder( null, results, matcherOut, xml );
2647			} else {
2648				push.apply( results, matcherOut );
2649			}
2650		}
2651	});
2652}
2653
2654function matcherFromTokens( tokens ) {
2655	var checkContext, matcher, j,
2656		len = tokens.length,
2657		leadingRelative = Expr.relative[ tokens[0].type ],
2658		implicitRelative = leadingRelative || Expr.relative[" "],
2659		i = leadingRelative ? 1 : 0,
2660
2661		// The foundational matcher ensures that elements are reachable from top-level context(s)
2662		matchContext = addCombinator( function( elem ) {
2663			return elem === checkContext;
2664		}, implicitRelative, true ),
2665		matchAnyContext = addCombinator( function( elem ) {
2666			return indexOf.call( checkContext, elem ) > -1;
2667		}, implicitRelative, true ),
2668		matchers = [ function( elem, context, xml ) {
2669			return ( !leadingRelative && ( xml || context !== outermostContext ) ) || (
2670				(checkContext = context).nodeType ?
2671					matchContext( elem, context, xml ) :
2672					matchAnyContext( elem, context, xml ) );
2673		} ];
2674
2675	for ( ; i < len; i++ ) {
2676		if ( (matcher = Expr.relative[ tokens[i].type ]) ) {
2677			matchers = [ addCombinator(elementMatcher( matchers ), matcher) ];
2678		} else {
2679			matcher = Expr.filter[ tokens[i].type ].apply( null, tokens[i].matches );
2680
2681			// Return special upon seeing a positional matcher
2682			if ( matcher[ expando ] ) {
2683				// Find the next relative operator (if any) for proper handling
2684				j = ++i;
2685				for ( ; j < len; j++ ) {
2686					if ( Expr.relative[ tokens[j].type ] ) {
2687						break;
2688					}
2689				}
2690				return setMatcher(
2691					i > 1 && elementMatcher( matchers ),
2692					i > 1 && toSelector(
2693						// If the preceding token was a descendant combinator, insert an implicit any-element `*`
2694						tokens.slice( 0, i - 1 ).concat({ value: tokens[ i - 2 ].type === " " ? "*" : "" })
2695					).replace( rtrim, "$1" ),
2696					matcher,
2697					i < j && matcherFromTokens( tokens.slice( i, j ) ),
2698					j < len && matcherFromTokens( (tokens = tokens.slice( j )) ),
2699					j < len && toSelector( tokens )
2700				);
2701			}
2702			matchers.push( matcher );
2703		}
2704	}
2705
2706	return elementMatcher( matchers );
2707}
2708
2709function matcherFromGroupMatchers( elementMatchers, setMatchers ) {
2710	// A counter to specify which element is currently being matched
2711	var matcherCachedRuns = 0,
2712		bySet = setMatchers.length > 0,
2713		byElement = elementMatchers.length > 0,
2714		superMatcher = function( seed, context, xml, results, expandContext ) {
2715			var elem, j, matcher,
2716				setMatched = [],
2717				matchedCount = 0,
2718				i = "0",
2719				unmatched = seed && [],
2720				outermost = expandContext != null,
2721				contextBackup = outermostContext,
2722				// We must always have either seed elements or context
2723				elems = seed || byElement && Expr.find["TAG"]( "*", expandContext && context.parentNode || context ),
2724				// Use integer dirruns iff this is the outermost matcher
2725				dirrunsUnique = (dirruns += contextBackup == null ? 1 : Math.random() || 0.1);
2726
2727			if ( outermost ) {
2728				outermostContext = context !== document && context;
2729				cachedruns = matcherCachedRuns;
2730			}
2731
2732			// Add elements passing elementMatchers directly to results
2733			// Keep `i` a string if there are no elements so `matchedCount` will be "00" below
2734			for ( ; (elem = elems[i]) != null; i++ ) {
2735				if ( byElement && elem ) {
2736					j = 0;
2737					while ( (matcher = elementMatchers[j++]) ) {
2738						if ( matcher( elem, context, xml ) ) {
2739							results.push( elem );
2740							break;
2741						}
2742					}
2743					if ( outermost ) {
2744						dirruns = dirrunsUnique;
2745						cachedruns = ++matcherCachedRuns;
2746					}
2747				}
2748
2749				// Track unmatched elements for set filters
2750				if ( bySet ) {
2751					// They will have gone through all possible matchers
2752					if ( (elem = !matcher && elem) ) {
2753						matchedCount--;
2754					}
2755
2756					// Lengthen the array for every element, matched or not
2757					if ( seed ) {
2758						unmatched.push( elem );
2759					}
2760				}
2761			}
2762
2763			// Apply set filters to unmatched elements
2764			matchedCount += i;
2765			if ( bySet && i !== matchedCount ) {
2766				j = 0;
2767				while ( (matcher = setMatchers[j++]) ) {
2768					matcher( unmatched, setMatched, context, xml );
2769				}
2770
2771				if ( seed ) {
2772					// Reintegrate element matches to eliminate the need for sorting
2773					if ( matchedCount > 0 ) {
2774						while ( i-- ) {
2775							if ( !(unmatched[i] || setMatched[i]) ) {
2776								setMatched[i] = pop.call( results );
2777							}
2778						}
2779					}
2780
2781					// Discard index placeholder values to get only actual matches
2782					setMatched = condense( setMatched );
2783				}
2784
2785				// Add matches to results
2786				push.apply( results, setMatched );
2787
2788				// Seedless set matches succeeding multiple successful matchers stipulate sorting
2789				if ( outermost && !seed && setMatched.length > 0 &&
2790					( matchedCount + setMatchers.length ) > 1 ) {
2791
2792					Sizzle.uniqueSort( results );
2793				}
2794			}
2795
2796			// Override manipulation of globals by nested matchers
2797			if ( outermost ) {
2798				dirruns = dirrunsUnique;
2799				outermostContext = contextBackup;
2800			}
2801
2802			return unmatched;
2803		};
2804
2805	return bySet ?
2806		markFunction( superMatcher ) :
2807		superMatcher;
2808}
2809
2810compile = Sizzle.compile = function( selector, group /* Internal Use Only */ ) {
2811	var i,
2812		setMatchers = [],
2813		elementMatchers = [],
2814		cached = compilerCache[ selector + " " ];
2815
2816	if ( !cached ) {
2817		// Generate a function of recursive functions that can be used to check each element
2818		if ( !group ) {
2819			group = tokenize( selector );
2820		}
2821		i = group.length;
2822		while ( i-- ) {
2823			cached = matcherFromTokens( group[i] );
2824			if ( cached[ expando ] ) {
2825				setMatchers.push( cached );
2826			} else {
2827				elementMatchers.push( cached );
2828			}
2829		}
2830
2831		// Cache the compiled function
2832		cached = compilerCache( selector, matcherFromGroupMatchers( elementMatchers, setMatchers ) );
2833	}
2834	return cached;
2835};
2836
2837function multipleContexts( selector, contexts, results ) {
2838	var i = 0,
2839		len = contexts.length;
2840	for ( ; i < len; i++ ) {
2841		Sizzle( selector, contexts[i], results );
2842	}
2843	return results;
2844}
2845
2846function select( selector, context, results, seed ) {
2847	var i, tokens, token, type, find,
2848		match = tokenize( selector );
2849
2850	if ( !seed ) {
2851		// Try to minimize operations if there is only one group
2852		if ( match.length === 1 ) {
2853
2854			// Take a shortcut and set the context if the root selector is an ID
2855			tokens = match[0] = match[0].slice( 0 );
2856			if ( tokens.length > 2 && (token = tokens[0]).type === "ID" &&
2857					support.getById && context.nodeType === 9 && documentIsHTML &&
2858					Expr.relative[ tokens[1].type ] ) {
2859
2860				context = ( Expr.find["ID"]( token.matches[0].replace(runescape, funescape), context ) || [] )[0];
2861				if ( !context ) {
2862					return results;
2863				}
2864				selector = selector.slice( tokens.shift().value.length );
2865			}
2866
2867			// Fetch a seed set for right-to-left matching
2868			i = matchExpr["needsContext"].test( selector ) ? 0 : tokens.length;
2869			while ( i-- ) {
2870				token = tokens[i];
2871
2872				// Abort if we hit a combinator
2873				if ( Expr.relative[ (type = token.type) ] ) {
2874					break;
2875				}
2876				if ( (find = Expr.find[ type ]) ) {
2877					// Search, expanding context for leading sibling combinators
2878					if ( (seed = find(
2879						token.matches[0].replace( runescape, funescape ),
2880						rsibling.test( tokens[0].type ) && context.parentNode || context
2881					)) ) {
2882
2883						// If seed is empty or no tokens remain, we can return early
2884						tokens.splice( i, 1 );
2885						selector = seed.length && toSelector( tokens );
2886						if ( !selector ) {
2887							push.apply( results, seed );
2888							return results;
2889						}
2890
2891						break;
2892					}
2893				}
2894			}
2895		}
2896	}
2897
2898	// Compile and execute a filtering function
2899	// Provide `match` to avoid retokenization if we modified the selector above
2900	compile( selector, match )(
2901		seed,
2902		context,
2903		!documentIsHTML,
2904		results,
2905		rsibling.test( selector )
2906	);
2907	return results;
2908}
2909
2910// One-time assignments
2911
2912// Sort stability
2913support.sortStable = expando.split("").sort( sortOrder ).join("") === expando;
2914
2915// Support: Chrome<14
2916// Always assume duplicates if they aren't passed to the comparison function
2917support.detectDuplicates = hasDuplicate;
2918
2919// Initialize against the default document
2920setDocument();
2921
2922// Support: Webkit<537.32 - Safari 6.0.3/Chrome 25 (fixed in Chrome 27)
2923// Detached nodes confoundingly follow *each other*
2924support.sortDetached = assert(function( div1 ) {
2925	// Should return 1, but returns 4 (following)
2926	return div1.compareDocumentPosition( document.createElement("div") ) & 1;
2927});
2928
2929// Support: IE<8
2930// Prevent attribute/property "interpolation"
2931// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
2932if ( !assert(function( div ) {
2933	div.innerHTML = "<a href='#'></a>";
2934	return div.firstChild.getAttribute("href") === "#" ;
2935}) ) {
2936	addHandle( "type|href|height|width", function( elem, name, isXML ) {
2937		if ( !isXML ) {
2938			return elem.getAttribute( name, name.toLowerCase() === "type" ? 1 : 2 );
2939		}
2940	});
2941}
2942
2943// Support: IE<9
2944// Use defaultValue in place of getAttribute("value")
2945if ( !support.attributes || !assert(function( div ) {
2946	div.innerHTML = "<input/>";
2947	div.firstChild.setAttribute( "value", "" );
2948	return div.firstChild.getAttribute( "value" ) === "";
2949}) ) {
2950	addHandle( "value", function( elem, name, isXML ) {
2951		if ( !isXML && elem.nodeName.toLowerCase() === "input" ) {
2952			return elem.defaultValue;
2953		}
2954	});
2955}
2956
2957// Support: IE<9
2958// Use getAttributeNode to fetch booleans when getAttribute lies
2959if ( !assert(function( div ) {
2960	return div.getAttribute("disabled") == null;
2961}) ) {
2962	addHandle( booleans, function( elem, name, isXML ) {
2963		var val;
2964		if ( !isXML ) {
2965			return (val = elem.getAttributeNode( name )) && val.specified ?
2966				val.value :
2967				elem[ name ] === true ? name.toLowerCase() : null;
2968		}
2969	});
2970}
2971
2972jQuery.find = Sizzle;
2973jQuery.expr = Sizzle.selectors;
2974jQuery.expr[":"] = jQuery.expr.pseudos;
2975jQuery.unique = Sizzle.uniqueSort;
2976jQuery.text = Sizzle.getText;
2977jQuery.isXMLDoc = Sizzle.isXML;
2978jQuery.contains = Sizzle.contains;
2979
2980
2981})( window );
2982// String to Object options format cache
2983var optionsCache = {};
2984
2985// Convert String-formatted options into Object-formatted ones and store in cache
2986function createOptions( options ) {
2987	var object = optionsCache[ options ] = {};
2988	jQuery.each( options.match( core_rnotwhite ) || [], function( _, flag ) {
2989		object[ flag ] = true;
2990	});
2991	return object;
2992}
2993
2994/*
2995 * Create a callback list using the following parameters:
2996 *
2997 *	options: an optional list of space-separated options that will change how
2998 *			the callback list behaves or a more traditional option object
2999 *
3000 * By default a callback list will act like an event callback list and can be
3001 * "fired" multiple times.
3002 *
3003 * Possible options:
3004 *
3005 *	once:			will ensure the callback list can only be fired once (like a Deferred)
3006 *
3007 *	memory:			will keep track of previous values and will call any callback added
3008 *					after the list has been fired right away with the latest "memorized"
3009 *					values (like a Deferred)
3010 *
3011 *	unique:			will ensure a callback can only be added once (no duplicate in the list)
3012 *
3013 *	stopOnFalse:	interrupt callings when a callback returns false
3014 *
3015 */
3016jQuery.Callbacks = function( options ) {
3017
3018	// Convert options from String-formatted to Object-formatted if needed
3019	// (we check in cache first)
3020	options = typeof options === "string" ?
3021		( optionsCache[ options ] || createOptions( options ) ) :
3022		jQuery.extend( {}, options );
3023
3024	var // Flag to know if list is currently firing
3025		firing,
3026		// Last fire value (for non-forgettable lists)
3027		memory,
3028		// Flag to know if list was already fired
3029		fired,
3030		// End of the loop when firing
3031		firingLength,
3032		// Index of currently firing callback (modified by remove if needed)
3033		firingIndex,
3034		// First callback to fire (used internally by add and fireWith)
3035		firingStart,
3036		// Actual callback list
3037		list = [],
3038		// Stack of fire calls for repeatable lists
3039		stack = !options.once && [],
3040		// Fire callbacks
3041		fire = function( data ) {
3042			memory = options.memory && data;
3043			fired = true;
3044			firingIndex = firingStart || 0;
3045			firingStart = 0;
3046			firingLength = list.length;
3047			firing = true;
3048			for ( ; list && firingIndex < firingLength; firingIndex++ ) {
3049				if ( list[ firingIndex ].apply( data[ 0 ], data[ 1 ] ) === false && options.stopOnFalse ) {
3050					memory = false; // To prevent further calls using add
3051					break;
3052				}
3053			}
3054			firing = false;
3055			if ( list ) {
3056				if ( stack ) {
3057					if ( stack.length ) {
3058						fire( stack.shift() );
3059					}
3060				} else if ( memory ) {
3061					list = [];
3062				} else {
3063					self.disable();
3064				}
3065			}
3066		},
3067		// Actual Callbacks object
3068		self = {
3069			// Add a callback or a collection of callbacks to the list
3070			add: function() {
3071				if ( list ) {
3072					// First, we save the current length
3073					var start = list.length;
3074					(function add( args ) {
3075						jQuery.each( args, function( _, arg ) {
3076							var type = jQuery.type( arg );
3077							if ( type === "function" ) {
3078								if ( !options.unique || !self.has( arg ) ) {
3079									list.push( arg );
3080								}
3081							} else if ( arg && arg.length && type !== "string" ) {
3082								// Inspect recursively
3083								add( arg );
3084							}
3085						});
3086					})( arguments );
3087					// Do we need to add the callbacks to the
3088					// current firing batch?
3089					if ( firing ) {
3090						firingLength = list.length;
3091					// With memory, if we're not firing then
3092					// we should call right away
3093					} else if ( memory ) {
3094						firingStart = start;
3095						fire( memory );
3096					}
3097				}
3098				return this;
3099			},
3100			// Remove a callback from the list
3101			remove: function() {
3102				if ( list ) {
3103					jQuery.each( arguments, function( _, arg ) {
3104						var index;
3105						while( ( index = jQuery.inArray( arg, list, index ) ) > -1 ) {
3106							list.splice( index, 1 );
3107							// Handle firing indexes
3108							if ( firing ) {
3109								if ( index <= firingLength ) {
3110									firingLength--;
3111								}
3112								if ( index <= firingIndex ) {
3113									firingIndex--;
3114								}
3115							}
3116						}
3117					});
3118				}
3119				return this;
3120			},
3121			// Check if a given callback is in the list.
3122			// If no argument is given, return whether or not list has callbacks attached.
3123			has: function( fn ) {
3124				return fn ? jQuery.inArray( fn, list ) > -1 : !!( list && list.length );
3125			},
3126			// Remove all callbacks from the list
3127			empty: function() {
3128				list = [];
3129				firingLength = 0;
3130				return this;
3131			},
3132			// Have the list do nothing anymore
3133			disable: function() {
3134				list = stack = memory = undefined;
3135				return this;
3136			},
3137			// Is it disabled?
3138			disabled: function() {
3139				return !list;
3140			},
3141			// Lock the list in its current state
3142			lock: function() {
3143				stack = undefined;
3144				if ( !memory ) {
3145					self.disable();
3146				}
3147				return this;
3148			},
3149			// Is it locked?
3150			locked: function() {
3151				return !stack;
3152			},
3153			// Call all callbacks with the given context and arguments
3154			fireWith: function( context, args ) {
3155				if ( list && ( !fired || stack ) ) {
3156					args = args || [];
3157					args = [ context, args.slice ? args.slice() : args ];
3158					if ( firing ) {
3159						stack.push( args );
3160					} else {
3161						fire( args );
3162					}
3163				}
3164				return this;
3165			},
3166			// Call all the callbacks with the given arguments
3167			fire: function() {
3168				self.fireWith( this, arguments );
3169				return this;
3170			},
3171			// To know if the callbacks have already been called at least once
3172			fired: function() {
3173				return !!fired;
3174			}
3175		};
3176
3177	return self;
3178};
3179jQuery.extend({
3180
3181	Deferred: function( func ) {
3182		var tuples = [
3183				// action, add listener, listener list, final state
3184				[ "resolve", "done", jQuery.Callbacks("once memory"), "resolved" ],
3185				[ "reject", "fail", jQuery.Callbacks("once memory"), "rejected" ],
3186				[ "notify", "progress", jQuery.Callbacks("memory") ]
3187			],
3188			state = "pending",
3189			promise = {
3190				state: function() {
3191					return state;
3192				},
3193				always: function() {
3194					deferred.done( arguments ).fail( arguments );
3195					return this;
3196				},
3197				then: function( /* fnDone, fnFail, fnProgress */ ) {
3198					var fns = arguments;
3199					return jQuery.Deferred(function( newDefer ) {
3200						jQuery.each( tuples, function( i, tuple ) {
3201							var action = tuple[ 0 ],
3202								fn = jQuery.isFunction( fns[ i ] ) && fns[ i ];
3203							// deferred[ done | fail | progress ] for forwarding actions to newDefer
3204							deferred[ tuple[1] ](function() {
3205								var returned = fn && fn.apply( this, arguments );
3206								if ( returned && jQuery.isFunction( returned.promise ) ) {
3207									returned.promise()
3208										.done( newDefer.resolve )
3209										.fail( newDefer.reject )
3210										.progress( newDefer.notify );
3211								} else {
3212									newDefer[ action + "With" ]( this === promise ? newDefer.promise() : this, fn ? [ returned ] : arguments );
3213								}
3214							});
3215						});
3216						fns = null;
3217					}).promise();
3218				},
3219				// Get a promise for this deferred
3220				// If obj is provided, the promise aspect is added to the object
3221				promise: function( obj ) {
3222					return obj != null ? jQuery.extend( obj, promise ) : promise;
3223				}
3224			},
3225			deferred = {};
3226
3227		// Keep pipe for back-compat
3228		promise.pipe = promise.then;
3229
3230		// Add list-specific methods
3231		jQuery.each( tuples, function( i, tuple ) {
3232			var list = tuple[ 2 ],
3233				stateString = tuple[ 3 ];
3234
3235			// promise[ done | fail | progress ] = list.add
3236			promise[ tuple[1] ] = list.add;
3237
3238			// Handle state
3239			if ( stateString ) {
3240				list.add(function() {
3241					// state = [ resolved | rejected ]
3242					state = stateString;
3243
3244				// [ reject_list | resolve_list ].disable; progress_list.lock
3245				}, tuples[ i ^ 1 ][ 2 ].disable, tuples[ 2 ][ 2 ].lock );
3246			}
3247
3248			// deferred[ resolve | reject | notify ]
3249			deferred[ tuple[0] ] = function() {
3250				deferred[ tuple[0] + "With" ]( this === deferred ? promise : this, arguments );
3251				return this;
3252			};
3253			deferred[ tuple[0] + "With" ] = list.fireWith;
3254		});
3255
3256		// Make the deferred a promise
3257		promise.promise( deferred );
3258
3259		// Call given func if any
3260		if ( func ) {
3261			func.call( deferred, deferred );
3262		}
3263
3264		// All done!
3265		return deferred;
3266	},
3267
3268	// Deferred helper
3269	when: function( subordinate /* , ..., subordinateN */ ) {
3270		var i = 0,
3271			resolveValues = core_slice.call( arguments ),
3272			length = resolveValues.length,
3273
3274			// the count of uncompleted subordinates
3275			remaining = length !== 1 || ( subordinate && jQuery.isFunction( subordinate.promise ) ) ? length : 0,
3276
3277			// the master Deferred. If resolveValues consist of only a single Deferred, just use that.
3278			deferred = remaining === 1 ? subordinate : jQuery.Deferred(),
3279
3280			// Update function for both resolve and progress values
3281			updateFunc = function( i, contexts, values ) {
3282				return function( value ) {
3283					contexts[ i ] = this;
3284					values[ i ] = arguments.length > 1 ? core_slice.call( arguments ) : value;
3285					if( values === progressValues ) {
3286						deferred.notifyWith( contexts, values );
3287					} else if ( !( --remaining ) ) {
3288						deferred.resolveWith( contexts, values );
3289					}
3290				};
3291			},
3292
3293			progressValues, progressContexts, resolveContexts;
3294
3295		// add listeners to Deferred subordinates; treat others as resolved
3296		if ( length > 1 ) {
3297			progressValues = new Array( length );
3298			progressContexts = new Array( length );
3299			resolveContexts = new Array( length );
3300			for ( ; i < length; i++ ) {
3301				if ( resolveValues[ i ] && jQuery.isFunction( resolveValues[ i ].promise ) ) {
3302					resolveValues[ i ].promise()
3303						.done( updateFunc( i, resolveContexts, resolveValues ) )
3304						.fail( deferred.reject )
3305						.progress( updateFunc( i, progressContexts, progressValues ) );
3306				} else {
3307					--remaining;
3308				}
3309			}
3310		}
3311
3312		// if we're not waiting on anything, resolve the master
3313		if ( !remaining ) {
3314			deferred.resolveWith( resolveContexts, resolveValues );
3315		}
3316
3317		return deferred.promise();
3318	}
3319});
3320jQuery.support = (function( support ) {
3321
3322	var all, a, input, select, fragment, opt, eventName, isSupported, i,
3323		div = document.createElement("div");
3324
3325	// Setup
3326	div.setAttribute( "className", "t" );
3327	div.innerHTML = "  <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
3328
3329	// Finish early in limited (non-browser) environments
3330	all = div.getElementsByTagName("*") || [];
3331	a = div.getElementsByTagName("a")[ 0 ];
3332	if ( !a || !a.style || !all.length ) {
3333		return support;
3334	}
3335
3336	// First batch of tests
3337	select = document.createElement("select");
3338	opt = select.appendChild( document.createElement("option") );
3339	input = div.getElementsByTagName("input")[ 0 ];
3340
3341	a.style.cssText = "top:1px;float:left;opacity:.5";
3342
3343	// Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7)
3344	support.getSetAttribute = div.className !== "t";
3345
3346	// IE strips leading whitespace when .innerHTML is used
3347	support.leadingWhitespace = div.firstChild.nodeType === 3;
3348
3349	// Make sure that tbody elements aren't automatically inserted
3350	// IE will insert them into empty tables
3351	support.tbody = !div.getElementsByTagName("tbody").length;
3352
3353	// Make sure that link elements get serialized correctly by innerHTML
3354	// This requires a wrapper element in IE
3355	support.htmlSerialize = !!div.getElementsByTagName("link").length;
3356
3357	// Get the style information from getAttribute
3358	// (IE uses .cssText instead)
3359	support.style = /top/.test( a.getAttribute("style") );
3360
3361	// Make sure that URLs aren't manipulated
3362	// (IE normalizes it by default)
3363	support.hrefNormalized = a.getAttribute("href") === "/a";
3364
3365	// Make sure that element opacity exists
3366	// (IE uses filter instead)
3367	// Use a regex to work around a WebKit issue. See #5145
3368	support.opacity = /^0.5/.test( a.style.opacity );
3369
3370	// Verify style float existence
3371	// (IE uses styleFloat instead of cssFloat)
3372	support.cssFloat = !!a.style.cssFloat;
3373
3374	// Check the default checkbox/radio value ("" on WebKit; "on" elsewhere)
3375	support.checkOn = !!input.value;
3376
3377	// Make sure that a selected-by-default option has a working selected property.
3378	// (WebKit defaults to false instead of true, IE too, if it's in an optgroup)
3379	support.optSelected = opt.selected;
3380
3381	// Tests for enctype support on a form (#6743)
3382	support.enctype = !!document.createElement("form").enctype;
3383
3384	// Makes sure cloning an html5 element does not cause problems
3385	// Where outerHTML is undefined, this still works
3386	support.html5Clone = document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav></:nav>";
3387
3388	// Will be defined later
3389	support.inlineBlockNeedsLayout = false;
3390	support.shrinkWrapBlocks = false;
3391	support.pixelPosition = false;
3392	support.deleteExpando = true;
3393	support.noCloneEvent = true;
3394	support.reliableMarginRight = true;
3395	support.boxSizingReliable = true;
3396
3397	// Make sure checked status is properly cloned
3398	input.checked = true;
3399	support.noCloneChecked = input.cloneNode( true ).checked;
3400
3401	// Make sure that the options inside disabled selects aren't marked as disabled
3402	// (WebKit marks them as disabled)
3403	select.disabled = true;
3404	support.optDisabled = !opt.disabled;
3405
3406	// Support: IE<9
3407	try {
3408		delete div.test;
3409	} catch( e ) {
3410		support.deleteExpando = false;
3411	}
3412
3413	// Check if we can trust getAttribute("value")
3414	input = document.createElement("input");
3415	input.setAttribute( "value", "" );
3416	support.input = input.getAttribute( "value" ) === "";
3417
3418	// Check if an input maintains its value after becoming a radio
3419	input.value = "t";
3420	input.setAttribute( "type", "radio" );
3421	support.radioValue = input.value === "t";
3422
3423	// #11217 - WebKit loses check when the name is after the checked attribute
3424	input.setAttribute( "checked", "t" );
3425	input.setAttribute( "name", "t" );
3426
3427	fragment = document.createDocumentFragment();
3428	fragment.appendChild( input );
3429
3430	// Check if a disconnected checkbox will retain its checked
3431	// value of true after appended to the DOM (IE6/7)
3432	support.appendChecked = input.checked;
3433
3434	// WebKit doesn't clone checked state correctly in fragments
3435	support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked;
3436
3437	// Support: IE<9
3438	// Opera does not clone events (and typeof div.attachEvent === undefined).
3439	// IE9-10 clones events bound via attachEvent, but they don't trigger with .click()
3440	if ( div.attachEvent ) {
3441		div.attachEvent( "onclick", function() {
3442			support.noCloneEvent = false;
3443		});
3444
3445		div.cloneNode( true ).click();
3446	}
3447
3448	// Support: IE<9 (lack submit/change bubble), Firefox 17+ (lack focusin event)
3449	// Beware of CSP restrictions (https://developer.mozilla.org/en/Security/CSP)
3450	for ( i in { submit: true, change: true, focusin: true }) {
3451		div.setAttribute( eventName = "on" + i, "t" );
3452
3453		support[ i + "Bubbles" ] = eventName in window || div.attributes[ eventName ].expando === false;
3454	}
3455
3456	div.style.backgroundClip = "content-box";
3457	div.cloneNode( true ).style.backgroundClip = "";
3458	support.clearCloneStyle = div.style.backgroundClip === "content-box";
3459
3460	// Support: IE<9
3461	// Iteration over object's inherited properties before its own.
3462	for ( i in jQuery( support ) ) {
3463		break;
3464	}
3465	support.ownLast = i !== "0";
3466
3467	// Run tests that need a body at doc ready
3468	jQuery(function() {
3469		var container, marginDiv, tds,
3470			divReset = "padding:0;margin:0;border:0;display:block;box-sizing:content-box;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;",
3471			body = document.getElementsByTagName("body")[0];
3472
3473		if ( !body ) {
3474			// Return for frameset docs that don't have a body
3475			return;
3476		}
3477
3478		container = document.createElement("div");
3479		container.style.cssText = "border:0;width:0;height:0;position:absolute;top:0;left:-9999px;margin-top:1px";
3480
3481		body.appendChild( container ).appendChild( div );
3482
3483		// Support: IE8
3484		// Check if table cells still have offsetWidth/Height when they are set
3485		// to display:none and there are still other visible table cells in a
3486		// table row; if so, offsetWidth/Height are not reliable for use when
3487		// determining if an element has been hidden directly using
3488		// display:none (it is still safe to use offsets if a parent element is
3489		// hidden; don safety goggles and see bug #4512 for more information).
3490		div.innerHTML = "<table><tr><td></td><td>t</td></tr></table>";
3491		tds = div.getElementsByTagName("td");
3492		tds[ 0 ].style.cssText = "padding:0;margin:0;border:0;display:none";
3493		isSupported = ( tds[ 0 ].offsetHeight === 0 );
3494
3495		tds[ 0 ].style.display = "";
3496		tds[ 1 ].style.display = "none";
3497
3498		// Support: IE8
3499		// Check if empty table cells still have offsetWidth/Height
3500		support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 );
3501
3502		// Check box-sizing and margin behavior.
3503		div.innerHTML = "";
3504		div.style.cssText = "box-sizing:border-box;-moz-box-sizing:border-box;-webkit-box-sizing:border-box;padding:1px;border:1px;display:block;width:4px;margin-top:1%;position:absolute;top:1%;";
3505
3506		// Workaround failing boxSizing test due to offsetWidth returning wrong value
3507		// with some non-1 values of body zoom, ticket #13543
3508		jQuery.swap( body, body.style.zoom != null ? { zoom: 1 } : {}, function() {
3509			support.boxSizing = div.offsetWidth === 4;
3510		});
3511
3512		// Use window.getComputedStyle because jsdom on node.js will break without it.
3513		if ( window.getComputedStyle ) {
3514			support.pixelPosition = ( window.getComputedStyle( div, null ) || {} ).top !== "1%";
3515			support.boxSizingReliable = ( window.getComputedStyle( div, null ) || { width: "4px" } ).width === "4px";
3516
3517			// Check if div with explicit width and no margin-right incorrectly
3518			// gets computed margin-right based on width of container. (#3333)
3519			// Fails in WebKit before Feb 2011 nightlies
3520			// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
3521			marginDiv = div.appendChild( document.createElement("div") );
3522			marginDiv.style.cssText = div.style.cssText = divReset;
3523			marginDiv.style.marginRight = marginDiv.style.width = "0";
3524			div.style.width = "1px";
3525
3526			support.reliableMarginRight =
3527				!parseFloat( ( window.getComputedStyle( marginDiv, null ) || {} ).marginRight );
3528		}
3529
3530		if ( typeof div.style.zoom !== core_strundefined ) {
3531			// Support: IE<8
3532			// Check if natively block-level elements act like inline-block
3533			// elements when setting their display to 'inline' and giving
3534			// them layout
3535			div.innerHTML = "";
3536			div.style.cssText = divReset + "width:1px;padding:1px;display:inline;zoom:1";
3537			support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 );
3538
3539			// Support: IE6
3540			// Check if elements with layout shrink-wrap their children
3541			div.style.display = "block";
3542			div.innerHTML = "<div></div>";
3543			div.firstChild.style.width = "5px";
3544			support.shrinkWrapBlocks = ( div.offsetWidth !== 3 );
3545
3546			if ( support.inlineBlockNeedsLayout ) {
3547				// Prevent IE 6 from affecting layout for positioned elements #11048
3548				// Prevent IE from shrinking the body in IE 7 mode #12869
vendor: 4,821 bytes, lines 3549-3726
3549				// Support: IE<8
3550				body.style.zoom = 1;
3551			}
3552		}
3553
3554		body.removeChild( container );
3555
3556		// Null elements to avoid leaks in IE
3557		container = div = tds = marginDiv = null;
3558	});
3559
3560	// Null elements to avoid leaks in IE
3561	all = select = fragment = opt = a = input = null;
3562
3563	return support;
3564})({});
3565
3566var rbrace = /(?:\{[\s\S]*\}|\[[\s\S]*\])$/,
3567	rmultiDash = /([A-Z])/g;
3568
3569function internalData( elem, name, data, pvt /* Internal Use Only */ ){
3570	if ( !jQuery.acceptData( elem ) ) {
3571		return;
3572	}
3573
3574	var ret, thisCache,
3575		internalKey = jQuery.expando,
3576
3577		// We have to handle DOM nodes and JS objects differently because IE6-7
3578		// can't GC object references properly across the DOM-JS boundary
3579		isNode = elem.nodeType,
3580
3581		// Only DOM nodes need the global jQuery cache; JS object data is
3582		// attached directly to the object so GC can occur automatically
3583		cache = isNode ? jQuery.cache : elem,
3584
3585		// Only defining an ID for JS objects if its cache already exists allows
3586		// the code to shortcut on the same path as a DOM node with no cache
3587		id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey;
3588
3589	// Avoid doing any more work than we need to when trying to get data on an
3590	// object that has no data at all
3591	if ( (!id || !cache[id] || (!pvt && !cache[id].data)) && data === undefined && typeof name === "string" ) {
3592		return;
3593	}
3594
3595	if ( !id ) {
3596		// Only DOM nodes need a new unique ID for each element since their data
3597		// ends up in the global cache
3598		if ( isNode ) {
3599			id = elem[ internalKey ] = core_deletedIds.pop() || jQuery.guid++;
3600		} else {
3601			id = internalKey;
3602		}
3603	}
3604
3605	if ( !cache[ id ] ) {
3606		// Avoid exposing jQuery metadata on plain JS objects when the object
3607		// is serialized using JSON.stringify
3608		cache[ id ] = isNode ? {} : { toJSON: jQuery.noop };
3609	}
3610
3611	// An object can be passed to jQuery.data instead of a key/value pair; this gets
3612	// shallow copied over onto the existing cache
3613	if ( typeof name === "object" || typeof name === "function" ) {
3614		if ( pvt ) {
3615			cache[ id ] = jQuery.extend( cache[ id ], name );
3616		} else {
3617			cache[ id ].data = jQuery.extend( cache[ id ].data, name );
3618		}
3619	}
3620
3621	thisCache = cache[ id ];
3622
3623	// jQuery data() is stored in a separate object inside the object's internal data
3624	// cache in order to avoid key collisions between internal data and user-defined
3625	// data.
3626	if ( !pvt ) {
3627		if ( !thisCache.data ) {
3628			thisCache.data = {};
3629		}
3630
3631		thisCache = thisCache.data;
3632	}
3633
3634	if ( data !== undefined ) {
3635		thisCache[ jQuery.camelCase( name ) ] = data;
3636	}
3637
3638	// Check for both converted-to-camel and non-converted data property names
3639	// If a data property was specified
3640	if ( typeof name === "string" ) {
3641
3642		// First Try to find as-is property data
3643		ret = thisCache[ name ];
3644
3645		// Test for null|undefined property data
3646		if ( ret == null ) {
3647
3648			// Try to find the camelCased property
3649			ret = thisCache[ jQuery.camelCase( name ) ];
3650		}
3651	} else {
3652		ret = thisCache;
3653	}
3654
3655	return ret;
3656}
3657
3658function internalRemoveData( elem, name, pvt ) {
3659	if ( !jQuery.acceptData( elem ) ) {
3660		return;
3661	}
3662
3663	var thisCache, i,
3664		isNode = elem.nodeType,
3665
3666		// See jQuery.data for more information
3667		cache = isNode ? jQuery.cache : elem,
3668		id = isNode ? elem[ jQuery.expando ] : jQuery.expando;
3669
3670	// If there is already no cache entry for this object, there is no
3671	// purpose in continuing
3672	if ( !cache[ id ] ) {
3673		return;
3674	}
3675
3676	if ( name ) {
3677
3678		thisCache = pvt ? cache[ id ] : cache[ id ].data;
3679
3680		if ( thisCache ) {
3681
3682			// Support array or space separated string names for data keys
3683			if ( !jQuery.isArray( name ) ) {
3684
3685				// try the string as a key before any manipulation
3686				if ( name in thisCache ) {
3687					name = [ name ];
3688				} else {
3689
3690					// split the camel cased version by spaces unless a key with the spaces exists
3691					name = jQuery.camelCase( name );
3692					if ( name in thisCache ) {
3693						name = [ name ];
3694					} else {
3695						name = name.split(" ");
3696					}
3697				}
3698			} else {
3699				// If "name" is an array of keys...
3700				// When data is initially created, via ("key", "val") signature,
3701				// keys will be converted to camelCase.
3702				// Since there is no way to tell _how_ a key was added, remove
3703				// both plain key and camelCase key. #12786
3704				// This will only penalize the array argument path.
3705				name = name.concat( jQuery.map( name, jQuery.camelCase ) );
3706			}
3707
3708			i = name.length;
3709			while ( i-- ) {
3710				delete thisCache[ name[i] ];
3711			}
3712
3713			// If there is no data left in the cache, we want to continue
3714			// and let the cache object itself get destroyed
3715			if ( pvt ? !isEmptyDataObject(thisCache) : !jQuery.isEmptyObject(thisCache) ) {
3716				return;
3717			}
3718		}
3719	}
3720
3721	// See jQuery.data for more information
3722	if ( !pvt ) {
3723		delete cache[ id ].data;
3724
3725		// Don't destroy the parent cache unless the internal data object
3726		// had been the only thing left in it
3727		if ( !isEmptyDataObject( cache[ id ] ) ) {
3728			return;
3729		}
3730	}
3731
3732	// Destroy the cache
3733	if ( isNode ) {
3734		jQuery.cleanData( [ elem ], true );
3735
3736	// Use delete when supported for expandos or `cache` is not a window per isWindow (#10080)
3737	/* jshint eqeqeq: false */
3738	} else if ( jQuery.support.deleteExpando || cache != cache.window ) {
3739		/* jshint eqeqeq: true */
3740		delete cache[ id ];
3741
3742	// When all else fails, null
3743	} else {
3744		cache[ id ] = null;
3745	}
3746}
3747
3748jQuery.extend({
3749	cache: {},
3750
3751	// The following elements throw uncatchable exceptions if you
3752	// attempt to add expando properties to them.
3753	noData: {
3754		"applet": true,
3755		"embed": true,
3756		// Ban all objects except for Flash (which handle expandos)
3757		"object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"
3758	},
3759
3760	hasData: function( elem ) {
3761		elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ];
3762		return !!elem && !isEmptyDataObject( elem );
3763	},
3764
3765	data: function( elem, name, data ) {
3766		return internalData( elem, name, data );
3767	},
3768
3769	removeData: function( elem, name ) {
3770		return internalRemoveData( elem, name );
3771	},
3772
3773	// For internal use only.
3774	_data: function( elem, name, data ) {
3775		return internalData( elem, name, data, true );
3776	},
3777
3778	_removeData: function( elem, name ) {
3779		return internalRemoveData( elem, name, true );
3780	},
3781
3782	// A method for determining if a DOM node can handle the data expando
3783	acceptData: function( elem ) {
3784		// Do not set data on non-element because it will not be cleared (#8335).
3785		if ( elem.nodeType && elem.nodeType !== 1 && elem.nodeType !== 9 ) {
3786			return false;
3787		}
3788
3789		var noData = elem.nodeName && jQuery.noData[ elem.nodeName.toLowerCase() ];
3790
3791		// nodes accept data unless otherwise specified; rejection can be conditional
3792		return !noData || noData !== true && elem.getAttribute("classid") === noData;
3793	}
3794});
3795
3796jQuery.fn.extend({
3797	data: function( key, value ) {
3798		var attrs, name,
3799			data = null,
3800			i = 0,
3801			elem = this[0];
3802
3803		// Special expections of .data basically thwart jQuery.access,
3804		// so implement the relevant behavior ourselves
3805
3806		// Gets all values
3807		if ( key === undefined ) {
3808			if ( this.length ) {
3809				data = jQuery.data( elem );
3810
3811				if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) {
3812					attrs = elem.attributes;
3813					for ( ; i < attrs.length; i++ ) {
3814						name = attrs[i].name;
3815
3816						if ( name.indexOf("data-") === 0 ) {
3817							name = jQuery.camelCase( name.slice(5) );
3818
3819							dataAttr( elem, name, data[ name ] );
3820						}
3821					}
3822					jQuery._data( elem, "parsedAttrs", true );
3823				}
3824			}
3825
3826			return data;
3827		}
3828
3829		// Sets multiple values
3830		if ( typeof key === "object" ) {
3831			return this.each(function() {
3832				jQuery.data( this, key );
3833			});
3834		}
3835
3836		return arguments.length > 1 ?
3837
3838			// Sets one value
3839			this.each(function() {
3840				jQuery.data( this, key, value );
3841			}) :
3842
3843			// Gets one value
3844			// Try to fetch any internally stored data first
3845			elem ? dataAttr( elem, key, jQuery.data( elem, key ) ) : null;
3846	},
3847
3848	removeData: function( key ) {
3849		return this.each(function() {
3850			jQuery.removeData( this, key );
3851		});
3852	}
3853});
3854
3855function dataAttr( elem, key, data ) {
3856	// If nothing was found internally, try to fetch any
3857	// data from the HTML5 data-* attribute
3858	if ( data === undefined && elem.nodeType === 1 ) {
3859
3860		var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase();
3861
3862		data = elem.getAttribute( name );
3863
3864		if ( typeof data === "string" ) {
3865			try {
3866				data = data === "true" ? true :
3867					data === "false" ? false :
3868					data === "null" ? null :
3869					// Only convert to a number if it doesn't change the string
3870					+data + "" === data ? +data :
3871					rbrace.test( data ) ? jQuery.parseJSON( data ) :
3872						data;
3873			} catch( e ) {}
3874
3875			// Make sure we set the data so it isn't changed later
3876			jQuery.data( elem, key, data );
3877
3878		} else {
3879			data = undefined;
3880		}
3881	}
3882
3883	return data;
3884}
3885
3886// checks a cache object for emptiness
3887function isEmptyDataObject( obj ) {
3888	var name;
3889	for ( name in obj ) {
3890
3891		// if the public data object is empty, the private is still empty
3892		if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) {
3893			continue;
3894		}
3895		if ( name !== "toJSON" ) {
3896			return false;
vendor: 5,969 bytes, lines 3897-4146
3897		}
3898	}
3899
3900	return true;
3901}
3902jQuery.extend({
3903	queue: function( elem, type, data ) {
3904		var queue;
3905
3906		if ( elem ) {
3907			type = ( type || "fx" ) + "queue";
3908			queue = jQuery._data( elem, type );
3909
3910			// Speed up dequeue by getting out quickly if this is just a lookup
3911			if ( data ) {
3912				if ( !queue || jQuery.isArray(data) ) {
3913					queue = jQuery._data( elem, type, jQuery.makeArray(data) );
3914				} else {
3915					queue.push( data );
3916				}
3917			}
3918			return queue || [];
3919		}
3920	},
3921
3922	dequeue: function( elem, type ) {
3923		type = type || "fx";
3924
3925		var queue = jQuery.queue( elem, type ),
3926			startLength = queue.length,
3927			fn = queue.shift(),
3928			hooks = jQuery._queueHooks( elem, type ),
3929			next = function() {
3930				jQuery.dequeue( elem, type );
3931			};
3932
3933		// If the fx queue is dequeued, always remove the progress sentinel
3934		if ( fn === "inprogress" ) {
3935			fn = queue.shift();
3936			startLength--;
3937		}
3938
3939		if ( fn ) {
3940
3941			// Add a progress sentinel to prevent the fx queue from being
3942			// automatically dequeued
3943			if ( type === "fx" ) {
3944				queue.unshift( "inprogress" );
3945			}
3946
3947			// clear up the last queue stop function
3948			delete hooks.stop;
3949			fn.call( elem, next, hooks );
3950		}
3951
3952		if ( !startLength && hooks ) {
3953			hooks.empty.fire();
3954		}
3955	},
3956
3957	// not intended for public consumption - generates a queueHooks object, or returns the current one
3958	_queueHooks: function( elem, type ) {
3959		var key = type + "queueHooks";
3960		return jQuery._data( elem, key ) || jQuery._data( elem, key, {
3961			empty: jQuery.Callbacks("once memory").add(function() {
3962				jQuery._removeData( elem, type + "queue" );
3963				jQuery._removeData( elem, key );
3964			})
3965		});
3966	}
3967});
3968
3969jQuery.fn.extend({
3970	queue: function( type, data ) {
3971		var setter = 2;
3972
3973		if ( typeof type !== "string" ) {
3974			data = type;
3975			type = "fx";
3976			setter--;
3977		}
3978
3979		if ( arguments.length < setter ) {
3980			return jQuery.queue( this[0], type );
3981		}
3982
3983		return data === undefined ?
3984			this :
3985			this.each(function() {
3986				var queue = jQuery.queue( this, type, data );
3987
3988				// ensure a hooks for this queue
3989				jQuery._queueHooks( this, type );
3990
3991				if ( type === "fx" && queue[0] !== "inprogress" ) {
3992					jQuery.dequeue( this, type );
3993				}
3994			});
3995	},
3996	dequeue: function( type ) {
3997		return this.each(function() {
3998			jQuery.dequeue( this, type );
3999		});
4000	},
4001	// Based off of the plugin by Clint Helfers, with permission.
4002	// http://blindsignals.com/index.php/2009/07/jquery-delay/
4003	delay: function( time, type ) {
4004		time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time;
4005		type = type || "fx";
4006
4007		return this.queue( type, function( next, hooks ) {
4008			var timeout = setTimeout( next, time );
4009			hooks.stop = function() {
4010				clearTimeout( timeout );
4011			};
4012		});
4013	},
4014	clearQueue: function( type ) {
4015		return this.queue( type || "fx", [] );
4016	},
4017	// Get a promise resolved when queues of a certain type
4018	// are emptied (fx is the type by default)
4019	promise: function( type, obj ) {
4020		var tmp,
4021			count = 1,
4022			defer = jQuery.Deferred(),
4023			elements = this,
4024			i = this.length,
4025			resolve = function() {
4026				if ( !( --count ) ) {
4027					defer.resolveWith( elements, [ elements ] );
4028				}
4029			};
4030
4031		if ( typeof type !== "string" ) {
4032			obj = type;
4033			type = undefined;
4034		}
4035		type = type || "fx";
4036
4037		while( i-- ) {
4038			tmp = jQuery._data( elements[ i ], type + "queueHooks" );
4039			if ( tmp && tmp.empty ) {
4040				count++;
4041				tmp.empty.add( resolve );
4042			}
4043		}
4044		resolve();
4045		return defer.promise( obj );
4046	}
4047});
4048var nodeHook, boolHook,
4049	rclass = /[\t\r\n\f]/g,
4050	rreturn = /\r/g,
4051	rfocusable = /^(?:input|select|textarea|button|object)$/i,
4052	rclickable = /^(?:a|area)$/i,
4053	ruseDefault = /^(?:checked|selected)$/i,
4054	getSetAttribute = jQuery.support.getSetAttribute,
4055	getSetInput = jQuery.support.input;
4056
4057jQuery.fn.extend({
4058	attr: function( name, value ) {
4059		return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 );
4060	},
4061
4062	removeAttr: function( name ) {
4063		return this.each(function() {
4064			jQuery.removeAttr( this, name );
4065		});
4066	},
4067
4068	prop: function( name, value ) {
4069		return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 );
4070	},
4071
4072	removeProp: function( name ) {
4073		name = jQuery.propFix[ name ] || name;
4074		return this.each(function() {
4075			// try/catch handles cases where IE balks (such as removing a property on window)
4076			try {
4077				this[ name ] = undefined;
4078				delete this[ name ];
4079			} catch( e ) {}
4080		});
4081	},
4082
4083	addClass: function( value ) {
4084		var classes, elem, cur, clazz, j,
4085			i = 0,
4086			len = this.length,
4087			proceed = typeof value === "string" && value;
4088
4089		if ( jQuery.isFunction( value ) ) {
4090			return this.each(function( j ) {
4091				jQuery( this ).addClass( value.call( this, j, this.className ) );
4092			});
4093		}
4094
4095		if ( proceed ) {
4096			// The disjunction here is for better compressibility (see removeClass)
4097			classes = ( value || "" ).match( core_rnotwhite ) || [];
4098
4099			for ( ; i < len; i++ ) {
4100				elem = this[ i ];
4101				cur = elem.nodeType === 1 && ( elem.className ?
4102					( " " + elem.className + " " ).replace( rclass, " " ) :
4103					" "
4104				);
4105
4106				if ( cur ) {
4107					j = 0;
4108					while ( (clazz = classes[j++]) ) {
4109						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
4110							cur += clazz + " ";
4111						}
4112					}
4113					elem.className = jQuery.trim( cur );
4114
4115				}
4116			}
4117		}
4118
4119		return this;
4120	},
4121
4122	removeClass: function( value ) {
4123		var classes, elem, cur, clazz, j,
4124			i = 0,
4125			len = this.length,
4126			proceed = arguments.length === 0 || typeof value === "string" && value;
4127
4128		if ( jQuery.isFunction( value ) ) {
4129			return this.each(function( j ) {
4130				jQuery( this ).removeClass( value.call( this, j, this.className ) );
4131			});
4132		}
4133		if ( proceed ) {
4134			classes = ( value || "" ).match( core_rnotwhite ) || [];
4135
4136			for ( ; i < len; i++ ) {
4137				elem = this[ i ];
4138				// This expression is here for better compressibility (see addClass)
4139				cur = elem.nodeType === 1 && ( elem.className ?
4140					( " " + elem.className + " " ).replace( rclass, " " ) :
4141					""
4142				);
4143
4144				if ( cur ) {
4145					j = 0;
4146					while ( (clazz = classes[j++]) ) {
vendor: 6,992 bytes, lines 4147-4411
4147						// Remove *all* instances
4148						while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
4149							cur = cur.replace( " " + clazz + " ", " " );
4150						}
4151					}
4152					elem.className = value ? jQuery.trim( cur ) : "";
4153				}
4154			}
4155		}
4156
4157		return this;
4158	},
4159
4160	toggleClass: function( value, stateVal ) {
4161		var type = typeof value;
4162
4163		if ( typeof stateVal === "boolean" && type === "string" ) {
4164			return stateVal ? this.addClass( value ) : this.removeClass( value );
4165		}
4166
4167		if ( jQuery.isFunction( value ) ) {
4168			return this.each(function( i ) {
4169				jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal );
4170			});
4171		}
4172
4173		return this.each(function() {
4174			if ( type === "string" ) {
4175				// toggle individual class names
4176				var className,
4177					i = 0,
4178					self = jQuery( this ),
4179					classNames = value.match( core_rnotwhite ) || [];
4180
4181				while ( (className = classNames[ i++ ]) ) {
4182					// check each className given, space separated list
4183					if ( self.hasClass( className ) ) {
4184						self.removeClass( className );
4185					} else {
4186						self.addClass( className );
4187					}
4188				}
4189
4190			// Toggle whole class name
4191			} else if ( type === core_strundefined || type === "boolean" ) {
4192				if ( this.className ) {
4193					// store className if set
4194					jQuery._data( this, "__className__", this.className );
4195				}
4196
4197				// If the element has a class name or if we're passed "false",
4198				// then remove the whole classname (if there was one, the above saved it).
4199				// Otherwise bring back whatever was previously saved (if anything),
4200				// falling back to the empty string if nothing was stored.
4201				this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || "";
4202			}
4203		});
4204	},
4205
4206	hasClass: function( selector ) {
4207		var className = " " + selector + " ",
4208			i = 0,
4209			l = this.length;
4210		for ( ; i < l; i++ ) {
4211			if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) >= 0 ) {
4212				return true;
4213			}
4214		}
4215
4216		return false;
4217	},
4218
4219	val: function( value ) {
4220		var ret, hooks, isFunction,
4221			elem = this[0];
4222
4223		if ( !arguments.length ) {
4224			if ( elem ) {
4225				hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ];
4226
4227				if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) {
4228					return ret;
4229				}
4230
4231				ret = elem.value;
4232
4233				return typeof ret === "string" ?
4234					// handle most common string cases
4235					ret.replace(rreturn, "") :
4236					// handle cases where value is null/undef or number
4237					ret == null ? "" : ret;
4238			}
4239
4240			return;
4241		}
4242
4243		isFunction = jQuery.isFunction( value );
4244
4245		return this.each(function( i ) {
4246			var val;
4247
4248			if ( this.nodeType !== 1 ) {
4249				return;
4250			}
4251
4252			if ( isFunction ) {
4253				val = value.call( this, i, jQuery( this ).val() );
4254			} else {
4255				val = value;
4256			}
4257
4258			// Treat null/undefined as ""; convert numbers to string
4259			if ( val == null ) {
4260				val = "";
4261			} else if ( typeof val === "number" ) {
4262				val += "";
4263			} else if ( jQuery.isArray( val ) ) {
4264				val = jQuery.map(val, function ( value ) {
4265					return value == null ? "" : value + "";
4266				});
4267			}
4268
4269			hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ];
4270
4271			// If set returns undefined, fall back to normal setting
4272			if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) {
4273				this.value = val;
4274			}
4275		});
4276	}
4277});
4278
4279jQuery.extend({
4280	valHooks: {
4281		option: {
4282			get: function( elem ) {
4283				// Use proper attribute retrieval(#6932, #12072)
4284				var val = jQuery.find.attr( elem, "value" );
4285				return val != null ?
4286					val :
4287					elem.text;
4288			}
4289		},
4290		select: {
4291			get: function( elem ) {
4292				var value, option,
4293					options = elem.options,
4294					index = elem.selectedIndex,
4295					one = elem.type === "select-one" || index < 0,
4296					values = one ? null : [],
4297					max = one ? index + 1 : options.length,
4298					i = index < 0 ?
4299						max :
4300						one ? index : 0;
4301
4302				// Loop through all the selected options
4303				for ( ; i < max; i++ ) {
4304					option = options[ i ];
4305
4306					// oldIE doesn't update selected after form reset (#2551)
4307					if ( ( option.selected || i === index ) &&
4308							// Don't return options that are disabled or in a disabled optgroup
4309							( jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null ) &&
4310							( !option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" ) ) ) {
4311
4312						// Get the specific value for the option
4313						value = jQuery( option ).val();
4314
4315						// We don't need an array for one selects
4316						if ( one ) {
4317							return value;
4318						}
4319
4320						// Multi-Selects return an array
4321						values.push( value );
4322					}
4323				}
4324
4325				return values;
4326			},
4327
4328			set: function( elem, value ) {
4329				var optionSet, option,
4330					options = elem.options,
4331					values = jQuery.makeArray( value ),
4332					i = options.length;
4333
4334				while ( i-- ) {
4335					option = options[ i ];
4336					if ( (option.selected = jQuery.inArray( jQuery(option).val(), values ) >= 0) ) {
4337						optionSet = true;
4338					}
4339				}
4340
4341				// force browsers to behave consistently when non-matching value is set
4342				if ( !optionSet ) {
4343					elem.selectedIndex = -1;
4344				}
4345				return values;
4346			}
4347		}
4348	},
4349
4350	attr: function( elem, name, value ) {
4351		var hooks, ret,
4352			nType = elem.nodeType;
4353
4354		// don't get/set attributes on text, comment and attribute nodes
4355		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
4356			return;
4357		}
4358
4359		// Fallback to prop when attributes are not supported
4360		if ( typeof elem.getAttribute === core_strundefined ) {
4361			return jQuery.prop( elem, name, value );
4362		}
4363
4364		// All attributes are lowercase
4365		// Grab necessary hook if one is defined
4366		if ( nType !== 1 || !jQuery.isXMLDoc( elem ) ) {
4367			name = name.toLowerCase();
4368			hooks = jQuery.attrHooks[ name ] ||
4369				( jQuery.expr.match.bool.test( name ) ? boolHook : nodeHook );
4370		}
4371
4372		if ( value !== undefined ) {
4373
4374			if ( value === null ) {
4375				jQuery.removeAttr( elem, name );
4376
4377			} else if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) {
4378				return ret;
4379
4380			} else {
4381				elem.setAttribute( name, value + "" );
4382				return value;
4383			}
4384
4385		} else if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) {
4386			return ret;
4387
4388		} else {
4389			ret = jQuery.find.attr( elem, name );
4390
4391			// Non-existent attributes return null, we normalize to undefined
4392			return ret == null ?
4393				undefined :
4394				ret;
4395		}
4396	},
4397
4398	removeAttr: function( elem, value ) {
4399		var name, propName,
4400			i = 0,
4401			attrNames = value && value.match( core_rnotwhite );
4402
4403		if ( attrNames && elem.nodeType === 1 ) {
4404			while ( (name = attrNames[i++]) ) {
4405				propName = jQuery.propFix[ name ] || name;
4406
4407				// Boolean attributes get special treatment (#10870)
4408				if ( jQuery.expr.match.bool.test( name ) ) {
4409					// Set corresponding property to false
4410					if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
4411						elem[ propName ] = false;
4412					// Support: IE<9
4413					// Also clear defaultChecked/defaultSelected (if appropriate)
4414					} else {
4415						elem[ jQuery.camelCase( "default-" + name ) ] =
4416							elem[ propName ] = false;
4417					}
4418
4419				// See #9699 for explanation of this approach (setting first, then removal)
4420				} else {
4421					jQuery.attr( elem, name, "" );
4422				}
4423
4424				elem.removeAttribute( getSetAttribute ? name : propName );
4425			}
4426		}
4427	},
4428
4429	attrHooks: {
4430		type: {
4431			set: function( elem, value ) {
4432				if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) {
4433					// Setting the type on a radio button after the value resets the value in IE6-9
4434					// Reset value to default in case type is set after value during creation
4435					var val = elem.value;
4436					elem.setAttribute( "type", value );
4437					if ( val ) {
4438						elem.value = val;
4439					}
4440					return value;
4441				}
4442			}
4443		}
4444	},
4445
4446	propFix: {
4447		"for": "htmlFor",
4448		"class": "className"
4449	},
4450
4451	prop: function( elem, name, value ) {
4452		var ret, hooks, notxml,
4453			nType = elem.nodeType;
4454
4455		// don't get/set properties on text, comment and attribute nodes
4456		if ( !elem || nType === 3 || nType === 8 || nType === 2 ) {
4457			return;
4458		}
4459
4460		notxml = nType !== 1 || !jQuery.isXMLDoc( elem );
4461
4462		if ( notxml ) {
4463			// Fix name and attach hooks
4464			name = jQuery.propFix[ name ] || name;
4465			hooks = jQuery.propHooks[ name ];
4466		}
4467
4468		if ( value !== undefined ) {
4469			return hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ?
4470				ret :
4471				( elem[ name ] = value );
4472
4473		} else {
4474			return hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ?
4475				ret :
4476				elem[ name ];
4477		}
4478	},
4479
4480	propHooks: {
4481		tabIndex: {
4482			get: function( elem ) {
4483				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
4484				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
4485				// Use proper attribute retrieval(#12072)
4486				var tabindex = jQuery.find.attr( elem, "tabindex" );
4487
4488				return tabindex ?
4489					parseInt( tabindex, 10 ) :
4490					rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ?
4491						0 :
4492						-1;
4493			}
4494		}
4495	}
4496});
4497
4498// Hooks for boolean attributes
4499boolHook = {
4500	set: function( elem, value, name ) {
4501		if ( value === false ) {
4502			// Remove boolean attributes when set to false
4503			jQuery.removeAttr( elem, name );
4504		} else if ( getSetInput && getSetAttribute || !ruseDefault.test( name ) ) {
4505			// IE<8 needs the *property* name
4506			elem.setAttribute( !getSetAttribute && jQuery.propFix[ name ] || name, name );
4507
4508		// Use defaultChecked and defaultSelected for oldIE
4509		} else {
4510			elem[ jQuery.camelCase( "default-" + name ) ] = elem[ name ] = true;
4511		}
4512
4513		return name;
4514	}
4515};
4516jQuery.each( jQuery.expr.match.bool.source.match( /\w+/g ), function( i, name ) {
4517	var getter = jQuery.expr.attrHandle[ name ] || jQuery.find.attr;
4518
4519	jQuery.expr.attrHandle[ name ] = getSetInput && getSetAttribute || !ruseDefault.test( name ) ?
4520		function( elem, name, isXML ) {
4521			var fn = jQuery.expr.attrHandle[ name ],
4522				ret = isXML ?
4523					undefined :
4524					/* jshint eqeqeq: false */
4525					(jQuery.expr.attrHandle[ name ] = undefined) !=
4526						getter( elem, name, isXML ) ?
4527
4528						name.toLowerCase() :
4529						null;
4530			jQuery.expr.attrHandle[ name ] = fn;
4531			return ret;
4532		} :
4533		function( elem, name, isXML ) {
4534			return isXML ?
4535				undefined :
4536				elem[ jQuery.camelCase( "default-" + name ) ] ?
4537					name.toLowerCase() :
4538					null;
4539		};
4540});
4541
4542// fix oldIE attroperties
4543if ( !getSetInput || !getSetAttribute ) {
4544	jQuery.attrHooks.value = {
4545		set: function( elem, value, name ) {
4546			if ( jQuery.nodeName( elem, "input" ) ) {
4547				// Does not return so that setAttribute is also used
4548				elem.defaultValue = value;
4549			} else {
4550				// Use nodeHook if defined (#1954); otherwise setAttribute is fine
4551				return nodeHook && nodeHook.set( elem, value, name );
4552			}
4553		}
4554	};
4555}
4556
4557// IE6/7 do not support getting/setting some attributes with get/setAttribute
4558if ( !getSetAttribute ) {
4559
4560	// Use this for any attribute in IE6/7
4561	// This fixes almost every IE6/7 issue
4562	nodeHook = {
4563		set: function( elem, value, name ) {
4564			// Set the existing or create a new attribute node
4565			var ret = elem.getAttributeNode( name );
4566			if ( !ret ) {
4567				elem.setAttributeNode(
4568					(ret = elem.ownerDocument.createAttribute( name ))
4569				);
4570			}
4571
4572			ret.value = value += "";
4573
4574			// Break association with cloned elements by also using setAttribute (#9646)
4575			return name === "value" || value === elem.getAttribute( name ) ?
4576				value :
4577				undefined;
4578		}
4579	};
4580	jQuery.expr.attrHandle.id = jQuery.expr.attrHandle.name = jQuery.expr.attrHandle.coords =
4581		// Some attributes are constructed with empty-string values when not defined
4582		function( elem, name, isXML ) {
4583			var ret;
4584			return isXML ?
4585				undefined :
4586				(ret = elem.getAttributeNode( name )) && ret.value !== "" ?
4587					ret.value :
4588					null;
4589		};
4590	jQuery.valHooks.button = {
4591		get: function( elem, name ) {
4592			var ret = elem.getAttributeNode( name );
4593			return ret && ret.specified ?
4594				ret.value :
4595				undefined;
4596		},
4597		set: nodeHook.set
4598	};
4599
4600	// Set contenteditable to false on removals(#10429)
vendor: 13,769 bytes, lines 4601-5064
4601	// Setting to empty string throws an error as an invalid value
4602	jQuery.attrHooks.contenteditable = {
4603		set: function( elem, value, name ) {
4604			nodeHook.set( elem, value === "" ? false : value, name );
4605		}
4606	};
4607
4608	// Set width and height to auto instead of 0 on empty string( Bug #8150 )
4609	// This is for removals
4610	jQuery.each([ "width", "height" ], function( i, name ) {
4611		jQuery.attrHooks[ name ] = {
4612			set: function( elem, value ) {
4613				if ( value === "" ) {
4614					elem.setAttribute( name, "auto" );
4615					return value;
4616				}
4617			}
4618		};
4619	});
4620}
4621
4622
4623// Some attributes require a special call on IE
4624// http://msdn.microsoft.com/en-us/library/ms536429%28VS.85%29.aspx
4625if ( !jQuery.support.hrefNormalized ) {
4626	// href/src property should get the full normalized URL (#10299/#12915)
4627	jQuery.each([ "href", "src" ], function( i, name ) {
4628		jQuery.propHooks[ name ] = {
4629			get: function( elem ) {
4630				return elem.getAttribute( name, 4 );
4631			}
4632		};
4633	});
4634}
4635
4636if ( !jQuery.support.style ) {
4637	jQuery.attrHooks.style = {
4638		get: function( elem ) {
4639			// Return undefined in the case of empty string
4640			// Note: IE uppercases css property names, but if we were to .toLowerCase()
4641			// .cssText, that would destroy case senstitivity in URL's, like in "background"
4642			return elem.style.cssText || undefined;
4643		},
4644		set: function( elem, value ) {
4645			return ( elem.style.cssText = value + "" );
4646		}
4647	};
4648}
4649
4650// Safari mis-reports the default selected property of an option
4651// Accessing the parent's selectedIndex property fixes it
4652if ( !jQuery.support.optSelected ) {
4653	jQuery.propHooks.selected = {
4654		get: function( elem ) {
4655			var parent = elem.parentNode;
4656
4657			if ( parent ) {
4658				parent.selectedIndex;
4659
4660				// Make sure that it also works with optgroups, see #5701
4661				if ( parent.parentNode ) {
4662					parent.parentNode.selectedIndex;
4663				}
4664			}
4665			return null;
4666		}
4667	};
4668}
4669
4670jQuery.each([
4671	"tabIndex",
4672	"readOnly",
4673	"maxLength",
4674	"cellSpacing",
4675	"cellPadding",
4676	"rowSpan",
4677	"colSpan",
4678	"useMap",
4679	"frameBorder",
4680	"contentEditable"
4681], function() {
4682	jQuery.propFix[ this.toLowerCase() ] = this;
4683});
4684
4685// IE6/7 call enctype encoding
4686if ( !jQuery.support.enctype ) {
4687	jQuery.propFix.enctype = "encoding";
4688}
4689
4690// Radios and checkboxes getter/setter
4691jQuery.each([ "radio", "checkbox" ], function() {
4692	jQuery.valHooks[ this ] = {
4693		set: function( elem, value ) {
4694			if ( jQuery.isArray( value ) ) {
4695				return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 );
4696			}
4697		}
4698	};
4699	if ( !jQuery.support.checkOn ) {
4700		jQuery.valHooks[ this ].get = function( elem ) {
4701			// Support: Webkit
4702			// "" is returned instead of "on" if a value isn't specified
4703			return elem.getAttribute("value") === null ? "on" : elem.value;
4704		};
4705	}
4706});
4707var rformElems = /^(?:input|select|textarea)$/i,
4708	rkeyEvent = /^key/,
4709	rmouseEvent = /^(?:mouse|contextmenu)|click/,
4710	rfocusMorph = /^(?:focusinfocus|focusoutblur)$/,
4711	rtypenamespace = /^([^.]*)(?:\.(.+)|)$/;
4712
4713function returnTrue() {
4714	return true;
4715}
4716
4717function returnFalse() {
4718	return false;
4719}
4720
4721function safeActiveElement() {
4722	try {
4723		return document.activeElement;
4724	} catch ( err ) { }
4725}
4726
4727/*
4728 * Helper functions for managing events -- not part of the public interface.
4729 * Props to Dean Edwards' addEvent library for many of the ideas.
4730 */
4731jQuery.event = {
4732
4733	global: {},
4734
4735	add: function( elem, types, handler, data, selector ) {
4736		var tmp, events, t, handleObjIn,
4737			special, eventHandle, handleObj,
4738			handlers, type, namespaces, origType,
4739			elemData = jQuery._data( elem );
4740
4741		// Don't attach events to noData or text/comment nodes (but allow plain objects)
4742		if ( !elemData ) {
4743			return;
4744		}
4745
4746		// Caller can pass in an object of custom data in lieu of the handler
4747		if ( handler.handler ) {
4748			handleObjIn = handler;
4749			handler = handleObjIn.handler;
4750			selector = handleObjIn.selector;
4751		}
4752
4753		// Make sure that the handler has a unique ID, used to find/remove it later
4754		if ( !handler.guid ) {
4755			handler.guid = jQuery.guid++;
4756		}
4757
4758		// Init the element's event structure and main handler, if this is the first
4759		if ( !(events = elemData.events) ) {
4760			events = elemData.events = {};
4761		}
4762		if ( !(eventHandle = elemData.handle) ) {
4763			eventHandle = elemData.handle = function( e ) {
4764				// Discard the second event of a jQuery.event.trigger() and
4765				// when an event is called after a page has unloaded
4766				return typeof jQuery !== core_strundefined && (!e || jQuery.event.triggered !== e.type) ?
4767					jQuery.event.dispatch.apply( eventHandle.elem, arguments ) :
4768					undefined;
4769			};
4770			// Add elem as a property of the handle fn to prevent a memory leak with IE non-native events
4771			eventHandle.elem = elem;
4772		}
4773
4774		// Handle multiple events separated by a space
4775		types = ( types || "" ).match( core_rnotwhite ) || [""];
4776		t = types.length;
4777		while ( t-- ) {
4778			tmp = rtypenamespace.exec( types[t] ) || [];
4779			type = origType = tmp[1];
4780			namespaces = ( tmp[2] || "" ).split( "." ).sort();
4781
4782			// There *must* be a type, no attaching namespace-only handlers
4783			if ( !type ) {
4784				continue;
4785			}
4786
4787			// If event changes its type, use the special event handlers for the changed type
4788			special = jQuery.event.special[ type ] || {};
4789
4790			// If selector defined, determine special event api type, otherwise given type
4791			type = ( selector ? special.delegateType : special.bindType ) || type;
4792
4793			// Update special based on newly reset type
4794			special = jQuery.event.special[ type ] || {};
4795
4796			// handleObj is passed to all event handlers
4797			handleObj = jQuery.extend({
4798				type: type,
4799				origType: origType,
4800				data: data,
4801				handler: handler,
4802				guid: handler.guid,
4803				selector: selector,
4804				needsContext: selector && jQuery.expr.match.needsContext.test( selector ),
4805				namespace: namespaces.join(".")
4806			}, handleObjIn );
4807
4808			// Init the event handler queue if we're the first
4809			if ( !(handlers = events[ type ]) ) {
4810				handlers = events[ type ] = [];
4811				handlers.delegateCount = 0;
4812
4813				// Only use addEventListener/attachEvent if the special events handler returns false
4814				if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
4815					// Bind the global event handler to the element
4816					if ( elem.addEventListener ) {
4817						elem.addEventListener( type, eventHandle, false );
4818
4819					} else if ( elem.attachEvent ) {
4820						elem.attachEvent( "on" + type, eventHandle );
4821					}
4822				}
4823			}
4824
4825			if ( special.add ) {
4826				special.add.call( elem, handleObj );
4827
4828				if ( !handleObj.handler.guid ) {
4829					handleObj.handler.guid = handler.guid;
4830				}
4831			}
4832
4833			// Add to the element's handler list, delegates in front
4834			if ( selector ) {
4835				handlers.splice( handlers.delegateCount++, 0, handleObj );
4836			} else {
4837				handlers.push( handleObj );
4838			}
4839
4840			// Keep track of which events have ever been used, for event optimization
4841			jQuery.event.global[ type ] = true;
4842		}
4843
4844		// Nullify elem to prevent memory leaks in IE
4845		elem = null;
4846	},
4847
4848	// Detach an event or set of events from an element
4849	remove: function( elem, types, handler, selector, mappedTypes ) {
4850		var j, handleObj, tmp,
4851			origCount, t, events,
4852			special, handlers, type,
4853			namespaces, origType,
4854			elemData = jQuery.hasData( elem ) && jQuery._data( elem );
4855
4856		if ( !elemData || !(events = elemData.events) ) {
4857			return;
4858		}
4859
4860		// Once for each type.namespace in types; type may be omitted
4861		types = ( types || "" ).match( core_rnotwhite ) || [""];
4862		t = types.length;
4863		while ( t-- ) {
4864			tmp = rtypenamespace.exec( types[t] ) || [];
4865			type = origType = tmp[1];
4866			namespaces = ( tmp[2] || "" ).split( "." ).sort();
4867
4868			// Unbind all events (on this namespace, if provided) for the element
4869			if ( !type ) {
4870				for ( type in events ) {
4871					jQuery.event.remove( elem, type + types[ t ], handler, selector, true );
4872				}
4873				continue;
4874			}
4875
4876			special = jQuery.event.special[ type ] || {};
4877			type = ( selector ? special.delegateType : special.bindType ) || type;
4878			handlers = events[ type ] || [];
4879			tmp = tmp[2] && new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" );
4880
4881			// Remove matching events
4882			origCount = j = handlers.length;
4883			while ( j-- ) {
4884				handleObj = handlers[ j ];
4885
4886				if ( ( mappedTypes || origType === handleObj.origType ) &&
4887					( !handler || handler.guid === handleObj.guid ) &&
4888					( !tmp || tmp.test( handleObj.namespace ) ) &&
4889					( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) {
4890					handlers.splice( j, 1 );
4891
4892					if ( handleObj.selector ) {
4893						handlers.delegateCount--;
4894					}
4895					if ( special.remove ) {
4896						special.remove.call( elem, handleObj );
4897					}
4898				}
4899			}
4900
4901			// Remove generic event handler if we removed something and no more handlers exist
4902			// (avoids potential for endless recursion during removal of special event handlers)
4903			if ( origCount && !handlers.length ) {
4904				if ( !special.teardown || special.teardown.call( elem, namespaces, elemData.handle ) === false ) {
4905					jQuery.removeEvent( elem, type, elemData.handle );
4906				}
4907
4908				delete events[ type ];
4909			}
4910		}
4911
4912		// Remove the expando if it's no longer used
4913		if ( jQuery.isEmptyObject( events ) ) {
4914			delete elemData.handle;
4915
4916			// removeData also checks for emptiness and clears the expando if empty
4917			// so use it instead of delete
4918			jQuery._removeData( elem, "events" );
4919		}
4920	},
4921
4922	trigger: function( event, data, elem, onlyHandlers ) {
4923		var handle, ontype, cur,
4924			bubbleType, special, tmp, i,
4925			eventPath = [ elem || document ],
4926			type = core_hasOwn.call( event, "type" ) ? event.type : event,
4927			namespaces = core_hasOwn.call( event, "namespace" ) ? event.namespace.split(".") : [];
4928
4929		cur = tmp = elem = elem || document;
4930
4931		// Don't do events on text and comment nodes
4932		if ( elem.nodeType === 3 || elem.nodeType === 8 ) {
4933			return;
4934		}
4935
4936		// focus/blur morphs to focusin/out; ensure we're not firing them right now
4937		if ( rfocusMorph.test( type + jQuery.event.triggered ) ) {
4938			return;
4939		}
4940
4941		if ( type.indexOf(".") >= 0 ) {
4942			// Namespaced trigger; create a regexp to match event type in handle()
4943			namespaces = type.split(".");
4944			type = namespaces.shift();
4945			namespaces.sort();
4946		}
4947		ontype = type.indexOf(":") < 0 && "on" + type;
4948
4949		// Caller can pass in a jQuery.Event object, Object, or just an event type string
4950		event = event[ jQuery.expando ] ?
4951			event :
4952			new jQuery.Event( type, typeof event === "object" && event );
4953
4954		// Trigger bitmask: & 1 for native handlers; & 2 for jQuery (always true)
4955		event.isTrigger = onlyHandlers ? 2 : 3;
4956		event.namespace = namespaces.join(".");
4957		event.namespace_re = event.namespace ?
4958			new RegExp( "(^|\\.)" + namespaces.join("\\.(?:.*\\.|)") + "(\\.|$)" ) :
4959			null;
4960
4961		// Clean up the event in case it is being reused
4962		event.result = undefined;
4963		if ( !event.target ) {
4964			event.target = elem;
4965		}
4966
4967		// Clone any incoming data and prepend the event, creating the handler arg list
4968		data = data == null ?
4969			[ event ] :
4970			jQuery.makeArray( data, [ event ] );
4971
4972		// Allow special events to draw outside the lines
4973		special = jQuery.event.special[ type ] || {};
4974		if ( !onlyHandlers && special.trigger && special.trigger.apply( elem, data ) === false ) {
4975			return;
4976		}
4977
4978		// Determine event propagation path in advance, per W3C events spec (#9951)
4979		// Bubble up to document, then to window; watch for a global ownerDocument var (#9724)
4980		if ( !onlyHandlers && !special.noBubble && !jQuery.isWindow( elem ) ) {
4981
4982			bubbleType = special.delegateType || type;
4983			if ( !rfocusMorph.test( bubbleType + type ) ) {
4984				cur = cur.parentNode;
4985			}
4986			for ( ; cur; cur = cur.parentNode ) {
4987				eventPath.push( cur );
4988				tmp = cur;
4989			}
4990
4991			// Only add window if we got to document (e.g., not plain obj or detached DOM)
4992			if ( tmp === (elem.ownerDocument || document) ) {
4993				eventPath.push( tmp.defaultView || tmp.parentWindow || window );
4994			}
4995		}
4996
4997		// Fire handlers on the event path
4998		i = 0;
4999		while ( (cur = eventPath[i++]) && !event.isPropagationStopped() ) {
5000
5001			event.type = i > 1 ?
5002				bubbleType :
5003				special.bindType || type;
5004
5005			// jQuery handler
5006			handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" );
5007			if ( handle ) {
5008				handle.apply( cur, data );
5009			}
5010
5011			// Native handler
5012			handle = ontype && cur[ ontype ];
5013			if ( handle && jQuery.acceptData( cur ) && handle.apply && handle.apply( cur, data ) === false ) {
5014				event.preventDefault();
5015			}
5016		}
5017		event.type = type;
5018
5019		// If nobody prevented the default action, do it now
5020		if ( !onlyHandlers && !event.isDefaultPrevented() ) {
5021
5022			if ( (!special._default || special._default.apply( eventPath.pop(), data ) === false) &&
5023				jQuery.acceptData( elem ) ) {
5024
5025				// Call a native DOM method on the target with the same name name as the event.
5026				// Can't use an .isFunction() check here because IE6/7 fails that test.
5027				// Don't do default actions on window, that's where global variables be (#6170)
5028				if ( ontype && elem[ type ] && !jQuery.isWindow( elem ) ) {
5029
5030					// Don't re-trigger an onFOO event when we call its FOO() method
5031					tmp = elem[ ontype ];
5032
5033					if ( tmp ) {
5034						elem[ ontype ] = null;
5035					}
5036
5037					// Prevent re-triggering of the same event, since we already bubbled it above
5038					jQuery.event.triggered = type;
5039					try {
5040						elem[ type ]();
5041					} catch ( e ) {
5042						// IE<9 dies on focus/blur to hidden element (#1486,#12518)
5043						// only reproducible on winXP IE8 native, not IE9 in IE8 mode
5044					}
5045					jQuery.event.triggered = undefined;
5046
5047					if ( tmp ) {
5048						elem[ ontype ] = tmp;
5049					}
5050				}
5051			}
5052		}
5053
5054		return event.result;
5055	},
5056
5057	dispatch: function( event ) {
5058
5059		// Make a writable jQuery.Event from the native event object
5060		event = jQuery.event.fix( event );
5061
5062		var i, ret, handleObj, matched, j,
5063			handlerQueue = [],
5064			args = core_
vendor: 7,307 bytes, lines 5064-5309
5064slice.call( arguments ),
5065			handlers = ( jQuery._data( this, "events" ) || {} )[ event.type ] || [],
5066			special = jQuery.event.special[ event.type ] || {};
5067
5068		// Use the fix-ed jQuery.Event rather than the (read-only) native event
5069		args[0] = event;
5070		event.delegateTarget = this;
5071
5072		// Call the preDispatch hook for the mapped type, and let it bail if desired
5073		if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) {
5074			return;
5075		}
5076
5077		// Determine handlers
5078		handlerQueue = jQuery.event.handlers.call( this, event, handlers );
5079
5080		// Run delegates first; they may want to stop propagation beneath us
5081		i = 0;
5082		while ( (matched = handlerQueue[ i++ ]) && !event.isPropagationStopped() ) {
5083			event.currentTarget = matched.elem;
5084
5085			j = 0;
5086			while ( (handleObj = matched.handlers[ j++ ]) && !event.isImmediatePropagationStopped() ) {
5087
5088				// Triggered event must either 1) have no namespace, or
5089				// 2) have namespace(s) a subset or equal to those in the bound event (both can have no namespace).
5090				if ( !event.namespace_re || event.namespace_re.test( handleObj.namespace ) ) {
5091
5092					event.handleObj = handleObj;
5093					event.data = handleObj.data;
5094
5095					ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler )
5096							.apply( matched.elem, args );
5097
5098					if ( ret !== undefined ) {
5099						if ( (event.result = ret) === false ) {
5100							event.preventDefault();
5101							event.stopPropagation();
5102						}
5103					}
5104				}
5105			}
5106		}
5107
5108		// Call the postDispatch hook for the mapped type
5109		if ( special.postDispatch ) {
5110			special.postDispatch.call( this, event );
5111		}
5112
5113		return event.result;
5114	},
5115
5116	handlers: function( event, handlers ) {
5117		var sel, handleObj, matches, i,
5118			handlerQueue = [],
5119			delegateCount = handlers.delegateCount,
5120			cur = event.target;
5121
5122		// Find delegate handlers
5123		// Black-hole SVG <use> instance trees (#13180)
5124		// Avoid non-left-click bubbling in Firefox (#3861)
5125		if ( delegateCount && cur.nodeType && (!event.button || event.type !== "click") ) {
5126
5127			/* jshint eqeqeq: false */
5128			for ( ; cur != this; cur = cur.parentNode || this ) {
5129				/* jshint eqeqeq: true */
5130
5131				// Don't check non-elements (#13208)
5132				// Don't process clicks on disabled elements (#6911, #8165, #11382, #11764)
5133				if ( cur.nodeType === 1 && (cur.disabled !== true || event.type !== "click") ) {
5134					matches = [];
5135					for ( i = 0; i < delegateCount; i++ ) {
5136						handleObj = handlers[ i ];
5137
5138						// Don't conflict with Object.prototype properties (#13203)
5139						sel = handleObj.selector + " ";
5140
5141						if ( matches[ sel ] === undefined ) {
5142							matches[ sel ] = handleObj.needsContext ?
5143								jQuery( sel, this ).index( cur ) >= 0 :
5144								jQuery.find( sel, this, null, [ cur ] ).length;
5145						}
5146						if ( matches[ sel ] ) {
5147							matches.push( handleObj );
5148						}
5149					}
5150					if ( matches.length ) {
5151						handlerQueue.push({ elem: cur, handlers: matches });
5152					}
5153				}
5154			}
5155		}
5156
5157		// Add the remaining (directly-bound) handlers
5158		if ( delegateCount < handlers.length ) {
5159			handlerQueue.push({ elem: this, handlers: handlers.slice( delegateCount ) });
5160		}
5161
5162		return handlerQueue;
5163	},
5164
5165	fix: function( event ) {
5166		if ( event[ jQuery.expando ] ) {
5167			return event;
5168		}
5169
5170		// Create a writable copy of the event object and normalize some properties
5171		var i, prop, copy,
5172			type = event.type,
5173			originalEvent = event,
5174			fixHook = this.fixHooks[ type ];
5175
5176		if ( !fixHook ) {
5177			this.fixHooks[ type ] = fixHook =
5178				rmouseEvent.test( type ) ? this.mouseHooks :
5179				rkeyEvent.test( type ) ? this.keyHooks :
5180				{};
5181		}
5182		copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props;
5183
5184		event = new jQuery.Event( originalEvent );
5185
5186		i = copy.length;
5187		while ( i-- ) {
5188			prop = copy[ i ];
5189			event[ prop ] = originalEvent[ prop ];
5190		}
5191
5192		// Support: IE<9
5193		// Fix target property (#1925)
5194		if ( !event.target ) {
5195			event.target = originalEvent.srcElement || document;
5196		}
5197
5198		// Support: Chrome 23+, Safari?
5199		// Target should not be a text node (#504, #13143)
5200		if ( event.target.nodeType === 3 ) {
5201			event.target = event.target.parentNode;
5202		}
5203
5204		// Support: IE<9
5205		// For mouse/key events, metaKey==false if it's undefined (#3368, #11328)
5206		event.metaKey = !!event.metaKey;
5207
5208		return fixHook.filter ? fixHook.filter( event, originalEvent ) : event;
5209	},
5210
5211	// Includes some event props shared by KeyEvent and MouseEvent
5212	props: "altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "),
5213
5214	fixHooks: {},
5215
5216	keyHooks: {
5217		props: "char charCode key keyCode".split(" "),
5218		filter: function( event, original ) {
5219
5220			// Add which for key events
5221			if ( event.which == null ) {
5222				event.which = original.charCode != null ? original.charCode : original.keyCode;
5223			}
5224
5225			return event;
5226		}
5227	},
5228
5229	mouseHooks: {
5230		props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "),
5231		filter: function( event, original ) {
5232			var body, eventDoc, doc,
5233				button = original.button,
5234				fromElement = original.fromElement;
5235
5236			// Calculate pageX/Y if missing and clientX/Y available
5237			if ( event.pageX == null && original.clientX != null ) {
5238				eventDoc = event.target.ownerDocument || document;
5239				doc = eventDoc.documentElement;
5240				body = eventDoc.body;
5241
5242				event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 );
5243				event.pageY = original.clientY + ( doc && doc.scrollTop  || body && body.scrollTop  || 0 ) - ( doc && doc.clientTop  || body && body.clientTop  || 0 );
5244			}
5245
5246			// Add relatedTarget, if necessary
5247			if ( !event.relatedTarget && fromElement ) {
5248				event.relatedTarget = fromElement === event.target ? original.toElement : fromElement;
5249			}
5250
5251			// Add which for click: 1 === left; 2 === middle; 3 === right
5252			// Note: button is not normalized, so don't use it
5253			if ( !event.which && button !== undefined ) {
5254				event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) );
5255			}
5256
5257			return event;
5258		}
5259	},
5260
5261	special: {
5262		load: {
5263			// Prevent triggered image.load events from bubbling to window.load
5264			noBubble: true
5265		},
5266		focus: {
5267			// Fire native event if possible so blur/focus sequence is correct
5268			trigger: function() {
5269				if ( this !== safeActiveElement() && this.focus ) {
5270					try {
5271						this.focus();
5272						return false;
5273					} catch ( e ) {
5274						// Support: IE<9
5275						// If we error on focus to hidden element (#1486, #12518),
5276						// let .trigger() run the handlers
5277					}
5278				}
5279			},
5280			delegateType: "focusin"
5281		},
5282		blur: {
5283			trigger: function() {
5284				if ( this === safeActiveElement() && this.blur ) {
5285					this.blur();
5286					return false;
5287				}
5288			},
5289			delegateType: "focusout"
5290		},
5291		click: {
5292			// For checkbox, fire native event so checked state will be right
5293			trigger: function() {
5294				if ( jQuery.nodeName( this, "input" ) && this.type === "checkbox" && this.click ) {
5295					this.click();
5296					return false;
5297				}
5298			},
5299
5300			// For cross-browser consistency, don't fire native .click() on links
5301			_default: function( event ) {
5302				return jQuery.nodeName( event.target, "a" );
5303			}
5304		},
5305
5306		beforeunload: {
5307			postDispatch: function( event ) {
5308
5309				// 
vendor: 4,409 bytes, lines 5309-5476
5309Even when returnValue equals to undefined Firefox will still show alert
5310				if ( event.result !== undefined ) {
5311					event.originalEvent.returnValue = event.result;
5312				}
5313			}
5314		}
5315	},
5316
5317	simulate: function( type, elem, event, bubble ) {
5318		// Piggyback on a donor event to simulate a different one.
5319		// Fake originalEvent to avoid donor's stopPropagation, but if the
5320		// simulated event prevents default then we do the same on the donor.
5321		var e = jQuery.extend(
5322			new jQuery.Event(),
5323			event,
5324			{
5325				type: type,
5326				isSimulated: true,
5327				originalEvent: {}
5328			}
5329		);
5330		if ( bubble ) {
5331			jQuery.event.trigger( e, null, elem );
5332		} else {
5333			jQuery.event.dispatch.call( elem, e );
5334		}
5335		if ( e.isDefaultPrevented() ) {
5336			event.preventDefault();
5337		}
5338	}
5339};
5340
5341jQuery.removeEvent = document.removeEventListener ?
5342	function( elem, type, handle ) {
5343		if ( elem.removeEventListener ) {
5344			elem.removeEventListener( type, handle, false );
5345		}
5346	} :
5347	function( elem, type, handle ) {
5348		var name = "on" + type;
5349
5350		if ( elem.detachEvent ) {
5351
5352			// #8545, #7054, preventing memory leaks for custom events in IE6-8
5353			// detachEvent needed property on element, by name of that event, to properly expose it to GC
5354			if ( typeof elem[ name ] === core_strundefined ) {
5355				elem[ name ] = null;
5356			}
5357
5358			elem.detachEvent( name, handle );
5359		}
5360	};
5361
5362jQuery.Event = function( src, props ) {
5363	// Allow instantiation without the 'new' keyword
5364	if ( !(this instanceof jQuery.Event) ) {
5365		return new jQuery.Event( src, props );
5366	}
5367
5368	// Event object
5369	if ( src && src.type ) {
5370		this.originalEvent = src;
5371		this.type = src.type;
5372
5373		// Events bubbling up the document may have been marked as prevented
5374		// by a handler lower down the tree; reflect the correct value.
5375		this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false ||
5376			src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse;
5377
5378	// Event type
5379	} else {
5380		this.type = src;
5381	}
5382
5383	// Put explicitly provided properties onto the event object
5384	if ( props ) {
5385		jQuery.extend( this, props );
5386	}
5387
5388	// Create a timestamp if incoming event doesn't have one
5389	this.timeStamp = src && src.timeStamp || jQuery.now();
5390
5391	// Mark it as fixed
5392	this[ jQuery.expando ] = true;
5393};
5394
5395// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
5396// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
5397jQuery.Event.prototype = {
5398	isDefaultPrevented: returnFalse,
5399	isPropagationStopped: returnFalse,
5400	isImmediatePropagationStopped: returnFalse,
5401
5402	preventDefault: function() {
5403		var e = this.originalEvent;
5404
5405		this.isDefaultPrevented = returnTrue;
5406		if ( !e ) {
5407			return;
5408		}
5409
5410		// If preventDefault exists, run it on the original event
5411		if ( e.preventDefault ) {
5412			e.preventDefault();
5413
5414		// Support: IE
5415		// Otherwise set the returnValue property of the original event to false
5416		} else {
5417			e.returnValue = false;
5418		}
5419	},
5420	stopPropagation: function() {
5421		var e = this.originalEvent;
5422
5423		this.isPropagationStopped = returnTrue;
5424		if ( !e ) {
5425			return;
5426		}
5427		// If stopPropagation exists, run it on the original event
5428		if ( e.stopPropagation ) {
5429			e.stopPropagation();
5430		}
5431
5432		// Support: IE
5433		// Set the cancelBubble property of the original event to true
5434		e.cancelBubble = true;
5435	},
5436	stopImmediatePropagation: function() {
5437		this.isImmediatePropagationStopped = returnTrue;
5438		this.stopPropagation();
5439	}
5440};
5441
5442// Create mouseenter/leave events using mouseover/out and event-time checks
5443jQuery.each({
5444	mouseenter: "mouseover",
5445	mouseleave: "mouseout"
5446}, function( orig, fix ) {
5447	jQuery.event.special[ orig ] = {
5448		delegateType: fix,
5449		bindType: fix,
5450
5451		handle: function( event ) {
5452			var ret,
5453				target = this,
5454				related = event.relatedTarget,
5455				handleObj = event.handleObj;
5456
5457			// For mousenter/leave call the handler if related is outside the target.
5458			// NB: No relatedTarget if the mouse left/entered the browser window
5459			if ( !related || (related !== target && !jQuery.contains( target, related )) ) {
5460				event.type = handleObj.origType;
5461				ret = handleObj.handler.apply( this, arguments );
5462				event.type = fix;
5463			}
5464			return ret;
5465		}
5466	};
5467});
5468
5469// IE submit delegation
5470if ( !jQuery.support.submitBubbles ) {
5471
5472	jQuery.event.special.submit = {
5473		setup: function() {
5474			// Only need this for delegated form submit events
5475			if ( jQuery.nodeName( this, "form" ) ) {
5476				return false;
vendor: 6,944 bytes, lines 5477-5720
5477			}
5478
5479			// Lazy-add a submit handler when a descendant form may potentially be submitted
5480			jQuery.event.add( this, "click._submit keypress._submit", function( e ) {
5481				// Node name check avoids a VML-related crash in IE (#9807)
5482				var elem = e.target,
5483					form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined;
5484				if ( form && !jQuery._data( form, "submitBubbles" ) ) {
5485					jQuery.event.add( form, "submit._submit", function( event ) {
5486						event._submit_bubble = true;
5487					});
5488					jQuery._data( form, "submitBubbles", true );
5489				}
5490			});
5491			// return undefined since we don't need an event listener
5492		},
5493
5494		postDispatch: function( event ) {
5495			// If form was submitted by the user, bubble the event up the tree
5496			if ( event._submit_bubble ) {
5497				delete event._submit_bubble;
5498				if ( this.parentNode && !event.isTrigger ) {
5499					jQuery.event.simulate( "submit", this.parentNode, event, true );
5500				}
5501			}
5502		},
5503
5504		teardown: function() {
5505			// Only need this for delegated form submit events
5506			if ( jQuery.nodeName( this, "form" ) ) {
5507				return false;
5508			}
5509
5510			// Remove delegated handlers; cleanData eventually reaps submit handlers attached above
5511			jQuery.event.remove( this, "._submit" );
5512		}
5513	};
5514}
5515
5516// IE change delegation and checkbox/radio fix
5517if ( !jQuery.support.changeBubbles ) {
5518
5519	jQuery.event.special.change = {
5520
5521		setup: function() {
5522
5523			if ( rformElems.test( this.nodeName ) ) {
5524				// IE doesn't fire change on a check/radio until blur; trigger it on click
5525				// after a propertychange. Eat the blur-change in special.change.handle.
5526				// This still fires onchange a second time for check/radio after blur.
5527				if ( this.type === "checkbox" || this.type === "radio" ) {
5528					jQuery.event.add( this, "propertychange._change", function( event ) {
5529						if ( event.originalEvent.propertyName === "checked" ) {
5530							this._just_changed = true;
5531						}
5532					});
5533					jQuery.event.add( this, "click._change", function( event ) {
5534						if ( this._just_changed && !event.isTrigger ) {
5535							this._just_changed = false;
5536						}
5537						// Allow triggered, simulated change events (#11500)
5538						jQuery.event.simulate( "change", this, event, true );
5539					});
5540				}
5541				return false;
5542			}
5543			// Delegated event; lazy-add a change handler on descendant inputs
5544			jQuery.event.add( this, "beforeactivate._change", function( e ) {
5545				var elem = e.target;
5546
5547				if ( rformElems.test( elem.nodeName ) && !jQuery._data( elem, "changeBubbles" ) ) {
5548					jQuery.event.add( elem, "change._change", function( event ) {
5549						if ( this.parentNode && !event.isSimulated && !event.isTrigger ) {
5550							jQuery.event.simulate( "change", this.parentNode, event, true );
5551						}
5552					});
5553					jQuery._data( elem, "changeBubbles", true );
5554				}
5555			});
5556		},
5557
5558		handle: function( event ) {
5559			var elem = event.target;
5560
5561			// Swallow native change events from checkbox/radio, we already triggered them above
5562			if ( this !== elem || event.isSimulated || event.isTrigger || (elem.type !== "radio" && elem.type !== "checkbox") ) {
5563				return event.handleObj.handler.apply( this, arguments );
5564			}
5565		},
5566
5567		teardown: function() {
5568			jQuery.event.remove( this, "._change" );
5569
5570			return !rformElems.test( this.nodeName );
5571		}
5572	};
5573}
5574
5575// Create "bubbling" focus and blur events
5576if ( !jQuery.support.focusinBubbles ) {
5577	jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) {
5578
5579		// Attach a single capturing handler while someone wants focusin/focusout
5580		var attaches = 0,
5581			handler = function( event ) {
5582				jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true );
5583			};
5584
5585		jQuery.event.special[ fix ] = {
5586			setup: function() {
5587				if ( attaches++ === 0 ) {
5588					document.addEventListener( orig, handler, true );
5589				}
5590			},
5591			teardown: function() {
5592				if ( --attaches === 0 ) {
5593					document.removeEventListener( orig, handler, true );
5594				}
5595			}
5596		};
5597	});
5598}
5599
5600jQuery.fn.extend({
5601
5602	on: function( types, selector, data, fn, /*INTERNAL*/ one ) {
5603		var type, origFn;
5604
5605		// Types can be a map of types/handlers
5606		if ( typeof types === "object" ) {
5607			// ( types-Object, selector, data )
5608			if ( typeof selector !== "string" ) {
5609				// ( types-Object, data )
5610				data = data || selector;
5611				selector = undefined;
5612			}
5613			for ( type in types ) {
5614				this.on( type, selector, data, types[ type ], one );
5615			}
5616			return this;
5617		}
5618
5619		if ( data == null && fn == null ) {
5620			// ( types, fn )
5621			fn = selector;
5622			data = selector = undefined;
5623		} else if ( fn == null ) {
5624			if ( typeof selector === "string" ) {
5625				// ( types, selector, fn )
5626				fn = data;
5627				data = undefined;
5628			} else {
5629				// ( types, data, fn )
5630				fn = data;
5631				data = selector;
5632				selector = undefined;
5633			}
5634		}
5635		if ( fn === false ) {
5636			fn = returnFalse;
5637		} else if ( !fn ) {
5638			return this;
5639		}
5640
5641		if ( one === 1 ) {
5642			origFn = fn;
5643			fn = function( event ) {
5644				// Can use an empty set, since event contains the info
5645				jQuery().off( event );
5646				return origFn.apply( this, arguments );
5647			};
5648			// Use same guid so caller can remove using origFn
5649			fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ );
5650		}
5651		return this.each( function() {
5652			jQuery.event.add( this, types, fn, data, selector );
5653		});
5654	},
5655	one: function( types, selector, data, fn ) {
5656		return this.on( types, selector, data, fn, 1 );
5657	},
5658	off: function( types, selector, fn ) {
5659		var handleObj, type;
5660		if ( types && types.preventDefault && types.handleObj ) {
5661			// ( event )  dispatched jQuery.Event
5662			handleObj = types.handleObj;
5663			jQuery( types.delegateTarget ).off(
5664				handleObj.namespace ? handleObj.origType + "." + handleObj.namespace : handleObj.origType,
5665				handleObj.selector,
5666				handleObj.handler
5667			);
5668			return this;
5669		}
5670		if ( typeof types === "object" ) {
5671			// ( types-object [, selector] )
5672			for ( type in types ) {
5673				this.off( type, selector, types[ type ] );
5674			}
5675			return this;
5676		}
5677		if ( selector === false || typeof selector === "function" ) {
5678			// ( types [, fn] )
5679			fn = selector;
5680			selector = undefined;
5681		}
5682		if ( fn === false ) {
5683			fn = returnFalse;
5684		}
5685		return this.each(function() {
5686			jQuery.event.remove( this, types, fn, selector );
5687		});
5688	},
5689
5690	trigger: function( type, data ) {
5691		return this.each(function() {
5692			jQuery.event.trigger( type, data, this );
5693		});
5694	},
5695	triggerHandler: function( type, data ) {
5696		var elem = this[0];
5697		if ( elem ) {
5698			return jQuery.event.trigger( type, data, elem, true );
5699		}
5700	}
5701});
5702var isSimple = /^.[^:#\[\.,]*$/,
5703	rparentsprev = /^(?:parents|prev(?:Until|All))/,
5704	rneedsContext = jQuery.expr.match.needsContext,
5705	// methods guaranteed to produce a unique set when starting from a unique set
5706	guaranteedUnique = {
5707		children: true,
5708		contents: true,
5709		next: true,
5710		prev: true
5711	};
5712
5713jQuery.fn.extend({
5714	find: function( selector ) {
5715		var i,
5716			ret = [],
5717			self = this,
5718			len = self.length;
5719
5720		if ( typeof selector !== "string" ) {
vendor: 37,017 bytes, lines 5721-7082
5721			return this.pushStack( jQuery( selector ).filter(function() {
5722				for ( i = 0; i < len; i++ ) {
5723					if ( jQuery.contains( self[ i ], this ) ) {
5724						return true;
5725					}
5726				}
5727			}) );
5728		}
5729
5730		for ( i = 0; i < len; i++ ) {
5731			jQuery.find( selector, self[ i ], ret );
5732		}
5733
5734		// Needed because $( selector, context ) becomes $( context ).find( selector )
5735		ret = this.pushStack( len > 1 ? jQuery.unique( ret ) : ret );
5736		ret.selector = this.selector ? this.selector + " " + selector : selector;
5737		return ret;
5738	},
5739
5740	has: function( target ) {
5741		var i,
5742			targets = jQuery( target, this ),
5743			len = targets.length;
5744
5745		return this.filter(function() {
5746			for ( i = 0; i < len; i++ ) {
5747				if ( jQuery.contains( this, targets[i] ) ) {
5748					return true;
5749				}
5750			}
5751		});
5752	},
5753
5754	not: function( selector ) {
5755		return this.pushStack( winnow(this, selector || [], true) );
5756	},
5757
5758	filter: function( selector ) {
5759		return this.pushStack( winnow(this, selector || [], false) );
5760	},
5761
5762	is: function( selector ) {
5763		return !!winnow(
5764			this,
5765
5766			// If this is a positional/relative selector, check membership in the returned set
5767			// so $("p:first").is("p:last") won't return true for a doc with two "p".
5768			typeof selector === "string" && rneedsContext.test( selector ) ?
5769				jQuery( selector ) :
5770				selector || [],
5771			false
5772		).length;
5773	},
5774
5775	closest: function( selectors, context ) {
5776		var cur,
5777			i = 0,
5778			l = this.length,
5779			ret = [],
5780			pos = rneedsContext.test( selectors ) || typeof selectors !== "string" ?
5781				jQuery( selectors, context || this.context ) :
5782				0;
5783
5784		for ( ; i < l; i++ ) {
5785			for ( cur = this[i]; cur && cur !== context; cur = cur.parentNode ) {
5786				// Always skip document fragments
5787				if ( cur.nodeType < 11 && (pos ?
5788					pos.index(cur) > -1 :
5789
5790					// Don't pass non-elements to Sizzle
5791					cur.nodeType === 1 &&
5792						jQuery.find.matchesSelector(cur, selectors)) ) {
5793
5794					cur = ret.push( cur );
5795					break;
5796				}
5797			}
5798		}
5799
5800		return this.pushStack( ret.length > 1 ? jQuery.unique( ret ) : ret );
5801	},
5802
5803	// Determine the position of an element within
5804	// the matched set of elements
5805	index: function( elem ) {
5806
5807		// No argument, return index in parent
5808		if ( !elem ) {
5809			return ( this[0] && this[0].parentNode ) ? this.first().prevAll().length : -1;
5810		}
5811
5812		// index in selector
5813		if ( typeof elem === "string" ) {
5814			return jQuery.inArray( this[0], jQuery( elem ) );
5815		}
5816
5817		// Locate the position of the desired element
5818		return jQuery.inArray(
5819			// If it receives a jQuery object, the first element is used
5820			elem.jquery ? elem[0] : elem, this );
5821	},
5822
5823	add: function( selector, context ) {
5824		var set = typeof selector === "string" ?
5825				jQuery( selector, context ) :
5826				jQuery.makeArray( selector && selector.nodeType ? [ selector ] : selector ),
5827			all = jQuery.merge( this.get(), set );
5828
5829		return this.pushStack( jQuery.unique(all) );
5830	},
5831
5832	addBack: function( selector ) {
5833		return this.add( selector == null ?
5834			this.prevObject : this.prevObject.filter(selector)
5835		);
5836	}
5837});
5838
5839function sibling( cur, dir ) {
5840	do {
5841		cur = cur[ dir ];
5842	} while ( cur && cur.nodeType !== 1 );
5843
5844	return cur;
5845}
5846
5847jQuery.each({
5848	parent: function( elem ) {
5849		var parent = elem.parentNode;
5850		return parent && parent.nodeType !== 11 ? parent : null;
5851	},
5852	parents: function( elem ) {
5853		return jQuery.dir( elem, "parentNode" );
5854	},
5855	parentsUntil: function( elem, i, until ) {
5856		return jQuery.dir( elem, "parentNode", until );
5857	},
5858	next: function( elem ) {
5859		return sibling( elem, "nextSibling" );
5860	},
5861	prev: function( elem ) {
5862		return sibling( elem, "previousSibling" );
5863	},
5864	nextAll: function( elem ) {
5865		return jQuery.dir( elem, "nextSibling" );
5866	},
5867	prevAll: function( elem ) {
5868		return jQuery.dir( elem, "previousSibling" );
5869	},
5870	nextUntil: function( elem, i, until ) {
5871		return jQuery.dir( elem, "nextSibling", until );
5872	},
5873	prevUntil: function( elem, i, until ) {
5874		return jQuery.dir( elem, "previousSibling", until );
5875	},
5876	siblings: function( elem ) {
5877		return jQuery.sibling( ( elem.parentNode || {} ).firstChild, elem );
5878	},
5879	children: function( elem ) {
5880		return jQuery.sibling( elem.firstChild );
5881	},
5882	contents: function( elem ) {
5883		return jQuery.nodeName( elem, "iframe" ) ?
5884			elem.contentDocument || elem.contentWindow.document :
5885			jQuery.merge( [], elem.childNodes );
5886	}
5887}, function( name, fn ) {
5888	jQuery.fn[ name ] = function( until, selector ) {
5889		var ret = jQuery.map( this, fn, until );
5890
5891		if ( name.slice( -5 ) !== "Until" ) {
5892			selector = until;
5893		}
5894
5895		if ( selector && typeof selector === "string" ) {
5896			ret = jQuery.filter( selector, ret );
5897		}
5898
5899		if ( this.length > 1 ) {
5900			// Remove duplicates
5901			if ( !guaranteedUnique[ name ] ) {
5902				ret = jQuery.unique( ret );
5903			}
5904
5905			// Reverse order for parents* and prev-derivatives
5906			if ( rparentsprev.test( name ) ) {
5907				ret = ret.reverse();
5908			}
5909		}
5910
5911		return this.pushStack( ret );
5912	};
5913});
5914
5915jQuery.extend({
5916	filter: function( expr, elems, not ) {
5917		var elem = elems[ 0 ];
5918
5919		if ( not ) {
5920			expr = ":not(" + expr + ")";
5921		}
5922
5923		return elems.length === 1 && elem.nodeType === 1 ?
5924			jQuery.find.matchesSelector( elem, expr ) ? [ elem ] : [] :
5925			jQuery.find.matches( expr, jQuery.grep( elems, function( elem ) {
5926				return elem.nodeType === 1;
5927			}));
5928	},
5929
5930	dir: function( elem, dir, until ) {
5931		var matched = [],
5932			cur = elem[ dir ];
5933
5934		while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) {
5935			if ( cur.nodeType === 1 ) {
5936				matched.push( cur );
5937			}
5938			cur = cur[dir];
5939		}
5940		return matched;
5941	},
5942
5943	sibling: function( n, elem ) {
5944		var r = [];
5945
5946		for ( ; n; n = n.nextSibling ) {
5947			if ( n.nodeType === 1 && n !== elem ) {
5948				r.push( n );
5949			}
5950		}
5951
5952		return r;
5953	}
5954});
5955
5956// Implement the identical functionality for filter and not
5957function winnow( elements, qualifier, not ) {
5958	if ( jQuery.isFunction( qualifier ) ) {
5959		return jQuery.grep( elements, function( elem, i ) {
5960			/* jshint -W018 */
5961			return !!qualifier.call( elem, i, elem ) !== not;
5962		});
5963
5964	}
5965
5966	if ( qualifier.nodeType ) {
5967		return jQuery.grep( elements, function( elem ) {
5968			return ( elem === qualifier ) !== not;
5969		});
5970
5971	}
5972
5973	if ( typeof qualifier === "string" ) {
5974		if ( isSimple.test( qualifier ) ) {
5975			return jQuery.filter( qualifier, elements, not );
5976		}
5977
5978		qualifier = jQuery.filter( qualifier, elements );
5979	}
5980
5981	return jQuery.grep( elements, function( elem ) {
5982		return ( jQuery.inArray( elem, qualifier ) >= 0 ) !== not;
5983	});
5984}
5985function createSafeFragment( document ) {
5986	var list = nodeNames.split( "|" ),
5987		safeFrag = document.createDocumentFragment();
5988
5989	if ( safeFrag.createElement ) {
5990		while ( list.length ) {
5991			safeFrag.createElement(
5992				list.pop()
5993			);
5994		}
5995	}
5996	return safeFrag;
5997}
5998
5999var nodeNames = "abbr|article|aside|audio|bdi|canvas|data|datalist|details|figcaption|figure|footer|" +
6000		"header|hgroup|mark|meter|nav|output|progress|section|summary|time|video",
6001	rinlinejQuery = / jQuery\d+="(?:null|\d+)"/g,
6002	rnoshimcache = new RegExp("<(?:" + nodeNames + ")[\\s/>]", "i"),
6003	rleadingWhitespace = /^\s+/,
6004	rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/gi,
6005	rtagName = /<([\w:]+)/,
6006	rtbody = /<tbody/i,
6007	rhtml = /<|&#?\w+;/,
6008	rnoInnerhtml = /<(?:script|style|link)/i,
6009	manipulation_rcheckableType = /^(?:checkbox|radio)$/i,
6010	// checked="checked" or checked
6011	rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i,
6012	rscriptType = /^$|\/(?:java|ecma)script/i,
6013	rscriptTypeMasked = /^true\/(.*)/,
6014	rcleanScript = /^\s*<!(?:\[CDATA\[|--)|(?:\]\]|--)>\s*$/g,
6015
6016	// We have to close these tags to support XHTML (#13200)
6017	wrapMap = {
6018		option: [ 1, "<select multiple='multiple'>", "</select>" ],
6019		legend: [ 1, "<fieldset>", "</fieldset>" ],
6020		area: [ 1, "<map>", "</map>" ],
6021		param: [ 1, "<object>", "</object>" ],
6022		thead: [ 1, "<table>", "</table>" ],
6023		tr: [ 2, "<table><tbody>", "</tbody></table>" ],
6024		col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ],
6025		td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ],
6026
6027		// IE6-8 can't serialize link, script, style, or any html5 (NoScope) tags,
6028		// unless wrapped in a div with non-breaking characters in front of it.
6029		_default: jQuery.support.htmlSerialize ? [ 0, "", "" ] : [ 1, "X<div>", "</div>"  ]
6030	},
6031	safeFragment = createSafeFragment( document ),
6032	fragmentDiv = safeFragment.appendChild( document.createElement("div") );
6033
6034wrapMap.optgroup = wrapMap.option;
6035wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead;
6036wrapMap.th = wrapMap.td;
6037
6038jQuery.fn.extend({
6039	text: function( value ) {
6040		return jQuery.access( this, function( value ) {
6041			return value === undefined ?
6042				jQuery.text( this ) :
6043				this.empty().append( ( this[0] && this[0].ownerDocument || document ).createTextNode( value ) );
6044		}, null, value, arguments.length );
6045	},
6046
6047	append: function() {
6048		return this.domManip( arguments, function( elem ) {
6049			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
6050				var target = manipulationTarget( this, elem );
6051				target.appendChild( elem );
6052			}
6053		});
6054	},
6055
6056	prepend: function() {
6057		return this.domManip( arguments, function( elem ) {
6058			if ( this.nodeType === 1 || this.nodeType === 11 || this.nodeType === 9 ) {
6059				var target = manipulationTarget( this, elem );
6060				target.insertBefore( elem, target.firstChild );
6061			}
6062		});
6063	},
6064
6065	before: function() {
6066		return this.domManip( arguments, function( elem ) {
6067			if ( this.parentNode ) {
6068				this.parentNode.insertBefore( elem, this );
6069			}
6070		});
6071	},
6072
6073	after: function() {
6074		return this.domManip( arguments, function( elem ) {
6075			if ( this.parentNode ) {
6076				this.parentNode.insertBefore( elem, this.nextSibling );
6077			}
6078		});
6079	},
6080
6081	// keepData is for internal use only--do not document
6082	remove: function( selector, keepData ) {
6083		var elem,
6084			elems = selector ? jQuery.filter( selector, this ) : this,
6085			i = 0;
6086
6087		for ( ; (elem = elems[i]) != null; i++ ) {
6088
6089			if ( !keepData && elem.nodeType === 1 ) {
6090				jQuery.cleanData( getAll( elem ) );
6091			}
6092
6093			if ( elem.parentNode ) {
6094				if ( keepData && jQuery.contains( elem.ownerDocument, elem ) ) {
6095					setGlobalEval( getAll( elem, "script" ) );
6096				}
6097				elem.parentNode.removeChild( elem );
6098			}
6099		}
6100
6101		return this;
6102	},
6103
6104	empty: function() {
6105		var elem,
6106			i = 0;
6107
6108		for ( ; (elem = this[i]) != null; i++ ) {
6109			// Remove element nodes and prevent memory leaks
6110			if ( elem.nodeType === 1 ) {
6111				jQuery.cleanData( getAll( elem, false ) );
6112			}
6113
6114			// Remove any remaining nodes
6115			while ( elem.firstChild ) {
6116				elem.removeChild( elem.firstChild );
6117			}
6118
6119			// If this is a select, ensure that it displays empty (#12336)
6120			// Support: IE<9
6121			if ( elem.options && jQuery.nodeName( elem, "select" ) ) {
6122				elem.options.length = 0;
6123			}
6124		}
6125
6126		return this;
6127	},
6128
6129	clone: function( dataAndEvents, deepDataAndEvents ) {
6130		dataAndEvents = dataAndEvents == null ? false : dataAndEvents;
6131		deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents;
6132
6133		return this.map( function () {
6134			return jQuery.clone( this, dataAndEvents, deepDataAndEvents );
6135		});
6136	},
6137
6138	html: function( value ) {
6139		return jQuery.access( this, function( value ) {
6140			var elem = this[0] || {},
6141				i = 0,
6142				l = this.length;
6143
6144			if ( value === undefined ) {
6145				return elem.nodeType === 1 ?
6146					elem.innerHTML.replace( rinlinejQuery, "" ) :
6147					undefined;
6148			}
6149
6150			// See if we can take a shortcut and just use innerHTML
6151			if ( typeof value === "string" && !rnoInnerhtml.test( value ) &&
6152				( jQuery.support.htmlSerialize || !rnoshimcache.test( value )  ) &&
6153				( jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value ) ) &&
6154				!wrapMap[ ( rtagName.exec( value ) || ["", ""] )[1].toLowerCase() ] ) {
6155
6156				value = value.replace( rxhtmlTag, "<$1></$2>" );
6157
6158				try {
6159					for (; i < l; i++ ) {
6160						// Remove element nodes and prevent memory leaks
6161						elem = this[i] || {};
6162						if ( elem.nodeType === 1 ) {
6163							jQuery.cleanData( getAll( elem, false ) );
6164							elem.innerHTML = value;
6165						}
6166					}
6167
6168					elem = 0;
6169
6170				// If using innerHTML throws an exception, use the fallback method
6171				} catch(e) {}
6172			}
6173
6174			if ( elem ) {
6175				this.empty().append( value );
6176			}
6177		}, null, value, arguments.length );
6178	},
6179
6180	replaceWith: function() {
6181		var
6182			// Snapshot the DOM in case .domManip sweeps something relevant into its fragment
6183			args = jQuery.map( this, function( elem ) {
6184				return [ elem.nextSibling, elem.parentNode ];
6185			}),
6186			i = 0;
6187
6188		// Make the changes, replacing each context element with the new content
6189		this.domManip( arguments, function( elem ) {
6190			var next = args[ i++ ],
6191				parent = args[ i++ ];
6192
6193			if ( parent ) {
6194				// Don't use the snapshot next if it has moved (#13810)
6195				if ( next && next.parentNode !== parent ) {
6196					next = this.nextSibling;
6197				}
6198				jQuery( this ).remove();
6199				parent.insertBefore( elem, next );
6200			}
6201		// Allow new content to include elements from the context set
6202		}, true );
6203
6204		// Force removal if there was no new content (e.g., from empty arguments)
6205		return i ? this : this.remove();
6206	},
6207
6208	detach: function( selector ) {
6209		return this.remove( selector, true );
6210	},
6211
6212	domManip: function( args, callback, allowIntersection ) {
6213
6214		// Flatten any nested arrays
6215		args = core_concat.apply( [], args );
6216
6217		var first, node, hasScripts,
6218			scripts, doc, fragment,
6219			i = 0,
6220			l = this.length,
6221			set = this,
6222			iNoClone = l - 1,
6223			value = args[0],
6224			isFunction = jQuery.isFunction( value );
6225
6226		// We can't cloneNode fragments that contain checked, in WebKit
6227		if ( isFunction || !( l <= 1 || typeof value !== "string" || jQuery.support.checkClone || !rchecked.test( value ) ) ) {
6228			return this.each(function( index ) {
6229				var self = set.eq( index );
6230				if ( isFunction ) {
6231					args[0] = value.call( this, index, self.html() );
6232				}
6233				self.domManip( args, callback, allowIntersection );
6234			});
6235		}
6236
6237		if ( l ) {
6238			fragment = jQuery.buildFragment( args, this[ 0 ].ownerDocument, false, !allowIntersection && this );
6239			first = fragment.firstChild;
6240
6241			if ( fragment.childNodes.length === 1 ) {
6242				fragment = first;
6243			}
6244
6245			if ( first ) {
6246				scripts = jQuery.map( getAll( fragment, "script" ), disableScript );
6247				hasScripts = scripts.length;
6248
6249				// Use the original fragment for the last item instead of the first because it can end up
6250				// being emptied incorrectly in certain situations (#8070).
6251				for ( ; i < l; i++ ) {
6252					node = fragment;
6253
6254					if ( i !== iNoClone ) {
6255						node = jQuery.clone( node, true, true );
6256
6257						// Keep references to cloned scripts for later restoration
6258						if ( hasScripts ) {
6259							jQuery.merge( scripts, getAll( node, "script" ) );
6260						}
6261					}
6262
6263					callback.call( this[i], node, i );
6264				}
6265
6266				if ( hasScripts ) {
6267					doc = scripts[ scripts.length - 1 ].ownerDocument;
6268
6269					// Reenable scripts
6270					jQuery.map( scripts, restoreScript );
6271
6272					// Evaluate executable scripts on first document insertion
6273					for ( i = 0; i < hasScripts; i++ ) {
6274						node = scripts[ i ];
6275						if ( rscriptType.test( node.type || "" ) &&
6276							!jQuery._data( node, "globalEval" ) && jQuery.contains( doc, node ) ) {
6277
6278							if ( node.src ) {
6279								// Hope ajax is available...
6280								jQuery._evalUrl( node.src );
6281							} else {
6282								jQuery.globalEval( ( node.text || node.textContent || node.innerHTML || "" ).replace( rcleanScript, "" ) );
6283							}
6284						}
6285					}
6286				}
6287
6288				// Fix #11809: Avoid leaking memory
6289				fragment = first = null;
6290			}
6291		}
6292
6293		return this;
6294	}
6295});
6296
6297// Support: IE<8
6298// Manipulating tables requires a tbody
6299function manipulationTarget( elem, content ) {
6300	return jQuery.nodeName( elem, "table" ) &&
6301		jQuery.nodeName( content.nodeType === 1 ? content : content.firstChild, "tr" ) ?
6302
6303		elem.getElementsByTagName("tbody")[0] ||
6304			elem.appendChild( elem.ownerDocument.createElement("tbody") ) :
6305		elem;
6306}
6307
6308// Replace/restore the type attribute of script elements for safe DOM manipulation
6309function disableScript( elem ) {
6310	elem.type = (jQuery.find.attr( elem, "type" ) !== null) + "/" + elem.type;
6311	return elem;
6312}
6313function restoreScript( elem ) {
6314	var match = rscriptTypeMasked.exec( elem.type );
6315	if ( match ) {
6316		elem.type = match[1];
6317	} else {
6318		elem.removeAttribute("type");
6319	}
6320	return elem;
6321}
6322
6323// Mark scripts as having already been evaluated
6324function setGlobalEval( elems, refElements ) {
6325	var elem,
6326		i = 0;
6327	for ( ; (elem = elems[i]) != null; i++ ) {
6328		jQuery._data( elem, "globalEval", !refElements || jQuery._data( refElements[i], "globalEval" ) );
6329	}
6330}
6331
6332function cloneCopyEvent( src, dest ) {
6333
6334	if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) {
6335		return;
6336	}
6337
6338	var type, i, l,
6339		oldData = jQuery._data( src ),
6340		curData = jQuery._data( dest, oldData ),
6341		events = oldData.events;
6342
6343	if ( events ) {
6344		delete curData.handle;
6345		curData.events = {};
6346
6347		for ( type in events ) {
6348			for ( i = 0, l = events[ type ].length; i < l; i++ ) {
6349				jQuery.event.add( dest, type, events[ type ][ i ] );
6350			}
6351		}
6352	}
6353
6354	// make the cloned public data object a copy from the original
6355	if ( curData.data ) {
6356		curData.data = jQuery.extend( {}, curData.data );
6357	}
6358}
6359
6360function fixCloneNodeIssues( src, dest ) {
6361	var nodeName, e, data;
6362
6363	// We do not need to do anything for non-Elements
6364	if ( dest.nodeType !== 1 ) {
6365		return;
6366	}
6367
6368	nodeName = dest.nodeName.toLowerCase();
6369
6370	// IE6-8 copies events bound via attachEvent when using cloneNode.
6371	if ( !jQuery.support.noCloneEvent && dest[ jQuery.expando ] ) {
6372		data = jQuery._data( dest );
6373
6374		for ( e in data.events ) {
6375			jQuery.removeEvent( dest, e, data.handle );
6376		}
6377
6378		// Event data gets referenced instead of copied if the expando gets copied too
6379		dest.removeAttribute( jQuery.expando );
6380	}
6381
6382	// IE blanks contents when cloning scripts, and tries to evaluate newly-set text
6383	if ( nodeName === "script" && dest.text !== src.text ) {
6384		disableScript( dest ).text = src.text;
6385		restoreScript( dest );
6386
6387	// IE6-10 improperly clones children of object elements using classid.
6388	// IE10 throws NoModificationAllowedError if parent is null, #12132.
6389	} else if ( nodeName === "object" ) {
6390		if ( dest.parentNode ) {
6391			dest.outerHTML = src.outerHTML;
6392		}
6393
6394		// This path appears unavoidable for IE9. When cloning an object
6395		// element in IE9, the outerHTML strategy above is not sufficient.
6396		// If the src has innerHTML and the destination does not,
6397		// copy the src.innerHTML into the dest.innerHTML. #10324
6398		if ( jQuery.support.html5Clone && ( src.innerHTML && !jQuery.trim(dest.innerHTML) ) ) {
6399			dest.innerHTML = src.innerHTML;
6400		}
6401
6402	} else if ( nodeName === "input" && manipulation_rcheckableType.test( src.type ) ) {
6403		// IE6-8 fails to persist the checked state of a cloned checkbox
6404		// or radio button. Worse, IE6-7 fail to give the cloned element
6405		// a checked appearance if the defaultChecked value isn't also set
6406
6407		dest.defaultChecked = dest.checked = src.checked;
6408
6409		// IE6-7 get confused and end up setting the value of a cloned
6410		// checkbox/radio button to an empty string instead of "on"
6411		if ( dest.value !== src.value ) {
6412			dest.value = src.value;
6413		}
6414
6415	// IE6-8 fails to return the selected option to the default selected
6416	// state when cloning options
6417	} else if ( nodeName === "option" ) {
6418		dest.defaultSelected = dest.selected = src.defaultSelected;
6419
6420	// IE6-8 fails to set the defaultValue to the correct value when
6421	// cloning other types of input fields
6422	} else if ( nodeName === "input" || nodeName === "textarea" ) {
6423		dest.defaultValue = src.defaultValue;
6424	}
6425}
6426
6427jQuery.each({
6428	appendTo: "append",
6429	prependTo: "prepend",
6430	insertBefore: "before",
6431	insertAfter: "after",
6432	replaceAll: "replaceWith"
6433}, function( name, original ) {
6434	jQuery.fn[ name ] = function( selector ) {
6435		var elems,
6436			i = 0,
6437			ret = [],
6438			insert = jQuery( selector ),
6439			last = insert.length - 1;
6440
6441		for ( ; i <= last; i++ ) {
6442			elems = i === last ? this : this.clone(true);
6443			jQuery( insert[i] )[ original ]( elems );
6444
6445			// Modern browsers can apply jQuery collections as arrays, but oldIE needs a .get()
6446			core_push.apply( ret, elems.get() );
6447		}
6448
6449		return this.pushStack( ret );
6450	};
6451});
6452
6453function getAll( context, tag ) {
6454	var elems, elem,
6455		i = 0,
6456		found = typeof context.getElementsByTagName !== core_strundefined ? context.getElementsByTagName( tag || "*" ) :
6457			typeof context.querySelectorAll !== core_strundefined ? context.querySelectorAll( tag || "*" ) :
6458			undefined;
6459
6460	if ( !found ) {
6461		for ( found = [], elems = context.childNodes || context; (elem = elems[i]) != null; i++ ) {
6462			if ( !tag || jQuery.nodeName( elem, tag ) ) {
6463				found.push( elem );
6464			} else {
6465				jQuery.merge( found, getAll( elem, tag ) );
6466			}
6467		}
6468	}
6469
6470	return tag === undefined || tag && jQuery.nodeName( context, tag ) ?
6471		jQuery.merge( [ context ], found ) :
6472		found;
6473}
6474
6475// Used in buildFragment, fixes the defaultChecked property
6476function fixDefaultChecked( elem ) {
6477	if ( manipulation_rcheckableType.test( elem.type ) ) {
6478		elem.defaultChecked = elem.checked;
6479	}
6480}
6481
6482jQuery.extend({
6483	clone: function( elem, dataAndEvents, deepDataAndEvents ) {
6484		var destElements, node, clone, i, srcElements,
6485			inPage = jQuery.contains( elem.ownerDocument, elem );
6486
6487		if ( jQuery.support.html5Clone || jQuery.isXMLDoc(elem) || !rnoshimcache.test( "<" + elem.nodeName + ">" ) ) {
6488			clone = elem.cloneNode( true );
6489
6490		// IE<=8 does not properly clone detached, unknown element nodes
6491		} else {
6492			fragmentDiv.innerHTML = elem.outerHTML;
6493			fragmentDiv.removeChild( clone = fragmentDiv.firstChild );
6494		}
6495
6496		if ( (!jQuery.support.noCloneEvent || !jQuery.support.noCloneChecked) &&
6497				(elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) {
6498
6499			// We eschew Sizzle here for performance reasons: http://jsperf.com/getall-vs-sizzle/2
6500			destElements = getAll( clone );
6501			srcElements = getAll( elem );
6502
6503			// Fix all IE cloning issues
6504			for ( i = 0; (node = srcElements[i]) != null; ++i ) {
6505				// Ensure that the destination node is not null; Fixes #9587
6506				if ( destElements[i] ) {
6507					fixCloneNodeIssues( node, destElements[i] );
6508				}
6509			}
6510		}
6511
6512		// Copy the events from the original to the clone
6513		if ( dataAndEvents ) {
6514			if ( deepDataAndEvents ) {
6515				srcElements = srcElements || getAll( elem );
6516				destElements = destElements || getAll( clone );
6517
6518				for ( i = 0; (node = srcElements[i]) != null; i++ ) {
6519					cloneCopyEvent( node, destElements[i] );
6520				}
6521			} else {
6522				cloneCopyEvent( elem, clone );
6523			}
6524		}
6525
6526		// Preserve script evaluation history
6527		destElements = getAll( clone, "script" );
6528		if ( destElements.length > 0 ) {
6529			setGlobalEval( destElements, !inPage && getAll( elem, "script" ) );
6530		}
6531
6532		destElements = srcElements = node = null;
6533
6534		// Return the cloned set
6535		return clone;
6536	},
6537
6538	buildFragment: function( elems, context, scripts, selection ) {
6539		var j, elem, contains,
6540			tmp, tag, tbody, wrap,
6541			l = elems.length,
6542
6543			// Ensure a safe fragment
6544			safe = createSafeFragment( context ),
6545
6546			nodes = [],
6547			i = 0;
6548
6549		for ( ; i < l; i++ ) {
6550			elem = elems[ i ];
6551
6552			if ( elem || elem === 0 ) {
6553
6554				// Add nodes directly
6555				if ( jQuery.type( elem ) === "object" ) {
6556					jQuery.merge( nodes, elem.nodeType ? [ elem ] : elem );
6557
6558				// Convert non-html into a text node
6559				} else if ( !rhtml.test( elem ) ) {
6560					nodes.push( context.createTextNode( elem ) );
6561
6562				// Convert html into DOM nodes
6563				} else {
6564					tmp = tmp || safe.appendChild( context.createElement("div") );
6565
6566					// Deserialize a standard representation
6567					tag = ( rtagName.exec( elem ) || ["", ""] )[1].toLowerCase();
6568					wrap = wrapMap[ tag ] || wrapMap._default;
6569
6570					tmp.innerHTML = wrap[1] + elem.replace( rxhtmlTag, "<$1></$2>" ) + wrap[2];
6571
6572					// Descend through wrappers to the right content
6573					j = wrap[0];
6574					while ( j-- ) {
6575						tmp = tmp.lastChild;
6576					}
6577
6578					// Manually add leading whitespace removed by IE
6579					if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) {
6580						nodes.push( context.createTextNode( rleadingWhitespace.exec( elem )[0] ) );
6581					}
6582
6583					// Remove IE's autoinserted <tbody> from table fragments
6584					if ( !jQuery.support.tbody ) {
6585
6586						// String was a <table>, *may* have spurious <tbody>
6587						elem = tag === "table" && !rtbody.test( elem ) ?
6588							tmp.firstChild :
6589
6590							// String was a bare <thead> or <tfoot>
6591							wrap[1] === "<table>" && !rtbody.test( elem ) ?
6592								tmp :
6593								0;
6594
6595						j = elem && elem.childNodes.length;
6596						while ( j-- ) {
6597							if ( jQuery.nodeName( (tbody = elem.childNodes[j]), "tbody" ) && !tbody.childNodes.length ) {
6598								elem.removeChild( tbody );
6599							}
6600						}
6601					}
6602
6603					jQuery.merge( nodes, tmp.childNodes );
6604
6605					// Fix #12392 for WebKit and IE > 9
6606					tmp.textContent = "";
6607
6608					// Fix #12392 for oldIE
6609					while ( tmp.firstChild ) {
6610						tmp.removeChild( tmp.firstChild );
6611					}
6612
6613					// Remember the top-level container for proper cleanup
6614					tmp = safe.lastChild;
6615				}
6616			}
6617		}
6618
6619		// Fix #11356: Clear elements from fragment
6620		if ( tmp ) {
6621			safe.removeChild( tmp );
6622		}
6623
6624		// Reset defaultChecked for any radios and checkboxes
6625		// about to be appended to the DOM in IE 6/7 (#8060)
6626		if ( !jQuery.support.appendChecked ) {
6627			jQuery.grep( getAll( nodes, "input" ), fixDefaultChecked );
6628		}
6629
6630		i = 0;
6631		while ( (elem = nodes[ i++ ]) ) {
6632
6633			// #4087 - If origin and destination elements are the same, and this is
6634			// that element, do not do anything
6635			if ( selection && jQuery.inArray( elem, selection ) !== -1 ) {
6636				continue;
6637			}
6638
6639			contains = jQuery.contains( elem.ownerDocument, elem );
6640
6641			// Append to fragment
6642			tmp = getAll( safe.appendChild( elem ), "script" );
6643
6644			// Preserve script evaluation history
6645			if ( contains ) {
6646				setGlobalEval( tmp );
6647			}
6648
6649			// Capture executables
6650			if ( scripts ) {
6651				j = 0;
6652				while ( (elem = tmp[ j++ ]) ) {
6653					if ( rscriptType.test( elem.type || "" ) ) {
6654						scripts.push( elem );
6655					}
6656				}
6657			}
6658		}
6659
6660		tmp = null;
6661
6662		return safe;
6663	},
6664
6665	cleanData: function( elems, /* internal */ acceptData ) {
6666		var elem, type, id, data,
6667			i = 0,
6668			internalKey = jQuery.expando,
6669			cache = jQuery.cache,
6670			deleteExpando = jQuery.support.deleteExpando,
6671			special = jQuery.event.special;
6672
6673		for ( ; (elem = elems[i]) != null; i++ ) {
6674
6675			if ( acceptData || jQuery.acceptData( elem ) ) {
6676
6677				id = elem[ internalKey ];
6678				data = id && cache[ id ];
6679
6680				if ( data ) {
6681					if ( data.events ) {
6682						for ( type in data.events ) {
6683							if ( special[ type ] ) {
6684								jQuery.event.remove( elem, type );
6685
6686							// This is a shortcut to avoid jQuery.event.remove's overhead
6687							} else {
6688								jQuery.removeEvent( elem, type, data.handle );
6689							}
6690						}
6691					}
6692
6693					// Remove cache only if it was not already removed by jQuery.event.remove
6694					if ( cache[ id ] ) {
6695
6696						delete cache[ id ];
6697
6698						// IE does not allow us to delete expando properties from nodes,
6699						// nor does it have a removeAttribute function on Document nodes;
6700						// we must handle all of these cases
6701						if ( deleteExpando ) {
6702							delete elem[ internalKey ];
6703
6704						} else if ( typeof elem.removeAttribute !== core_strundefined ) {
6705							elem.removeAttribute( internalKey );
6706
6707						} else {
6708							elem[ internalKey ] = null;
6709						}
6710
6711						core_deletedIds.push( id );
6712					}
6713				}
6714			}
6715		}
6716	},
6717
6718	_evalUrl: function( url ) {
6719		return jQuery.ajax({
6720			url: url,
6721			type: "GET",
6722			dataType: "script",
6723			async: false,
6724			global: false,
6725			"throws": true
6726		});
6727	}
6728});
6729jQuery.fn.extend({
6730	wrapAll: function( html ) {
6731		if ( jQuery.isFunction( html ) ) {
6732			return this.each(function(i) {
6733				jQuery(this).wrapAll( html.call(this, i) );
6734			});
6735		}
6736
6737		if ( this[0] ) {
6738			// The elements to wrap the target around
6739			var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true);
6740
6741			if ( this[0].parentNode ) {
6742				wrap.insertBefore( this[0] );
6743			}
6744
6745			wrap.map(function() {
6746				var elem = this;
6747
6748				while ( elem.firstChild && elem.firstChild.nodeType === 1 ) {
6749					elem = elem.firstChild;
6750				}
6751
6752				return elem;
6753			}).append( this );
6754		}
6755
6756		return this;
6757	},
6758
6759	wrapInner: function( html ) {
6760		if ( jQuery.isFunction( html ) ) {
6761			return this.each(function(i) {
6762				jQuery(this).wrapInner( html.call(this, i) );
6763			});
6764		}
6765
6766		return this.each(function() {
6767			var self = jQuery( this ),
6768				contents = self.contents();
6769
6770			if ( contents.length ) {
6771				contents.wrapAll( html );
6772
6773			} else {
6774				self.append( html );
6775			}
6776		});
6777	},
6778
6779	wrap: function( html ) {
6780		var isFunction = jQuery.isFunction( html );
6781
6782		return this.each(function(i) {
6783			jQuery( this ).wrapAll( isFunction ? html.call(this, i) : html );
6784		});
6785	},
6786
6787	unwrap: function() {
6788		return this.parent().each(function() {
6789			if ( !jQuery.nodeName( this, "body" ) ) {
6790				jQuery( this ).replaceWith( this.childNodes );
6791			}
6792		}).end();
6793	}
6794});
6795var iframe, getStyles, curCSS,
6796	ralpha = /alpha\([^)]*\)/i,
6797	ropacity = /opacity\s*=\s*([^)]*)/,
6798	rposition = /^(top|right|bottom|left)$/,
6799	// swappable if display is none or starts with table except "table", "table-cell", or "table-caption"
6800	// see here for display values: https://developer.mozilla.org/en-US/docs/CSS/display
6801	rdisplayswap = /^(none|table(?!-c[ea]).+)/,
6802	rmargin = /^margin/,
6803	rnumsplit = new RegExp( "^(" + core_pnum + ")(.*)$", "i" ),
6804	rnumnonpx = new RegExp( "^(" + core_pnum + ")(?!px)[a-z%]+$", "i" ),
6805	rrelNum = new RegExp( "^([+-])=(" + core_pnum + ")", "i" ),
6806	elemdisplay = { BODY: "block" },
6807
6808	cssShow = { position: "absolute", visibility: "hidden", display: "block" },
6809	cssNormalTransform = {
6810		letterSpacing: 0,
6811		fontWeight: 400
6812	},
6813
6814	cssExpand = [ "Top", "Right", "Bottom", "Left" ],
6815	cssPrefixes = [ "Webkit", "O", "Moz", "ms" ];
6816
6817// return a css property mapped to a potentially vendor prefixed property
6818function vendorPropName( style, name ) {
6819
6820	// shortcut for names that are not vendor prefixed
6821	if ( name in style ) {
6822		return name;
6823	}
6824
6825	// check for vendor prefixed names
6826	var capName = name.charAt(0).toUpperCase() + name.slice(1),
6827		origName = name,
6828		i = cssPrefixes.length;
6829
6830	while ( i-- ) {
6831		name = cssPrefixes[ i ] + capName;
6832		if ( name in style ) {
6833			return name;
6834		}
6835	}
6836
6837	return origName;
6838}
6839
6840function isHidden( elem, el ) {
6841	// isHidden might be called from jQuery#filter function;
6842	// in that case, element will be second argument
6843	elem = el || elem;
6844	return jQuery.css( elem, "display" ) === "none" || !jQuery.contains( elem.ownerDocument, elem );
6845}
6846
6847function showHide( elements, show ) {
6848	var display, elem, hidden,
6849		values = [],
6850		index = 0,
6851		length = elements.length;
6852
6853	for ( ; index < length; index++ ) {
6854		elem = elements[ index ];
6855		if ( !elem.style ) {
6856			continue;
6857		}
6858
6859		values[ index ] = jQuery._data( elem, "olddisplay" );
6860		display = elem.style.display;
6861		if ( show ) {
6862			// Reset the inline display of this element to learn if it is
6863			// being hidden by cascaded rules or not
6864			if ( !values[ index ] && display === "none" ) {
6865				elem.style.display = "";
6866			}
6867
6868			// Set elements which have been overridden with display: none
6869			// in a stylesheet to whatever the default browser style is
6870			// for such an element
6871			if ( elem.style.display === "" && isHidden( elem ) ) {
6872				values[ index ] = jQuery._data( elem, "olddisplay", css_defaultDisplay(elem.nodeName) );
6873			}
6874		} else {
6875
6876			if ( !values[ index ] ) {
6877				hidden = isHidden( elem );
6878
6879				if ( display && display !== "none" || !hidden ) {
6880					jQuery._data( elem, "olddisplay", hidden ? display : jQuery.css( elem, "display" ) );
6881				}
6882			}
6883		}
6884	}
6885
6886	// Set the display of most of the elements in a second loop
6887	// to avoid the constant reflow
6888	for ( index = 0; index < length; index++ ) {
6889		elem = elements[ index ];
6890		if ( !elem.style ) {
6891			continue;
6892		}
6893		if ( !show || elem.style.display === "none" || elem.style.display === "" ) {
6894			elem.style.display = show ? values[ index ] || "" : "none";
6895		}
6896	}
6897
6898	return elements;
6899}
6900
6901jQuery.fn.extend({
6902	css: function( name, value ) {
6903		return jQuery.access( this, function( elem, name, value ) {
6904			var len, styles,
6905				map = {},
6906				i = 0;
6907
6908			if ( jQuery.isArray( name ) ) {
6909				styles = getStyles( elem );
6910				len = name.length;
6911
6912				for ( ; i < len; i++ ) {
6913					map[ name[ i ] ] = jQuery.css( elem, name[ i ], false, styles );
6914				}
6915
6916				return map;
6917			}
6918
6919			return value !== undefined ?
6920				jQuery.style( elem, name, value ) :
6921				jQuery.css( elem, name );
6922		}, name, value, arguments.length > 1 );
6923	},
6924	show: function() {
6925		return showHide( this, true );
6926	},
6927	hide: function() {
6928		return showHide( this );
6929	},
6930	toggle: function( state ) {
6931		if ( typeof state === "boolean" ) {
6932			return state ? this.show() : this.hide();
6933		}
6934
6935		return this.each(function() {
6936			if ( isHidden( this ) ) {
6937				jQuery( this ).show();
6938			} else {
6939				jQuery( this ).hide();
6940			}
6941		});
6942	}
6943});
6944
6945jQuery.extend({
6946	// Add in style property hooks for overriding the default
6947	// behavior of getting and setting a style property
6948	cssHooks: {
6949		opacity: {
6950			get: function( elem, computed ) {
6951				if ( computed ) {
6952					// We should always get a number back from opacity
6953					var ret = curCSS( elem, "opacity" );
6954					return ret === "" ? "1" : ret;
6955				}
6956			}
6957		}
6958	},
6959
6960	// Don't automatically add "px" to these possibly-unitless properties
6961	cssNumber: {
6962		"columnCount": true,
6963		"fillOpacity": true,
6964		"fontWeight": true,
6965		"lineHeight": true,
6966		"opacity": true,
6967		"order": true,
6968		"orphans": true,
6969		"widows": true,
6970		"zIndex": true,
6971		"zoom": true
6972	},
6973
6974	// Add in properties whose names you wish to fix before
6975	// setting or getting the value
6976	cssProps: {
6977		// normalize float css property
6978		"float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat"
6979	},
6980
6981	// Get and set the style property on a DOM Node
6982	style: function( elem, name, value, extra ) {
6983		// Don't set styles on text and comment nodes
6984		if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) {
6985			return;
6986		}
6987
6988		// Make sure that we're working with the right name
6989		var ret, type, hooks,
6990			origName = jQuery.camelCase( name ),
6991			style = elem.style;
6992
6993		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( style, origName ) );
6994
6995		// gets hook for the prefixed version
6996		// followed by the unprefixed version
6997		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
6998
6999		// Check if we're setting a value
7000		if ( value !== undefined ) {
7001			type = typeof value;
7002
7003			// convert relative number strings (+= or -=) to relative numbers. #7345
7004			if ( type === "string" && (ret = rrelNum.exec( value )) ) {
7005				value = ( ret[1] + 1 ) * ret[2] + parseFloat( jQuery.css( elem, name ) );
7006				// Fixes bug #9237
7007				type = "number";
7008			}
7009
7010			// Make sure that NaN and null values aren't set. See: #7116
7011			if ( value == null || type === "number" && isNaN( value ) ) {
7012				return;
7013			}
7014
7015			// If a number was passed in, add 'px' to the (except for certain CSS properties)
7016			if ( type === "number" && !jQuery.cssNumber[ origName ] ) {
7017				value += "px";
7018			}
7019
7020			// Fixes #8908, it can be done more correctly by specifing setters in cssHooks,
7021			// but it would mean to define eight (for every problematic property) identical functions
7022			if ( !jQuery.support.clearCloneStyle && value === "" && name.indexOf("background") === 0 ) {
7023				style[ name ] = "inherit";
7024			}
7025
7026			// If a hook was provided, use that value, otherwise just set the specified value
7027			if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value, extra )) !== undefined ) {
7028
7029				// Wrapped to prevent IE from throwing errors when 'invalid' values are provided
7030				// Fixes bug #5509
7031				try {
7032					style[ name ] = value;
7033				} catch(e) {}
7034			}
7035
7036		} else {
7037			// If a hook was provided get the non-computed value from there
7038			if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) {
7039				return ret;
7040			}
7041
7042			// Otherwise just get the value from the style object
7043			return style[ name ];
7044		}
7045	},
7046
7047	css: function( elem, name, extra, styles ) {
7048		var num, val, hooks,
7049			origName = jQuery.camelCase( name );
7050
7051		// Make sure that we're working with the right name
7052		name = jQuery.cssProps[ origName ] || ( jQuery.cssProps[ origName ] = vendorPropName( elem.style, origName ) );
7053
7054		// gets hook for the prefixed version
7055		// followed by the unprefixed version
7056		hooks = jQuery.cssHooks[ name ] || jQuery.cssHooks[ origName ];
7057
7058		// If a hook was provided get the computed value from there
7059		if ( hooks && "get" in hooks ) {
7060			val = hooks.get( elem, true, extra );
7061		}
7062
7063		// Otherwise, if a way to get the computed value exists, use that
7064		if ( val === undefined ) {
7065			val = curCSS( elem, name, styles );
7066		}
7067
7068		//convert "normal" to computed value
7069		if ( val === "normal" && name in cssNormalTransform ) {
7070			val = cssNormalTransform[ name ];
7071		}
7072
7073		// Return, converting to number if forced or a qualifier was provided and val looks numeric
7074		if ( extra === "" || extra ) {
7075			num = parseFloat( val );
7076			return extra === true || jQuery.isNumeric( num ) ? num || 0 : val;
7077		}
7078		return val;
7079	}
7080});
7081
7082// NOTE: we've included the "window" in window.getComputedStyle
7083// because jsdom on node.js will break without it.
7084if ( window.getComputedStyle ) {
7085	getStyles = function( elem ) {
7086		return window.getComputedStyle( elem, null );
7087	};
7088
7089	curCSS = function( elem, name, _computed ) {
7090		var width, minWidth, maxWidth,
7091			computed = _computed || getStyles( elem ),
7092
7093			// getPropertyValue is only needed for .css('filter') in IE9, see #12537
7094			ret = computed ? computed.getPropertyValue( name ) || computed[ name ] : undefined,
7095			style = elem.style;
7096
7097		if ( computed ) {
7098
7099			if ( ret === "" && !jQuery.contains( elem.ownerDocument, elem ) ) {
7100				ret = jQuery.style( elem, name );
7101			}
7102
7103			// A tribute to the "awesome hack by Dean Edwards"
7104			// Chrome < 17 and Safari 5.0 uses "computed value" instead of "used value" for margin-right
7105			// Safari 5.1.7 (at least) returns percentage for a larger set of values, but width seems to be reliably pixels
7106			// this is against the CSSOM draft spec: http://dev.w3.org/csswg/cssom/#resolved-values
7107			if ( rnumnonpx.test( ret ) && rmargin.test( name ) ) {
7108
7109				// Remember the original values
7110				width = style.width;
7111				minWidth = style.minWidth;
7112				maxWidth = style.maxWidth;
7113
7114				// Put in the new values to get a computed value out
7115				style.minWidth = style.maxWidth = style.width = ret;
7116				ret = computed.width;
7117
7118				// Revert the changed values
7119				style.width = width;
7120				style.minWidth = minWidth;
7121				style.maxWidth = maxWidth;
7122			}
7123		}
7124
7125		return ret;
7126	};
7127} else if ( document.documentElement.currentStyle ) {
7128	getStyles = function( elem ) {
7129		return elem.currentStyle;
7130	};
7131
7132	curCSS = function( elem, name, _computed ) {
7133		var left, rs, rsLeft,
7134			computed = _computed || getStyles( elem ),
7135			ret = computed ? computed[ name ] : undefined,
7136			style = elem.style;
7137
7138		// Avoid setting ret to empty string here
7139		// so we don't default to auto
7140		if ( ret == null && style && style[ name ] ) {
7141			ret = style[ name ];
7142		}
7143
7144		// From the awesome hack by Dean Edwards
7145		// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
7146
7147		// If we're not dealing with a regular pixel number
7148		// but a number that has a weird ending, we need to convert it to pixels
7149		// but not position css attributes, as those are proportional to the parent element instead
7150		// and we can't measure the parent instead because it might trigger a "stacking dolls" problem
7151		if ( rnumnonpx.test( ret ) && !rposition.test( name ) ) {
7152
7153			// Remember the original values
7154			left = style.left;
7155			rs = elem.runtimeStyle;
7156			rsLeft = rs && rs.left;
7157
7158			// Put in the new values to get a computed value out
7159			if ( rsLeft ) {
7160				rs.left = elem.currentStyle.left;
7161			}
7162			style.left = name === "fontSize" ? "1em" : ret;
7163			ret = style.pixelLeft + "px";
7164
7165			// Revert the changed values
7166			style.left = left;
7167			if ( rsLeft ) {
7168				rs.left = rsLeft;
7169			}
7170		}
7171
7172		return ret === "" ? "auto" : ret;
7173	};
7174}
7175
7176function setPositiveNumber( elem, value, subtract ) {
7177	var matches = rnumsplit.exec( value );
7178	return matches ?
7179		// Guard against undefined "subtract", e.g., when used as in cssHooks
7180		Math.max( 0, matches[ 1 ] - ( subtract || 0 ) ) + ( matches[ 2 ] || "px" ) :
7181		value;
7182}
7183
7184function augmentWidthOrHeight( elem, name, extra, isBorderBox, styles ) {
7185	var i = extra === ( isBorderBox ? "border" : "content" ) ?
7186		// If we already have the right measurement, avoid augmentation
7187		4 :
7188		// Otherwise initialize for horizontal or vertical properties
7189		name === "width" ? 1 : 0,
7190
7191		val = 0;
7192
7193	for ( ; i < 4; i += 2 ) {
7194		// both box models exclude margin, so add it if we want it
7195		if ( extra === "margin" ) {
7196			val += jQuery.css( elem, extra + cssExpand[ i ], true, styles );
7197		}
7198
7199		if ( isBorderBox ) {
7200			// border-box includes padding, so remove it if we want content
7201			if ( extra === "content" ) {
7202				val -= jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
7203			}
7204
7205			// at this point, extra isn't border nor margin, so remove border
7206			if ( extra !== "margin" ) {
7207				val -= jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
7208			}
7209		} else {
7210			// at this point, extra isn't content, so add padding
7211			val += jQuery.css( elem, "padding" + cssExpand[ i ], true, styles );
7212
7213			// at this point, extra isn't content nor padding, so add border
7214			if ( extra !== "padding" ) {
7215				val += jQuery.css( elem, "border" + cssExpand[ i ] + "Width", true, styles );
7216			}
7217		}
7218	}
7219
7220	return val;
7221}
7222
7223function getWidthOrHeight( elem, name, extra ) {
7224
7225	// Start with offset property, which is equivalent to the border-box value
7226	var valueIsBorderBox = true,
7227		val = name === "width" ? elem.offsetWidth : elem.offsetHeight,
7228		styles = getStyles( elem ),
7229		isBorderBox = jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box";
7230
7231	// some non-html elements return undefined for offsetWidth, so check for null/undefined
7232	// svg - https://bugzilla.mozilla.org/show_bug.cgi?id=649285
7233	// MathML - https://bugzilla.mozilla.org/show_bug.cgi?id=491668
7234	if ( val <= 0 || val == null ) {
7235		// Fall back to computed then uncomputed css if necessary
7236		val = curCSS( elem, name, styles );
7237		if ( val < 0 || val == null ) {
7238			val = elem.style[ name ];
7239		}
7240
7241		// Computed unit is not pixels. Stop here and return.
7242		if ( rnumnonpx.test(val) ) {
7243			return val;
7244		}
7245
7246		// we need the check for style in case a browser which returns unreliable values
7247		// for getComputedStyle silently falls back to the reliable elem.style
7248		valueIsBorderBox = isBorderBox && ( jQuery.support.boxSizingReliable || val === elem.style[ name ] );
7249
7250		// Normalize "", auto, and prepare for extra
7251		val = parseFloat( val ) || 0;
7252	}
7253
7254	// use the active box-sizing model to add/subtract irrelevant styles
7255	return ( val +
7256		augmentWidthOrHeight(
7257			elem,
7258			name,
7259			extra || ( isBorderBox ? "border" : "content" ),
7260			valueIsBorderBox,
7261			styles
7262		)
7263	) + "px";
7264}
7265
7266// Try to determine the default display value of an element
7267function css_defaultDisplay( nodeName ) {
7268	var doc = document,
7269		display = elemdisplay[ nodeName ];
7270
7271	if ( !display ) {
7272		display = actualDisplay( nodeName, doc );
7273
7274		// If the simple way fails, read from inside an iframe
7275		if ( display === "none" || !display ) {
7276			// Use the already-created iframe if possible
7277			iframe = ( iframe ||
7278				jQuery("<iframe frameborder='0' width='0' height='0'/>")
7279				.css( "cssText", "display:block !important" )
7280			).appendTo( doc.documentElement );
7281
7282			// Always write a new HTML skeleton so Webkit and Firefox don't choke on reuse
7283			doc = ( iframe[0].contentWindow || iframe[0].contentDocument ).document;
7284			doc.write("<!doctype html><html><body>");
7285			doc.close();
7286
7287			display = actualDisplay( nodeName, doc );
7288			iframe.detach();
7289		}
7290
7291		// Store the correct default display
7292		elemdisplay[ nodeName ] = display;
7293	}
7294
7295	return display;
7296}
7297
7298// Called ONLY from within css_defaultDisplay
7299function actualDisplay( name, doc ) {
7300	var elem = jQuery( doc.createElement( name ) ).appendTo( doc.body ),
7301		display = jQuery.css( elem[0], "display" );
7302	elem.remove();
7303	return display;
7304}
7305
7306jQuery.each([ "height", "width" ], function( i, name ) {
7307	jQuery.cssHooks[ name ] = {
7308		get: function( elem, computed, extra ) {
7309			if ( computed ) {
7310				// certain elements can have dimension info if we invisibly show them
7311				// however, it must have a current display style that would benefit from this
7312				return elem.offsetWidth === 0 && rdisplayswap.test( jQuery.css( elem, "display" ) ) ?
7313					jQuery.swap( elem, cssShow, function() {
7314						return getWidthOrHeight( elem, name, extra );
7315					}) :
7316					getWidthOrHeight( elem, name, extra );
7317			}
7318		},
7319
7320		set: function( elem, value, extra ) {
7321			var styles = extra && getStyles( elem );
7322			return setPositiveNumber( elem, value, extra ?
7323				augmentWidthOrHeight(
7324					elem,
7325					name,
7326					extra,
7327					jQuery.support.boxSizing && jQuery.css( elem, "boxSizing", false, styles ) === "border-box",
7328					styles
7329				) : 0
7330			);
7331		}
7332	};
7333});
7334
7335if ( !jQuery.support.opacity ) {
7336	jQuery.cssHooks.opacity = {
7337		get: function( elem, computed ) {
7338			// IE uses filters for opacity
7339			return ropacity.test( (computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "" ) ?
7340				( 0.01 * parseFloat( RegExp.$1 ) ) + "" :
7341				computed ? "1" : "";
7342		},
7343
7344		set: function( elem, value ) {
7345			var style = elem.style,
7346				currentStyle = elem.currentStyle,
7347				opacity = jQuery.isNumeric( value ) ? "alpha(opacity=" + value * 100 + ")" : "",
7348				filter = currentStyle && currentStyle.filter || style.filter || "";
7349
7350			// IE has trouble with opacity if it does not have layout
7351			// Force it by setting the zoom level
7352			style.zoom = 1;
7353
7354			// if setting opacity to 1, and no other filters exist - attempt to remove filter attribute #6652
7355			// if value === "", then remove inline opacity #12685
7356			if ( ( value >= 1 || value === "" ) &&
7357					jQuery.trim( filter.replace( ralpha, "" ) ) === "" &&
7358					style.removeAttribute ) {
7359
7360				// Setting style.filter to null, "" & " " still leave "filter:" in the cssText
7361				// if "filter:" is present at all, clearType is disabled, we want to avoid this
7362				// style.removeAttribute is IE Only, but so apparently is this code path...
7363				style.removeAttribute( "filter" );
7364
7365				// if there is no filter style applied in a css rule or unset inline opacity, we are done
7366				if ( value === "" || currentStyle && !currentStyle.filter ) {
7367					return;
7368				}
7369			}
7370
7371			// otherwise, set new filter values
7372			style.filter = ralpha.test( filter ) ?
7373				filter.replace( ralpha, opacity ) :
7374				filter + " " + opacity;
7375		}
7376	};
7377}
7378
7379// These hooks cannot be added until DOM ready because the support test
7380// for it is not run until after DOM ready
7381jQuery(function() {
7382	if ( !jQuery.support.reliableMarginRight ) {
7383		jQuery.cssHooks.marginRight = {
7384			get: function( elem, computed ) {
7385				if ( computed ) {
7386					// WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right
7387					// Work around by temporarily setting element display to inline-block
7388					return jQuery.swap( elem, { "display": "inline-block" },
7389						curCSS, [ elem, "marginRight" ] );
7390				}
7391			}
7392		};
7393	}
7394
7395	// Webkit bug: https://bugs.webkit.org/show_bug.cgi?id=29084
7396	// getComputedStyle returns percent when specified for top/left/bottom/right
7397	// rather than make the css module depend on the offset module, we just check for it here
7398	if ( !jQuery.support.pixelPosition && jQuery.fn.position ) {
7399		jQuery.each( [ "top", "left" ], function( i, prop ) {
7400			jQuery.cssHooks[ prop ] = {
7401				get: function( elem, computed ) {
7402					if ( computed ) {
7403						computed = curCSS( elem, prop );
7404						// if curCSS returns percentage, fallback to offset
7405						return rnumnonpx.test( computed ) ?
7406							jQuery( elem ).position()[ prop ] + "px" :
7407							computed;
7408					}
7409				}
7410			};
7411		});
7412	}
7413
7414});
7415
7416if ( jQuery.expr && jQuery.expr.filters ) {
7417	jQuery.expr.filters.hidden = function( elem ) {
vendor: 5,333 bytes, lines 7418-7596
7418		// Support: Opera <= 12.12
7419		// Opera reports offsetWidths and offsetHeights less than zero on some elements
7420		return elem.offsetWidth <= 0 && elem.offsetHeight <= 0 ||
7421			(!jQuery.support.reliableHiddenOffsets && ((elem.style && elem.style.display) || jQuery.css( elem, "display" )) === "none");
7422	};
7423
7424	jQuery.expr.filters.visible = function( elem ) {
7425		return !jQuery.expr.filters.hidden( elem );
7426	};
7427}
7428
7429// These hooks are used by animate to expand properties
7430jQuery.each({
7431	margin: "",
7432	padding: "",
7433	border: "Width"
7434}, function( prefix, suffix ) {
7435	jQuery.cssHooks[ prefix + suffix ] = {
7436		expand: function( value ) {
7437			var i = 0,
7438				expanded = {},
7439
7440				// assumes a single number if not a string
7441				parts = typeof value === "string" ? value.split(" ") : [ value ];
7442
7443			for ( ; i < 4; i++ ) {
7444				expanded[ prefix + cssExpand[ i ] + suffix ] =
7445					parts[ i ] || parts[ i - 2 ] || parts[ 0 ];
7446			}
7447
7448			return expanded;
7449		}
7450	};
7451
7452	if ( !rmargin.test( prefix ) ) {
7453		jQuery.cssHooks[ prefix + suffix ].set = setPositiveNumber;
7454	}
7455});
7456var r20 = /%20/g,
7457	rbracket = /\[\]$/,
7458	rCRLF = /\r?\n/g,
7459	rsubmitterTypes = /^(?:submit|button|image|reset|file)$/i,
7460	rsubmittable = /^(?:input|select|textarea|keygen)/i;
7461
7462jQuery.fn.extend({
7463	serialize: function() {
7464		return jQuery.param( this.serializeArray() );
7465	},
7466	serializeArray: function() {
7467		return this.map(function(){
7468			// Can add propHook for "elements" to filter or add form elements
7469			var elements = jQuery.prop( this, "elements" );
7470			return elements ? jQuery.makeArray( elements ) : this;
7471		})
7472		.filter(function(){
7473			var type = this.type;
7474			// Use .is(":disabled") so that fieldset[disabled] works
7475			return this.name && !jQuery( this ).is( ":disabled" ) &&
7476				rsubmittable.test( this.nodeName ) && !rsubmitterTypes.test( type ) &&
7477				( this.checked || !manipulation_rcheckableType.test( type ) );
7478		})
7479		.map(function( i, elem ){
7480			var val = jQuery( this ).val();
7481
7482			return val == null ?
7483				null :
7484				jQuery.isArray( val ) ?
7485					jQuery.map( val, function( val ){
7486						return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
7487					}) :
7488					{ name: elem.name, value: val.replace( rCRLF, "\r\n" ) };
7489		}).get();
7490	}
7491});
7492
7493//Serialize an array of form elements or a set of
7494//key/values into a query string
7495jQuery.param = function( a, traditional ) {
7496	var prefix,
7497		s = [],
7498		add = function( key, value ) {
7499			// If value is a function, invoke it and return its value
7500			value = jQuery.isFunction( value ) ? value() : ( value == null ? "" : value );
7501			s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value );
7502		};
7503
7504	// Set traditional to true for jQuery <= 1.3.2 behavior.
7505	if ( traditional === undefined ) {
7506		traditional = jQuery.ajaxSettings && jQuery.ajaxSettings.traditional;
7507	}
7508
7509	// If an array was passed in, assume that it is an array of form elements.
7510	if ( jQuery.isArray( a ) || ( a.jquery && !jQuery.isPlainObject( a ) ) ) {
7511		// Serialize the form elements
7512		jQuery.each( a, function() {
7513			add( this.name, this.value );
7514		});
7515
7516	} else {
7517		// If traditional, encode the "old" way (the way 1.3.2 or older
7518		// did it), otherwise encode params recursively.
7519		for ( prefix in a ) {
7520			buildParams( prefix, a[ prefix ], traditional, add );
7521		}
7522	}
7523
7524	// Return the resulting serialization
7525	return s.join( "&" ).replace( r20, "+" );
7526};
7527
7528function buildParams( prefix, obj, traditional, add ) {
7529	var name;
7530
7531	if ( jQuery.isArray( obj ) ) {
7532		// Serialize array item.
7533		jQuery.each( obj, function( i, v ) {
7534			if ( traditional || rbracket.test( prefix ) ) {
7535				// Treat each array item as a scalar.
7536				add( prefix, v );
7537
7538			} else {
7539				// Item is non-scalar (array or object), encode its numeric index.
7540				buildParams( prefix + "[" + ( typeof v === "object" ? i : "" ) + "]", v, traditional, add );
7541			}
7542		});
7543
7544	} else if ( !traditional && jQuery.type( obj ) === "object" ) {
7545		// Serialize object item.
7546		for ( name in obj ) {
7547			buildParams( prefix + "[" + name + "]", obj[ name ], traditional, add );
7548		}
7549
7550	} else {
7551		// Serialize scalar item.
7552		add( prefix, obj );
7553	}
7554}
7555jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " +
7556	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
7557	"change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) {
7558
7559	// Handle event binding
7560	jQuery.fn[ name ] = function( data, fn ) {
7561		return arguments.length > 0 ?
7562			this.on( name, null, data, fn ) :
7563			this.trigger( name );
7564	};
7565});
7566
7567jQuery.fn.extend({
7568	hover: function( fnOver, fnOut ) {
7569		return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver );
7570	},
7571
7572	bind: function( types, data, fn ) {
7573		return this.on( types, null, data, fn );
7574	},
7575	unbind: function( types, fn ) {
7576		return this.off( types, null, fn );
7577	},
7578
7579	delegate: function( selector, types, data, fn ) {
7580		return this.on( types, selector, data, fn );
7581	},
7582	undelegate: function( selector, types, fn ) {
7583		// ( namespace ) or ( selector, types [, fn] )
7584		return arguments.length === 1 ? this.off( selector, "**" ) : this.off( types, selector || "**", fn );
7585	}
7586});
7587var
7588	// Document location
7589	ajaxLocParts,
7590	ajaxLocation,
7591	ajax_nonce = jQuery.now(),
7592
7593	ajax_rquery = /\?/,
7594	rhash = /#.*$/,
7595	rts = /([?&])_=[^&]*/,
7596	rheaders = /^(.*?):[ \t]*([^\r\n]*)\r?$/mg, // IE leaves an \r character at EOL
vendor: 4,281 bytes, lines 7597-7740
7597	// #7653, #8125, #8152: local protocol detection
7598	rlocalProtocol = /^(?:about|app|app-storage|.+-extension|file|res|widget):$/,
7599	rnoContent = /^(?:GET|HEAD)$/,
7600	rprotocol = /^\/\//,
7601	rurl = /^([\w.+-]+:)(?:\/\/([^\/?#:]*)(?::(\d+)|)|)/,
7602
7603	// Keep a copy of the old load method
7604	_load = jQuery.fn.load,
7605
7606	/* Prefilters
7607	 * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example)
7608	 * 2) These are called:
7609	 *    - BEFORE asking for a transport
7610	 *    - AFTER param serialization (s.data is a string if s.processData is true)
7611	 * 3) key is the dataType
7612	 * 4) the catchall symbol "*" can be used
7613	 * 5) execution will start with transport dataType and THEN continue down to "*" if needed
7614	 */
7615	prefilters = {},
7616
7617	/* Transports bindings
7618	 * 1) key is the dataType
7619	 * 2) the catchall symbol "*" can be used
7620	 * 3) selection will start with transport dataType and THEN go to "*" if needed
7621	 */
7622	transports = {},
7623
7624	// Avoid comment-prolog char sequence (#10098); must appease lint and evade compression
7625	allTypes = "*/".concat("*");
7626
7627// #8138, IE may throw an exception when accessing
7628// a field from window.location if document.domain has been set
7629try {
7630	ajaxLocation = location.href;
7631} catch( e ) {
7632	// Use the href attribute of an A element
7633	// since IE will modify it given document.location
7634	ajaxLocation = document.createElement( "a" );
7635	ajaxLocation.href = "";
7636	ajaxLocation = ajaxLocation.href;
7637}
7638
7639// Segment location into parts
7640ajaxLocParts = rurl.exec( ajaxLocation.toLowerCase() ) || [];
7641
7642// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
7643function addToPrefiltersOrTransports( structure ) {
7644
7645	// dataTypeExpression is optional and defaults to "*"
7646	return function( dataTypeExpression, func ) {
7647
7648		if ( typeof dataTypeExpression !== "string" ) {
7649			func = dataTypeExpression;
7650			dataTypeExpression = "*";
7651		}
7652
7653		var dataType,
7654			i = 0,
7655			dataTypes = dataTypeExpression.toLowerCase().match( core_rnotwhite ) || [];
7656
7657		if ( jQuery.isFunction( func ) ) {
7658			// For each dataType in the dataTypeExpression
7659			while ( (dataType = dataTypes[i++]) ) {
7660				// Prepend if requested
7661				if ( dataType[0] === "+" ) {
7662					dataType = dataType.slice( 1 ) || "*";
7663					(structure[ dataType ] = structure[ dataType ] || []).unshift( func );
7664
7665				// Otherwise append
7666				} else {
7667					(structure[ dataType ] = structure[ dataType ] || []).push( func );
7668				}
7669			}
7670		}
7671	};
7672}
7673
7674// Base inspection function for prefilters and transports
7675function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
7676
7677	var inspected = {},
7678		seekingTransport = ( structure === transports );
7679
7680	function inspect( dataType ) {
7681		var selected;
7682		inspected[ dataType ] = true;
7683		jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
7684			var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
7685			if( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
7686				options.dataTypes.unshift( dataTypeOrTransport );
7687				inspect( dataTypeOrTransport );
7688				return false;
7689			} else if ( seekingTransport ) {
7690				return !( selected = dataTypeOrTransport );
7691			}
7692		});
7693		return selected;
7694	}
7695
7696	return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
7697}
7698
7699// A special extend for ajax options
7700// that takes "flat" options (not to be deep extended)
7701// Fixes #9887
7702function ajaxExtend( target, src ) {
7703	var deep, key,
7704		flatOptions = jQuery.ajaxSettings.flatOptions || {};
7705
7706	for ( key in src ) {
7707		if ( src[ key ] !== undefined ) {
7708			( flatOptions[ key ] ? target : ( deep || (deep = {}) ) )[ key ] = src[ key ];
7709		}
7710	}
7711	if ( deep ) {
7712		jQuery.extend( true, target, deep );
7713	}
7714
7715	return target;
7716}
7717
7718jQuery.fn.load = function( url, params, callback ) {
7719	if ( typeof url !== "string" && _load ) {
7720		return _load.apply( this, arguments );
7721	}
7722
7723	var selector, response, type,
7724		self = this,
7725		off = url.indexOf(" ");
7726
7727	if ( off >= 0 ) {
7728		selector = url.slice( off, url.length );
7729		url = url.slice( 0, off );
7730	}
7731
7732	// If it's a function
7733	if ( jQuery.isFunction( params ) ) {
7734
7735		// We assume that it's the callback
7736		callback = params;
7737		params = undefined;
7738
7739	// Otherwise, build a param string
7740	} else if ( params && typeof params === "object" ) {
vendor: 15,508 bytes, lines 7741-8323
7741		type = "POST";
7742	}
7743
7744	// If we have elements to modify, make the request
7745	if ( self.length > 0 ) {
7746		jQuery.ajax({
7747			url: url,
7748
7749			// if "type" variable is undefined, then "GET" method will be used
7750			type: type,
7751			dataType: "html",
7752			data: params
7753		}).done(function( responseText ) {
7754
7755			// Save response for use in complete callback
7756			response = arguments;
7757
7758			self.html( selector ?
7759
7760				// If a selector was specified, locate the right elements in a dummy div
7761				// Exclude scripts to avoid IE 'Permission Denied' errors
7762				jQuery("<div>").append( jQuery.parseHTML( responseText ) ).find( selector ) :
7763
7764				// Otherwise use the full result
7765				responseText );
7766
7767		}).complete( callback && function( jqXHR, status ) {
7768			self.each( callback, response || [ jqXHR.responseText, status, jqXHR ] );
7769		});
7770	}
7771
7772	return this;
7773};
7774
7775// Attach a bunch of functions for handling common AJAX events
7776jQuery.each( [ "ajaxStart", "ajaxStop", "ajaxComplete", "ajaxError", "ajaxSuccess", "ajaxSend" ], function( i, type ){
7777	jQuery.fn[ type ] = function( fn ){
7778		return this.on( type, fn );
7779	};
7780});
7781
7782jQuery.extend({
7783
7784	// Counter for holding the number of active queries
7785	active: 0,
7786
7787	// Last-Modified header cache for next request
7788	lastModified: {},
7789	etag: {},
7790
7791	ajaxSettings: {
7792		url: ajaxLocation,
7793		type: "GET",
7794		isLocal: rlocalProtocol.test( ajaxLocParts[ 1 ] ),
7795		global: true,
7796		processData: true,
7797		async: true,
7798		contentType: "application/x-www-form-urlencoded; charset=UTF-8",
7799		/*
7800		timeout: 0,
7801		data: null,
7802		dataType: null,
7803		username: null,
7804		password: null,
7805		cache: null,
7806		throws: false,
7807		traditional: false,
7808		headers: {},
7809		*/
7810
7811		accepts: {
7812			"*": allTypes,
7813			text: "text/plain",
7814			html: "text/html",
7815			xml: "application/xml, text/xml",
7816			json: "application/json, text/javascript"
7817		},
7818
7819		contents: {
7820			xml: /xml/,
7821			html: /html/,
7822			json: /json/
7823		},
7824
7825		responseFields: {
7826			xml: "responseXML",
7827			text: "responseText",
7828			json: "responseJSON"
7829		},
7830
7831		// Data converters
7832		// Keys separate source (or catchall "*") and destination types with a single space
7833		converters: {
7834
7835			// Convert anything to text
7836			"* text": String,
7837
7838			// Text to html (true = no transformation)
7839			"text html": true,
7840
7841			// Evaluate text as a json expression
7842			"text json": jQuery.parseJSON,
7843
7844			// Parse text as xml
7845			"text xml": jQuery.parseXML
7846		},
7847
7848		// For options that shouldn't be deep extended:
7849		// you can add your own custom options here if
7850		// and when you create one that shouldn't be
7851		// deep extended (see ajaxExtend)
7852		flatOptions: {
7853			url: true,
7854			context: true
7855		}
7856	},
7857
7858	// Creates a full fledged settings object into target
7859	// with both ajaxSettings and settings fields.
7860	// If target is omitted, writes into ajaxSettings.
7861	ajaxSetup: function( target, settings ) {
7862		return settings ?
7863
7864			// Building a settings object
7865			ajaxExtend( ajaxExtend( target, jQuery.ajaxSettings ), settings ) :
7866
7867			// Extending ajaxSettings
7868			ajaxExtend( jQuery.ajaxSettings, target );
7869	},
7870
7871	ajaxPrefilter: addToPrefiltersOrTransports( prefilters ),
7872	ajaxTransport: addToPrefiltersOrTransports( transports ),
7873
7874	// Main method
7875	ajax: function( url, options ) {
7876
7877		// If url is an object, simulate pre-1.5 signature
7878		if ( typeof url === "object" ) {
7879			options = url;
7880			url = undefined;
7881		}
7882
7883		// Force options to be an object
7884		options = options || {};
7885
7886		var // Cross-domain detection vars
7887			parts,
7888			// Loop variable
7889			i,
7890			// URL without anti-cache param
7891			cacheURL,
7892			// Response headers as string
7893			responseHeadersString,
7894			// timeout handle
7895			timeoutTimer,
7896
7897			// To know if global events are to be dispatched
7898			fireGlobals,
7899
7900			transport,
7901			// Response headers
7902			responseHeaders,
7903			// Create the final options object
7904			s = jQuery.ajaxSetup( {}, options ),
7905			// Callbacks context
7906			callbackContext = s.context || s,
7907			// Context for global events is callbackContext if it is a DOM node or jQuery collection
7908			globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
7909				jQuery( callbackContext ) :
7910				jQuery.event,
7911			// Deferreds
7912			deferred = jQuery.Deferred(),
7913			completeDeferred = jQuery.Callbacks("once memory"),
7914			// Status-dependent callbacks
7915			statusCode = s.statusCode || {},
7916			// Headers (they are sent all at once)
7917			requestHeaders = {},
7918			requestHeadersNames = {},
7919			// The jqXHR state
7920			state = 0,
7921			// Default abort message
7922			strAbort = "canceled",
7923			// Fake xhr
7924			jqXHR = {
7925				readyState: 0,
7926
7927				// Builds headers hashtable if needed
7928				getResponseHeader: function( key ) {
7929					var match;
7930					if ( state === 2 ) {
7931						if ( !responseHeaders ) {
7932							responseHeaders = {};
7933							while ( (match = rheaders.exec( responseHeadersString )) ) {
7934								responseHeaders[ match[1].toLowerCase() ] = match[ 2 ];
7935							}
7936						}
7937						match = responseHeaders[ key.toLowerCase() ];
7938					}
7939					return match == null ? null : match;
7940				},
7941
7942				// Raw string
7943				getAllResponseHeaders: function() {
7944					return state === 2 ? responseHeadersString : null;
7945				},
7946
7947				// Caches the header
7948				setRequestHeader: function( name, value ) {
7949					var lname = name.toLowerCase();
7950					if ( !state ) {
7951						name = requestHeadersNames[ lname ] = requestHeadersNames[ lname ] || name;
7952						requestHeaders[ name ] = value;
7953					}
7954					return this;
7955				},
7956
7957				// Overrides response content-type header
7958				overrideMimeType: function( type ) {
7959					if ( !state ) {
7960						s.mimeType = type;
7961					}
7962					return this;
7963				},
7964
7965				// Status-dependent callbacks
7966				statusCode: function( map ) {
7967					var code;
7968					if ( map ) {
7969						if ( state < 2 ) {
7970							for ( code in map ) {
7971								// Lazy-add the new callback in a way that preserves old ones
7972								statusCode[ code ] = [ statusCode[ code ], map[ code ] ];
7973							}
7974						} else {
7975							// Execute the appropriate callbacks
7976							jqXHR.always( map[ jqXHR.status ] );
7977						}
7978					}
7979					return this;
7980				},
7981
7982				// Cancel the request
7983				abort: function( statusText ) {
7984					var finalText = statusText || strAbort;
7985					if ( transport ) {
7986						transport.abort( finalText );
7987					}
7988					done( 0, finalText );
7989					return this;
7990				}
7991			};
7992
7993		// Attach deferreds
7994		deferred.promise( jqXHR ).complete = completeDeferred.add;
7995		jqXHR.success = jqXHR.done;
7996		jqXHR.error = jqXHR.fail;
7997
7998		// Remove hash character (#7531: and string promotion)
7999		// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
8000		// Handle falsy url in the settings object (#10093: consistency with old signature)
8001		// We also use the url parameter if available
8002		s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );
8003
8004		// Alias method option to type as per ticket #12004
8005		s.type = options.method || options.type || s.method || s.type;
8006
8007		// Extract dataTypes list
8008		s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( core_rnotwhite ) || [""];
8009
8010		// A cross-domain request is in order when we have a protocol:host:port mismatch
8011		if ( s.crossDomain == null ) {
8012			parts = rurl.exec( s.url.toLowerCase() );
8013			s.crossDomain = !!( parts &&
8014				( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
8015					( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
8016						( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
8017			);
8018		}
8019
8020		// Convert data if not already a string
8021		if ( s.data && s.processData && typeof s.data !== "string" ) {
8022			s.data = jQuery.param( s.data, s.traditional );
8023		}
8024
8025		// Apply prefilters
8026		inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );
8027
8028		// If request was aborted inside a prefilter, stop there
8029		if ( state === 2 ) {
8030			return jqXHR;
8031		}
8032
8033		// We can fire global events as of now if asked to
8034		fireGlobals = s.global;
8035
8036		// Watch for a new set of requests
8037		if ( fireGlobals && jQuery.active++ === 0 ) {
8038			jQuery.event.trigger("ajaxStart");
8039		}
8040
8041		// Uppercase the type
8042		s.type = s.type.toUpperCase();
8043
8044		// Determine if request has content
8045		s.hasContent = !rnoContent.test( s.type );
8046
8047		// Save the URL in case we're toying with the If-Modified-Since
8048		// and/or If-None-Match header later on
8049		cacheURL = s.url;
8050
8051		// More options handling for requests with no content
8052		if ( !s.hasContent ) {
8053
8054			// If data is available, append data to url
8055			if ( s.data ) {
8056				cacheURL = ( s.url += ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + s.data );
8057				// #9682: remove data so that it's not used in an eventual retry
8058				delete s.data;
8059			}
8060
8061			// Add anti-cache in url if needed
8062			if ( s.cache === false ) {
8063				s.url = rts.test( cacheURL ) ?
8064
8065					// If there is already a '_' parameter, set its value
8066					cacheURL.replace( rts, "$1_=" + ajax_nonce++ ) :
8067
8068					// Otherwise add one to the end
8069					cacheURL + ( ajax_rquery.test( cacheURL ) ? "&" : "?" ) + "_=" + ajax_nonce++;
8070			}
8071		}
8072
8073		// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
8074		if ( s.ifModified ) {
8075			if ( jQuery.lastModified[ cacheURL ] ) {
8076				jqXHR.setRequestHeader( "If-Modified-Since", jQuery.lastModified[ cacheURL ] );
8077			}
8078			if ( jQuery.etag[ cacheURL ] ) {
8079				jqXHR.setRequestHeader( "If-None-Match", jQuery.etag[ cacheURL ] );
8080			}
8081		}
8082
8083		// Set the correct header, if data is being sent
8084		if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) {
8085			jqXHR.setRequestHeader( "Content-Type", s.contentType );
8086		}
8087
8088		// Set the Accepts header for the server, depending on the dataType
8089		jqXHR.setRequestHeader(
8090			"Accept",
8091			s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ?
8092				s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", " + allTypes + "; q=0.01" : "" ) :
8093				s.accepts[ "*" ]
8094		);
8095
8096		// Check for headers option
8097		for ( i in s.headers ) {
8098			jqXHR.setRequestHeader( i, s.headers[ i ] );
8099		}
8100
8101		// Allow custom headers/mimetypes and early abort
8102		if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
8103			// Abort if not done already and return
8104			return jqXHR.abort();
8105		}
8106
8107		// aborting is no longer a cancellation
8108		strAbort = "abort";
8109
8110		// Install callbacks on deferreds
8111		for ( i in { success: 1, error: 1, complete: 1 } ) {
8112			jqXHR[ i ]( s[ i ] );
8113		}
8114
8115		// Get transport
8116		transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );
8117
8118		// If no transport, we auto-abort
8119		if ( !transport ) {
8120			done( -1, "No Transport" );
8121		} else {
8122			jqXHR.readyState = 1;
8123
8124			// Send global event
8125			if ( fireGlobals ) {
8126				globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
8127			}
8128			// Timeout
8129			if ( s.async && s.timeout > 0 ) {
8130				timeoutTimer = setTimeout(function() {
8131					jqXHR.abort("timeout");
8132				}, s.timeout );
8133			}
8134
8135			try {
8136				state = 1;
8137				transport.send( requestHeaders, done );
8138			} catch ( e ) {
8139				// Propagate exception as error if not done
8140				if ( state < 2 ) {
8141					done( -1, e );
8142				// Simply rethrow otherwise
8143				} else {
8144					throw e;
8145				}
8146			}
8147		}
8148
8149		// Callback for when everything is done
8150		function done( status, nativeStatusText, responses, headers ) {
8151			var isSuccess, success, error, response, modified,
8152				statusText = nativeStatusText;
8153
8154			// Called once
8155			if ( state === 2 ) {
8156				return;
8157			}
8158
8159			// State is "done" now
8160			state = 2;
8161
8162			// Clear timeout if it exists
8163			if ( timeoutTimer ) {
8164				clearTimeout( timeoutTimer );
8165			}
8166
8167			// Dereference transport for early garbage collection
8168			// (no matter how long the jqXHR object will be used)
8169			transport = undefined;
8170
8171			// Cache response headers
8172			responseHeadersString = headers || "";
8173
8174			// Set readyState
8175			jqXHR.readyState = status > 0 ? 4 : 0;
8176
8177			// Determine if successful
8178			isSuccess = status >= 200 && status < 300 || status === 304;
8179
8180			// Get response data
8181			if ( responses ) {
8182				response = ajaxHandleResponses( s, jqXHR, responses );
8183			}
8184
8185			// Convert no matter what (that way responseXXX fields are always set)
8186			response = ajaxConvert( s, response, jqXHR, isSuccess );
8187
8188			// If successful, handle type chaining
8189			if ( isSuccess ) {
8190
8191				// Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode.
8192				if ( s.ifModified ) {
8193					modified = jqXHR.getResponseHeader("Last-Modified");
8194					if ( modified ) {
8195						jQuery.lastModified[ cacheURL ] = modified;
8196					}
8197					modified = jqXHR.getResponseHeader("etag");
8198					if ( modified ) {
8199						jQuery.etag[ cacheURL ] = modified;
8200					}
8201				}
8202
8203				// if no content
8204				if ( status === 204 || s.type === "HEAD" ) {
8205					statusText = "nocontent";
8206
8207				// if not modified
8208				} else if ( status === 304 ) {
8209					statusText = "notmodified";
8210
8211				// If we have data, let's convert it
8212				} else {
8213					statusText = response.state;
8214					success = response.data;
8215					error = response.error;
8216					isSuccess = !error;
8217				}
8218			} else {
8219				// We extract error from statusText
8220				// then normalize statusText and status for non-aborts
8221				error = statusText;
8222				if ( status || !statusText ) {
8223					statusText = "error";
8224					if ( status < 0 ) {
8225						status = 0;
8226					}
8227				}
8228			}
8229
8230			// Set data for the fake xhr object
8231			jqXHR.status = status;
8232			jqXHR.statusText = ( nativeStatusText || statusText ) + "";
8233
8234			// Success/Error
8235			if ( isSuccess ) {
8236				deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
8237			} else {
8238				deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
8239			}
8240
8241			// Status-dependent callbacks
8242			jqXHR.statusCode( statusCode );
8243			statusCode = undefined;
8244
8245			if ( fireGlobals ) {
8246				globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
8247					[ jqXHR, s, isSuccess ? success : error ] );
8248			}
8249
8250			// Complete
8251			completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );
8252
8253			if ( fireGlobals ) {
8254				globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
8255				// Handle the global AJAX counter
8256				if ( !( --jQuery.active ) ) {
8257					jQuery.event.trigger("ajaxStop");
8258				}
8259			}
8260		}
8261
8262		return jqXHR;
8263	},
8264
8265	getJSON: function( url, data, callback ) {
8266		return jQuery.get( url, data, callback, "json" );
8267	},
8268
8269	getScript: function( url, callback ) {
8270		return jQuery.get( url, undefined, callback, "script" );
8271	}
8272});
8273
8274jQuery.each( [ "get", "post" ], function( i, method ) {
8275	jQuery[ method ] = function( url, data, callback, type ) {
8276		// shift arguments if data argument was omitted
8277		if ( jQuery.isFunction( data ) ) {
8278			type = type || callback;
8279			callback = data;
8280			data = undefined;
8281		}
8282
8283		return jQuery.ajax({
8284			url: url,
8285			type: method,
8286			dataType: type,
8287			data: data,
8288			success: callback
8289		});
8290	};
8291});
8292
8293/* Handles responses to an ajax request:
8294 * - finds the right dataType (mediates between content-type and expected dataType)
8295 * - returns the corresponding response
8296 */
8297function ajaxHandleResponses( s, jqXHR, responses ) {
8298	var firstDataType, ct, finalDataType, type,
8299		contents = s.contents,
8300		dataTypes = s.dataTypes;
8301
8302	// Remove auto dataType and get content-type in the process
8303	while( dataTypes[ 0 ] === "*" ) {
8304		dataTypes.shift();
8305		if ( ct === undefined ) {
8306			ct = s.mimeType || jqXHR.getResponseHeader("Content-Type");
8307		}
8308	}
8309
8310	// Check if we're dealing with a known content-type
8311	if ( ct ) {
8312		for ( type in contents ) {
8313			if ( contents[ type ] && contents[ type ].test( ct ) ) {
8314				dataTypes.unshift( type );
8315				break;
8316			}
8317		}
8318	}
8319
8320	// Check to see if we have a response for the expected dataType
8321	if ( dataTypes[ 0 ] in responses ) {
8322		finalDataType = dataTypes[ 0 ];
8323	} else {
vendor: 7,605 bytes, lines 8324-8613
8324		// Try convertible dataTypes
8325		for ( type in responses ) {
8326			if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) {
8327				finalDataType = type;
8328				break;
8329			}
8330			if ( !firstDataType ) {
8331				firstDataType = type;
8332			}
8333		}
8334		// Or just use first one
8335		finalDataType = finalDataType || firstDataType;
8336	}
8337
8338	// If we found a dataType
8339	// We add the dataType to the list if needed
8340	// and return the corresponding response
8341	if ( finalDataType ) {
8342		if ( finalDataType !== dataTypes[ 0 ] ) {
8343			dataTypes.unshift( finalDataType );
8344		}
8345		return responses[ finalDataType ];
8346	}
8347}
8348
8349/* Chain conversions given the request and the original response
8350 * Also sets the responseXXX fields on the jqXHR instance
8351 */
8352function ajaxConvert( s, response, jqXHR, isSuccess ) {
8353	var conv2, current, conv, tmp, prev,
8354		converters = {},
8355		// Work with a copy of dataTypes in case we need to modify it for conversion
8356		dataTypes = s.dataTypes.slice();
8357
8358	// Create converters map with lowercased keys
8359	if ( dataTypes[ 1 ] ) {
8360		for ( conv in s.converters ) {
8361			converters[ conv.toLowerCase() ] = s.converters[ conv ];
8362		}
8363	}
8364
8365	current = dataTypes.shift();
8366
8367	// Convert to each sequential dataType
8368	while ( current ) {
8369
8370		if ( s.responseFields[ current ] ) {
8371			jqXHR[ s.responseFields[ current ] ] = response;
8372		}
8373
8374		// Apply the dataFilter if provided
8375		if ( !prev && isSuccess && s.dataFilter ) {
8376			response = s.dataFilter( response, s.dataType );
8377		}
8378
8379		prev = current;
8380		current = dataTypes.shift();
8381
8382		if ( current ) {
8383
8384			// There's only work to do if current dataType is non-auto
8385			if ( current === "*" ) {
8386
8387				current = prev;
8388
8389			// Convert response if prev dataType is non-auto and differs from current
8390			} else if ( prev !== "*" && prev !== current ) {
8391
8392				// Seek a direct converter
8393				conv = converters[ prev + " " + current ] || converters[ "* " + current ];
8394
8395				// If none found, seek a pair
8396				if ( !conv ) {
8397					for ( conv2 in converters ) {
8398
8399						// If conv2 outputs current
8400						tmp = conv2.split( " " );
8401						if ( tmp[ 1 ] === current ) {
8402
8403							// If prev can be converted to accepted input
8404							conv = converters[ prev + " " + tmp[ 0 ] ] ||
8405								converters[ "* " + tmp[ 0 ] ];
8406							if ( conv ) {
8407								// Condense equivalence converters
8408								if ( conv === true ) {
8409									conv = converters[ conv2 ];
8410
8411								// Otherwise, insert the intermediate dataType
8412								} else if ( converters[ conv2 ] !== true ) {
8413									current = tmp[ 0 ];
8414									dataTypes.unshift( tmp[ 1 ] );
8415								}
8416								break;
8417							}
8418						}
8419					}
8420				}
8421
8422				// Apply converter (if not an equivalence)
8423				if ( conv !== true ) {
8424
8425					// Unless errors are allowed to bubble, catch and return them
8426					if ( conv && s[ "throws" ] ) {
8427						response = conv( response );
8428					} else {
8429						try {
8430							response = conv( response );
8431						} catch ( e ) {
8432							return { state: "parsererror", error: conv ? e : "No conversion from " + prev + " to " + current };
8433						}
8434					}
8435				}
8436			}
8437		}
8438	}
8439
8440	return { state: "success", data: response };
8441}
8442// Install script dataType
8443jQuery.ajaxSetup({
8444	accepts: {
8445		script: "text/javascript, application/javascript, application/ecmascript, application/x-ecmascript"
8446	},
8447	contents: {
8448		script: /(?:java|ecma)script/
8449	},
8450	converters: {
8451		"text script": function( text ) {
8452			jQuery.globalEval( text );
8453			return text;
8454		}
8455	}
8456});
8457
8458// Handle cache's special case and global
8459jQuery.ajaxPrefilter( "script", function( s ) {
8460	if ( s.cache === undefined ) {
8461		s.cache = false;
8462	}
8463	if ( s.crossDomain ) {
8464		s.type = "GET";
8465		s.global = false;
8466	}
8467});
8468
8469// Bind script tag hack transport
8470jQuery.ajaxTransport( "script", function(s) {
8471
8472	// This transport only deals with cross domain requests
8473	if ( s.crossDomain ) {
8474
8475		var script,
8476			head = document.head || jQuery("head")[0] || document.documentElement;
8477
8478		return {
8479
8480			send: function( _, callback ) {
8481
8482				script = document.createElement("script");
8483
8484				script.async = true;
8485
8486				if ( s.scriptCharset ) {
8487					script.charset = s.scriptCharset;
8488				}
8489
8490				script.src = s.url;
8491
8492				// Attach handlers for all browsers
8493				script.onload = script.onreadystatechange = function( _, isAbort ) {
8494
8495					if ( isAbort || !script.readyState || /loaded|complete/.test( script.readyState ) ) {
8496
8497						// Handle memory leak in IE
8498						script.onload = script.onreadystatechange = null;
8499
8500						// Remove the script
8501						if ( script.parentNode ) {
8502							script.parentNode.removeChild( script );
8503						}
8504
8505						// Dereference the script
8506						script = null;
8507
8508						// Callback if not abort
8509						if ( !isAbort ) {
8510							callback( 200, "success" );
8511						}
8512					}
8513				};
8514
8515				// Circumvent IE6 bugs with base elements (#2709 and #4378) by prepending
8516				// Use native DOM manipulation to avoid our domManip AJAX trickery
8517				head.insertBefore( script, head.firstChild );
8518			},
8519
8520			abort: function() {
8521				if ( script ) {
8522					script.onload( undefined, true );
8523				}
8524			}
8525		};
8526	}
8527});
8528var oldCallbacks = [],
8529	rjsonp = /(=)\?(?=&|$)|\?\?/;
8530
8531// Default jsonp settings
8532jQuery.ajaxSetup({
8533	jsonp: "callback",
8534	jsonpCallback: function() {
8535		var callback = oldCallbacks.pop() || ( jQuery.expando + "_" + ( ajax_nonce++ ) );
8536		this[ callback ] = true;
8537		return callback;
8538	}
8539});
8540
8541// Detect, normalize options and install callbacks for jsonp requests
8542jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {
8543
8544	var callbackName, overwritten, responseContainer,
8545		jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
8546			"url" :
8547			typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
8548		);
8549
8550	// Handle iff the expected data type is "jsonp" or we have a parameter to set
8551	if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {
8552
8553		// Get callback name, remembering preexisting value associated with it
8554		callbackName = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ?
8555			s.jsonpCallback() :
8556			s.jsonpCallback;
8557
8558		// Insert callback into url or form data
8559		if ( jsonProp ) {
8560			s[ jsonProp ] = s[ jsonProp ].replace( rjsonp, "$1" + callbackName );
8561		} else if ( s.jsonp !== false ) {
8562			s.url += ( ajax_rquery.test( s.url ) ? "&" : "?" ) + s.jsonp + "=" + callbackName;
8563		}
8564
8565		// Use data converter to retrieve json after script execution
8566		s.converters["script json"] = function() {
8567			if ( !responseContainer ) {
8568				jQuery.error( callbackName + " was not called" );
8569			}
8570			return responseContainer[ 0 ];
8571		};
8572
8573		// force json dataType
8574		s.dataTypes[ 0 ] = "json";
8575
8576		// Install callback
8577		overwritten = window[ callbackName ];
8578		window[ callbackName ] = function() {
8579			responseContainer = arguments;
8580		};
8581
8582		// Clean-up function (fires after converters)
8583		jqXHR.always(function() {
8584			// Restore preexisting value
8585			window[ callbackName ] = overwritten;
8586
8587			// Save back as free
8588			if ( s[ callbackName ] ) {
8589				// make sure that re-using the options doesn't screw things around
8590				s.jsonpCallback = originalSettings.jsonpCallback;
8591
8592				// save the callback name for future use
8593				oldCallbacks.push( callbackName );
8594			}
8595
8596			// Call if it was a function and we have a response
8597			if ( responseContainer && jQuery.isFunction( overwritten ) ) {
8598				overwritten( responseContainer[ 0 ] );
8599			}
8600
8601			responseContainer = overwritten = undefined;
8602		});
8603
8604		// Delegate to script
8605		return "script";
8606	}
8607});
8608var xhrCallbacks, xhrSupported,
8609	xhrId = 0,
8610	// #5280: Internet Explorer will keep connections alive if we don't abort on unload
8611	xhrOnUnloadAbort = window.ActiveXObject && function() {
8612		// Abort all pending requests
8613		var key;
8614		for ( key in xhrCallbacks ) {
8615			xhrCallbacks[ key ]( undefined, true );
8616		}
8617	};
8618
8619// Functions to create xhrs
8620function createStandardXHR() {
8621	try {
8622		return new window.XMLHttpRequest();
8623	} catch( e ) {}
8624}
8625
8626function createActiveXHR() {
8627	try {
8628		return new window.ActiveXObject("Microsoft.XMLHTTP");
8629	} catch( e ) {}
8630}
8631
8632// Create the request object
8633// (This is still attached to ajaxSettings for backward compatibility)
8634jQuery.ajaxSettings.xhr = window.ActiveXObject ?
8635	/* Microsoft failed to properly
8636	 * implement the XMLHttpRequest in IE7 (can't request local files),
8637	 * so we use the ActiveXObject when it is available
8638	 * Additionally XMLHttpRequest can be disabled in IE7/IE8 so
8639	 * we need a fallback.
8640	 */
8641	function() {
8642		return !this.isLocal && createStandardXHR() || createActiveXHR();
8643	} :
8644	// For all other browsers, use the standard XMLHttpRequest object
8645	createStandardXHR;
8646
8647// Determine support properties
8648xhrSupported = jQuery.ajaxSettings.xhr();
8649jQuery.support.cors = !!xhrSupported && ( "withCredentials" in xhrSupported );
8650xhrSupported = jQuery.support.ajax = !!xhrSupported;
8651
8652// Create transport if the browser can provide an xhr
8653if ( xhrSupported ) {
8654
8655	jQuery.ajaxTransport(function( s ) {
8656		// Cross domain only allowed if supported through XMLHttpRequest
8657		if ( !s.crossDomain || jQuery.support.cors ) {
8658
8659			var callback;
8660
8661			return {
8662				send: function( headers, complete ) {
8663
8664					// Get a new xhr
8665					var handle, i,
8666						xhr = s.xhr();
8667
8668					// Open the socket
8669					// Passing null username, generates a login popup on Opera (#2865)
8670					if ( s.username ) {
8671						xhr.open( s.type, s.url, s.async, s.username, s.password );
8672					} else {
8673						xhr.open( s.type, s.url, s.async );
8674					}
8675
8676					// Apply custom fields if provided
8677					if ( s.xhrFields ) {
8678						for ( i in s.xhrFields ) {
8679							xhr[ i ] = s.xhrFields[ i ];
8680						}
8681					}
8682
8683					// Override mime type if needed
8684					if ( s.mimeType && xhr.overrideMimeType ) {
8685						xhr.overrideMimeType( s.mimeType );
8686					}
8687
8688					// X-Requested-With header
8689					// For cross-domain requests, seeing as conditions for a preflight are
8690					// akin to a jigsaw puzzle, we simply never set it to be sure.
8691					// (it can always be set on a per-request basis or even using ajaxSetup)
8692					// For same-domain requests, won't change header if already provided.
8693					if ( !s.crossDomain && !headers["X-Requested-With"] ) {
8694						headers["X-Requested-With"] = "XMLHttpRequest";
8695					}
8696
8697					// Need an extra try/catch for cross domain requests in Firefox 3
8698					try {
8699						for ( i in headers ) {
8700							xhr.setRequestHeader( i, headers[ i ] );
8701						}
8702					} catch( err ) {}
8703
8704					// Do send the request
8705					// This may raise an exception which is actually
8706					// handled in jQuery.ajax (so no try/catch here)
8707					xhr.send( ( s.hasContent && s.data ) || null );
8708
8709					// Listener
8710					callback = function( _, isAbort ) {
8711						var status, responseHeaders, statusText, responses;
8712
8713						// Firefox throws exceptions when accessing properties
8714						// of an xhr when a network error occurred
8715						// http://helpful.knobs-dials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)
8716						try {
8717
8718							// Was never called and is aborted or complete
8719							if ( callback && ( isAbort || xhr.readyState === 4 ) ) {
8720
8721								// Only called once
8722								callback = undefined;
8723
8724								// Do not keep as active anymore
8725								if ( handle ) {
8726									xhr.onreadystatechange = jQuery.noop;
8727									if ( xhrOnUnloadAbort ) {
8728										delete xhrCallbacks[ handle ];
8729									}
8730								}
8731
8732								// If it's an abort
8733								if ( isAbort ) {
8734									// Abort it manually if needed
8735									if ( xhr.readyState !== 4 ) {
8736										xhr.abort();
8737									}
8738								} else {
8739									responses = {};
8740									status = xhr.status;
8741									responseHeaders = xhr.getAllResponseHeaders();
8742
8743									// When requesting binary data, IE6-9 will throw an exception
8744									// on any attempt to access responseText (#11426)
8745									if ( typeof xhr.responseText === "string" ) {
8746										responses.text = xhr.responseText;
8747									}
8748
8749									// Firefox throws an exception when accessing
8750									// statusText for faulty cross-domain requests
8751									try {
8752										statusText = xhr.statusText;
8753									} catch( e ) {
8754										// We normalize with Webkit giving an empty statusText
8755										statusText = "";
8756									}
8757
8758									// Filter status for non standard behaviors
8759
8760									// If the request is local and we have data: assume a success
8761									// (success with no data won't get notified, that's the best we
8762									// can do given current implementations)
8763									if ( !status && s.isLocal && !s.crossDomain ) {
8764										status = responses.text ? 200 : 404;
8765									// IE - #1450: sometimes returns 1223 when it should be 204
8766									} else if ( status === 1223 ) {
8767										status = 204;
8768									}
8769								}
8770							}
8771						} catch( firefoxAccessException ) {
8772							if ( !isAbort ) {
8773								complete( -1, firefoxAccessException );
8774							}
8775						}
8776
8777						// Call complete if needed
8778						if ( responses ) {
8779							complete( status, statusText, responses, responseHeaders );
8780						}
8781					};
8782
8783					if ( !s.async ) {
8784						// if we're in sync mode we fire the callback
8785						callback();
8786					} else if ( xhr.readyState === 4 ) {
8787						// (IE6 & IE7) if it's in cache and has been
8788						// retrieved directly we need to fire the callback
8789						setTimeout( callback );
8790					} else {
8791						handle = ++xhrId;
8792						if ( xhrOnUnloadAbort ) {
8793							// Create the active xhrs callbacks list if needed
8794							// and attach the unload handler
8795							if ( !xhrCallbacks ) {
8796								xhrCallbacks = {};
8797								jQuery( window ).unload( xhrOnUnloadAbort );
8798							}
8799							// Add to list of active xhrs callbacks
8800							xhrCallbacks[ handle ] = callback;
8801						}
8802						xhr.onreadystatechange = callback;
8803					}
8804				},
8805
8806				abort: function() {
8807					if ( callback ) {
8808						callback( undefined, true );
8809					}
8810				}
8811			};
8812		}
8813	});
8814}
8815var fxNow, timerId,
8816	rfxtypes = /^(?:toggle|show|hide)$/,
8817	rfxnum = new RegExp( "^(?:([+-])=|)(" + core_pnum + ")([a-z%]*)$", "i" ),
8818	rrun = /queueHooks$/,
8819	animationPrefilters = [ defaultPrefilter ],
8820	tweeners = {
8821		"*": [function( prop, value ) {
8822			var tween = this.createTween( prop, value ),
8823				target = tween.cur(),
8824				parts = rfxnum.exec( value ),
8825				unit = parts && parts[ 3 ] || ( jQuery.cssNumber[ prop ] ? "" : "px" ),
vendor: 23,716 bytes, lines 8826-9717
8826
8827				// Starting value computation is required for potential unit mismatches
8828				start = ( jQuery.cssNumber[ prop ] || unit !== "px" && +target ) &&
8829					rfxnum.exec( jQuery.css( tween.elem, prop ) ),
8830				scale = 1,
8831				maxIterations = 20;
8832
8833			if ( start && start[ 3 ] !== unit ) {
8834				// Trust units reported by jQuery.css
8835				unit = unit || start[ 3 ];
8836
8837				// Make sure we update the tween properties later on
8838				parts = parts || [];
8839
8840				// Iteratively approximate from a nonzero starting point
8841				start = +target || 1;
8842
8843				do {
8844					// If previous iteration zeroed out, double until we get *something*
8845					// Use a string for doubling factor so we don't accidentally see scale as unchanged below
8846					scale = scale || ".5";
8847
8848					// Adjust and apply
8849					start = start / scale;
8850					jQuery.style( tween.elem, prop, start + unit );
8851
8852				// Update scale, tolerating zero or NaN from tween.cur()
8853				// And breaking the loop if scale is unchanged or perfect, or if we've just had enough
8854				} while ( scale !== (scale = tween.cur() / target) && scale !== 1 && --maxIterations );
8855			}
8856
8857			// Update tween properties
8858			if ( parts ) {
8859				start = tween.start = +start || +target || 0;
8860				tween.unit = unit;
8861				// If a +=/-= token was provided, we're doing a relative animation
8862				tween.end = parts[ 1 ] ?
8863					start + ( parts[ 1 ] + 1 ) * parts[ 2 ] :
8864					+parts[ 2 ];
8865			}
8866
8867			return tween;
8868		}]
8869	};
8870
8871// Animations created synchronously will run synchronously
8872function createFxNow() {
8873	setTimeout(function() {
8874		fxNow = undefined;
8875	});
8876	return ( fxNow = jQuery.now() );
8877}
8878
8879function createTween( value, prop, animation ) {
8880	var tween,
8881		collection = ( tweeners[ prop ] || [] ).concat( tweeners[ "*" ] ),
8882		index = 0,
8883		length = collection.length;
8884	for ( ; index < length; index++ ) {
8885		if ( (tween = collection[ index ].call( animation, prop, value )) ) {
8886
8887			// we're done with this property
8888			return tween;
8889		}
8890	}
8891}
8892
8893function Animation( elem, properties, options ) {
8894	var result,
8895		stopped,
8896		index = 0,
8897		length = animationPrefilters.length,
8898		deferred = jQuery.Deferred().always( function() {
8899			// don't match elem in the :animated selector
8900			delete tick.elem;
8901		}),
8902		tick = function() {
8903			if ( stopped ) {
8904				return false;
8905			}
8906			var currentTime = fxNow || createFxNow(),
8907				remaining = Math.max( 0, animation.startTime + animation.duration - currentTime ),
8908				// archaic crash bug won't allow us to use 1 - ( 0.5 || 0 ) (#12497)
8909				temp = remaining / animation.duration || 0,
8910				percent = 1 - temp,
8911				index = 0,
8912				length = animation.tweens.length;
8913
8914			for ( ; index < length ; index++ ) {
8915				animation.tweens[ index ].run( percent );
8916			}
8917
8918			deferred.notifyWith( elem, [ animation, percent, remaining ]);
8919
8920			if ( percent < 1 && length ) {
8921				return remaining;
8922			} else {
8923				deferred.resolveWith( elem, [ animation ] );
8924				return false;
8925			}
8926		},
8927		animation = deferred.promise({
8928			elem: elem,
8929			props: jQuery.extend( {}, properties ),
8930			opts: jQuery.extend( true, { specialEasing: {} }, options ),
8931			originalProperties: properties,
8932			originalOptions: options,
8933			startTime: fxNow || createFxNow(),
8934			duration: options.duration,
8935			tweens: [],
8936			createTween: function( prop, end ) {
8937				var tween = jQuery.Tween( elem, animation.opts, prop, end,
8938						animation.opts.specialEasing[ prop ] || animation.opts.easing );
8939				animation.tweens.push( tween );
8940				return tween;
8941			},
8942			stop: function( gotoEnd ) {
8943				var index = 0,
8944					// if we are going to the end, we want to run all the tweens
8945					// otherwise we skip this part
8946					length = gotoEnd ? animation.tweens.length : 0;
8947				if ( stopped ) {
8948					return this;
8949				}
8950				stopped = true;
8951				for ( ; index < length ; index++ ) {
8952					animation.tweens[ index ].run( 1 );
8953				}
8954
8955				// resolve when we played the last frame
8956				// otherwise, reject
8957				if ( gotoEnd ) {
8958					deferred.resolveWith( elem, [ animation, gotoEnd ] );
8959				} else {
8960					deferred.rejectWith( elem, [ animation, gotoEnd ] );
8961				}
8962				return this;
8963			}
8964		}),
8965		props = animation.props;
8966
8967	propFilter( props, animation.opts.specialEasing );
8968
8969	for ( ; index < length ; index++ ) {
8970		result = animationPrefilters[ index ].call( animation, elem, props, animation.opts );
8971		if ( result ) {
8972			return result;
8973		}
8974	}
8975
8976	jQuery.map( props, createTween, animation );
8977
8978	if ( jQuery.isFunction( animation.opts.start ) ) {
8979		animation.opts.start.call( elem, animation );
8980	}
8981
8982	jQuery.fx.timer(
8983		jQuery.extend( tick, {
8984			elem: elem,
8985			anim: animation,
8986			queue: animation.opts.queue
8987		})
8988	);
8989
8990	// attach callbacks from options
8991	return animation.progress( animation.opts.progress )
8992		.done( animation.opts.done, animation.opts.complete )
8993		.fail( animation.opts.fail )
8994		.always( animation.opts.always );
8995}
8996
8997function propFilter( props, specialEasing ) {
8998	var index, name, easing, value, hooks;
8999
9000	// camelCase, specialEasing and expand cssHook pass
9001	for ( index in props ) {
9002		name = jQuery.camelCase( index );
9003		easing = specialEasing[ name ];
9004		value = props[ index ];
9005		if ( jQuery.isArray( value ) ) {
9006			easing = value[ 1 ];
9007			value = props[ index ] = value[ 0 ];
9008		}
9009
9010		if ( index !== name ) {
9011			props[ name ] = value;
9012			delete props[ index ];
9013		}
9014
9015		hooks = jQuery.cssHooks[ name ];
9016		if ( hooks && "expand" in hooks ) {
9017			value = hooks.expand( value );
9018			delete props[ name ];
9019
9020			// not quite $.extend, this wont overwrite keys already present.
9021			// also - reusing 'index' from above because we have the correct "name"
9022			for ( index in value ) {
9023				if ( !( index in props ) ) {
9024					props[ index ] = value[ index ];
9025					specialEasing[ index ] = easing;
9026				}
9027			}
9028		} else {
9029			specialEasing[ name ] = easing;
9030		}
9031	}
9032}
9033
9034jQuery.Animation = jQuery.extend( Animation, {
9035
9036	tweener: function( props, callback ) {
9037		if ( jQuery.isFunction( props ) ) {
9038			callback = props;
9039			props = [ "*" ];
9040		} else {
9041			props = props.split(" ");
9042		}
9043
9044		var prop,
9045			index = 0,
9046			length = props.length;
9047
9048		for ( ; index < length ; index++ ) {
9049			prop = props[ index ];
9050			tweeners[ prop ] = tweeners[ prop ] || [];
9051			tweeners[ prop ].unshift( callback );
9052		}
9053	},
9054
9055	prefilter: function( callback, prepend ) {
9056		if ( prepend ) {
9057			animationPrefilters.unshift( callback );
9058		} else {
9059			animationPrefilters.push( callback );
9060		}
9061	}
9062});
9063
9064function defaultPrefilter( elem, props, opts ) {
9065	/* jshint validthis: true */
9066	var prop, value, toggle, tween, hooks, oldfire,
9067		anim = this,
9068		orig = {},
9069		style = elem.style,
9070		hidden = elem.nodeType && isHidden( elem ),
9071		dataShow = jQuery._data( elem, "fxshow" );
9072
9073	// handle queue: false promises
9074	if ( !opts.queue ) {
9075		hooks = jQuery._queueHooks( elem, "fx" );
9076		if ( hooks.unqueued == null ) {
9077			hooks.unqueued = 0;
9078			oldfire = hooks.empty.fire;
9079			hooks.empty.fire = function() {
9080				if ( !hooks.unqueued ) {
9081					oldfire();
9082				}
9083			};
9084		}
9085		hooks.unqueued++;
9086
9087		anim.always(function() {
9088			// doing this makes sure that the complete handler will be called
9089			// before this completes
9090			anim.always(function() {
9091				hooks.unqueued--;
9092				if ( !jQuery.queue( elem, "fx" ).length ) {
9093					hooks.empty.fire();
9094				}
9095			});
9096		});
9097	}
9098
9099	// height/width overflow pass
9100	if ( elem.nodeType === 1 && ( "height" in props || "width" in props ) ) {
9101		// Make sure that nothing sneaks out
9102		// Record all 3 overflow attributes because IE does not
9103		// change the overflow attribute when overflowX and
9104		// overflowY are set to the same value
9105		opts.overflow = [ style.overflow, style.overflowX, style.overflowY ];
9106
9107		// Set display property to inline-block for height/width
9108		// animations on inline elements that are having width/height animated
9109		if ( jQuery.css( elem, "display" ) === "inline" &&
9110				jQuery.css( elem, "float" ) === "none" ) {
9111
9112			// inline-level elements accept inline-block;
9113			// block-level elements need to be inline with layout
9114			if ( !jQuery.support.inlineBlockNeedsLayout || css_defaultDisplay( elem.nodeName ) === "inline" ) {
9115				style.display = "inline-block";
9116
9117			} else {
9118				style.zoom = 1;
9119			}
9120		}
9121	}
9122
9123	if ( opts.overflow ) {
9124		style.overflow = "hidden";
9125		if ( !jQuery.support.shrinkWrapBlocks ) {
9126			anim.always(function() {
9127				style.overflow = opts.overflow[ 0 ];
9128				style.overflowX = opts.overflow[ 1 ];
9129				style.overflowY = opts.overflow[ 2 ];
9130			});
9131		}
9132	}
9133
9134
9135	// show/hide pass
9136	for ( prop in props ) {
9137		value = props[ prop ];
9138		if ( rfxtypes.exec( value ) ) {
9139			delete props[ prop ];
9140			toggle = toggle || value === "toggle";
9141			if ( value === ( hidden ? "hide" : "show" ) ) {
9142				continue;
9143			}
9144			orig[ prop ] = dataShow && dataShow[ prop ] || jQuery.style( elem, prop );
9145		}
9146	}
9147
9148	if ( !jQuery.isEmptyObject( orig ) ) {
9149		if ( dataShow ) {
9150			if ( "hidden" in dataShow ) {
9151				hidden = dataShow.hidden;
9152			}
9153		} else {
9154			dataShow = jQuery._data( elem, "fxshow", {} );
9155		}
9156
9157		// store state if its toggle - enables .stop().toggle() to "reverse"
9158		if ( toggle ) {
9159			dataShow.hidden = !hidden;
9160		}
9161		if ( hidden ) {
9162			jQuery( elem ).show();
9163		} else {
9164			anim.done(function() {
9165				jQuery( elem ).hide();
9166			});
9167		}
9168		anim.done(function() {
9169			var prop;
9170			jQuery._removeData( elem, "fxshow" );
9171			for ( prop in orig ) {
9172				jQuery.style( elem, prop, orig[ prop ] );
9173			}
9174		});
9175		for ( prop in orig ) {
9176			tween = createTween( hidden ? dataShow[ prop ] : 0, prop, anim );
9177
9178			if ( !( prop in dataShow ) ) {
9179				dataShow[ prop ] = tween.start;
9180				if ( hidden ) {
9181					tween.end = tween.start;
9182					tween.start = prop === "width" || prop === "height" ? 1 : 0;
9183				}
9184			}
9185		}
9186	}
9187}
9188
9189function Tween( elem, options, prop, end, easing ) {
9190	return new Tween.prototype.init( elem, options, prop, end, easing );
9191}
9192jQuery.Tween = Tween;
9193
9194Tween.prototype = {
9195	constructor: Tween,
9196	init: function( elem, options, prop, end, easing, unit ) {
9197		this.elem = elem;
9198		this.prop = prop;
9199		this.easing = easing || "swing";
9200		this.options = options;
9201		this.start = this.now = this.cur();
9202		this.end = end;
9203		this.unit = unit || ( jQuery.cssNumber[ prop ] ? "" : "px" );
9204	},
9205	cur: function() {
9206		var hooks = Tween.propHooks[ this.prop ];
9207
9208		return hooks && hooks.get ?
9209			hooks.get( this ) :
9210			Tween.propHooks._default.get( this );
9211	},
9212	run: function( percent ) {
9213		var eased,
9214			hooks = Tween.propHooks[ this.prop ];
9215
9216		if ( this.options.duration ) {
9217			this.pos = eased = jQuery.easing[ this.easing ](
9218				percent, this.options.duration * percent, 0, 1, this.options.duration
9219			);
9220		} else {
9221			this.pos = eased = percent;
9222		}
9223		this.now = ( this.end - this.start ) * eased + this.start;
9224
9225		if ( this.options.step ) {
9226			this.options.step.call( this.elem, this.now, this );
9227		}
9228
9229		if ( hooks && hooks.set ) {
9230			hooks.set( this );
9231		} else {
9232			Tween.propHooks._default.set( this );
9233		}
9234		return this;
9235	}
9236};
9237
9238Tween.prototype.init.prototype = Tween.prototype;
9239
9240Tween.propHooks = {
9241	_default: {
9242		get: function( tween ) {
9243			var result;
9244
9245			if ( tween.elem[ tween.prop ] != null &&
9246				(!tween.elem.style || tween.elem.style[ tween.prop ] == null) ) {
9247				return tween.elem[ tween.prop ];
9248			}
9249
9250			// passing an empty string as a 3rd parameter to .css will automatically
9251			// attempt a parseFloat and fallback to a string if the parse fails
9252			// so, simple values such as "10px" are parsed to Float.
9253			// complex values such as "rotate(1rad)" are returned as is.
9254			result = jQuery.css( tween.elem, tween.prop, "" );
9255			// Empty strings, null, undefined and "auto" are converted to 0.
9256			return !result || result === "auto" ? 0 : result;
9257		},
9258		set: function( tween ) {
9259			// use step hook for back compat - use cssHook if its there - use .style if its
9260			// available and use plain properties where available
9261			if ( jQuery.fx.step[ tween.prop ] ) {
9262				jQuery.fx.step[ tween.prop ]( tween );
9263			} else if ( tween.elem.style && ( tween.elem.style[ jQuery.cssProps[ tween.prop ] ] != null || jQuery.cssHooks[ tween.prop ] ) ) {
9264				jQuery.style( tween.elem, tween.prop, tween.now + tween.unit );
9265			} else {
9266				tween.elem[ tween.prop ] = tween.now;
9267			}
9268		}
9269	}
9270};
9271
9272// Support: IE <=9
9273// Panic based approach to setting things on disconnected nodes
9274
9275Tween.propHooks.scrollTop = Tween.propHooks.scrollLeft = {
9276	set: function( tween ) {
9277		if ( tween.elem.nodeType && tween.elem.parentNode ) {
9278			tween.elem[ tween.prop ] = tween.now;
9279		}
9280	}
9281};
9282
9283jQuery.each([ "toggle", "show", "hide" ], function( i, name ) {
9284	var cssFn = jQuery.fn[ name ];
9285	jQuery.fn[ name ] = function( speed, easing, callback ) {
9286		return speed == null || typeof speed === "boolean" ?
9287			cssFn.apply( this, arguments ) :
9288			this.animate( genFx( name, true ), speed, easing, callback );
9289	};
9290});
9291
9292jQuery.fn.extend({
9293	fadeTo: function( speed, to, easing, callback ) {
9294
9295		// show any hidden elements after setting opacity to 0
9296		return this.filter( isHidden ).css( "opacity", 0 ).show()
9297
9298			// animate to the value specified
9299			.end().animate({ opacity: to }, speed, easing, callback );
9300	},
9301	animate: function( prop, speed, easing, callback ) {
9302		var empty = jQuery.isEmptyObject( prop ),
9303			optall = jQuery.speed( speed, easing, callback ),
9304			doAnimation = function() {
9305				// Operate on a copy of prop so per-property easing won't be lost
9306				var anim = Animation( this, jQuery.extend( {}, prop ), optall );
9307
9308				// Empty animations, or finishing resolves immediately
9309				if ( empty || jQuery._data( this, "finish" ) ) {
9310					anim.stop( true );
9311				}
9312			};
9313			doAnimation.finish = doAnimation;
9314
9315		return empty || optall.queue === false ?
9316			this.each( doAnimation ) :
9317			this.queue( optall.queue, doAnimation );
9318	},
9319	stop: function( type, clearQueue, gotoEnd ) {
9320		var stopQueue = function( hooks ) {
9321			var stop = hooks.stop;
9322			delete hooks.stop;
9323			stop( gotoEnd );
9324		};
9325
9326		if ( typeof type !== "string" ) {
9327			gotoEnd = clearQueue;
9328			clearQueue = type;
9329			type = undefined;
9330		}
9331		if ( clearQueue && type !== false ) {
9332			this.queue( type || "fx", [] );
9333		}
9334
9335		return this.each(function() {
9336			var dequeue = true,
9337				index = type != null && type + "queueHooks",
9338				timers = jQuery.timers,
9339				data = jQuery._data( this );
9340
9341			if ( index ) {
9342				if ( data[ index ] && data[ index ].stop ) {
9343					stopQueue( data[ index ] );
9344				}
9345			} else {
9346				for ( index in data ) {
9347					if ( data[ index ] && data[ index ].stop && rrun.test( index ) ) {
9348						stopQueue( data[ index ] );
9349					}
9350				}
9351			}
9352
9353			for ( index = timers.length; index--; ) {
9354				if ( timers[ index ].elem === this && (type == null || timers[ index ].queue === type) ) {
9355					timers[ index ].anim.stop( gotoEnd );
9356					dequeue = false;
9357					timers.splice( index, 1 );
9358				}
9359			}
9360
9361			// start the next in the queue if the last step wasn't forced
9362			// timers currently will call their complete callbacks, which will dequeue
9363			// but only if they were gotoEnd
9364			if ( dequeue || !gotoEnd ) {
9365				jQuery.dequeue( this, type );
9366			}
9367		});
9368	},
9369	finish: function( type ) {
9370		if ( type !== false ) {
9371			type = type || "fx";
9372		}
9373		return this.each(function() {
9374			var index,
9375				data = jQuery._data( this ),
9376				queue = data[ type + "queue" ],
9377				hooks = data[ type + "queueHooks" ],
9378				timers = jQuery.timers,
9379				length = queue ? queue.length : 0;
9380
9381			// enable finishing flag on private data
9382			data.finish = true;
9383
9384			// empty the queue first
9385			jQuery.queue( this, type, [] );
9386
9387			if ( hooks && hooks.stop ) {
9388				hooks.stop.call( this, true );
9389			}
9390
9391			// look for any active animations, and finish them
9392			for ( index = timers.length; index--; ) {
9393				if ( timers[ index ].elem === this && timers[ index ].queue === type ) {
9394					timers[ index ].anim.stop( true );
9395					timers.splice( index, 1 );
9396				}
9397			}
9398
9399			// look for any animations in the old queue and finish them
9400			for ( index = 0; index < length; index++ ) {
9401				if ( queue[ index ] && queue[ index ].finish ) {
9402					queue[ index ].finish.call( this );
9403				}
9404			}
9405
9406			// turn off finishing flag
9407			delete data.finish;
9408		});
9409	}
9410});
9411
9412// Generate parameters to create a standard animation
9413function genFx( type, includeWidth ) {
9414	var which,
9415		attrs = { height: type },
9416		i = 0;
9417
9418	// if we include width, step value is 1 to do all cssExpand values,
9419	// if we don't include width, step value is 2 to skip over Left and Right
9420	includeWidth = includeWidth? 1 : 0;
9421	for( ; i < 4 ; i += 2 - includeWidth ) {
9422		which = cssExpand[ i ];
9423		attrs[ "margin" + which ] = attrs[ "padding" + which ] = type;
9424	}
9425
9426	if ( includeWidth ) {
9427		attrs.opacity = attrs.width = type;
9428	}
9429
9430	return attrs;
9431}
9432
9433// Generate shortcuts for custom animations
9434jQuery.each({
9435	slideDown: genFx("show"),
9436	slideUp: genFx("hide"),
9437	slideToggle: genFx("toggle"),
9438	fadeIn: { opacity: "show" },
9439	fadeOut: { opacity: "hide" },
9440	fadeToggle: { opacity: "toggle" }
9441}, function( name, props ) {
9442	jQuery.fn[ name ] = function( speed, easing, callback ) {
9443		return this.animate( props, speed, easing, callback );
9444	};
9445});
9446
9447jQuery.speed = function( speed, easing, fn ) {
9448	var opt = speed && typeof speed === "object" ? jQuery.extend( {}, speed ) : {
9449		complete: fn || !fn && easing ||
9450			jQuery.isFunction( speed ) && speed,
9451		duration: speed,
9452		easing: fn && easing || easing && !jQuery.isFunction( easing ) && easing
9453	};
9454
9455	opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
9456		opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[ opt.duration ] : jQuery.fx.speeds._default;
9457
9458	// normalize opt.queue - true/undefined/null -> "fx"
9459	if ( opt.queue == null || opt.queue === true ) {
9460		opt.queue = "fx";
9461	}
9462
9463	// Queueing
9464	opt.old = opt.complete;
9465
9466	opt.complete = function() {
9467		if ( jQuery.isFunction( opt.old ) ) {
9468			opt.old.call( this );
9469		}
9470
9471		if ( opt.queue ) {
9472			jQuery.dequeue( this, opt.queue );
9473		}
9474	};
9475
9476	return opt;
9477};
9478
9479jQuery.easing = {
9480	linear: function( p ) {
9481		return p;
9482	},
9483	swing: function( p ) {
9484		return 0.5 - Math.cos( p*Math.PI ) / 2;
9485	}
9486};
9487
9488jQuery.timers = [];
9489jQuery.fx = Tween.prototype.init;
9490jQuery.fx.tick = function() {
9491	var timer,
9492		timers = jQuery.timers,
9493		i = 0;
9494
9495	fxNow = jQuery.now();
9496
9497	for ( ; i < timers.length; i++ ) {
9498		timer = timers[ i ];
9499		// Checks the timer has not already been removed
9500		if ( !timer() && timers[ i ] === timer ) {
9501			timers.splice( i--, 1 );
9502		}
9503	}
9504
9505	if ( !timers.length ) {
9506		jQuery.fx.stop();
9507	}
9508	fxNow = undefined;
9509};
9510
9511jQuery.fx.timer = function( timer ) {
9512	if ( timer() && jQuery.timers.push( timer ) ) {
9513		jQuery.fx.start();
9514	}
9515};
9516
9517jQuery.fx.interval = 13;
9518
9519jQuery.fx.start = function() {
9520	if ( !timerId ) {
9521		timerId = setInterval( jQuery.fx.tick, jQuery.fx.interval );
9522	}
9523};
9524
9525jQuery.fx.stop = function() {
9526	clearInterval( timerId );
9527	timerId = null;
9528};
9529
9530jQuery.fx.speeds = {
9531	slow: 600,
9532	fast: 200,
9533	// Default speed
9534	_default: 400
9535};
9536
9537// Back Compat <1.8 extension point
9538jQuery.fx.step = {};
9539
9540if ( jQuery.expr && jQuery.expr.filters ) {
9541	jQuery.expr.filters.animated = function( elem ) {
9542		return jQuery.grep(jQuery.timers, function( fn ) {
9543			return elem === fn.elem;
9544		}).length;
9545	};
9546}
9547jQuery.fn.offset = function( options ) {
9548	if ( arguments.length ) {
9549		return options === undefined ?
9550			this :
9551			this.each(function( i ) {
9552				jQuery.offset.setOffset( this, options, i );
9553			});
9554	}
9555
9556	var docElem, win,
9557		box = { top: 0, left: 0 },
9558		elem = this[ 0 ],
9559		doc = elem && elem.ownerDocument;
9560
9561	if ( !doc ) {
9562		return;
9563	}
9564
9565	docElem = doc.documentElement;
9566
9567	// Make sure it's not a disconnected DOM node
9568	if ( !jQuery.contains( docElem, elem ) ) {
9569		return box;
9570	}
9571
9572	// If we don't have gBCR, just use 0,0 rather than error
9573	// BlackBerry 5, iOS 3 (original iPhone)
9574	if ( typeof elem.getBoundingClientRect !== core_strundefined ) {
9575		box = elem.getBoundingClientRect();
9576	}
9577	win = getWindow( doc );
9578	return {
9579		top: box.top  + ( win.pageYOffset || docElem.scrollTop )  - ( docElem.clientTop  || 0 ),
9580		left: box.left + ( win.pageXOffset || docElem.scrollLeft ) - ( docElem.clientLeft || 0 )
9581	};
9582};
9583
9584jQuery.offset = {
9585
9586	setOffset: function( elem, options, i ) {
9587		var position = jQuery.css( elem, "position" );
9588
9589		// set position first, in-case top/left are set even on static elem
9590		if ( position === "static" ) {
9591			elem.style.position = "relative";
9592		}
9593
9594		var curElem = jQuery( elem ),
9595			curOffset = curElem.offset(),
9596			curCSSTop = jQuery.css( elem, "top" ),
9597			curCSSLeft = jQuery.css( elem, "left" ),
9598			calculatePosition = ( position === "absolute" || position === "fixed" ) && jQuery.inArray("auto", [curCSSTop, curCSSLeft]) > -1,
9599			props = {}, curPosition = {}, curTop, curLeft;
9600
9601		// need to be able to calculate position if either top or left is auto and position is either absolute or fixed
9602		if ( calculatePosition ) {
9603			curPosition = curElem.position();
9604			curTop = curPosition.top;
9605			curLeft = curPosition.left;
9606		} else {
9607			curTop = parseFloat( curCSSTop ) || 0;
9608			curLeft = parseFloat( curCSSLeft ) || 0;
9609		}
9610
9611		if ( jQuery.isFunction( options ) ) {
9612			options = options.call( elem, i, curOffset );
9613		}
9614
9615		if ( options.top != null ) {
9616			props.top = ( options.top - curOffset.top ) + curTop;
9617		}
9618		if ( options.left != null ) {
9619			props.left = ( options.left - curOffset.left ) + curLeft;
9620		}
9621
9622		if ( "using" in options ) {
9623			options.using.call( elem, props );
9624		} else {
9625			curElem.css( props );
9626		}
9627	}
9628};
9629
9630
9631jQuery.fn.extend({
9632
9633	position: function() {
9634		if ( !this[ 0 ] ) {
9635			return;
9636		}
9637
9638		var offsetParent, offset,
9639			parentOffset = { top: 0, left: 0 },
9640			elem = this[ 0 ];
9641
9642		// fixed elements are offset from window (parentOffset = {top:0, left: 0}, because it is it's only offset parent
9643		if ( jQuery.css( elem, "position" ) === "fixed" ) {
9644			// we assume that getBoundingClientRect is available when computed position is fixed
9645			offset = elem.getBoundingClientRect();
9646		} else {
9647			// Get *real* offsetParent
9648			offsetParent = this.offsetParent();
9649
9650			// Get correct offsets
9651			offset = this.offset();
9652			if ( !jQuery.nodeName( offsetParent[ 0 ], "html" ) ) {
9653				parentOffset = offsetParent.offset();
9654			}
9655
9656			// Add offsetParent borders
9657			parentOffset.top  += jQuery.css( offsetParent[ 0 ], "borderTopWidth", true );
9658			parentOffset.left += jQuery.css( offsetParent[ 0 ], "borderLeftWidth", true );
9659		}
9660
9661		// Subtract parent offsets and element margins
9662		// note: when an element has margin: auto the offsetLeft and marginLeft
9663		// are the same in Safari causing offset.left to incorrectly be 0
9664		return {
9665			top:  offset.top  - parentOffset.top - jQuery.css( elem, "marginTop", true ),
9666			left: offset.left - parentOffset.left - jQuery.css( elem, "marginLeft", true)
9667		};
9668	},
9669
9670	offsetParent: function() {
9671		return this.map(function() {
9672			var offsetParent = this.offsetParent || docElem;
9673			while ( offsetParent && ( !jQuery.nodeName( offsetParent, "html" ) && jQuery.css( offsetParent, "position") === "static" ) ) {
9674				offsetParent = offsetParent.offsetParent;
9675			}
9676			return offsetParent || docElem;
9677		});
9678	}
9679});
9680
9681
9682// Create scrollLeft and scrollTop methods
9683jQuery.each( {scrollLeft: "pageXOffset", scrollTop: "pageYOffset"}, function( method, prop ) {
9684	var top = /Y/.test( prop );
9685
9686	jQuery.fn[ method ] = function( val ) {
9687		return jQuery.access( this, function( elem, method, val ) {
9688			var win = getWindow( elem );
9689
9690			if ( val === undefined ) {
9691				return win ? (prop in win) ? win[ prop ] :
9692					win.document.documentElement[ method ] :
9693					elem[ method ];
9694			}
9695
9696			if ( win ) {
9697				win.scrollTo(
9698					!top ? val : jQuery( win ).scrollLeft(),
9699					top ? val : jQuery( win ).scrollTop()
9700				);
9701
9702			} else {
9703				elem[ method ] = val;
9704			}
9705		}, method, val, arguments.length, null );
9706	};
9707});
9708
9709function getWindow( elem ) {
9710	return jQuery.isWindow( elem ) ?
9711		elem :
9712		elem.nodeType === 9 ?
9713			elem.defaultView || elem.parentWindow :
9714			false;
9715}
9716// Create innerHeight, innerWidth, height, width, outerHeight and outerWidth methods
9717jQuery.each( { Height: "height", Width: "width" }, function( name, type ) {
9718	jQuery.each( { padding: "inner" + name, content: type, "": "outer" + name }, function( defaultExtra, funcName ) {
9719		// margin is only for outerHeight, outerWidth
9720		jQuery.fn[ funcName ] = function( margin, value ) {
9721			var chainable = arguments.length && ( defaultExtra || typeof margin !== "boolean" ),
9722				extra = defaultExtra || ( margin === true || value === true ? "margin" : "border" );
9723
9724			return jQuery.access( this, function( elem, type, value ) {
9725				var doc;
9726
9727				if ( jQuery.isWindow( elem ) ) {
9728					// As of 5/8/2012 this will yield incorrect results for Mobile Safari, but there
9729					// isn't a whole lot we can do. See pull request at this URL for discussion:
9730					// https://github.com/jquery/jquery/pull/764
9731					return elem.document.documentElement[ "client" + name ];
9732				}
9733
9734				// Get document width or height
9735				if ( elem.nodeType === 9 ) {
9736					doc = elem.documentElement;
9737
9738					// Either scroll[Width/Height] or offset[Width/Height] or client[Width/Height], whichever is greatest
9739					// unfortunately, this causes bug #3838 in IE6/8 only, but there is currently no good, small way to fix it.
9740					return Math.max(
9741						elem.body[ "scroll" + name ], doc[ "scroll" + name ],
9742						elem.body[ "offset" + name ], doc[ "offset" + name ],
9743						doc[ "client" + name ]
9744					);
9745				}
9746
9747				return value === undefined ?
9748					// Get width or height on the element, requesting but not forcing parseFloat
9749					jQuery.css( elem, type, extra ) :
9750
9751					// Set width or height on the element
9752					jQuery.style( elem, type, value, extra );
9753			}, type, chainable ? margin : undefined, chainable, null );
9754		};
9755	});
9756});
9757// Limit scope pollution from any deprecated API
9758// (function() {
9759
9760// The number of elements contained in the matched element set
9761jQuery.fn.size = function() {
9762	return this.length;
9763};
9764
9765jQuery.fn.andSelf = jQuery.fn.addBack;
9766
9767// })();
9768if ( typeof module === "object" && module && typeof module.exports === "object" ) {
9769	// Expose jQuery as module.exports in loaders that implement the Node
9770	// module pattern (including browserify). Do not create the global, since
9771	// the user will be storing it themselves locally, and globals are frowned
9772	// upon in the Node module world.
9773	module.exports = jQuery;
9774} else {
9775	// Otherwise expose jQuery to the global object as usual
9776	window.jQuery = window.$ = jQuery;
9777
9778	// Register as a named AMD module, since jQuery can be concatenated with other
9779	// files that may use define, but not via a proper concatenation script that
9780	// understands anonymous AMD modules. A named AMD is safest and most robust
9781	// way to register. Lowercase jquery is used because AMD module names are
9782	// derived from file names, and jQuery is normally delivered in a lowercase
9783	// file name. Do this after creating the global so that if an AMD module wants
9784	// to call noConflict to hide this version of jQuery, it will work.
9785	if ( typeof define === "function" && define.amd ) {
9786		define( "jquery", [], function () { return jQuery; } );
9787	}
9788}
9789
9790})( window );
9791/*! jQuery Migrate v1.2.1 | (c) 2005, 2013 jQuery Foundation, Inc. and other contributors | jquery.org/license */
9792
9793jQuery.migrateMute===void 0&&(jQuery.migrateMute=!0),function(e,t,n){function r(n){var r=t.console;i[n]||(i[n]=!0,e.migrateWarnings.push(n),r&&r.warn&&!e.migrateMute&&(r.warn("JQMIGRATE: "+n),e.migrateTrace&&r.trace&&r.trace()))}function a(t,a,i,o){if(Object.defineProperty)try{return Object.defineProperty(t,a,{configurable:!0,enumerable:!0,get:function(){return r(o),i},set:function(e){r(o),i=e}}),n}catch(s){}e._definePropertyBroken=!0,t[a]=i}var i={};e.migrateWarnings=[],!e.migrateMute&&t.console&&t.console.log&&t.console.log("JQMIGRATE: Logging is active"),e.migrateTrace===n&&(e.migrateTrace=!0),e.migrateReset=function(){i={},e.migrateWarnings.length=0},"BackCompat"===document.compatMode&&r("jQuery is not compatible with Quirks Mode");var o=e("<input/>",{size:1}).attr("size")&&e.attrFn,s=e.attr,u=e.attrHooks.value&&e.attrHooks.value.get||function(){return null},c=e.attrHooks.value&&e.attrHooks.value.set||function(){return n},l=/^(?:input|button)$/i,d=/^[238]$/,p=/^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,f=/^(?:checked|selected)$/i;a(e,"attrFn",o||{},"jQuery.attrFn is deprecated"),e.attr=function(t,a,i,u){var c=a.toLowerCase(),g=t&&t.nodeType;return u&&(4>s.length&&r("jQuery.fn.attr( props, pass ) is deprecated"),t&&!d.test(g)&&(o?a in o:e.isFunction(e.fn[a])))?e(t)[a](i):("type"===a&&i!==n&&l.test(t.no
vendor: 5,981 bytes, lines 9793-9809
9793deName)&&t.parentNode&&r("Can't change the 'type' of an input or button in IE 6/7/8"),!e.attrHooks[c]&&p.test(c)&&(e.attrHooks[c]={get:function(t,r){var a,i=e.prop(t,r);return i===!0||"boolean"!=typeof i&&(a=t.getAttributeNode(r))&&a.nodeValue!==!1?r.toLowerCase():n},set:function(t,n,r){var a;return n===!1?e.removeAttr(t,r):(a=e.propFix[r]||r,a in t&&(t[a]=!0),t.setAttribute(r,r.toLowerCase())),r}},f.test(c)&&r("jQuery.fn.attr('"+c+"') may use property instead of attribute")),s.call(e,t,a,i))},e.attrHooks.value={get:function(e,t){var n=(e.nodeName||"").toLowerCase();return"button"===n?u.apply(this,arguments):("input"!==n&&"option"!==n&&r("jQuery.fn.attr('value') no longer gets properties"),t in e?e.value:null)},set:function(e,t){var a=(e.nodeName||"").toLowerCase();return"button"===a?c.apply(this,arguments):("input"!==a&&"option"!==a&&r("jQuery.fn.attr('value', val) no longer sets properties"),e.value=t,n)}};var g,h,v=e.fn.init,m=e.parseJSON,y=/^([^<]*)(<[\w\W]+>)([^>]*)$/;e.fn.init=function(t,n,a){var i;return t&&"string"==typeof t&&!e.isPlainObject(n)&&(i=y.exec(e.trim(t)))&&i[0]&&("<"!==t.charAt(0)&&r("$(html) HTML strings must start with '<' character"),i[3]&&r("$(html) HTML text after last tag is ignored"),"#"===i[0].charAt(0)&&(r("HTML string cannot start with a '#' character"),e.error("JQMIGRATE: Invalid selector string (XSS)")),n&&n.context&&(n=n.context),e.parseHTML)?v.call(this,e.parseHTML(i[2],n,!0),n,a):v.apply(this,arguments)},e.fn.init.prototype=e.fn,e.parseJSON=function(e){return e||null===e?m.apply(this,arguments):(r("jQuery.parseJSON requires a valid JSON string"),null)},e.uaMatch=function(e){e=e.toLowerCase();var t=/(chrome)[ \/]([\w.]+)/.exec(e)||/(webkit)[ \/]([\w.]+)/.exec(e)||/(opera)(?:.*version|)[ \/]([\w.]+)/.exec(e)||/(msie) ([\w.]+)/.exec(e)||0>e.indexOf("compatible")&&/(mozilla)(?:.*? rv:([\w.]+)|)/.exec(e)||[];return{browser:t[1]||"",version:t[2]||"0"}},e.browser||(g=e.uaMatch(navigator.userAgent),h={},g.browser&&(h[g.browser]=!0,h.version=g.version),h.chrome?h.webkit=!0:h.webkit&&(h.safari=!0),e.browser=h),a(e,"browser",e.browser,"jQuery.browser is deprecated"),e.sub=function(){function t(e,n){return new t.fn.init(e,n)}e.extend(!0,t,this),t.superclass=this,t.fn=t.prototype=this(),t.fn.constructor=t,t.sub=this.sub,t.fn.init=function(r,a){return a&&a instanceof e&&!(a instanceof t)&&(a=t(a)),e.fn.init.call(this,r,a,n)},t.fn.init.prototype=t.fn;var n=t(document);return r("jQuery.sub() is deprecated"),t},e.ajaxSetup({converters:{"text json":e.parseJSON}});var b=e.fn.data;e.fn.data=function(t){var a,i,o=this[0];return!o||"events"!==t||1!==arguments.length||(a=e.data(o,t),i=e._data(o,t),a!==n&&a!==i||i===n)?b.apply(this,arguments):(r("Use of jQuery.fn.data('events') is deprecated"),i)};var j=/\/(java|ecma)script/i,w=e.fn.andSelf||e.fn.addBack;e.fn.andSelf=function(){return r("jQuery.fn.andSelf() replaced by jQuery.fn.addBack()"),w.apply(this,arguments)},e.clean||(e.clean=function(t,a,i,o){a=a||document,a=!a.nodeType&&a[0]||a,a=a.ownerDocument||a,r("jQuery.clean() is deprecated");var s,u,c,l,d=[];if(e.merge(d,e.buildFragment(t,a).childNodes),i)for(c=function(e){return!e.type||j.test(e.type)?o?o.push(e.parentNode?e.parentNode.removeChild(e):e):i.appendChild(e):n},s=0;null!=(u=d[s]);s++)e.nodeName(u,"script")&&c(u)||(i.appendChild(u),u.getElementsByTagName!==n&&(l=e.grep(e.merge([],u.getElementsByTagName("script")),c),d.splice.apply(d,[s+1,0].concat(l)),s+=l.length));return d});var Q=e.event.add,x=e.event.remove,k=e.event.trigger,N=e.fn.toggle,T=e.fn.live,M=e.fn.die,S="ajaxStart|ajaxStop|ajaxSend|ajaxComplete|ajaxError|ajaxSuccess",C=RegExp("\\b(?:"+S+")\\b"),H=/(?:^|\s)hover(\.\S+|)\b/,A=function(t){return"string"!=typeof t||e.event.special.hover?t:(H.test(t)&&r("'hover' pseudo-event is deprecated, use 'mouseenter mouseleave'"),t&&t.replace(H,"mouseenter$1 mouseleave$1"))};e.event.props&&"attrChange"!==e.event.props[0]&&e.event.props.unshift("attrChange","attrName","relatedNode","srcElement"),e.event.dispatch&&a(e.event,"handle",e.event.dispatch,"jQuery.event.handle is undocumented and deprecated"),e.event.add=function(e,t,n,a,i){e!==document&&C.test(t)&&r("AJAX events should be attached to document: "+t),Q.call(this,e,A(t||""),n,a,i)},e.event.remove=function(e,t,n,r,a){x.call(this,e,A(t)||"",n,r,a)},e.fn.error=function(){var e=Array.prototype.slice.call(arguments,0);return r("jQuery.fn.error() is deprecated"),e.splice(0,0,"error"),arguments.length?this.bind.apply(this,e):(this.triggerHandler.apply(this,e),this)},e.fn.toggle=function(t,n){if(!e.isFunction(t)||!e.isFunction(n))return N.apply(this,arguments);r("jQuery.fn.toggle(handler, handler...) is deprecated");var a=arguments,i=t.guid||e.guid++,o=0,s=function(n){var r=(e._data(this,"lastToggle"+t.guid)||0)%o;return e._data(this,"lastToggle"+t.guid,r+1),n.preventDefault(),a[r].apply(this,arguments)||!1};for(s.guid=i;a.length>o;)a[o++].guid=i;return this.click(s)},e.fn.live=function(t,n,a){return r("jQuery.fn.live() is deprecated"),T?T.apply(this,arguments):(e(this.context).on(t,this.selector,n,a),this)},e.fn.die=function(t,n){return r("jQuery.fn.die() is deprecated"),M?M.apply(this,arguments):(e(this.context).off(t,this.selector||"**",n),this)},e.event.trigger=function(e,t,n,a){return n||C.test(e)||r("Global events are undocumented and deprecated"),k.call(this,e,t,n||document,a)},e.each(S.split("|"),function(t,n){e.event.special[n]={setup:function(){var t=this;return t!==document&&(e.event.add(document,n+"."+e.guid,function(){e.event.trigger(n,null,t,!0)}),e._data(this,n,e.guid++)),!1},teardown:function(){return this!==document&&e.event.remove(document,n+"."+e._data(this,n)),!1}}})}(jQuery,window);
9794/*!
9795 * jQuery UI Core 1.10.3
9796 * http://jqueryui.com
9797 *
9798 * Copyright 2013 jQuery Foundation and other contributors
9799 * Released under the MIT license.
9800 * http://jquery.org/license
9801 *
9802 * http://api.jqueryui.com/category/ui-core/
9803 */
9804
9805(function( $, undefined ) {
9806
9807var uuid = 0,
9808	runiqueId = /^ui-id-\d+$/;
9809
vendor: 5,040 bytes, lines 9810-9997
9810// $.ui might exist from components with no dependencies, e.g., $.ui.position
9811$.ui = $.ui || {};
9812
9813$.extend( $.ui, {
9814	version: "1.10.3",
9815
9816	keyCode: {
9817		BACKSPACE: 8,
9818		COMMA: 188,
9819		DELETE: 46,
9820		DOWN: 40,
9821		END: 35,
9822		ENTER: 13,
9823		ESCAPE: 27,
9824		HOME: 36,
9825		LEFT: 37,
9826		NUMPAD_ADD: 107,
9827		NUMPAD_DECIMAL: 110,
9828		NUMPAD_DIVIDE: 111,
9829		NUMPAD_ENTER: 108,
9830		NUMPAD_MULTIPLY: 106,
9831		NUMPAD_SUBTRACT: 109,
9832		PAGE_DOWN: 34,
9833		PAGE_UP: 33,
9834		PERIOD: 190,
9835		RIGHT: 39,
9836		SPACE: 32,
9837		TAB: 9,
9838		UP: 38
9839	}
9840});
9841
9842// plugins
9843$.fn.extend({
9844	focus: (function( orig ) {
9845		return function( delay, fn ) {
9846			return typeof delay === "number" ?
9847				this.each(function() {
9848					var elem = this;
9849					setTimeout(function() {
9850						$( elem ).focus();
9851						if ( fn ) {
9852							fn.call( elem );
9853						}
9854					}, delay );
9855				}) :
9856				orig.apply( this, arguments );
9857		};
9858	})( $.fn.focus ),
9859
9860	scrollParent: function() {
9861		var scrollParent;
9862		if (($.ui.ie && (/(static|relative)/).test(this.css("position"))) || (/absolute/).test(this.css("position"))) {
9863			scrollParent = this.parents().filter(function() {
9864				return (/(relative|absolute|fixed)/).test($.css(this,"position")) && (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
9865			}).eq(0);
9866		} else {
9867			scrollParent = this.parents().filter(function() {
9868				return (/(auto|scroll)/).test($.css(this,"overflow")+$.css(this,"overflow-y")+$.css(this,"overflow-x"));
9869			}).eq(0);
9870		}
9871
9872		return (/fixed/).test(this.css("position")) || !scrollParent.length ? $(document) : scrollParent;
9873	},
9874
9875	zIndex: function( zIndex ) {
9876		if ( zIndex !== undefined ) {
9877			return this.css( "zIndex", zIndex );
9878		}
9879
9880		if ( this.length ) {
9881			var elem = $( this[ 0 ] ), position, value;
9882			while ( elem.length && elem[ 0 ] !== document ) {
9883				// Ignore z-index if position is set to a value where z-index is ignored by the browser
9884				// This makes behavior of this function consistent across browsers
9885				// WebKit always returns auto if the element is positioned
9886				position = elem.css( "position" );
9887				if ( position === "absolute" || position === "relative" || position === "fixed" ) {
9888					// IE returns 0 when zIndex is not specified
9889					// other browsers return a string
9890					// we ignore the case of nested elements with an explicit value of 0
9891					// <div style="z-index: -10;"><div style="z-index: 0;"></div></div>
9892					value = parseInt( elem.css( "zIndex" ), 10 );
9893					if ( !isNaN( value ) && value !== 0 ) {
9894						return value;
9895					}
9896				}
9897				elem = elem.parent();
9898			}
9899		}
9900
9901		return 0;
9902	},
9903
9904	uniqueId: function() {
9905		return this.each(function() {
9906			if ( !this.id ) {
9907				this.id = "ui-id-" + (++uuid);
9908			}
9909		});
9910	},
9911
9912	removeUniqueId: function() {
9913		return this.each(function() {
9914			if ( runiqueId.test( this.id ) ) {
9915				$( this ).removeAttr( "id" );
9916			}
9917		});
9918	}
9919});
9920
9921// selectors
9922function focusable( element, isTabIndexNotNaN ) {
9923	var map, mapName, img,
9924		nodeName = element.nodeName.toLowerCase();
9925	if ( "area" === nodeName ) {
9926		map = element.parentNode;
9927		mapName = map.name;
9928		if ( !element.href || !mapName || map.nodeName.toLowerCase() !== "map" ) {
9929			return false;
9930		}
9931		img = $( "img[usemap=#" + mapName + "]" )[0];
9932		return !!img && visible( img );
9933	}
9934	return ( /input|select|textarea|button|object/.test( nodeName ) ?
9935		!element.disabled :
9936		"a" === nodeName ?
9937			element.href || isTabIndexNotNaN :
9938			isTabIndexNotNaN) &&
9939		// the element and all of its ancestors must be visible
9940		visible( element );
9941}
9942
9943function visible( element ) {
9944	return $.expr.filters.visible( element ) &&
9945		!$( element ).parents().addBack().filter(function() {
9946			return $.css( this, "visibility" ) === "hidden";
9947		}).length;
9948}
9949
9950$.extend( $.expr[ ":" ], {
9951	data: $.expr.createPseudo ?
9952		$.expr.createPseudo(function( dataName ) {
9953			return function( elem ) {
9954				return !!$.data( elem, dataName );
9955			};
9956		}) :
9957		// support: jQuery <1.8
9958		function( elem, i, match ) {
9959			return !!$.data( elem, match[ 3 ] );
9960		},
9961
9962	focusable: function( element ) {
9963		return focusable( element, !isNaN( $.attr( element, "tabindex" ) ) );
9964	},
9965
9966	tabbable: function( element ) {
9967		var tabIndex = $.attr( element, "tabindex" ),
9968			isTabIndexNaN = isNaN( tabIndex );
9969		return ( isTabIndexNaN || tabIndex >= 0 ) && focusable( element, !isTabIndexNaN );
9970	}
9971});
9972
9973// support: jQuery <1.8
9974if ( !$( "<a>" ).outerWidth( 1 ).jquery ) {
9975	$.each( [ "Width", "Height" ], function( i, name ) {
9976		var side = name === "Width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ],
9977			type = name.toLowerCase(),
9978			orig = {
9979				innerWidth: $.fn.innerWidth,
9980				innerHeight: $.fn.innerHeight,
9981				outerWidth: $.fn.outerWidth,
9982				outerHeight: $.fn.outerHeight
9983			};
9984
9985		function reduce( elem, size, border, margin ) {
9986			$.each( side, function() {
9987				size -= parseFloat( $.css( elem, "padding" + this ) ) || 0;
9988				if ( border ) {
9989					size -= parseFloat( $.css( elem, "border" + this + "Width" ) ) || 0;
9990				}
9991				if ( margin ) {
9992					size -= parseFloat( $.css( elem, "margin" + this ) ) || 0;
9993				}
9994			});
9995			return size;
9996		}
9997
9998		$.fn[ "inner" + name ] = function( size ) {
9999			if ( size === undefined ) {
10000				return orig[ "inner" + name ].call( this );
10001			}
10002
10003			return this.each(function() {
10004				$( this ).css( type, reduce( this, size ) + "px" );
10005			});
10006		};
10007
10008		$.fn[ "outer" + name] = function( size, margin ) {
10009			if ( typeof size !== "number" ) {
10010				return orig[ "outer" + name ].call( this, size );
10011			}
10012
10013			return this.each(function() {
10014				$( this).css( type, reduce( this, size, true, margin ) + "px" );
10015			});
10016		};
10017	});
10018}
10019
10020// support: jQuery <1.8
10021if ( !$.fn.addBack ) {
10022	$.fn.addBack = function( selector ) {
10023		return this.add( selector == null ?
10024			this.prevObject : this.prevObject.filter( selector )
10025		);
10026	};
10027}
10028
10029// support: jQuery 1.6.1, 1.6.2 (http://bugs.jquery.com/ticket/9413)
10030if ( $( "<a>" ).data( "a-b", "a" ).removeData( "a-b" ).data( "a-b" ) ) {
10031	$.fn.removeData = (function( removeData ) {
10032		return function( key ) {
10033			if ( arguments.length ) {
10034				return removeData.call( this, $.camelCase( key ) );
10035			} else {
10036				return removeData.call( this );
10037			}
10038		};
10039	})( $.fn.removeData );
10040}
10041
10042
10043
10044
10045
10046// deprecated
10047$.ui.ie = !!/msie [\w.]+/.exec( navigator.userAgent.toLowerCase() );
10048
10049$.support.selectstart = "onselectstart" in document.createElement( "div" );
10050$.fn.extend({
10051	disableSelection: function() {
10052		return this.bind( ( $.support.selectstart ? "selectstart" : "mousedown" ) +
10053			".ui-disableSelection", function( event ) {
10054				event.preventDefault();
10055			});
10056	},
10057
10058	enableSelection: function() {
10059		return this.unbind( ".ui-disableSelection" );
10060	}
10061});
10062
10063$.extend( $.ui, {
10064	// $.ui.plugin is deprecated. Use $.widget() extensions instead.
10065	plugin: {
10066		add: function( module, option, set ) {
10067			var i,
10068				proto = $.ui[ module ].prototype;
10069			for ( i in set ) {
10070				proto.plugins[ i ] = proto.plugins[ i ] || [];
10071				proto.plugins[ i ].push( [ option, set[ i ] ] );
10072			}
10073		},
10074		call: function( instance, name, args ) {
10075			var i,
10076				set = instance.plugins[ name ];
10077			if ( !set || !instance.element[ 0 ].parentNode || instance.element[ 0 ].parentNode.nodeType === 11 ) {
10078				return;
10079			}
10080
10081			for ( i = 0; i < set.length; i++ ) {
10082				if ( instance.options[ set[ i ][ 0 ] ] ) {
10083					set[ i ][ 1 ].apply( instance.element, args );
10084				}
10085			}
10086		}
10087	},
10088
10089	// only used by resizable
10090	hasScroll: function( el, a ) {
10091
10092		//If overflow is hidden, the element might have extra content, but the user wants to hide it
10093		if ( $( el ).css( "overflow" ) === "hidden") {
10094			return false;
10095		}
10096
10097		var scroll = ( a && a === "left" ) ? "scrollLeft" : "scrollTop",
10098			has = false;
10099
10100		if ( el[ scroll ] > 0 ) {
10101			return true;
10102		}
10103
10104		// TODO: determine which cases actually cause this to happen
10105		// if the element doesn't have the scroll set, see if it's possible to
10106		// set the scroll
10107		el[ scroll ] = 1;
10108		has = ( el[ scroll ] > 0 );
10109		el[ scroll ] = 0;
10110		return has;
10111	}
10112});
10113
10114})( jQuery );
10115/*!
10116 * jQuery UI Widget 1.10.3
10117 * http://jqueryui.com
10118 *
10119 * Copyright 2013 jQuery Foundation and other contributors
10120 * Released under the MIT license.
10121 * http://jquery.org/license
10122 *
10123 * http://api.jqueryui.com/jQuery.widget/
10124 */
10125
10126(function( $, undefined ) {
10127
10128var uuid = 0,
10129	slice = Array.prototype.slice,
10130	_cleanData = $.cleanData;
10131$.cleanData = function( elems ) {
10132	for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) {
10133		try {
10134			$( elem ).triggerHandler( "remove" );
10135		// http://bugs.jquery.com/ticket/8235
10136		} catch( e ) {}
10137	}
10138	_cleanData( elems );
10139};
10140
10141$.widget = function( name, base, prototype ) {
10142	var fullName, existingConstructor, constructor, basePrototype,
10143		// proxiedPrototype allows the provided prototype to remain unmodified
10144		// so that it can be used as a mixin for multiple widgets (#8876)
10145		proxiedPrototype = {},
10146		namespace = name.split( "." )[ 0 ];
10147
10148	name = name.split( "." )[ 1 ];
10149	fullName = namespace + "-" + name;
10150
10151	if ( !prototype ) {
10152		prototype = base;
10153		base = $.Widget;
10154	}
10155
10156	// create selector for plugin
10157	$.expr[ ":" ][ fullName.toLowerCase() ] = function( elem ) {
10158		return !!$.data( elem, fullName );
10159	};
10160
10161	$[ namespace ] = $[ namespace ] || {};
10162	existingConstructor = $[ namespace ][ name ];
10163	constructor = $[ namespace ][ name ] = function( options, element ) {
10164		// allow instantiation without "new" keyword
10165		if ( !this._createWidget ) {
10166			return new constructor( options, element );
10167		}
10168
10169		// allow instantiation without initializing for simple inheritance
10170		// must use "new" keyword (the code above always passes args)
10171		if ( arguments.length ) {
10172			this._createWidget( options, element );
10173		}
10174	};
10175	// extend with the existing constructor to carry over any static properties
10176	$.extend( constructor, existingConstructor, {
10177		version: prototype.version,
10178		// copy the object used to create the prototype in case we need to
10179		// redefine the widget later
10180		_proto: $.extend( {}, prototype ),
10181		// track widgets that inherit from this widget in case this widget is
10182		// redefined after a widget inherits from it
10183		_childConstructors: []
10184	});
10185
10186	basePrototype = new base();
10187	// we need to make the options hash a property directly on the new instance
10188	// otherwise we'll modify the options hash on the prototype that we're
10189	// inheriting from
10190	basePrototype.options = $.widget.extend( {}, basePrototype.options );
10191	$.each( prototype, function( prop, value ) {
10192		if ( !$.isFunction( value ) ) {
10193			proxiedPrototype[ prop ] = value;
10194			return;
10195		}
10196		proxiedPrototype[ prop ] = (function() {
10197			var _super = function() {
10198					return base.prototype[ prop ].apply( this, arguments );
10199				},
10200				_superApply = function( args ) {
10201					return base.prototype[ prop ].apply( this, args );
10202				};
10203			return function() {
10204				var __super = this._super,
10205					__superApply = this._superApply,
10206					returnValue;
10207
10208				this._super = _super;
10209				this._superApply = _superApply;
10210
vendor: 11,913 bytes, lines 10211-10622
10211				returnValue = value.apply( this, arguments );
10212
10213				this._super = __super;
10214				this._superApply = __superApply;
10215
10216				return returnValue;
10217			};
10218		})();
10219	});
10220	constructor.prototype = $.widget.extend( basePrototype, {
10221		// TODO: remove support for widgetEventPrefix
10222		// always use the name + a colon as the prefix, e.g., draggable:start
10223		// don't prefix for widgets that aren't DOM-based
10224		widgetEventPrefix: existingConstructor ? basePrototype.widgetEventPrefix : name
10225	}, proxiedPrototype, {
10226		constructor: constructor,
10227		namespace: namespace,
10228		widgetName: name,
10229		widgetFullName: fullName
10230	});
10231
10232	// If this widget is being redefined then we need to find all widgets that
10233	// are inheriting from it and redefine all of them so that they inherit from
10234	// the new version of this widget. We're essentially trying to replace one
10235	// level in the prototype chain.
10236	if ( existingConstructor ) {
10237		$.each( existingConstructor._childConstructors, function( i, child ) {
10238			var childPrototype = child.prototype;
10239
10240			// redefine the child widget using the same prototype that was
10241			// originally used, but inherit from the new version of the base
10242			$.widget( childPrototype.namespace + "." + childPrototype.widgetName, constructor, child._proto );
10243		});
10244		// remove the list of existing child constructors from the old constructor
10245		// so the old child constructors can be garbage collected
10246		delete existingConstructor._childConstructors;
10247	} else {
10248		base._childConstructors.push( constructor );
10249	}
10250
10251	$.widget.bridge( name, constructor );
10252};
10253
10254$.widget.extend = function( target ) {
10255	var input = slice.call( arguments, 1 ),
10256		inputIndex = 0,
10257		inputLength = input.length,
10258		key,
10259		value;
10260	for ( ; inputIndex < inputLength; inputIndex++ ) {
10261		for ( key in input[ inputIndex ] ) {
10262			value = input[ inputIndex ][ key ];
10263			if ( input[ inputIndex ].hasOwnProperty( key ) && value !== undefined ) {
10264				// Clone objects
10265				if ( $.isPlainObject( value ) ) {
10266					target[ key ] = $.isPlainObject( target[ key ] ) ?
10267						$.widget.extend( {}, target[ key ], value ) :
10268						// Don't extend strings, arrays, etc. with objects
10269						$.widget.extend( {}, value );
10270				// Copy everything else by reference
10271				} else {
10272					target[ key ] = value;
10273				}
10274			}
10275		}
10276	}
10277	return target;
10278};
10279
10280$.widget.bridge = function( name, object ) {
10281	var fullName = object.prototype.widgetFullName || name;
10282	$.fn[ name ] = function( options ) {
10283		var isMethodCall = typeof options === "string",
10284			args = slice.call( arguments, 1 ),
10285			returnValue = this;
10286
10287		// allow multiple hashes to be passed on init
10288		options = !isMethodCall && args.length ?
10289			$.widget.extend.apply( null, [ options ].concat(args) ) :
10290			options;
10291
10292		if ( isMethodCall ) {
10293			this.each(function() {
10294				var methodValue,
10295					instance = $.data( this, fullName );
10296				if ( !instance ) {
10297					return $.error( "cannot call methods on " + name + " prior to initialization; " +
10298						"attempted to call method '" + options + "'" );
10299				}
10300				if ( !$.isFunction( instance[options] ) || options.charAt( 0 ) === "_" ) {
10301					return $.error( "no such method '" + options + "' for " + name + " widget instance" );
10302				}
10303				methodValue = instance[ options ].apply( instance, args );
10304				if ( methodValue !== instance && methodValue !== undefined ) {
10305					returnValue = methodValue && methodValue.jquery ?
10306						returnValue.pushStack( methodValue.get() ) :
10307						methodValue;
10308					return false;
10309				}
10310			});
10311		} else {
10312			this.each(function() {
10313				var instance = $.data( this, fullName );
10314				if ( instance ) {
10315					instance.option( options || {} )._init();
10316				} else {
10317					$.data( this, fullName, new object( options, this ) );
10318				}
10319			});
10320		}
10321
10322		return returnValue;
10323	};
10324};
10325
10326$.Widget = function( /* options, element */ ) {};
10327$.Widget._childConstructors = [];
10328
10329$.Widget.prototype = {
10330	widgetName: "widget",
10331	widgetEventPrefix: "",
10332	defaultElement: "<div>",
10333	options: {
10334		disabled: false,
10335
10336		// callbacks
10337		create: null
10338	},
10339	_createWidget: function( options, element ) {
10340		element = $( element || this.defaultElement || this )[ 0 ];
10341		this.element = $( element );
10342		this.uuid = uuid++;
10343		this.eventNamespace = "." + this.widgetName + this.uuid;
10344		this.options = $.widget.extend( {},
10345			this.options,
10346			this._getCreateOptions(),
10347			options );
10348
10349		this.bindings = $();
10350		this.hoverable = $();
10351		this.focusable = $();
10352
10353		if ( element !== this ) {
10354			$.data( element, this.widgetFullName, this );
10355			this._on( true, this.element, {
10356				remove: function( event ) {
10357					if ( event.target === element ) {
10358						this.destroy();
10359					}
10360				}
10361			});
10362			this.document = $( element.style ?
10363				// element within the document
10364				element.ownerDocument :
10365				// element is window or document
10366				element.document || element );
10367			this.window = $( this.document[0].defaultView || this.document[0].parentWindow );
10368		}
10369
10370		this._create();
10371		this._trigger( "create", null, this._getCreateEventData() );
10372		this._init();
10373	},
10374	_getCreateOptions: $.noop,
10375	_getCreateEventData: $.noop,
10376	_create: $.noop,
10377	_init: $.noop,
10378
10379	destroy: function() {
10380		this._destroy();
10381		// we can probably remove the unbind calls in 2.0
10382		// all event bindings should go through this._on()
10383		this.element
10384			.unbind( this.eventNamespace )
10385			// 1.9 BC for #7810
10386			// TODO remove dual storage
10387			.removeData( this.widgetName )
10388			.removeData( this.widgetFullName )
10389			// support: jquery <1.6.3
10390			// http://bugs.jquery.com/ticket/9413
10391			.removeData( $.camelCase( this.widgetFullName ) );
10392		this.widget()
10393			.unbind( this.eventNamespace )
10394			.removeAttr( "aria-disabled" )
10395			.removeClass(
10396				this.widgetFullName + "-disabled " +
10397				"ui-state-disabled" );
10398
10399		// clean up events and states
10400		this.bindings.unbind( this.eventNamespace );
10401		this.hoverable.removeClass( "ui-state-hover" );
10402		this.focusable.removeClass( "ui-state-focus" );
10403	},
10404	_destroy: $.noop,
10405
10406	widget: function() {
10407		return this.element;
10408	},
10409
10410	option: function( key, value ) {
10411		var options = key,
10412			parts,
10413			curOption,
10414			i;
10415
10416		if ( arguments.length === 0 ) {
10417			// don't return a reference to the internal hash
10418			return $.widget.extend( {}, this.options );
10419		}
10420
10421		if ( typeof key === "string" ) {
10422			// handle nested keys, e.g., "foo.bar" => { foo: { bar: ___ } }
10423			options = {};
10424			parts = key.split( "." );
10425			key = parts.shift();
10426			if ( parts.length ) {
10427				curOption = options[ key ] = $.widget.extend( {}, this.options[ key ] );
10428				for ( i = 0; i < parts.length - 1; i++ ) {
10429					curOption[ parts[ i ] ] = curOption[ parts[ i ] ] || {};
10430					curOption = curOption[ parts[ i ] ];
10431				}
10432				key = parts.pop();
10433				if ( value === undefined ) {
10434					return curOption[ key ] === undefined ? null : curOption[ key ];
10435				}
10436				curOption[ key ] = value;
10437			} else {
10438				if ( value === undefined ) {
10439					return this.options[ key ] === undefined ? null : this.options[ key ];
10440				}
10441				options[ key ] = value;
10442			}
10443		}
10444
10445		this._setOptions( options );
10446
10447		return this;
10448	},
10449	_setOptions: function( options ) {
10450		var key;
10451
10452		for ( key in options ) {
10453			this._setOption( key, options[ key ] );
10454		}
10455
10456		return this;
10457	},
10458	_setOption: function( key, value ) {
10459		this.options[ key ] = value;
10460
10461		if ( key === "disabled" ) {
10462			this.widget()
10463				.toggleClass( this.widgetFullName + "-disabled ui-state-disabled", !!value )
10464				.attr( "aria-disabled", value );
10465			this.hoverable.removeClass( "ui-state-hover" );
10466			this.focusable.removeClass( "ui-state-focus" );
10467		}
10468
10469		return this;
10470	},
10471
10472	enable: function() {
10473		return this._setOption( "disabled", false );
10474	},
10475	disable: function() {
10476		return this._setOption( "disabled", true );
10477	},
10478
10479	_on: function( suppressDisabledCheck, element, handlers ) {
10480		var delegateElement,
10481			instance = this;
10482
10483		// no suppressDisabledCheck flag, shuffle arguments
10484		if ( typeof suppressDisabledCheck !== "boolean" ) {
10485			handlers = element;
10486			element = suppressDisabledCheck;
10487			suppressDisabledCheck = false;
10488		}
10489
10490		// no element argument, shuffle and use this.element
10491		if ( !handlers ) {
10492			handlers = element;
10493			element = this.element;
10494			delegateElement = this.widget();
10495		} else {
10496			// accept selectors, DOM elements
10497			element = delegateElement = $( element );
10498			this.bindings = this.bindings.add( element );
10499		}
10500
10501		$.each( handlers, function( event, handler ) {
10502			function handlerProxy() {
10503				// allow widgets to customize the disabled handling
10504				// - disabled as an array instead of boolean
10505				// - disabled class as method for disabling individual parts
10506				if ( !suppressDisabledCheck &&
10507						( instance.options.disabled === true ||
10508							$( this ).hasClass( "ui-state-disabled" ) ) ) {
10509					return;
10510				}
10511				return ( typeof handler === "string" ? instance[ handler ] : handler )
10512					.apply( instance, arguments );
10513			}
10514
10515			// copy the guid so direct unbinding works
10516			if ( typeof handler !== "string" ) {
10517				handlerProxy.guid = handler.guid =
10518					handler.guid || handlerProxy.guid || $.guid++;
10519			}
10520
10521			var match = event.match( /^(\w+)\s*(.*)$/ ),
10522				eventName = match[1] + instance.eventNamespace,
10523				selector = match[2];
10524			if ( selector ) {
10525				delegateElement.delegate( selector, eventName, handlerProxy );
10526			} else {
10527				element.bind( eventName, handlerProxy );
10528			}
10529		});
10530	},
10531
10532	_off: function( element, eventName ) {
10533		eventName = (eventName || "").split( " " ).join( this.eventNamespace + " " ) + this.eventNamespace;
10534		element.unbind( eventName ).undelegate( eventName );
10535	},
10536
10537	_delay: function( handler, delay ) {
10538		function handlerProxy() {
10539			return ( typeof handler === "string" ? instance[ handler ] : handler )
10540				.apply( instance, arguments );
10541		}
10542		var instance = this;
10543		return setTimeout( handlerProxy, delay || 0 );
10544	},
10545
10546	_hoverable: function( element ) {
10547		this.hoverable = this.hoverable.add( element );
10548		this._on( element, {
10549			mouseenter: function( event ) {
10550				$( event.currentTarget ).addClass( "ui-state-hover" );
10551			},
10552			mouseleave: function( event ) {
10553				$( event.currentTarget ).removeClass( "ui-state-hover" );
10554			}
10555		});
10556	},
10557
10558	_focusable: function( element ) {
10559		this.focusable = this.focusable.add( element );
10560		this._on( element, {
10561			focusin: function( event ) {
10562				$( event.currentTarget ).addClass( "ui-state-focus" );
10563			},
10564			focusout: function( event ) {
10565				$( event.currentTarget ).removeClass( "ui-state-focus" );
10566			}
10567		});
10568	},
10569
10570	_trigger: function( type, event, data ) {
10571		var prop, orig,
10572			callback = this.options[ type ];
10573
10574		data = data || {};
10575		event = $.Event( event );
10576		event.type = ( type === this.widgetEventPrefix ?
10577			type :
10578			this.widgetEventPrefix + type ).toLowerCase();
10579		// the original event may come from any element
10580		// so we need to reset the target on the new event
10581		event.target = this.element[ 0 ];
10582
10583		// copy original event properties over to the new event
10584		orig = event.originalEvent;
10585		if ( orig ) {
10586			for ( prop in orig ) {
10587				if ( !( prop in event ) ) {
10588					event[ prop ] = orig[ prop ];
10589				}
10590			}
10591		}
10592
10593		this.element.trigger( event, data );
10594		return !( $.isFunction( callback ) &&
10595			callback.apply( this.element[0], [ event ].concat( data ) ) === false ||
10596			event.isDefaultPrevented() );
10597	}
10598};
10599
10600$.each( { show: "fadeIn", hide: "fadeOut" }, function( method, defaultEffect ) {
10601	$.Widget.prototype[ "_" + method ] = function( element, options, callback ) {
10602		if ( typeof options === "string" ) {
10603			options = { effect: options };
10604		}
10605		var hasOptions,
10606			effectName = !options ?
10607				method :
10608				options === true || typeof options === "number" ?
10609					defaultEffect :
10610					options.effect || defaultEffect;
10611		options = options || {};
10612		if ( typeof options === "number" ) {
10613			options = { duration: options };
10614		}
10615		hasOptions = !$.isEmptyObject( options );
10616		options.complete = callback;
10617		if ( options.delay ) {
10618			element.delay( options.delay );
10619		}
10620		if ( hasOptions && $.effects && $.effects.effect[ effectName ] ) {
10621			element[ method ]( options );
10622		} else if ( effectName !== method && element[ effectName ] ) {
vendor: 31,725 bytes, lines 10623-11758
10623			element[ effectName ]( options.duration, options.easing, callback );
10624		} else {
10625			element.queue(function( next ) {
10626				$( this )[ method ]();
10627				if ( callback ) {
10628					callback.call( element[ 0 ] );
10629				}
10630				next();
10631			});
10632		}
10633	};
10634});
10635
10636})( jQuery );
10637
10638
10639
10640/*!
10641 * jQuery UI Accordion 1.10.3
10642 * http://jqueryui.com
10643 *
10644 * Copyright 2013 jQuery Foundation and other contributors
10645 * Released under the MIT license.
10646 * http://jquery.org/license
10647 *
10648 * http://api.jqueryui.com/accordion/
10649 *
10650 * Depends:
10651 *	jquery.ui.core.js
10652 *	jquery.ui.widget.js
10653 */
10654
10655(function( $, undefined ) {
10656
10657var uid = 0,
10658	hideProps = {},
10659	showProps = {};
10660
10661hideProps.height = hideProps.paddingTop = hideProps.paddingBottom =
10662	hideProps.borderTopWidth = hideProps.borderBottomWidth = "hide";
10663showProps.height = showProps.paddingTop = showProps.paddingBottom =
10664	showProps.borderTopWidth = showProps.borderBottomWidth = "show";
10665
10666$.widget( "ui.accordion", {
10667	version: "1.10.3",
10668	options: {
10669		active: 0,
10670		animate: {},
10671		collapsible: false,
10672		event: "click",
10673		header: "> li > :first-child,> :not(li):even",
10674		heightStyle: "auto",
10675		icons: {
10676			activeHeader: "ui-icon-triangle-1-s",
10677			header: "ui-icon-triangle-1-e"
10678		},
10679
10680		// callbacks
10681		activate: null,
10682		beforeActivate: null
10683	},
10684
10685	_create: function() {
10686		var options = this.options;
10687		this.prevShow = this.prevHide = $();
10688		this.element.addClass( "ui-accordion ui-widget ui-helper-reset" )
10689			// ARIA
10690			.attr( "role", "tablist" );
10691
10692		// don't allow collapsible: false and active: false / null
10693		if ( !options.collapsible && (options.active === false || options.active == null) ) {
10694			options.active = 0;
10695		}
10696
10697		this._processPanels();
10698		// handle negative values
10699		if ( options.active < 0 ) {
10700			options.active += this.headers.length;
10701		}
10702		this._refresh();
10703	},
10704
10705	_getCreateEventData: function() {
10706		return {
10707			header: this.active,
10708			panel: !this.active.length ? $() : this.active.next(),
10709			content: !this.active.length ? $() : this.active.next()
10710		};
10711	},
10712
10713	_createIcons: function() {
10714		var icons = this.options.icons;
10715		if ( icons ) {
10716			$( "<span>" )
10717				.addClass( "ui-accordion-header-icon ui-icon " + icons.header )
10718				.prependTo( this.headers );
10719			this.active.children( ".ui-accordion-header-icon" )
10720				.removeClass( icons.header )
10721				.addClass( icons.activeHeader );
10722			this.headers.addClass( "ui-accordion-icons" );
10723		}
10724	},
10725
10726	_destroyIcons: function() {
10727		this.headers
10728			.removeClass( "ui-accordion-icons" )
10729			.children( ".ui-accordion-header-icon" )
10730				.remove();
10731	},
10732
10733	_destroy: function() {
10734		var contents;
10735
10736		// clean up main element
10737		this.element
10738			.removeClass( "ui-accordion ui-widget ui-helper-reset" )
10739			.removeAttr( "role" );
10740
10741		// clean up headers
10742		this.headers
10743			.removeClass( "ui-accordion-header ui-accordion-header-active ui-helper-reset ui-state-default ui-corner-all ui-state-active ui-state-disabled ui-corner-top" )
10744			.removeAttr( "role" )
10745			.removeAttr( "aria-selected" )
10746			.removeAttr( "aria-controls" )
10747			.removeAttr( "tabIndex" )
10748			.each(function() {
10749				if ( /^ui-accordion/.test( this.id ) ) {
10750					this.removeAttribute( "id" );
10751				}
10752			});
10753		this._destroyIcons();
10754
10755		// clean up content panels
10756		contents = this.headers.next()
10757			.css( "display", "" )
10758			.removeAttr( "role" )
10759			.removeAttr( "aria-expanded" )
10760			.removeAttr( "aria-hidden" )
10761			.removeAttr( "aria-labelledby" )
10762			.removeClass( "ui-helper-reset ui-widget-content ui-corner-bottom ui-accordion-content ui-accordion-content-active ui-state-disabled" )
10763			.each(function() {
10764				if ( /^ui-accordion/.test( this.id ) ) {
10765					this.removeAttribute( "id" );
10766				}
10767			});
10768		if ( this.options.heightStyle !== "content" ) {
10769			contents.css( "height", "" );
10770		}
10771	},
10772
10773	_setOption: function( key, value ) {
10774		if ( key === "active" ) {
10775			// _activate() will handle invalid values and update this.options
10776			this._activate( value );
10777			return;
10778		}
10779
10780		if ( key === "event" ) {
10781			if ( this.options.event ) {
10782				this._off( this.headers, this.options.event );
10783			}
10784			this._setupEvents( value );
10785		}
10786
10787		this._super( key, value );
10788
10789		// setting collapsible: false while collapsed; open first panel
10790		if ( key === "collapsible" && !value && this.options.active === false ) {
10791			this._activate( 0 );
10792		}
10793
10794		if ( key === "icons" ) {
10795			this._destroyIcons();
10796			if ( value ) {
10797				this._createIcons();
10798			}
10799		}
10800
10801		// #5332 - opacity doesn't cascade to positioned elements in IE
10802		// so we need to add the disabled class to the headers and panels
10803		if ( key === "disabled" ) {
10804			this.headers.add( this.headers.next() )
10805				.toggleClass( "ui-state-disabled", !!value );
10806		}
10807	},
10808
10809	_keydown: function( event ) {
10810		/*jshint maxcomplexity:15*/
10811		if ( event.altKey || event.ctrlKey ) {
10812			return;
10813		}
10814
10815		var keyCode = $.ui.keyCode,
10816			length = this.headers.length,
10817			currentIndex = this.headers.index( event.target ),
10818			toFocus = false;
10819
10820		switch ( event.keyCode ) {
10821			case keyCode.RIGHT:
10822			case keyCode.DOWN:
10823				toFocus = this.headers[ ( currentIndex + 1 ) % length ];
10824				break;
10825			case keyCode.LEFT:
10826			case keyCode.UP:
10827				toFocus = this.headers[ ( currentIndex - 1 + length ) % length ];
10828				break;
10829			case keyCode.SPACE:
10830			case keyCode.ENTER:
10831				this._eventHandler( event );
10832				break;
10833			case keyCode.HOME:
10834				toFocus = this.headers[ 0 ];
10835				break;
10836			case keyCode.END:
10837				toFocus = this.headers[ length - 1 ];
10838				break;
10839		}
10840
10841		if ( toFocus ) {
10842			$( event.target ).attr( "tabIndex", -1 );
10843			$( toFocus ).attr( "tabIndex", 0 );
10844			toFocus.focus();
10845			event.preventDefault();
10846		}
10847	},
10848
10849	_panelKeyDown : function( event ) {
10850		if ( event.keyCode === $.ui.keyCode.UP && event.ctrlKey ) {
10851			$( event.currentTarget ).prev().focus();
10852		}
10853	},
10854
10855	refresh: function() {
10856		var options = this.options;
10857		this._processPanels();
10858
10859		// was collapsed or no panel
10860		if ( ( options.active === false && options.collapsible === true ) || !this.headers.length ) {
10861			options.active = false;
10862			this.active = $();
10863		// active false only when collapsible is true
10864		} else if ( options.active === false ) {
10865			this._activate( 0 );
10866		// was active, but active panel is gone
10867		} else if ( this.active.length && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
10868			// all remaining panel are disabled
10869			if ( this.headers.length === this.headers.find(".ui-state-disabled").length ) {
10870				options.active = false;
10871				this.active = $();
10872			// activate previous panel
10873			} else {
10874				this._activate( Math.max( 0, options.active - 1 ) );
10875			}
10876		// was active, active panel still exists
10877		} else {
10878			// make sure active index is correct
10879			options.active = this.headers.index( this.active );
10880		}
10881
10882		this._destroyIcons();
10883
10884		this._refresh();
10885	},
10886
10887	_processPanels: function() {
10888		this.headers = this.element.find( this.options.header )
10889			.addClass( "ui-accordion-header ui-helper-reset ui-state-default ui-corner-all" );
10890
10891		this.headers.next()
10892			.addClass( "ui-accordion-content ui-helper-reset ui-widget-content ui-corner-bottom" )
10893			.filter(":not(.ui-accordion-content-active)")
10894			.hide();
10895	},
10896
10897	_refresh: function() {
10898		var maxHeight,
10899			options = this.options,
10900			heightStyle = options.heightStyle,
10901			parent = this.element.parent(),
10902			accordionId = this.accordionId = "ui-accordion-" +
10903				(this.element.attr( "id" ) || ++uid);
10904
10905		this.active = this._findActive( options.active )
10906			.addClass( "ui-accordion-header-active ui-state-active ui-corner-top" )
10907			.removeClass( "ui-corner-all" );
10908		this.active.next()
10909			.addClass( "ui-accordion-content-active" )
10910			.show();
10911
10912		this.headers
10913			.attr( "role", "tab" )
10914			.each(function( i ) {
10915				var header = $( this ),
10916					headerId = header.attr( "id" ),
10917					panel = header.next(),
10918					panelId = panel.attr( "id" );
10919				if ( !headerId ) {
10920					headerId = accordionId + "-header-" + i;
10921					header.attr( "id", headerId );
10922				}
10923				if ( !panelId ) {
10924					panelId = accordionId + "-panel-" + i;
10925					panel.attr( "id", panelId );
10926				}
10927				header.attr( "aria-controls", panelId );
10928				panel.attr( "aria-labelledby", headerId );
10929			})
10930			.next()
10931				.attr( "role", "tabpanel" );
10932
10933		this.headers
10934			.not( this.active )
10935			.attr({
10936				"aria-selected": "false",
10937				tabIndex: -1
10938			})
10939			.next()
10940				.attr({
10941					"aria-expanded": "false",
10942					"aria-hidden": "true"
10943				})
10944				.hide();
10945
10946		// make sure at least one header is in the tab order
10947		if ( !this.active.length ) {
10948			this.headers.eq( 0 ).attr( "tabIndex", 0 );
10949		} else {
10950			this.active.attr({
10951				"aria-selected": "true",
10952				tabIndex: 0
10953			})
10954			.next()
10955				.attr({
10956					"aria-expanded": "true",
10957					"aria-hidden": "false"
10958				});
10959		}
10960
10961		this._createIcons();
10962
10963		this._setupEvents( options.event );
10964
10965		if ( heightStyle === "fill" ) {
10966			maxHeight = parent.height();
10967			this.element.siblings( ":visible" ).each(function() {
10968				var elem = $( this ),
10969					position = elem.css( "position" );
10970
10971				if ( position === "absolute" || position === "fixed" ) {
10972					return;
10973				}
10974				maxHeight -= elem.outerHeight( true );
10975			});
10976
10977			this.headers.each(function() {
10978				maxHeight -= $( this ).outerHeight( true );
10979			});
10980
10981			this.headers.next()
10982				.each(function() {
10983					$( this ).height( Math.max( 0, maxHeight -
10984						$( this ).innerHeight() + $( this ).height() ) );
10985				})
10986				.css( "overflow", "auto" );
10987		} else if ( heightStyle === "auto" ) {
10988			maxHeight = 0;
10989			this.headers.next()
10990				.each(function() {
10991					maxHeight = Math.max( maxHeight, $( this ).css( "height", "" ).height() );
10992				})
10993				.height( maxHeight );
10994		}
10995	},
10996
10997	_activate: function( index ) {
10998		var active = this._findActive( index )[ 0 ];
10999
11000		// trying to activate the already active panel
11001		if ( active === this.active[ 0 ] ) {
11002			return;
11003		}
11004
11005		// trying to collapse, simulate a click on the currently active header
11006		active = active || this.active[ 0 ];
11007
11008		this._eventHandler({
11009			target: active,
11010			currentTarget: active,
11011			preventDefault: $.noop
11012		});
11013	},
11014
11015	_findActive: function( selector ) {
11016		return typeof selector === "number" ? this.headers.eq( selector ) : $();
11017	},
11018
11019	_setupEvents: function( event ) {
11020		var events = {
11021			keydown: "_keydown"
11022		};
11023		if ( event ) {
11024			$.each( event.split(" "), function( index, eventName ) {
11025				events[ eventName ] = "_eventHandler";
11026			});
11027		}
11028
11029		this._off( this.headers.add( this.headers.next() ) );
11030		this._on( this.headers, events );
11031		this._on( this.headers.next(), { keydown: "_panelKeyDown" });
11032		this._hoverable( this.headers );
11033		this._focusable( this.headers );
11034	},
11035
11036	_eventHandler: function( event ) {
11037		var options = this.options,
11038			active = this.active,
11039			clicked = $( event.currentTarget ),
11040			clickedIsActive = clicked[ 0 ] === active[ 0 ],
11041			collapsing = clickedIsActive && options.collapsible,
11042			toShow = collapsing ? $() : clicked.next(),
11043			toHide = active.next(),
11044			eventData = {
11045				oldHeader: active,
11046				oldPanel: toHide,
11047				newHeader: collapsing ? $() : clicked,
11048				newPanel: toShow
11049			};
11050
11051		event.preventDefault();
11052
11053		if (
11054				// click on active header, but not collapsible
11055				( clickedIsActive && !options.collapsible ) ||
11056				// allow canceling activation
11057				( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
11058			return;
11059		}
11060
11061		options.active = collapsing ? false : this.headers.index( clicked );
11062
11063		// when the call to ._toggle() comes after the class changes
11064		// it causes a very odd bug in IE 8 (see #6720)
11065		this.active = clickedIsActive ? $() : clicked;
11066		this._toggle( eventData );
11067
11068		// switch classes
11069		// corner classes on the previously active header stay after the animation
11070		active.removeClass( "ui-accordion-header-active ui-state-active" );
11071		if ( options.icons ) {
11072			active.children( ".ui-accordion-header-icon" )
11073				.removeClass( options.icons.activeHeader )
11074				.addClass( options.icons.header );
11075		}
11076
11077		if ( !clickedIsActive ) {
11078			clicked
11079				.removeClass( "ui-corner-all" )
11080				.addClass( "ui-accordion-header-active ui-state-active ui-corner-top" );
11081			if ( options.icons ) {
11082				clicked.children( ".ui-accordion-header-icon" )
11083					.removeClass( options.icons.header )
11084					.addClass( options.icons.activeHeader );
11085			}
11086
11087			clicked
11088				.next()
11089				.addClass( "ui-accordion-content-active" );
11090		}
11091	},
11092
11093	_toggle: function( data ) {
11094		var toShow = data.newPanel,
11095			toHide = this.prevShow.length ? this.prevShow : data.oldPanel;
11096
11097		// handle activating a panel during the animation for another activation
11098		this.prevShow.add( this.prevHide ).stop( true, true );
11099		this.prevShow = toShow;
11100		this.prevHide = toHide;
11101
11102		if ( this.options.animate ) {
11103			this._animate( toShow, toHide, data );
11104		} else {
11105			toHide.hide();
11106			toShow.show();
11107			this._toggleComplete( data );
11108		}
11109
11110		toHide.attr({
11111			"aria-expanded": "false",
11112			"aria-hidden": "true"
11113		});
11114		toHide.prev().attr( "aria-selected", "false" );
11115		// if we're switching panels, remove the old header from the tab order
11116		// if we're opening from collapsed state, remove the previous header from the tab order
11117		// if we're collapsing, then keep the collapsing header in the tab order
11118		if ( toShow.length && toHide.length ) {
11119			toHide.prev().attr( "tabIndex", -1 );
11120		} else if ( toShow.length ) {
11121			this.headers.filter(function() {
11122				return $( this ).attr( "tabIndex" ) === 0;
11123			})
11124			.attr( "tabIndex", -1 );
11125		}
11126
11127		toShow
11128			.attr({
11129				"aria-expanded": "true",
11130				"aria-hidden": "false"
11131			})
11132			.prev()
11133				.attr({
11134					"aria-selected": "true",
11135					tabIndex: 0
11136				});
11137	},
11138
11139	_animate: function( toShow, toHide, data ) {
11140		var total, easing, duration,
11141			that = this,
11142			adjust = 0,
11143			down = toShow.length &&
11144				( !toHide.length || ( toShow.index() < toHide.index() ) ),
11145			animate = this.options.animate || {},
11146			options = down && animate.down || animate,
11147			complete = function() {
11148				that._toggleComplete( data );
11149			};
11150
11151		if ( typeof options === "number" ) {
11152			duration = options;
11153		}
11154		if ( typeof options === "string" ) {
11155			easing = options;
11156		}
11157		// fall back from options to animation in case of partial down settings
11158		easing = easing || options.easing || animate.easing;
11159		duration = duration || options.duration || animate.duration;
11160
11161		if ( !toHide.length ) {
11162			return toShow.animate( showProps, duration, easing, complete );
11163		}
11164		if ( !toShow.length ) {
11165			return toHide.animate( hideProps, duration, easing, complete );
11166		}
11167
11168		total = toShow.show().outerHeight();
11169		toHide.animate( hideProps, {
11170			duration: duration,
11171			easing: easing,
11172			step: function( now, fx ) {
11173				fx.now = Math.round( now );
11174			}
11175		});
11176		toShow
11177			.hide()
11178			.animate( showProps, {
11179				duration: duration,
11180				easing: easing,
11181				complete: complete,
11182				step: function( now, fx ) {
11183					fx.now = Math.round( now );
11184					if ( fx.prop !== "height" ) {
11185						adjust += fx.now;
11186					} else if ( that.options.heightStyle !== "content" ) {
11187						fx.now = Math.round( total - toHide.outerHeight() - adjust );
11188						adjust = 0;
11189					}
11190				}
11191			});
11192	},
11193
11194	_toggleComplete: function( data ) {
11195		var toHide = data.oldPanel;
11196
11197		toHide
11198			.removeClass( "ui-accordion-content-active" )
11199			.prev()
11200				.removeClass( "ui-corner-top" )
11201				.addClass( "ui-corner-all" );
11202
11203		// Work around for rendering bug in IE (#5421)
11204		if ( toHide.length ) {
11205			toHide.parent()[0].className = toHide.parent()[0].className;
11206		}
11207
11208		this._trigger( "activate", null, data );
11209	}
11210});
11211
11212})( jQuery );
11213/*!
11214 * jQuery UI Position 1.10.3
11215 * http://jqueryui.com
11216 *
11217 * Copyright 2013 jQuery Foundation and other contributors
11218 * Released under the MIT license.
11219 * http://jquery.org/license
11220 *
11221 * http://api.jqueryui.com/position/
11222 */
11223
11224(function( $, undefined ) {
11225
11226$.ui = $.ui || {};
11227
11228var cachedScrollbarWidth,
11229	max = Math.max,
11230	abs = Math.abs,
11231	round = Math.round,
11232	rhorizontal = /left|center|right/,
11233	rvertical = /top|center|bottom/,
11234	roffset = /[\+\-]\d+(\.[\d]+)?%?/,
11235	rposition = /^\w+/,
11236	rpercent = /%$/,
11237	_position = $.fn.position;
11238
11239function getOffsets( offsets, width, height ) {
11240	return [
11241		parseFloat( offsets[ 0 ] ) * ( rpercent.test( offsets[ 0 ] ) ? width / 100 : 1 ),
11242		parseFloat( offsets[ 1 ] ) * ( rpercent.test( offsets[ 1 ] ) ? height / 100 : 1 )
11243	];
11244}
11245
11246function parseCss( element, property ) {
11247	return parseInt( $.css( element, property ), 10 ) || 0;
11248}
11249
11250function getDimensions( elem ) {
11251	var raw = elem[0];
11252	if ( raw.nodeType === 9 ) {
11253		return {
11254			width: elem.width(),
11255			height: elem.height(),
11256			offset: { top: 0, left: 0 }
11257		};
11258	}
11259	if ( $.isWindow( raw ) ) {
11260		return {
11261			width: elem.width(),
11262			height: elem.height(),
11263			offset: { top: elem.scrollTop(), left: elem.scrollLeft() }
11264		};
11265	}
11266	if ( raw.preventDefault ) {
11267		return {
11268			width: 0,
11269			height: 0,
11270			offset: { top: raw.pageY, left: raw.pageX }
11271		};
11272	}
11273	return {
11274		width: elem.outerWidth(),
11275		height: elem.outerHeight(),
11276		offset: elem.offset()
11277	};
11278}
11279
11280$.position = {
11281	scrollbarWidth: function() {
11282		if ( cachedScrollbarWidth !== undefined ) {
11283			return cachedScrollbarWidth;
11284		}
11285		var w1, w2,
11286			div = $( "<div style='display:block;width:50px;height:50px;overflow:hidden;'><div style='height:100px;width:auto;'></div></div>" ),
11287			innerDiv = div.children()[0];
11288
11289		$( "body" ).append( div );
11290		w1 = innerDiv.offsetWidth;
11291		div.css( "overflow", "scroll" );
11292
11293		w2 = innerDiv.offsetWidth;
11294
11295		if ( w1 === w2 ) {
11296			w2 = div[0].clientWidth;
11297		}
11298
11299		div.remove();
11300
11301		return (cachedScrollbarWidth = w1 - w2);
11302	},
11303	getScrollInfo: function( within ) {
11304		var overflowX = within.isWindow ? "" : within.element.css( "overflow-x" ),
11305			overflowY = within.isWindow ? "" : within.element.css( "overflow-y" ),
11306			hasOverflowX = overflowX === "scroll" ||
11307				( overflowX === "auto" && within.width < within.element[0].scrollWidth ),
11308			hasOverflowY = overflowY === "scroll" ||
11309				( overflowY === "auto" && within.height < within.element[0].scrollHeight );
11310		return {
11311			width: hasOverflowY ? $.position.scrollbarWidth() : 0,
11312			height: hasOverflowX ? $.position.scrollbarWidth() : 0
11313		};
11314	},
11315	getWithinInfo: function( element ) {
11316		var withinElement = $( element || window ),
11317			isWindow = $.isWindow( withinElement[0] );
11318		return {
11319			element: withinElement,
11320			isWindow: isWindow,
11321			offset: withinElement.offset() || { left: 0, top: 0 },
11322			scrollLeft: withinElement.scrollLeft(),
11323			scrollTop: withinElement.scrollTop(),
11324			width: isWindow ? withinElement.width() : withinElement.outerWidth(),
11325			height: isWindow ? withinElement.height() : withinElement.outerHeight()
11326		};
11327	}
11328};
11329
11330$.fn.position = function( options ) {
11331	if ( !options || !options.of ) {
11332		return _position.apply( this, arguments );
11333	}
11334
11335	// make a copy, we don't want to modify arguments
11336	options = $.extend( {}, options );
11337
11338	var atOffset, targetWidth, targetHeight, targetOffset, basePosition, dimensions,
11339		target = $( options.of ),
11340		within = $.position.getWithinInfo( options.within ),
11341		scrollInfo = $.position.getScrollInfo( within ),
11342		collision = ( options.collision || "flip" ).split( " " ),
11343		offsets = {};
11344
11345	dimensions = getDimensions( target );
11346	if ( target[0].preventDefault ) {
11347		// force left top to allow flipping
11348		options.at = "left top";
11349	}
11350	targetWidth = dimensions.width;
11351	targetHeight = dimensions.height;
11352	targetOffset = dimensions.offset;
11353	// clone to reuse original targetOffset later
11354	basePosition = $.extend( {}, targetOffset );
11355
11356	// force my and at to have valid horizontal and vertical positions
11357	// if a value is missing or invalid, it will be converted to center
11358	$.each( [ "my", "at" ], function() {
11359		var pos = ( options[ this ] || "" ).split( " " ),
11360			horizontalOffset,
11361			verticalOffset;
11362
11363		if ( pos.length === 1) {
11364			pos = rhorizontal.test( pos[ 0 ] ) ?
11365				pos.concat( [ "center" ] ) :
11366				rvertical.test( pos[ 0 ] ) ?
11367					[ "center" ].concat( pos ) :
11368					[ "center", "center" ];
11369		}
11370		pos[ 0 ] = rhorizontal.test( pos[ 0 ] ) ? pos[ 0 ] : "center";
11371		pos[ 1 ] = rvertical.test( pos[ 1 ] ) ? pos[ 1 ] : "center";
11372
11373		// calculate offsets
11374		horizontalOffset = roffset.exec( pos[ 0 ] );
11375		verticalOffset = roffset.exec( pos[ 1 ] );
11376		offsets[ this ] = [
11377			horizontalOffset ? horizontalOffset[ 0 ] : 0,
11378			verticalOffset ? verticalOffset[ 0 ] : 0
11379		];
11380
11381		// reduce to just the positions without the offsets
11382		options[ this ] = [
11383			rposition.exec( pos[ 0 ] )[ 0 ],
11384			rposition.exec( pos[ 1 ] )[ 0 ]
11385		];
11386	});
11387
11388	// normalize collision option
11389	if ( collision.length === 1 ) {
11390		collision[ 1 ] = collision[ 0 ];
11391	}
11392
11393	if ( options.at[ 0 ] === "right" ) {
11394		basePosition.left += targetWidth;
11395	} else if ( options.at[ 0 ] === "center" ) {
11396		basePosition.left += targetWidth / 2;
11397	}
11398
11399	if ( options.at[ 1 ] === "bottom" ) {
11400		basePosition.top += targetHeight;
11401	} else if ( options.at[ 1 ] === "center" ) {
11402		basePosition.top += targetHeight / 2;
11403	}
11404
11405	atOffset = getOffsets( offsets.at, targetWidth, targetHeight );
11406	basePosition.left += atOffset[ 0 ];
11407	basePosition.top += atOffset[ 1 ];
11408
11409	return this.each(function() {
11410		var collisionPosition, using,
11411			elem = $( this ),
11412			elemWidth = elem.outerWidth(),
11413			elemHeight = elem.outerHeight(),
11414			marginLeft = parseCss( this, "marginLeft" ),
11415			marginTop = parseCss( this, "marginTop" ),
11416			collisionWidth = elemWidth + marginLeft + parseCss( this, "marginRight" ) + scrollInfo.width,
11417			collisionHeight = elemHeight + marginTop + parseCss( this, "marginBottom" ) + scrollInfo.height,
11418			position = $.extend( {}, basePosition ),
11419			myOffset = getOffsets( offsets.my, elem.outerWidth(), elem.outerHeight() );
11420
11421		if ( options.my[ 0 ] === "right" ) {
11422			position.left -= elemWidth;
11423		} else if ( options.my[ 0 ] === "center" ) {
11424			position.left -= elemWidth / 2;
11425		}
11426
11427		if ( options.my[ 1 ] === "bottom" ) {
11428			position.top -= elemHeight;
11429		} else if ( options.my[ 1 ] === "center" ) {
11430			position.top -= elemHeight / 2;
11431		}
11432
11433		position.left += myOffset[ 0 ];
11434		position.top += myOffset[ 1 ];
11435
11436		// if the browser doesn't support fractions, then round for consistent results
11437		if ( !$.support.offsetFractions ) {
11438			position.left = round( position.left );
11439			position.top = round( position.top );
11440		}
11441
11442		collisionPosition = {
11443			marginLeft: marginLeft,
11444			marginTop: marginTop
11445		};
11446
11447		$.each( [ "left", "top" ], function( i, dir ) {
11448			if ( $.ui.position[ collision[ i ] ] ) {
11449				$.ui.position[ collision[ i ] ][ dir ]( position, {
11450					targetWidth: targetWidth,
11451					targetHeight: targetHeight,
11452					elemWidth: elemWidth,
11453					elemHeight: elemHeight,
11454					collisionPosition: collisionPosition,
11455					collisionWidth: collisionWidth,
11456					collisionHeight: collisionHeight,
11457					offset: [ atOffset[ 0 ] + myOffset[ 0 ], atOffset [ 1 ] + myOffset[ 1 ] ],
11458					my: options.my,
11459					at: options.at,
11460					within: within,
11461					elem : elem
11462				});
11463			}
11464		});
11465
11466		if ( options.using ) {
11467			// adds feedback as second argument to using callback, if present
11468			using = function( props ) {
11469				var left = targetOffset.left - position.left,
11470					right = left + targetWidth - elemWidth,
11471					top = targetOffset.top - position.top,
11472					bottom = top + targetHeight - elemHeight,
11473					feedback = {
11474						target: {
11475							element: target,
11476							left: targetOffset.left,
11477							top: targetOffset.top,
11478							width: targetWidth,
11479							height: targetHeight
11480						},
11481						element: {
11482							element: elem,
11483							left: position.left,
11484							top: position.top,
11485							width: elemWidth,
11486							height: elemHeight
11487						},
11488						horizontal: right < 0 ? "left" : left > 0 ? "right" : "center",
11489						vertical: bottom < 0 ? "top" : top > 0 ? "bottom" : "middle"
11490					};
11491				if ( targetWidth < elemWidth && abs( left + right ) < targetWidth ) {
11492					feedback.horizontal = "center";
11493				}
11494				if ( targetHeight < elemHeight && abs( top + bottom ) < targetHeight ) {
11495					feedback.vertical = "middle";
11496				}
11497				if ( max( abs( left ), abs( right ) ) > max( abs( top ), abs( bottom ) ) ) {
11498					feedback.important = "horizontal";
11499				} else {
11500					feedback.important = "vertical";
11501				}
11502				options.using.call( this, props, feedback );
11503			};
11504		}
11505
11506		elem.offset( $.extend( position, { using: using } ) );
11507	});
11508};
11509
11510$.ui.position = {
11511	fit: {
11512		left: function( position, data ) {
11513			var within = data.within,
11514				withinOffset = within.isWindow ? within.scrollLeft : within.offset.left,
11515				outerWidth = within.width,
11516				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
11517				overLeft = withinOffset - collisionPosLeft,
11518				overRight = collisionPosLeft + data.collisionWidth - outerWidth - withinOffset,
11519				newOverRight;
11520
11521			// element is wider than within
11522			if ( data.collisionWidth > outerWidth ) {
11523				// element is initially over the left side of within
11524				if ( overLeft > 0 && overRight <= 0 ) {
11525					newOverRight = position.left + overLeft + data.collisionWidth - outerWidth - withinOffset;
11526					position.left += overLeft - newOverRight;
11527				// element is initially over right side of within
11528				} else if ( overRight > 0 && overLeft <= 0 ) {
11529					position.left = withinOffset;
11530				// element is initially over both left and right sides of within
11531				} else {
11532					if ( overLeft > overRight ) {
11533						position.left = withinOffset + outerWidth - data.collisionWidth;
11534					} else {
11535						position.left = withinOffset;
11536					}
11537				}
11538			// too far left -> align with left edge
11539			} else if ( overLeft > 0 ) {
11540				position.left += overLeft;
11541			// too far right -> align with right edge
11542			} else if ( overRight > 0 ) {
11543				position.left -= overRight;
11544			// adjust based on position and margin
11545			} else {
11546				position.left = max( position.left - collisionPosLeft, position.left );
11547			}
11548		},
11549		top: function( position, data ) {
11550			var within = data.within,
11551				withinOffset = within.isWindow ? within.scrollTop : within.offset.top,
11552				outerHeight = data.within.height,
11553				collisionPosTop = position.top - data.collisionPosition.marginTop,
11554				overTop = withinOffset - collisionPosTop,
11555				overBottom = collisionPosTop + data.collisionHeight - outerHeight - withinOffset,
11556				newOverBottom;
11557
11558			// element is taller than within
11559			if ( data.collisionHeight > outerHeight ) {
11560				// element is initially over the top of within
11561				if ( overTop > 0 && overBottom <= 0 ) {
11562					newOverBottom = position.top + overTop + data.collisionHeight - outerHeight - withinOffset;
11563					position.top += overTop - newOverBottom;
11564				// element is initially over bottom of within
11565				} else if ( overBottom > 0 && overTop <= 0 ) {
11566					position.top = withinOffset;
11567				// element is initially over both top and bottom of within
11568				} else {
11569					if ( overTop > overBottom ) {
11570						position.top = withinOffset + outerHeight - data.collisionHeight;
11571					} else {
11572						position.top = withinOffset;
11573					}
11574				}
11575			// too far up -> align with top
11576			} else if ( overTop > 0 ) {
11577				position.top += overTop;
11578			// too far down -> align with bottom edge
11579			} else if ( overBottom > 0 ) {
11580				position.top -= overBottom;
11581			// adjust based on position and margin
11582			} else {
11583				position.top = max( position.top - collisionPosTop, position.top );
11584			}
11585		}
11586	},
11587	flip: {
11588		left: function( position, data ) {
11589			var within = data.within,
11590				withinOffset = within.offset.left + within.scrollLeft,
11591				outerWidth = within.width,
11592				offsetLeft = within.isWindow ? within.scrollLeft : within.offset.left,
11593				collisionPosLeft = position.left - data.collisionPosition.marginLeft,
11594				overLeft = collisionPosLeft - offsetLeft,
11595				overRight = collisionPosLeft + data.collisionWidth - outerWidth - offsetLeft,
11596				myOffset = data.my[ 0 ] === "left" ?
11597					-data.elemWidth :
11598					data.my[ 0 ] === "right" ?
11599						data.elemWidth :
11600						0,
11601				atOffset = data.at[ 0 ] === "left" ?
11602					data.targetWidth :
11603					data.at[ 0 ] === "right" ?
11604						-data.targetWidth :
11605						0,
11606				offset = -2 * data.offset[ 0 ],
11607				newOverRight,
11608				newOverLeft;
11609
11610			if ( overLeft < 0 ) {
11611				newOverRight = position.left + myOffset + atOffset + offset + data.collisionWidth - outerWidth - withinOffset;
11612				if ( newOverRight < 0 || newOverRight < abs( overLeft ) ) {
11613					position.left += myOffset + atOffset + offset;
11614				}
11615			}
11616			else if ( overRight > 0 ) {
11617				newOverLeft = position.left - data.collisionPosition.marginLeft + myOffset + atOffset + offset - offsetLeft;
11618				if ( newOverLeft > 0 || abs( newOverLeft ) < overRight ) {
11619					position.left += myOffset + atOffset + offset;
11620				}
11621			}
11622		},
11623		top: function( position, data ) {
11624			var within = data.within,
11625				withinOffset = within.offset.top + within.scrollTop,
11626				outerHeight = within.height,
11627				offsetTop = within.isWindow ? within.scrollTop : within.offset.top,
11628				collisionPosTop = position.top - data.collisionPosition.marginTop,
11629				overTop = collisionPosTop - offsetTop,
11630				overBottom = collisionPosTop + data.collisionHeight - outerHeight - offsetTop,
11631				top = data.my[ 1 ] === "top",
11632				myOffset = top ?
11633					-data.elemHeight :
11634					data.my[ 1 ] === "bottom" ?
11635						data.elemHeight :
11636						0,
11637				atOffset = data.at[ 1 ] === "top" ?
11638					data.targetHeight :
11639					data.at[ 1 ] === "bottom" ?
11640						-data.targetHeight :
11641						0,
11642				offset = -2 * data.offset[ 1 ],
11643				newOverTop,
11644				newOverBottom;
11645			if ( overTop < 0 ) {
11646				newOverBottom = position.top + myOffset + atOffset + offset + data.collisionHeight - outerHeight - withinOffset;
11647				if ( ( position.top + myOffset + atOffset + offset) > overTop && ( newOverBottom < 0 || newOverBottom < abs( overTop ) ) ) {
11648					position.top += myOffset + atOffset + offset;
11649				}
11650			}
11651			else if ( overBottom > 0 ) {
11652				newOverTop = position.top -  data.collisionPosition.marginTop + myOffset + atOffset + offset - offsetTop;
11653				if ( ( position.top + myOffset + atOffset + offset) > overBottom && ( newOverTop > 0 || abs( newOverTop ) < overBottom ) ) {
11654					position.top += myOffset + atOffset + offset;
11655				}
11656			}
11657		}
11658	},
11659	flipfit: {
11660		left: function() {
11661			$.ui.position.flip.left.apply( this, arguments );
11662			$.ui.position.fit.left.apply( this, arguments );
11663		},
11664		top: function() {
11665			$.ui.position.flip.top.apply( this, arguments );
11666			$.ui.position.fit.top.apply( this, arguments );
11667		}
11668	}
11669};
11670
11671// fraction support test
11672(function () {
11673	var testElement, testElementParent, testElementStyle, offsetLeft, i,
11674		body = document.getElementsByTagName( "body" )[ 0 ],
11675		div = document.createElement( "div" );
11676
11677	//Create a "fake body" for testing based on method used in jQuery.support
11678	testElement = document.createElement( body ? "div" : "body" );
11679	testElementStyle = {
11680		visibility: "hidden",
11681		width: 0,
11682		height: 0,
11683		border: 0,
11684		margin: 0,
11685		background: "none"
11686	};
11687	if ( body ) {
11688		$.extend( testElementStyle, {
11689			position: "absolute",
11690			left: "-1000px",
11691			top: "-1000px"
11692		});
11693	}
11694	for ( i in testElementStyle ) {
11695		testElement.style[ i ] = testElementStyle[ i ];
11696	}
11697	testElement.appendChild( div );
11698	testElementParent = body || document.documentElement;
11699	testElementParent.insertBefore( testElement, testElementParent.firstChild );
11700
11701	div.style.cssText = "position: absolute; left: 10.7432222px;";
11702
11703	offsetLeft = $( div ).offset().left;
11704	$.support.offsetFractions = offsetLeft > 10 && offsetLeft < 11;
11705
11706	testElement.innerHTML = "";
11707	testElementParent.removeChild( testElement );
11708})();
11709
11710}( jQuery ) );
11711
11712
11713
11714
11715/*!
11716 * jQuery UI Menu 1.10.3
11717 * http://jqueryui.com
11718 *
11719 * Copyright 2013 jQuery Foundation and other contributors
11720 * Released under the MIT license.
11721 * http://jquery.org/license
11722 *
11723 * http://api.jqueryui.com/menu/
11724 *
11725 * Depends:
11726 *	jquery.ui.core.js
11727 *	jquery.ui.widget.js
11728 *	jquery.ui.position.js
11729 */
11730
11731(function( $, undefined ) {
11732
11733$.widget( "ui.menu", {
11734	version: "1.10.3",
11735	defaultElement: "<ul>",
11736	delay: 300,
11737	options: {
11738		icons: {
11739			submenu: "ui-icon-carat-1-e"
11740		},
11741		menus: "ul",
11742		position: {
11743			my: "left top",
11744			at: "right top"
11745		},
11746		role: "menu",
11747
11748		// callbacks
11749		blur: null,
11750		focus: null,
11751		select: null
11752	},
11753
11754	_create: function() {
11755		this.activeMenu = this.element;
11756		// flag used to prevent firing of the click handler
11757		// as the event bubbles up through nested menus
11758		this.mouseHandled = false;
11759		this.element
11760			.uniqueId()
11761			.addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
11762			.toggleClass( "ui-menu-icons", !!this.element.find( ".ui-icon" ).length )
11763			.attr({
11764				role: this.options.role,
11765				tabIndex: 0
11766			})
11767			// need to catch all clicks on disabled menu
11768			// not possible through _on
11769			.bind( "click" + this.eventNamespace, $.proxy(function( event ) {
11770				if ( this.options.disabled ) {
11771					event.preventDefault();
11772				}
11773			}, this ));
11774
11775		if ( this.options.disabled ) {
11776			this.element
11777				.addClass( "ui-state-disabled" )
11778				.attr( "aria-disabled", "true" );
11779		}
11780
11781		this._on({
11782			// Prevent focus from sticking to links inside menu after clicking
11783			// them (focus should always stay on UL during navigation).
11784			"mousedown .ui-menu-item > a": function( event ) {
11785				event.preventDefault();
11786			},
11787			"click .ui-state-disabled > a": function( event ) {
11788				event.preventDefault();
11789			},
11790			"click .ui-menu-item:has(a)": function( event ) {
11791				var target = $( event.target ).closest( ".ui-menu-item" );
11792				if ( !this.mouseHandled && target.not( ".ui-state-disabled" ).length ) {
11793					this.mouseHandled = true;
11794
11795					this.select( event );
11796					// Open submenu on click
11797					if ( target.has( ".ui-menu" ).length ) {
11798						this.expand( event );
11799					} else if ( !this.element.is( ":focus" ) ) {
11800						// Redirect focus to the menu
11801						this.element.trigger( "focus", [ true ] );
11802
11803						// If the active item is on the top level, let it stay active.
11804						// Otherwise, blur the active item since it is no longer visible.
11805						if ( this.active && this.active.parents( ".ui-menu" ).length === 1 ) {
11806							clearTimeout( this.timer );
11807						}
11808					}
11809				}
11810			},
11811			"mouseenter .ui-menu-item": function( event ) {
11812				var target = $( event.currentTarget );
11813				// Remove ui-state-active class from siblings of the newly focused menu item
11814				// to avoid a jump caused by adjacent elements both having a class with a border
11815				target.siblings().children( ".ui-state-active" ).removeClass( "ui-state-active" );
11816				this.focus( event, target );
11817			},
11818			mouseleave: "collapseAll",
11819			"mouseleave .ui-menu": "collapseAll",
11820			focus: function( event, keepActiveItem ) {
11821				// If there's already an active item, keep it active
11822				// If not, activate the first item
11823				var item = this.active || this.element.children( ".ui-menu-item" ).eq( 0 );
11824
11825				if ( !keepActiveItem ) {
11826					this.focus( event, item );
11827				}
11828			},
11829			blur: function( event ) {
11830				this._delay(function() {
11831					if ( !$.contains( this.element[0], this.document[0].activeElement ) ) {
11832						this.collapseAll( event );
11833					}
11834				});
11835			},
11836			keydown: "_keydown"
11837		});
11838
11839		this.refresh();
11840
vendor: 36,920 bytes, lines 11841-13224
11841		// Clicks outside of a menu collapse any open menus
11842		this._on( this.document, {
11843			click: function( event ) {
11844				if ( !$( event.target ).closest( ".ui-menu" ).length ) {
11845					this.collapseAll( event );
11846				}
11847
11848				// Reset the mouseHandled flag
11849				this.mouseHandled = false;
11850			}
11851		});
11852	},
11853
11854	_destroy: function() {
11855		// Destroy (sub)menus
11856		this.element
11857			.removeAttr( "aria-activedescendant" )
11858			.find( ".ui-menu" ).addBack()
11859				.removeClass( "ui-menu ui-widget ui-widget-content ui-corner-all ui-menu-icons" )
11860				.removeAttr( "role" )
11861				.removeAttr( "tabIndex" )
11862				.removeAttr( "aria-labelledby" )
11863				.removeAttr( "aria-expanded" )
11864				.removeAttr( "aria-hidden" )
11865				.removeAttr( "aria-disabled" )
11866				.removeUniqueId()
11867				.show();
11868
11869		// Destroy menu items
11870		this.element.find( ".ui-menu-item" )
11871			.removeClass( "ui-menu-item" )
11872			.removeAttr( "role" )
11873			.removeAttr( "aria-disabled" )
11874			.children( "a" )
11875				.removeUniqueId()
11876				.removeClass( "ui-corner-all ui-state-hover" )
11877				.removeAttr( "tabIndex" )
11878				.removeAttr( "role" )
11879				.removeAttr( "aria-haspopup" )
11880				.children().each( function() {
11881					var elem = $( this );
11882					if ( elem.data( "ui-menu-submenu-carat" ) ) {
11883						elem.remove();
11884					}
11885				});
11886
11887		// Destroy menu dividers
11888		this.element.find( ".ui-menu-divider" ).removeClass( "ui-menu-divider ui-widget-content" );
11889	},
11890
11891	_keydown: function( event ) {
11892		/*jshint maxcomplexity:20*/
11893		var match, prev, character, skip, regex,
11894			preventDefault = true;
11895
11896		function escape( value ) {
11897			return value.replace( /[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&" );
11898		}
11899
11900		switch ( event.keyCode ) {
11901		case $.ui.keyCode.PAGE_UP:
11902			this.previousPage( event );
11903			break;
11904		case $.ui.keyCode.PAGE_DOWN:
11905			this.nextPage( event );
11906			break;
11907		case $.ui.keyCode.HOME:
11908			this._move( "first", "first", event );
11909			break;
11910		case $.ui.keyCode.END:
11911			this._move( "last", "last", event );
11912			break;
11913		case $.ui.keyCode.UP:
11914			this.previous( event );
11915			break;
11916		case $.ui.keyCode.DOWN:
11917			this.next( event );
11918			break;
11919		case $.ui.keyCode.LEFT:
11920			this.collapse( event );
11921			break;
11922		case $.ui.keyCode.RIGHT:
11923			if ( this.active && !this.active.is( ".ui-state-disabled" ) ) {
11924				this.expand( event );
11925			}
11926			break;
11927		case $.ui.keyCode.ENTER:
11928		case $.ui.keyCode.SPACE:
11929			this._activate( event );
11930			break;
11931		case $.ui.keyCode.ESCAPE:
11932			this.collapse( event );
11933			break;
11934		default:
11935			preventDefault = false;
11936			prev = this.previousFilter || "";
11937			character = String.fromCharCode( event.keyCode );
11938			skip = false;
11939
11940			clearTimeout( this.filterTimer );
11941
11942			if ( character === prev ) {
11943				skip = true;
11944			} else {
11945				character = prev + character;
11946			}
11947
11948			regex = new RegExp( "^" + escape( character ), "i" );
11949			match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
11950				return regex.test( $( this ).children( "a" ).text() );
11951			});
11952			match = skip && match.index( this.active.next() ) !== -1 ?
11953				this.active.nextAll( ".ui-menu-item" ) :
11954				match;
11955
11956			// If no matches on the current filter, reset to the last character pressed
11957			// to move down the menu to the first item that starts with that character
11958			if ( !match.length ) {
11959				character = String.fromCharCode( event.keyCode );
11960				regex = new RegExp( "^" + escape( character ), "i" );
11961				match = this.activeMenu.children( ".ui-menu-item" ).filter(function() {
11962					return regex.test( $( this ).children( "a" ).text() );
11963				});
11964			}
11965
11966			if ( match.length ) {
11967				this.focus( event, match );
11968				if ( match.length > 1 ) {
11969					this.previousFilter = character;
11970					this.filterTimer = this._delay(function() {
11971						delete this.previousFilter;
11972					}, 1000 );
11973				} else {
11974					delete this.previousFilter;
11975				}
11976			} else {
11977				delete this.previousFilter;
11978			}
11979		}
11980
11981		if ( preventDefault ) {
11982			event.preventDefault();
11983		}
11984	},
11985
11986	_activate: function( event ) {
11987		if ( !this.active.is( ".ui-state-disabled" ) ) {
11988			if ( this.active.children( "a[aria-haspopup='true']" ).length ) {
11989				this.expand( event );
11990			} else {
11991				this.select( event );
11992			}
11993		}
11994	},
11995
11996	refresh: function() {
11997		var menus,
11998			icon = this.options.icons.submenu,
11999			submenus = this.element.find( this.options.menus );
12000
12001		// Initialize nested menus
12002		submenus.filter( ":not(.ui-menu)" )
12003			.addClass( "ui-menu ui-widget ui-widget-content ui-corner-all" )
12004			.hide()
12005			.attr({
12006				role: this.options.role,
12007				"aria-hidden": "true",
12008				"aria-expanded": "false"
12009			})
12010			.each(function() {
12011				var menu = $( this ),
12012					item = menu.prev( "a" ),
12013					submenuCarat = $( "<span>" )
12014						.addClass( "ui-menu-icon ui-icon " + icon )
12015						.data( "ui-menu-submenu-carat", true );
12016
12017				item
12018					.attr( "aria-haspopup", "true" )
12019					.prepend( submenuCarat );
12020				menu.attr( "aria-labelledby", item.attr( "id" ) );
12021			});
12022
12023		menus = submenus.add( this.element );
12024
12025		// Don't refresh list items that are already adapted
12026		menus.children( ":not(.ui-menu-item):has(a)" )
12027			.addClass( "ui-menu-item" )
12028			.attr( "role", "presentation" )
12029			.children( "a" )
12030				.uniqueId()
12031				.addClass( "ui-corner-all" )
12032				.attr({
12033					tabIndex: -1,
12034					role: this._itemRole()
12035				});
12036
12037		// Initialize unlinked menu-items containing spaces and/or dashes only as dividers
12038		menus.children( ":not(.ui-menu-item)" ).each(function() {
12039			var item = $( this );
12040			// hyphen, em dash, en dash
12041			if ( !/[^\-\u2014\u2013\s]/.test( item.text() ) ) {
12042				item.addClass( "ui-widget-content ui-menu-divider" );
12043			}
12044		});
12045
12046		// Add aria-disabled attribute to any disabled menu item
12047		menus.children( ".ui-state-disabled" ).attr( "aria-disabled", "true" );
12048
12049		// If the active item has been removed, blur the menu
12050		if ( this.active && !$.contains( this.element[ 0 ], this.active[ 0 ] ) ) {
12051			this.blur();
12052		}
12053	},
12054
12055	_itemRole: function() {
12056		return {
12057			menu: "menuitem",
12058			listbox: "option"
12059		}[ this.options.role ];
12060	},
12061
12062	_setOption: function( key, value ) {
12063		if ( key === "icons" ) {
12064			this.element.find( ".ui-menu-icon" )
12065				.removeClass( this.options.icons.submenu )
12066				.addClass( value.submenu );
12067		}
12068		this._super( key, value );
12069	},
12070
12071	focus: function( event, item ) {
12072		var nested, focused;
12073		this.blur( event, event && event.type === "focus" );
12074
12075		this._scrollIntoView( item );
12076
12077		this.active = item.first();
12078		focused = this.active.children( "a" ).addClass( "ui-state-focus" );
12079		// Only update aria-activedescendant if there's a role
12080		// otherwise we assume focus is managed elsewhere
12081		if ( this.options.role ) {
12082			this.element.attr( "aria-activedescendant", focused.attr( "id" ) );
12083		}
12084
12085		// Highlight active parent menu item, if any
12086		this.active
12087			.parent()
12088			.closest( ".ui-menu-item" )
12089			.children( "a:first" )
12090			.addClass( "ui-state-active" );
12091
12092		if ( event && event.type === "keydown" ) {
12093			this._close();
12094		} else {
12095			this.timer = this._delay(function() {
12096				this._close();
12097			}, this.delay );
12098		}
12099
12100		nested = item.children( ".ui-menu" );
12101		if ( nested.length && ( /^mouse/.test( event.type ) ) ) {
12102			this._startOpening(nested);
12103		}
12104		this.activeMenu = item.parent();
12105
12106		this._trigger( "focus", event, { item: item } );
12107	},
12108
12109	_scrollIntoView: function( item ) {
12110		var borderTop, paddingTop, offset, scroll, elementHeight, itemHeight;
12111		if ( this._hasScroll() ) {
12112			borderTop = parseFloat( $.css( this.activeMenu[0], "borderTopWidth" ) ) || 0;
12113			paddingTop = parseFloat( $.css( this.activeMenu[0], "paddingTop" ) ) || 0;
12114			offset = item.offset().top - this.activeMenu.offset().top - borderTop - paddingTop;
12115			scroll = this.activeMenu.scrollTop();
12116			elementHeight = this.activeMenu.height();
12117			itemHeight = item.height();
12118
12119			if ( offset < 0 ) {
12120				this.activeMenu.scrollTop( scroll + offset );
12121			} else if ( offset + itemHeight > elementHeight ) {
12122				this.activeMenu.scrollTop( scroll + offset - elementHeight + itemHeight );
12123			}
12124		}
12125	},
12126
12127	blur: function( event, fromFocus ) {
12128		if ( !fromFocus ) {
12129			clearTimeout( this.timer );
12130		}
12131
12132		if ( !this.active ) {
12133			return;
12134		}
12135
12136		this.active.children( "a" ).removeClass( "ui-state-focus" );
12137		this.active = null;
12138
12139		this._trigger( "blur", event, { item: this.active } );
12140	},
12141
12142	_startOpening: function( submenu ) {
12143		clearTimeout( this.timer );
12144
12145		// Don't open if already open fixes a Firefox bug that caused a .5 pixel
12146		// shift in the submenu position when mousing over the carat icon
12147		if ( submenu.attr( "aria-hidden" ) !== "true" ) {
12148			return;
12149		}
12150
12151		this.timer = this._delay(function() {
12152			this._close();
12153			this._open( submenu );
12154		}, this.delay );
12155	},
12156
12157	_open: function( submenu ) {
12158		var position = $.extend({
12159			of: this.active
12160		}, this.options.position );
12161
12162		clearTimeout( this.timer );
12163		this.element.find( ".ui-menu" ).not( submenu.parents( ".ui-menu" ) )
12164			.hide()
12165			.attr( "aria-hidden", "true" );
12166
12167		submenu
12168			.show()
12169			.removeAttr( "aria-hidden" )
12170			.attr( "aria-expanded", "true" )
12171			.position( position );
12172	},
12173
12174	collapseAll: function( event, all ) {
12175		clearTimeout( this.timer );
12176		this.timer = this._delay(function() {
12177			// If we were passed an event, look for the submenu that contains the event
12178			var currentMenu = all ? this.element :
12179				$( event && event.target ).closest( this.element.find( ".ui-menu" ) );
12180
12181			// If we found no valid submenu ancestor, use the main menu to close all sub menus anyway
12182			if ( !currentMenu.length ) {
12183				currentMenu = this.element;
12184			}
12185
12186			this._close( currentMenu );
12187
12188			this.blur( event );
12189			this.activeMenu = currentMenu;
12190		}, this.delay );
12191	},
12192
12193	// With no arguments, closes the currently active menu - if nothing is active
12194	// it closes all menus.  If passed an argument, it will search for menus BELOW
12195	_close: function( startMenu ) {
12196		if ( !startMenu ) {
12197			startMenu = this.active ? this.active.parent() : this.element;
12198		}
12199
12200		startMenu
12201			.find( ".ui-menu" )
12202				.hide()
12203				.attr( "aria-hidden", "true" )
12204				.attr( "aria-expanded", "false" )
12205			.end()
12206			.find( "a.ui-state-active" )
12207				.removeClass( "ui-state-active" );
12208	},
12209
12210	collapse: function( event ) {
12211		var newItem = this.active &&
12212			this.active.parent().closest( ".ui-menu-item", this.element );
12213		if ( newItem && newItem.length ) {
12214			this._close();
12215			this.focus( event, newItem );
12216		}
12217	},
12218
12219	expand: function( event ) {
12220		var newItem = this.active &&
12221			this.active
12222				.children( ".ui-menu " )
12223				.children( ".ui-menu-item" )
12224				.first();
12225
12226		if ( newItem && newItem.length ) {
12227			this._open( newItem.parent() );
12228
12229			// Delay so Firefox will not hide activedescendant change in expanding submenu from AT
12230			this._delay(function() {
12231				this.focus( event, newItem );
12232			});
12233		}
12234	},
12235
12236	next: function( event ) {
12237		this._move( "next", "first", event );
12238	},
12239
12240	previous: function( event ) {
12241		this._move( "prev", "last", event );
12242	},
12243
12244	isFirstItem: function() {
12245		return this.active && !this.active.prevAll( ".ui-menu-item" ).length;
12246	},
12247
12248	isLastItem: function() {
12249		return this.active && !this.active.nextAll( ".ui-menu-item" ).length;
12250	},
12251
12252	_move: function( direction, filter, event ) {
12253		var next;
12254		if ( this.active ) {
12255			if ( direction === "first" || direction === "last" ) {
12256				next = this.active
12257					[ direction === "first" ? "prevAll" : "nextAll" ]( ".ui-menu-item" )
12258					.eq( -1 );
12259			} else {
12260				next = this.active
12261					[ direction + "All" ]( ".ui-menu-item" )
12262					.eq( 0 );
12263			}
12264		}
12265		if ( !next || !next.length || !this.active ) {
12266			next = this.activeMenu.children( ".ui-menu-item" )[ filter ]();
12267		}
12268
12269		this.focus( event, next );
12270	},
12271
12272	nextPage: function( event ) {
12273		var item, base, height;
12274
12275		if ( !this.active ) {
12276			this.next( event );
12277			return;
12278		}
12279		if ( this.isLastItem() ) {
12280			return;
12281		}
12282		if ( this._hasScroll() ) {
12283			base = this.active.offset().top;
12284			height = this.element.height();
12285			this.active.nextAll( ".ui-menu-item" ).each(function() {
12286				item = $( this );
12287				return item.offset().top - base - height < 0;
12288			});
12289
12290			this.focus( event, item );
12291		} else {
12292			this.focus( event, this.activeMenu.children( ".ui-menu-item" )
12293				[ !this.active ? "first" : "last" ]() );
12294		}
12295	},
12296
12297	previousPage: function( event ) {
12298		var item, base, height;
12299		if ( !this.active ) {
12300			this.next( event );
12301			return;
12302		}
12303		if ( this.isFirstItem() ) {
12304			return;
12305		}
12306		if ( this._hasScroll() ) {
12307			base = this.active.offset().top;
12308			height = this.element.height();
12309			this.active.prevAll( ".ui-menu-item" ).each(function() {
12310				item = $( this );
12311				return item.offset().top - base + height > 0;
12312			});
12313
12314			this.focus( event, item );
12315		} else {
12316			this.focus( event, this.activeMenu.children( ".ui-menu-item" ).first() );
12317		}
12318	},
12319
12320	_hasScroll: function() {
12321		return this.element.outerHeight() < this.element.prop( "scrollHeight" );
12322	},
12323
12324	select: function( event ) {
12325		// TODO: It should never be possible to not have an active item at this
12326		// point, but the tests don't trigger mouseenter before click.
12327		this.active = this.active || $( event.target ).closest( ".ui-menu-item" );
12328		var ui = { item: this.active };
12329		if ( !this.active.has( ".ui-menu" ).length ) {
12330			this.collapseAll( event, true );
12331		}
12332		this._trigger( "select", event, ui );
12333	}
12334});
12335
12336}( jQuery ));
12337
12338
12339
12340
12341
12342/*!
12343 * jQuery UI Autocomplete 1.10.3
12344 * http://jqueryui.com
12345 *
12346 * Copyright 2013 jQuery Foundation and other contributors
12347 * Released under the MIT license.
12348 * http://jquery.org/license
12349 *
12350 * http://api.jqueryui.com/autocomplete/
12351 *
12352 * Depends:
12353 *	jquery.ui.core.js
12354 *	jquery.ui.widget.js
12355 *	jquery.ui.position.js
12356 *	jquery.ui.menu.js
12357 */
12358
12359(function( $, undefined ) {
12360
12361// used to prevent race conditions with remote data sources
12362var requestIndex = 0;
12363
12364$.widget( "ui.autocomplete", {
12365	version: "1.10.3",
12366	defaultElement: "<input>",
12367	options: {
12368		appendTo: null,
12369		autoFocus: false,
12370		delay: 300,
12371		minLength: 1,
12372		position: {
12373			my: "left top",
12374			at: "left bottom",
12375			collision: "none"
12376		},
12377		source: null,
12378
12379		// callbacks
12380		change: null,
12381		close: null,
12382		focus: null,
12383		open: null,
12384		response: null,
12385		search: null,
12386		select: null
12387	},
12388
12389	pending: 0,
12390
12391	_create: function() {
12392		// Some browsers only repeat keydown events, not keypress events,
12393		// so we use the suppressKeyPress flag to determine if we've already
12394		// handled the keydown event. #7269
12395		// Unfortunately the code for & in keypress is the same as the up arrow,
12396		// so we use the suppressKeyPressRepeat flag to avoid handling keypress
12397		// events when we know the keydown event was used to modify the
12398		// search term. #7799
12399		var suppressKeyPress, suppressKeyPressRepeat, suppressInput,
12400			nodeName = this.element[0].nodeName.toLowerCase(),
12401			isTextarea = nodeName === "textarea",
12402			isInput = nodeName === "input";
12403
12404		this.isMultiLine =
12405			// Textareas are always multi-line
12406			isTextarea ? true :
12407			// Inputs are always single-line, even if inside a contentEditable element
12408			// IE also treats inputs as contentEditable
12409			isInput ? false :
12410			// All other element types are determined by whether or not they're contentEditable
12411			this.element.prop( "isContentEditable" );
12412
12413		this.valueMethod = this.element[ isTextarea || isInput ? "val" : "text" ];
12414		this.isNewMenu = true;
12415
12416		this.element
12417			.addClass( "ui-autocomplete-input" )
12418			.attr( "autocomplete", "off" );
12419
12420		this._on( this.element, {
12421			keydown: function( event ) {
12422				/*jshint maxcomplexity:15*/
12423				if ( this.element.prop( "readOnly" ) ) {
12424					suppressKeyPress = true;
12425					suppressInput = true;
12426					suppressKeyPressRepeat = true;
12427					return;
12428				}
12429
12430				suppressKeyPress = false;
12431				suppressInput = false;
12432				suppressKeyPressRepeat = false;
12433				var keyCode = $.ui.keyCode;
12434				switch( event.keyCode ) {
12435				case keyCode.PAGE_UP:
12436					suppressKeyPress = true;
12437					this._move( "previousPage", event );
12438					break;
12439				case keyCode.PAGE_DOWN:
12440					suppressKeyPress = true;
12441					this._move( "nextPage", event );
12442					break;
12443				case keyCode.UP:
12444					suppressKeyPress = true;
12445					this._keyEvent( "previous", event );
12446					break;
12447				case keyCode.DOWN:
12448					suppressKeyPress = true;
12449					this._keyEvent( "next", event );
12450					break;
12451				case keyCode.ENTER:
12452				case keyCode.NUMPAD_ENTER:
12453					// when menu is open and has focus
12454					if ( this.menu.active ) {
12455						// #6055 - Opera still allows the keypress to occur
12456						// which causes forms to submit
12457						suppressKeyPress = true;
12458						event.preventDefault();
12459						this.menu.select( event );
12460					}
12461					break;
12462				case keyCode.TAB:
12463					if ( this.menu.active ) {
12464						this.menu.select( event );
12465					}
12466					break;
12467				case keyCode.ESCAPE:
12468					if ( this.menu.element.is( ":visible" ) ) {
12469						this._value( this.term );
12470						this.close( event );
12471						// Different browsers have different default behavior for escape
12472						// Single press can mean undo or clear
12473						// Double press in IE means clear the whole form
12474						event.preventDefault();
12475					}
12476					break;
12477				default:
12478					suppressKeyPressRepeat = true;
12479					// search timeout should be triggered before the input value is changed
12480					this._searchTimeout( event );
12481					break;
12482				}
12483			},
12484			keypress: function( event ) {
12485				if ( suppressKeyPress ) {
12486					suppressKeyPress = false;
12487					if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
12488						event.preventDefault();
12489					}
12490					return;
12491				}
12492				if ( suppressKeyPressRepeat ) {
12493					return;
12494				}
12495
12496				// replicate some key handlers to allow them to repeat in Firefox and Opera
12497				var keyCode = $.ui.keyCode;
12498				switch( event.keyCode ) {
12499				case keyCode.PAGE_UP:
12500					this._move( "previousPage", event );
12501					break;
12502				case keyCode.PAGE_DOWN:
12503					this._move( "nextPage", event );
12504					break;
12505				case keyCode.UP:
12506					this._keyEvent( "previous", event );
12507					break;
12508				case keyCode.DOWN:
12509					this._keyEvent( "next", event );
12510					break;
12511				}
12512			},
12513			input: function( event ) {
12514				if ( suppressInput ) {
12515					suppressInput = false;
12516					event.preventDefault();
12517					return;
12518				}
12519				this._searchTimeout( event );
12520			},
12521			focus: function() {
12522				this.selectedItem = null;
12523				this.previous = this._value();
12524			},
12525			blur: function( event ) {
12526				if ( this.cancelBlur ) {
12527					delete this.cancelBlur;
12528					return;
12529				}
12530
12531				clearTimeout( this.searching );
12532				this.close( event );
12533				this._change( event );
12534			}
12535		});
12536
12537		this._initSource();
12538		this.menu = $( "<ul>" )
12539			.addClass( "ui-autocomplete ui-front" )
12540			.appendTo( this._appendTo() )
12541			.menu({
12542				// disable ARIA support, the live region takes care of that
12543				role: null
12544			})
12545			.hide()
12546			.data( "ui-menu" );
12547
12548		this._on( this.menu.element, {
12549			mousedown: function( event ) {
12550				// prevent moving focus out of the text field
12551				event.preventDefault();
12552
12553				// IE doesn't prevent moving focus even with event.preventDefault()
12554				// so we set a flag to know when we should ignore the blur event
12555				this.cancelBlur = true;
12556				this._delay(function() {
12557					delete this.cancelBlur;
12558				});
12559
12560				// clicking on the scrollbar causes focus to shift to the body
12561				// but we can't detect a mouseup or a click immediately afterward
12562				// so we have to track the next mousedown and close the menu if
12563				// the user clicks somewhere outside of the autocomplete
12564				var menuElement = this.menu.element[ 0 ];
12565				if ( !$( event.target ).closest( ".ui-menu-item" ).length ) {
12566					this._delay(function() {
12567						var that = this;
12568						this.document.one( "mousedown", function( event ) {
12569							if ( event.target !== that.element[ 0 ] &&
12570									event.target !== menuElement &&
12571									!$.contains( menuElement, event.target ) ) {
12572								that.close();
12573							}
12574						});
12575					});
12576				}
12577			},
12578			menufocus: function( event, ui ) {
12579				// support: Firefox
12580				// Prevent accidental activation of menu items in Firefox (#7024 #9118)
12581				if ( this.isNewMenu ) {
12582					this.isNewMenu = false;
12583					if ( event.originalEvent && /^mouse/.test( event.originalEvent.type ) ) {
12584						this.menu.blur();
12585
12586						this.document.one( "mousemove", function() {
12587							$( event.target ).trigger( event.originalEvent );
12588						});
12589
12590						return;
12591					}
12592				}
12593
12594				var item = ui.item.data( "ui-autocomplete-item" );
12595				if ( false !== this._trigger( "focus", event, { item: item } ) ) {
12596					// use value to match what will end up in the input, if it was a key event
12597					if ( event.originalEvent && /^key/.test( event.originalEvent.type ) ) {
12598						this._value( item.value );
12599					}
12600				} else {
12601					// Normally the input is populated with the item's value as the
12602					// menu is navigated, causing screen readers to notice a change and
12603					// announce the item. Since the focus event was canceled, this doesn't
12604					// happen, so we update the live region so that screen readers can
12605					// still notice the change and announce it.
12606					this.liveRegion.text( item.value );
12607				}
12608			},
12609			menuselect: function( event, ui ) {
12610				var item = ui.item.data( "ui-autocomplete-item" ),
12611					previous = this.previous;
12612
12613				// only trigger when focus was lost (click on menu)
12614				if ( this.element[0] !== this.document[0].activeElement ) {
12615					this.element.focus();
12616					this.previous = previous;
12617					// #6109 - IE triggers two focus events and the second
12618					// is asynchronous, so we need to reset the previous
12619					// term synchronously and asynchronously :-(
12620					this._delay(function() {
12621						this.previous = previous;
12622						this.selectedItem = item;
12623					});
12624				}
12625
12626				if ( false !== this._trigger( "select", event, { item: item } ) ) {
12627					this._value( item.value );
12628				}
12629				// reset the term after the select event
12630				// this allows custom select handling to work properly
12631				this.term = this._value();
12632
12633				this.close( event );
12634				this.selectedItem = item;
12635			}
12636		});
12637
12638		this.liveRegion = $( "<span>", {
12639				role: "status",
12640				"aria-live": "polite"
12641			})
12642			.addClass( "ui-helper-hidden-accessible" )
12643			.insertBefore( this.element );
12644
12645		// turning off autocomplete prevents the browser from remembering the
12646		// value when navigating through history, so we re-enable autocomplete
12647		// if the page is unloaded before the widget is destroyed. #7790
12648		this._on( this.window, {
12649			beforeunload: function() {
12650				this.element.removeAttr( "autocomplete" );
12651			}
12652		});
12653	},
12654
12655	_destroy: function() {
12656		clearTimeout( this.searching );
12657		this.element
12658			.removeClass( "ui-autocomplete-input" )
12659			.removeAttr( "autocomplete" );
12660		this.menu.element.remove();
12661		this.liveRegion.remove();
12662	},
12663
12664	_setOption: function( key, value ) {
12665		this._super( key, value );
12666		if ( key === "source" ) {
12667			this._initSource();
12668		}
12669		if ( key === "appendTo" ) {
12670			this.menu.element.appendTo( this._appendTo() );
12671		}
12672		if ( key === "disabled" && value && this.xhr ) {
12673			this.xhr.abort();
12674		}
12675	},
12676
12677	_appendTo: function() {
12678		var element = this.options.appendTo;
12679
12680		if ( element ) {
12681			element = element.jquery || element.nodeType ?
12682				$( element ) :
12683				this.document.find( element ).eq( 0 );
12684		}
12685
12686		if ( !element ) {
12687			element = this.element.closest( ".ui-front" );
12688		}
12689
12690		if ( !element.length ) {
12691			element = this.document[0].body;
12692		}
12693
12694		return element;
12695	},
12696
12697	_initSource: function() {
12698		var array, url,
12699			that = this;
12700		if ( $.isArray(this.options.source) ) {
12701			array = this.options.source;
12702			this.source = function( request, response ) {
12703				response( $.ui.autocomplete.filter( array, request.term ) );
12704			};
12705		} else if ( typeof this.options.source === "string" ) {
12706			url = this.options.source;
12707			this.source = function( request, response ) {
12708				if ( that.xhr ) {
12709					that.xhr.abort();
12710				}
12711				that.xhr = $.ajax({
12712					url: url,
12713					data: request,
12714					dataType: "json",
12715					success: function( data ) {
12716						response( data );
12717					},
12718					error: function() {
12719						response( [] );
12720					}
12721				});
12722			};
12723		} else {
12724			this.source = this.options.source;
12725		}
12726	},
12727
12728	_searchTimeout: function( event ) {
12729		clearTimeout( this.searching );
12730		this.searching = this._delay(function() {
12731			// only search if the value has changed
12732			if ( this.term !== this._value() ) {
12733				this.selectedItem = null;
12734				this.search( null, event );
12735			}
12736		}, this.options.delay );
12737	},
12738
12739	search: function( value, event ) {
12740		value = value != null ? value : this._value();
12741
12742		// always save the actual value, not the one passed as an argument
12743		this.term = this._value();
12744
12745		if ( value.length < this.options.minLength ) {
12746			return this.close( event );
12747		}
12748
12749		if ( this._trigger( "search", event ) === false ) {
12750			return;
12751		}
12752
12753		return this._search( value );
12754	},
12755
12756	_search: function( value ) {
12757		this.pending++;
12758		this.element.addClass( "ui-autocomplete-loading" );
12759		this.cancelSearch = false;
12760
12761		this.source( { term: value }, this._response() );
12762	},
12763
12764	_response: function() {
12765		var that = this,
12766			index = ++requestIndex;
12767
12768		return function( content ) {
12769			if ( index === requestIndex ) {
12770				that.__response( content );
12771			}
12772
12773			that.pending--;
12774			if ( !that.pending ) {
12775				that.element.removeClass( "ui-autocomplete-loading" );
12776			}
12777		};
12778	},
12779
12780	__response: function( content ) {
12781		if ( content ) {
12782			content = this._normalize( content );
12783		}
12784		this._trigger( "response", null, { content: content } );
12785		if ( !this.options.disabled && content && content.length && !this.cancelSearch ) {
12786			this._suggest( content );
12787			this._trigger( "open" );
12788		} else {
12789			// use ._close() instead of .close() so we don't cancel future searches
12790			this._close();
12791		}
12792	},
12793
12794	close: function( event ) {
12795		this.cancelSearch = true;
12796		this._close( event );
12797	},
12798
12799	_close: function( event ) {
12800		if ( this.menu.element.is( ":visible" ) ) {
12801			this.menu.element.hide();
12802			this.menu.blur();
12803			this.isNewMenu = true;
12804			this._trigger( "close", event );
12805		}
12806	},
12807
12808	_change: function( event ) {
12809		if ( this.previous !== this._value() ) {
12810			this._trigger( "change", event, { item: this.selectedItem } );
12811		}
12812	},
12813
12814	_normalize: function( items ) {
12815		// assume all items have the right format when the first item is complete
12816		if ( items.length && items[0].label && items[0].value ) {
12817			return items;
12818		}
12819		return $.map( items, function( item ) {
12820			if ( typeof item === "string" ) {
12821				return {
12822					label: item,
12823					value: item
12824				};
12825			}
12826			return $.extend({
12827				label: item.label || item.value,
12828				value: item.value || item.label
12829			}, item );
12830		});
12831	},
12832
12833	_suggest: function( items ) {
12834		var ul = this.menu.element.empty();
12835		this._renderMenu( ul, items );
12836		this.isNewMenu = true;
12837		this.menu.refresh();
12838
12839		// size and position menu
12840		ul.show();
12841		this._resizeMenu();
12842		ul.position( $.extend({
12843			of: this.element
12844		}, this.options.position ));
12845
12846		if ( this.options.autoFocus ) {
12847			this.menu.next();
12848		}
12849	},
12850
12851	_resizeMenu: function() {
12852		var ul = this.menu.element;
12853		ul.outerWidth( Math.max(
12854			// Firefox wraps long text (possibly a rounding bug)
12855			// so we add 1px to avoid the wrapping (#7513)
12856			ul.width( "" ).outerWidth() + 1,
12857			this.element.outerWidth()
12858		) );
12859	},
12860
12861	_renderMenu: function( ul, items ) {
12862		var that = this;
12863		$.each( items, function( index, item ) {
12864			that._renderItemData( ul, item );
12865		});
12866	},
12867
12868	_renderItemData: function( ul, item ) {
12869		return this._renderItem( ul, item ).data( "ui-autocomplete-item", item );
12870	},
12871
12872	_renderItem: function( ul, item ) {
12873		return $( "<li>" )
12874			.append( $( "<a>" ).text( item.label ) )
12875			.appendTo( ul );
12876	},
12877
12878	_move: function( direction, event ) {
12879		if ( !this.menu.element.is( ":visible" ) ) {
12880			this.search( null, event );
12881			return;
12882		}
12883		if ( this.menu.isFirstItem() && /^previous/.test( direction ) ||
12884				this.menu.isLastItem() && /^next/.test( direction ) ) {
12885			this._value( this.term );
12886			this.menu.blur();
12887			return;
12888		}
12889		this.menu[ direction ]( event );
12890	},
12891
12892	widget: function() {
12893		return this.menu.element;
12894	},
12895
12896	_value: function() {
12897		return this.valueMethod.apply( this.element, arguments );
12898	},
12899
12900	_keyEvent: function( keyEvent, event ) {
12901		if ( !this.isMultiLine || this.menu.element.is( ":visible" ) ) {
12902			this._move( keyEvent, event );
12903
12904			// prevents moving cursor to beginning/end of the text field in some browsers
12905			event.preventDefault();
12906		}
12907	}
12908});
12909
12910$.extend( $.ui.autocomplete, {
12911	escapeRegex: function( value ) {
12912		return value.replace(/[\-\[\]{}()*+?.,\\\^$|#\s]/g, "\\$&");
12913	},
12914	filter: function(array, term) {
12915		var matcher = new RegExp( $.ui.autocomplete.escapeRegex(term), "i" );
12916		return $.grep( array, function(value) {
12917			return matcher.test( value.label || value.value || value );
12918		});
12919	}
12920});
12921
12922
12923// live region extension, adding a `messages` option
12924// NOTE: This is an experimental API. We are still investigating
12925// a full solution for string manipulation and internationalization.
12926$.widget( "ui.autocomplete", $.ui.autocomplete, {
12927	options: {
12928		messages: {
12929			noResults: "No search results.",
12930			results: function( amount ) {
12931				return amount + ( amount > 1 ? " results are" : " result is" ) +
12932					" available, use up and down arrow keys to navigate.";
12933			}
12934		}
12935	},
12936
12937	__response: function( content ) {
12938		var message;
12939		this._superApply( arguments );
12940		if ( this.options.disabled || this.cancelSearch ) {
12941			return;
12942		}
12943		if ( content && content.length ) {
12944			message = this.options.messages.results( content.length );
12945		} else {
12946			message = this.options.messages.noResults;
12947		}
12948		this.liveRegion.text( message );
12949	}
12950});
12951
12952}( jQuery ));
12953
12954
12955
12956/*!
12957 * jQuery UI Button 1.10.3
12958 * http://jqueryui.com
12959 *
12960 * Copyright 2013 jQuery Foundation and other contributors
12961 * Released under the MIT license.
12962 * http://jquery.org/license
12963 *
12964 * http://api.jqueryui.com/button/
12965 *
12966 * Depends:
12967 *	jquery.ui.core.js
12968 *	jquery.ui.widget.js
12969 */
12970
12971(function( $, undefined ) {
12972
12973var lastActive, startXPos, startYPos, clickDragged,
12974	baseClasses = "ui-button ui-widget ui-state-default ui-corner-all",
12975	stateClasses = "ui-state-hover ui-state-active ",
12976	typeClasses = "ui-button-icons-only ui-button-icon-only ui-button-text-icons ui-button-text-icon-primary ui-button-text-icon-secondary ui-button-text-only",
12977	formResetHandler = function() {
12978		var form = $( this );
12979		setTimeout(function() {
12980			form.find( ":ui-button" ).button( "refresh" );
12981		}, 1 );
12982	},
12983	radioGroup = function( radio ) {
12984		var name = radio.name,
12985			form = radio.form,
12986			radios = $( [] );
12987		if ( name ) {
12988			name = name.replace( /'/g, "\\'" );
12989			if ( form ) {
12990				radios = $( form ).find( "[name='" + name + "']" );
12991			} else {
12992				radios = $( "[name='" + name + "']", radio.ownerDocument )
12993					.filter(function() {
12994						return !this.form;
12995					});
12996			}
12997		}
12998		return radios;
12999	};
13000
13001$.widget( "ui.button", {
13002	version: "1.10.3",
13003	defaultElement: "<button>",
13004	options: {
13005		disabled: null,
13006		text: true,
13007		label: null,
13008		icons: {
13009			primary: null,
13010			secondary: null
13011		}
13012	},
13013	_create: function() {
13014		this.element.closest( "form" )
13015			.unbind( "reset" + this.eventNamespace )
13016			.bind( "reset" + this.eventNamespace, formResetHandler );
13017
13018		if ( typeof this.options.disabled !== "boolean" ) {
13019			this.options.disabled = !!this.element.prop( "disabled" );
13020		} else {
13021			this.element.prop( "disabled", this.options.disabled );
13022		}
13023
13024		this._determineButtonType();
13025		this.hasTitle = !!this.buttonElement.attr( "title" );
13026
13027		var that = this,
13028			options = this.options,
13029			toggleButton = this.type === "checkbox" || this.type === "radio",
13030			activeClass = !toggleButton ? "ui-state-active" : "",
13031			focusClass = "ui-state-focus";
13032
13033		if ( options.label === null ) {
13034			options.label = (this.type === "input" ? this.buttonElement.val() : this.buttonElement.html());
13035		}
13036
13037		this._hoverable( this.buttonElement );
13038
13039		this.buttonElement
13040			.addClass( baseClasses )
13041			.attr( "role", "button" )
13042			.bind( "mouseenter" + this.eventNamespace, function() {
13043				if ( options.disabled ) {
13044					return;
13045				}
13046				if ( this === lastActive ) {
13047					$( this ).addClass( "ui-state-active" );
13048				}
13049			})
13050			.bind( "mouseleave" + this.eventNamespace, function() {
13051				if ( options.disabled ) {
13052					return;
13053				}
13054				$( this ).removeClass( activeClass );
13055			})
13056			.bind( "click" + this.eventNamespace, function( event ) {
13057				if ( options.disabled ) {
13058					event.preventDefault();
13059					event.stopImmediatePropagation();
13060				}
13061			});
13062
13063		this.element
13064			.bind( "focus" + this.eventNamespace, function() {
13065				// no need to check disabled, focus won't be triggered anyway
13066				that.buttonElement.addClass( focusClass );
13067			})
13068			.bind( "blur" + this.eventNamespace, function() {
13069				that.buttonElement.removeClass( focusClass );
13070			});
13071
13072		if ( toggleButton ) {
13073			this.element.bind( "change" + this.eventNamespace, function() {
13074				if ( clickDragged ) {
13075					return;
13076				}
13077				that.refresh();
13078			});
13079			// if mouse moves between mousedown and mouseup (drag) set clickDragged flag
13080			// prevents issue where button state changes but checkbox/radio checked state
13081			// does not in Firefox (see ticket #6970)
13082			this.buttonElement
13083				.bind( "mousedown" + this.eventNamespace, function( event ) {
13084					if ( options.disabled ) {
13085						return;
13086					}
13087					clickDragged = false;
13088					startXPos = event.pageX;
13089					startYPos = event.pageY;
13090				})
13091				.bind( "mouseup" + this.eventNamespace, function( event ) {
13092					if ( options.disabled ) {
13093						return;
13094					}
13095					if ( startXPos !== event.pageX || startYPos !== event.pageY ) {
13096						clickDragged = true;
13097					}
13098			});
13099		}
13100
13101		if ( this.type === "checkbox" ) {
13102			this.buttonElement.bind( "click" + this.eventNamespace, function() {
13103				if ( options.disabled || clickDragged ) {
13104					return false;
13105				}
13106			});
13107		} else if ( this.type === "radio" ) {
13108			this.buttonElement.bind( "click" + this.eventNamespace, function() {
13109				if ( options.disabled || clickDragged ) {
13110					return false;
13111				}
13112				$( this ).addClass( "ui-state-active" );
13113				that.buttonElement.attr( "aria-pressed", "true" );
13114
13115				var radio = that.element[ 0 ];
13116				radioGroup( radio )
13117					.not( radio )
13118					.map(function() {
13119						return $( this ).button( "widget" )[ 0 ];
13120					})
13121					.removeClass( "ui-state-active" )
13122					.attr( "aria-pressed", "false" );
13123			});
13124		} else {
13125			this.buttonElement
13126				.bind( "mousedown" + this.eventNamespace, function() {
13127					if ( options.disabled ) {
13128						return false;
13129					}
13130					$( this ).addClass( "ui-state-active" );
13131					lastActive = this;
13132					that.document.one( "mouseup", function() {
13133						lastActive = null;
13134					});
13135				})
13136				.bind( "mouseup" + this.eventNamespace, function() {
13137					if ( options.disabled ) {
13138						return false;
13139					}
13140					$( this ).removeClass( "ui-state-active" );
13141				})
13142				.bind( "keydown" + this.eventNamespace, function(event) {
13143					if ( options.disabled ) {
13144						return false;
13145					}
13146					if ( event.keyCode === $.ui.keyCode.SPACE || event.keyCode === $.ui.keyCode.ENTER ) {
13147						$( this ).addClass( "ui-state-active" );
13148					}
13149				})
13150				// see #8559, we bind to blur here in case the button element loses
13151				// focus between keydown and keyup, it would be left in an "active" state
13152				.bind( "keyup" + this.eventNamespace + " blur" + this.eventNamespace, function() {
13153					$( this ).removeClass( "ui-state-active" );
13154				});
13155
13156			if ( this.buttonElement.is("a") ) {
13157				this.buttonElement.keyup(function(event) {
13158					if ( event.keyCode === $.ui.keyCode.SPACE ) {
13159						// TODO pass through original event correctly (just as 2nd argument doesn't work)
13160						$( this ).click();
13161					}
13162				});
13163			}
13164		}
13165
13166		// TODO: pull out $.Widget's handling for the disabled option into
13167		// $.Widget.prototype._setOptionDisabled so it's easy to proxy and can
13168		// be overridden by individual plugins
13169		this._setOption( "disabled", options.disabled );
13170		this._resetButton();
13171	},
13172
13173	_determineButtonType: function() {
13174		var ancestor, labelSelector, checked;
13175
13176		if ( this.element.is("[type=checkbox]") ) {
13177			this.type = "checkbox";
13178		} else if ( this.element.is("[type=radio]") ) {
13179			this.type = "radio";
13180		} else if ( this.element.is("input") ) {
13181			this.type = "input";
13182		} else {
13183			this.type = "button";
13184		}
13185
13186		if ( this.type === "checkbox" || this.type === "radio" ) {
13187			// we don't search against the document in case the element
13188			// is disconnected from the DOM
13189			ancestor = this.element.parents().last();
13190			labelSelector = "label[for='" + this.element.attr("id") + "']";
13191			this.buttonElement = ancestor.find( labelSelector );
13192			if ( !this.buttonElement.length ) {
13193				ancestor = ancestor.length ? ancestor.siblings() : this.element.siblings();
13194				this.buttonElement = ancestor.filter( labelSelector );
13195				if ( !this.buttonElement.length ) {
13196					this.buttonElement = ancestor.find( labelSelector );
13197				}
13198			}
13199			this.element.addClass( "ui-helper-hidden-accessible" );
13200
13201			checked = this.element.is( ":checked" );
13202			if ( checked ) {
13203				this.buttonElement.addClass( "ui-state-active" );
13204			}
13205			this.buttonElement.prop( "aria-pressed", checked );
13206		} else {
13207			this.buttonElement = this.element;
13208		}
13209	},
13210
13211	widget: function() {
13212		return this.buttonElement;
13213	},
13214
13215	_destroy: function() {
13216		this.element
13217			.removeClass( "ui-helper-hidden-accessible" );
13218		this.buttonElement
13219			.removeClass( baseClasses + " " + stateClasses + " " + typeClasses )
13220			.removeAttr( "role" )
13221			.removeAttr( "aria-pressed" )
13222			.html( this.buttonElement.find(".ui-button-text").html() );
13223
13224		if ( !this.hasTitle ) {
vendor: 4,220 bytes, lines 13225-13390
13225			this.buttonElement.removeAttr( "title" );
13226		}
13227	},
13228
13229	_setOption: function( key, value ) {
13230		this._super( key, value );
13231		if ( key === "disabled" ) {
13232			if ( value ) {
13233				this.element.prop( "disabled", true );
13234			} else {
13235				this.element.prop( "disabled", false );
13236			}
13237			return;
13238		}
13239		this._resetButton();
13240	},
13241
13242	refresh: function() {
13243		//See #8237 & #8828
13244		var isDisabled = this.element.is( "input, button" ) ? this.element.is( ":disabled" ) : this.element.hasClass( "ui-button-disabled" );
13245
13246		if ( isDisabled !== this.options.disabled ) {
13247			this._setOption( "disabled", isDisabled );
13248		}
13249		if ( this.type === "radio" ) {
13250			radioGroup( this.element[0] ).each(function() {
13251				if ( $( this ).is( ":checked" ) ) {
13252					$( this ).button( "widget" )
13253						.addClass( "ui-state-active" )
13254						.attr( "aria-pressed", "true" );
13255				} else {
13256					$( this ).button( "widget" )
13257						.removeClass( "ui-state-active" )
13258						.attr( "aria-pressed", "false" );
13259				}
13260			});
13261		} else if ( this.type === "checkbox" ) {
13262			if ( this.element.is( ":checked" ) ) {
13263				this.buttonElement
13264					.addClass( "ui-state-active" )
13265					.attr( "aria-pressed", "true" );
13266			} else {
13267				this.buttonElement
13268					.removeClass( "ui-state-active" )
13269					.attr( "aria-pressed", "false" );
13270			}
13271		}
13272	},
13273
13274	_resetButton: function() {
13275		if ( this.type === "input" ) {
13276			if ( this.options.label ) {
13277				this.element.val( this.options.label );
13278			}
13279			return;
13280		}
13281		var buttonElement = this.buttonElement.removeClass( typeClasses ),
13282			buttonText = $( "<span></span>", this.document[0] )
13283				.addClass( "ui-button-text" )
13284				.html( this.options.label )
13285				.appendTo( buttonElement.empty() )
13286				.text(),
13287			icons = this.options.icons,
13288			multipleIcons = icons.primary && icons.secondary,
13289			buttonClasses = [];
13290
13291		if ( icons.primary || icons.secondary ) {
13292			if ( this.options.text ) {
13293				buttonClasses.push( "ui-button-text-icon" + ( multipleIcons ? "s" : ( icons.primary ? "-primary" : "-secondary" ) ) );
13294			}
13295
13296			if ( icons.primary ) {
13297				buttonElement.prepend( "<span class='ui-button-icon-primary ui-icon " + icons.primary + "'></span>" );
13298			}
13299
13300			if ( icons.secondary ) {
13301				buttonElement.append( "<span class='ui-button-icon-secondary ui-icon " + icons.secondary + "'></span>" );
13302			}
13303
13304			if ( !this.options.text ) {
13305				buttonClasses.push( multipleIcons ? "ui-button-icons-only" : "ui-button-icon-only" );
13306
13307				if ( !this.hasTitle ) {
13308					buttonElement.attr( "title", $.trim( buttonText ) );
13309				}
13310			}
13311		} else {
13312			buttonClasses.push( "ui-button-text-only" );
13313		}
13314		buttonElement.addClass( buttonClasses.join( " " ) );
13315	}
13316});
13317
13318$.widget( "ui.buttonset", {
13319	version: "1.10.3",
13320	options: {
13321		items: "button, input[type=button], input[type=submit], input[type=reset], input[type=checkbox], input[type=radio], a, :data(ui-button)"
13322	},
13323
13324	_create: function() {
13325		this.element.addClass( "ui-buttonset" );
13326	},
13327
13328	_init: function() {
13329		this.refresh();
13330	},
13331
13332	_setOption: function( key, value ) {
13333		if ( key === "disabled" ) {
13334			this.buttons.button( "option", key, value );
13335		}
13336
13337		this._super( key, value );
13338	},
13339
13340	refresh: function() {
13341		var rtl = this.element.css( "direction" ) === "rtl";
13342
13343		this.buttons = this.element.find( this.options.items )
13344			.filter( ":ui-button" )
13345				.button( "refresh" )
13346			.end()
13347			.not( ":ui-button" )
13348				.button()
13349			.end()
13350			.map(function() {
13351				return $( this ).button( "widget" )[ 0 ];
13352			})
13353				.removeClass( "ui-corner-all ui-corner-left ui-corner-right" )
13354				.filter( ":first" )
13355					.addClass( rtl ? "ui-corner-right" : "ui-corner-left" )
13356				.end()
13357				.filter( ":last" )
13358					.addClass( rtl ? "ui-corner-left" : "ui-corner-right" )
13359				.end()
13360			.end();
13361	},
13362
13363	_destroy: function() {
13364		this.element.removeClass( "ui-buttonset" );
13365		this.buttons
13366			.map(function() {
13367				return $( this ).button( "widget" )[ 0 ];
13368			})
13369				.removeClass( "ui-corner-left ui-corner-right" )
13370			.end()
13371			.button( "destroy" );
13372	}
13373});
13374
13375}( jQuery ) );
13376
13377
13378/*!
13379 * jQuery UI Datepicker 1.10.3
13380 * http://jqueryui.com
13381 *
13382 * Copyright 2013 jQuery Foundation and other contributors
13383 * Released under the MIT license.
13384 * http://jquery.org/license
13385 *
13386 * http://api.jqueryui.com/datepicker/
13387 *
13388 * Depends:
13389 *	jquery.ui.core.js
13390 */
vendor: 76,059 bytes, lines 13391-15416
13391
13392(function( $, undefined ) {
13393
13394$.extend($.ui, { datepicker: { version: "1.10.3" } });
13395
13396var PROP_NAME = "datepicker",
13397	instActive;
13398
13399/* Date picker manager.
13400   Use the singleton instance of this class, $.datepicker, to interact with the date picker.
13401   Settings for (groups of) date pickers are maintained in an instance object,
13402   allowing multiple different settings on the same page. */
13403
13404function Datepicker() {
13405	this._curInst = null; // The current instance in use
13406	this._keyEvent = false; // If the last event was a key event
13407	this._disabledInputs = []; // List of date picker inputs that have been disabled
13408	this._datepickerShowing = false; // True if the popup picker is showing , false if not
13409	this._inDialog = false; // True if showing within a "dialog", false if not
13410	this._mainDivId = "ui-datepicker-div"; // The ID of the main datepicker division
13411	this._inlineClass = "ui-datepicker-inline"; // The name of the inline marker class
13412	this._appendClass = "ui-datepicker-append"; // The name of the append marker class
13413	this._triggerClass = "ui-datepicker-trigger"; // The name of the trigger marker class
13414	this._dialogClass = "ui-datepicker-dialog"; // The name of the dialog marker class
13415	this._disableClass = "ui-datepicker-disabled"; // The name of the disabled covering marker class
13416	this._unselectableClass = "ui-datepicker-unselectable"; // The name of the unselectable cell marker class
13417	this._currentClass = "ui-datepicker-current-day"; // The name of the current day marker class
13418	this._dayOverClass = "ui-datepicker-days-cell-over"; // The name of the day hover marker class
13419	this.regional = []; // Available regional settings, indexed by language code
13420	this.regional[""] = { // Default regional settings
13421		closeText: "Done", // Display text for close link
13422		prevText: "Prev", // Display text for previous month link
13423		nextText: "Next", // Display text for next month link
13424		currentText: "Today", // Display text for current month link
13425		monthNames: ["January","February","March","April","May","June",
13426			"July","August","September","October","November","December"], // Names of months for drop-down and formatting
13427		monthNamesShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], // For formatting
13428		dayNames: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday"], // For formatting
13429		dayNamesShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"], // For formatting
13430		dayNamesMin: ["Su","Mo","Tu","We","Th","Fr","Sa"], // Column headings for days starting at Sunday
13431		weekHeader: "Wk", // Column header for week of the year
13432		dateFormat: "mm/dd/yy", // See format options on parseDate
13433		firstDay: 0, // The first day of the week, Sun = 0, Mon = 1, ...
13434		isRTL: false, // True if right-to-left language, false if left-to-right
13435		showMonthAfterYear: false, // True if the year select precedes month, false for month then year
13436		yearSuffix: "" // Additional text to append to the year in the month headers
13437	};
13438	this._defaults = { // Global defaults for all the date picker instances
13439		showOn: "focus", // "focus" for popup on focus,
13440			// "button" for trigger button, or "both" for either
13441		showAnim: "fadeIn", // Name of jQuery animation for popup
13442		showOptions: {}, // Options for enhanced animations
13443		defaultDate: null, // Used when field is blank: actual date,
13444			// +/-number for offset from today, null for today
13445		appendText: "", // Display text following the input box, e.g. showing the format
13446		buttonText: "...", // Text for trigger button
13447		buttonImage: "", // URL for trigger button image
13448		buttonImageOnly: false, // True if the image appears alone, false if it appears on a button
13449		hideIfNoPrevNext: false, // True to hide next/previous month links
13450			// if not applicable, false to just disable them
13451		navigationAsDateFormat: false, // True if date formatting applied to prev/today/next links
13452		gotoCurrent: false, // True if today link goes back to current selection instead
13453		changeMonth: false, // True if month can be selected directly, false if only prev/next
13454		changeYear: false, // True if year can be selected directly, false if only prev/next
13455		yearRange: "c-10:c+10", // Range of years to display in drop-down,
13456			// either relative to today's year (-nn:+nn), relative to currently displayed year
13457			// (c-nn:c+nn), absolute (nnnn:nnnn), or a combination of the above (nnnn:-n)
13458		showOtherMonths: false, // True to show dates in other months, false to leave blank
13459		selectOtherMonths: false, // True to allow selection of dates in other months, false for unselectable
13460		showWeek: false, // True to show week of the year, false to not show it
13461		calculateWeek: this.iso8601Week, // How to calculate the week of the year,
13462			// takes a Date and returns the number of the week for it
13463		shortYearCutoff: "+10", // Short year values < this are in the current century,
13464			// > this are in the previous century,
13465			// string value starting with "+" for current year + value
13466		minDate: null, // The earliest selectable date, or null for no limit
13467		maxDate: null, // The latest selectable date, or null for no limit
13468		duration: "fast", // Duration of display/closure
13469		beforeShowDay: null, // Function that takes a date and returns an array with
13470			// [0] = true if selectable, false if not, [1] = custom CSS class name(s) or "",
13471			// [2] = cell title (optional), e.g. $.datepicker.noWeekends
13472		beforeShow: null, // Function that takes an input field and
13473			// returns a set of custom settings for the date picker
13474		onSelect: null, // Define a callback function when a date is selected
13475		onChangeMonthYear: null, // Define a callback function when the month or year is changed
13476		onClose: null, // Define a callback function when the datepicker is closed
13477		numberOfMonths: 1, // Number of months to show at a time
13478		showCurrentAtPos: 0, // The position in multipe months at which to show the current month (starting at 0)
13479		stepMonths: 1, // Number of months to step back/forward
13480		stepBigMonths: 12, // Number of months to step back/forward for the big links
13481		altField: "", // Selector for an alternate field to store selected dates into
13482		altFormat: "", // The date format to use for the alternate field
13483		constrainInput: true, // The input is constrained by the current date format
13484		showButtonPanel: false, // True to show button panel, false to not show it
13485		autoSize: false, // True to size the input for the date format, false to leave as is
13486		disabled: false // The initial disabled state
13487	};
13488	$.extend(this._defaults, this.regional[""]);
13489	this.dpDiv = bindHover($("<div id='" + this._mainDivId + "' class='ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>"));
13490}
13491
13492$.extend(Datepicker.prototype, {
13493	/* Class name added to elements to indicate already configured with a date picker. */
13494	markerClassName: "hasDatepicker",
13495
13496	//Keep track of the maximum number of rows displayed (see #7043)
13497	maxRows: 4,
13498
13499	// TODO rename to "widget" when switching to widget factory
13500	_widgetDatepicker: function() {
13501		return this.dpDiv;
13502	},
13503
13504	/* Override the default settings for all instances of the date picker.
13505	 * @param  settings  object - the new settings to use as defaults (anonymous object)
13506	 * @return the manager object
13507	 */
13508	setDefaults: function(settings) {
13509		extendRemove(this._defaults, settings || {});
13510		return this;
13511	},
13512
13513	/* Attach the date picker to a jQuery selection.
13514	 * @param  target	element - the target input field or division or span
13515	 * @param  settings  object - the new settings to use for this date picker instance (anonymous)
13516	 */
13517	_attachDatepicker: function(target, settings) {
13518		var nodeName, inline, inst;
13519		nodeName = target.nodeName.toLowerCase();
13520		inline = (nodeName === "div" || nodeName === "span");
13521		if (!target.id) {
13522			this.uuid += 1;
13523			target.id = "dp" + this.uuid;
13524		}
13525		inst = this._newInst($(target), inline);
13526		inst.settings = $.extend({}, settings || {});
13527		if (nodeName === "input") {
13528			this._connectDatepicker(target, inst);
13529		} else if (inline) {
13530			this._inlineDatepicker(target, inst);
13531		}
13532	},
13533
13534	/* Create a new instance object. */
13535	_newInst: function(target, inline) {
13536		var id = target[0].id.replace(/([^A-Za-z0-9_\-])/g, "\\\\$1"); // escape jQuery meta chars
13537		return {id: id, input: target, // associated target
13538			selectedDay: 0, selectedMonth: 0, selectedYear: 0, // current selection
13539			drawMonth: 0, drawYear: 0, // month being drawn
13540			inline: inline, // is datepicker inline or not
13541			dpDiv: (!inline ? this.dpDiv : // presentation div
13542			bindHover($("<div class='" + this._inlineClass + " ui-datepicker ui-widget ui-widget-content ui-helper-clearfix ui-corner-all'></div>")))};
13543	},
13544
13545	/* Attach the date picker to an input field. */
13546	_connectDatepicker: function(target, inst) {
13547		var input = $(target);
13548		inst.append = $([]);
13549		inst.trigger = $([]);
13550		if (input.hasClass(this.markerClassName)) {
13551			return;
13552		}
13553		this._attachments(input, inst);
13554		input.addClass(this.markerClassName).keydown(this._doKeyDown).
13555			keypress(this._doKeyPress).keyup(this._doKeyUp);
13556		this._autoSize(inst);
13557		$.data(target, PROP_NAME, inst);
13558		//If disabled option is true, disable the datepicker once it has been attached to the input (see ticket #5665)
13559		if( inst.settings.disabled ) {
13560			this._disableDatepicker( target );
13561		}
13562	},
13563
13564	/* Make attachments based on settings. */
13565	_attachments: function(input, inst) {
13566		var showOn, buttonText, buttonImage,
13567			appendText = this._get(inst, "appendText"),
13568			isRTL = this._get(inst, "isRTL");
13569
13570		if (inst.append) {
13571			inst.append.remove();
13572		}
13573		if (appendText) {
13574			inst.append = $("<span class='" + this._appendClass + "'>" + appendText + "</span>");
13575			input[isRTL ? "before" : "after"](inst.append);
13576		}
13577
13578		input.unbind("focus", this._showDatepicker);
13579
13580		if (inst.trigger) {
13581			inst.trigger.remove();
13582		}
13583
13584		showOn = this._get(inst, "showOn");
13585		if (showOn === "focus" || showOn === "both") { // pop-up date picker when in the marked field
13586			input.focus(this._showDatepicker);
13587		}
13588		if (showOn === "button" || showOn === "both") { // pop-up date picker when button clicked
13589			buttonText = this._get(inst, "buttonText");
13590			buttonImage = this._get(inst, "buttonImage");
13591			inst.trigger = $(this._get(inst, "buttonImageOnly") ?
13592				$("<img/>").addClass(this._triggerClass).
13593					attr({ src: buttonImage, alt: buttonText, title: buttonText }) :
13594				$("<button type='button'></button>").addClass(this._triggerClass).
13595					html(!buttonImage ? buttonText : $("<img/>").attr(
13596					{ src:buttonImage, alt:buttonText, title:buttonText })));
13597			input[isRTL ? "before" : "after"](inst.trigger);
13598			inst.trigger.click(function() {
13599				if ($.datepicker._datepickerShowing && $.datepicker._lastInput === input[0]) {
13600					$.datepicker._hideDatepicker();
13601				} else if ($.datepicker._datepickerShowing && $.datepicker._lastInput !== input[0]) {
13602					$.datepicker._hideDatepicker();
13603					$.datepicker._showDatepicker(input[0]);
13604				} else {
13605					$.datepicker._showDatepicker(input[0]);
13606				}
13607				return false;
13608			});
13609		}
13610	},
13611
13612	/* Apply the maximum length for the date format. */
13613	_autoSize: function(inst) {
13614		if (this._get(inst, "autoSize") && !inst.inline) {
13615			var findMax, max, maxI, i,
13616				date = new Date(2009, 12 - 1, 20), // Ensure double digits
13617				dateFormat = this._get(inst, "dateFormat");
13618
13619			if (dateFormat.match(/[DM]/)) {
13620				findMax = function(names) {
13621					max = 0;
13622					maxI = 0;
13623					for (i = 0; i < names.length; i++) {
13624						if (names[i].length > max) {
13625							max = names[i].length;
13626							maxI = i;
13627						}
13628					}
13629					return maxI;
13630				};
13631				date.setMonth(findMax(this._get(inst, (dateFormat.match(/MM/) ?
13632					"monthNames" : "monthNamesShort"))));
13633				date.setDate(findMax(this._get(inst, (dateFormat.match(/DD/) ?
13634					"dayNames" : "dayNamesShort"))) + 20 - date.getDay());
13635			}
13636			inst.input.attr("size", this._formatDate(inst, date).length);
13637		}
13638	},
13639
13640	/* Attach an inline date picker to a div. */
13641	_inlineDatepicker: function(target, inst) {
13642		var divSpan = $(target);
13643		if (divSpan.hasClass(this.markerClassName)) {
13644			return;
13645		}
13646		divSpan.addClass(this.markerClassName).append(inst.dpDiv);
13647		$.data(target, PROP_NAME, inst);
13648		this._setDate(inst, this._getDefaultDate(inst), true);
13649		this._updateDatepicker(inst);
13650		this._updateAlternate(inst);
13651		//If disabled option is true, disable the datepicker before showing it (see ticket #5665)
13652		if( inst.settings.disabled ) {
13653			this._disableDatepicker( target );
13654		}
13655		// Set display:block in place of inst.dpDiv.show() which won't work on disconnected elements
13656		// http://bugs.jqueryui.com/ticket/7552 - A Datepicker created on a detached div has zero height
13657		inst.dpDiv.css( "display", "block" );
13658	},
13659
13660	/* Pop-up the date picker in a "dialog" box.
13661	 * @param  input element - ignored
13662	 * @param  date	string or Date - the initial date to display
13663	 * @param  onSelect  function - the function to call when a date is selected
13664	 * @param  settings  object - update the dialog date picker instance's settings (anonymous object)
13665	 * @param  pos int[2] - coordinates for the dialog's position within the screen or
13666	 *					event - with x/y coordinates or
13667	 *					leave empty for default (screen centre)
13668	 * @return the manager object
13669	 */
13670	_dialogDatepicker: function(input, date, onSelect, settings, pos) {
13671		var id, browserWidth, browserHeight, scrollX, scrollY,
13672			inst = this._dialogInst; // internal instance
13673
13674		if (!inst) {
13675			this.uuid += 1;
13676			id = "dp" + this.uuid;
13677			this._dialogInput = $("<input type='text' id='" + id +
13678				"' style='position: absolute; top: -100px; width: 0px;'/>");
13679			this._dialogInput.keydown(this._doKeyDown);
13680			$("body").append(this._dialogInput);
13681			inst = this._dialogInst = this._newInst(this._dialogInput, false);
13682			inst.settings = {};
13683			$.data(this._dialogInput[0], PROP_NAME, inst);
13684		}
13685		extendRemove(inst.settings, settings || {});
13686		date = (date && date.constructor === Date ? this._formatDate(inst, date) : date);
13687		this._dialogInput.val(date);
13688
13689		this._pos = (pos ? (pos.length ? pos : [pos.pageX, pos.pageY]) : null);
13690		if (!this._pos) {
13691			browserWidth = document.documentElement.clientWidth;
13692			browserHeight = document.documentElement.clientHeight;
13693			scrollX = document.documentElement.scrollLeft || document.body.scrollLeft;
13694			scrollY = document.documentElement.scrollTop || document.body.scrollTop;
13695			this._pos = // should use actual width/height below
13696				[(browserWidth / 2) - 100 + scrollX, (browserHeight / 2) - 150 + scrollY];
13697		}
13698
13699		// move input on screen for focus, but hidden behind dialog
13700		this._dialogInput.css("left", (this._pos[0] + 20) + "px").css("top", this._pos[1] + "px");
13701		inst.settings.onSelect = onSelect;
13702		this._inDialog = true;
13703		this.dpDiv.addClass(this._dialogClass);
13704		this._showDatepicker(this._dialogInput[0]);
13705		if ($.blockUI) {
13706			$.blockUI(this.dpDiv);
13707		}
13708		$.data(this._dialogInput[0], PROP_NAME, inst);
13709		return this;
13710	},
13711
13712	/* Detach a datepicker from its control.
13713	 * @param  target	element - the target input field or division or span
13714	 */
13715	_destroyDatepicker: function(target) {
13716		var nodeName,
13717			$target = $(target),
13718			inst = $.data(target, PROP_NAME);
13719
13720		if (!$target.hasClass(this.markerClassName)) {
13721			return;
13722		}
13723
13724		nodeName = target.nodeName.toLowerCase();
13725		$.removeData(target, PROP_NAME);
13726		if (nodeName === "input") {
13727			inst.append.remove();
13728			inst.trigger.remove();
13729			$target.removeClass(this.markerClassName).
13730				unbind("focus", this._showDatepicker).
13731				unbind("keydown", this._doKeyDown).
13732				unbind("keypress", this._doKeyPress).
13733				unbind("keyup", this._doKeyUp);
13734		} else if (nodeName === "div" || nodeName === "span") {
13735			$target.removeClass(this.markerClassName).empty();
13736		}
13737	},
13738
13739	/* Enable the date picker to a jQuery selection.
13740	 * @param  target	element - the target input field or division or span
13741	 */
13742	_enableDatepicker: function(target) {
13743		var nodeName, inline,
13744			$target = $(target),
13745			inst = $.data(target, PROP_NAME);
13746
13747		if (!$target.hasClass(this.markerClassName)) {
13748			return;
13749		}
13750
13751		nodeName = target.nodeName.toLowerCase();
13752		if (nodeName === "input") {
13753			target.disabled = false;
13754			inst.trigger.filter("button").
13755				each(function() { this.disabled = false; }).end().
13756				filter("img").css({opacity: "1.0", cursor: ""});
13757		} else if (nodeName === "div" || nodeName === "span") {
13758			inline = $target.children("." + this._inlineClass);
13759			inline.children().removeClass("ui-state-disabled");
13760			inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
13761				prop("disabled", false);
13762		}
13763		this._disabledInputs = $.map(this._disabledInputs,
13764			function(value) { return (value === target ? null : value); }); // delete entry
13765	},
13766
13767	/* Disable the date picker to a jQuery selection.
13768	 * @param  target	element - the target input field or division or span
13769	 */
13770	_disableDatepicker: function(target) {
13771		var nodeName, inline,
13772			$target = $(target),
13773			inst = $.data(target, PROP_NAME);
13774
13775		if (!$target.hasClass(this.markerClassName)) {
13776			return;
13777		}
13778
13779		nodeName = target.nodeName.toLowerCase();
13780		if (nodeName === "input") {
13781			target.disabled = true;
13782			inst.trigger.filter("button").
13783				each(function() { this.disabled = true; }).end().
13784				filter("img").css({opacity: "0.5", cursor: "default"});
13785		} else if (nodeName === "div" || nodeName === "span") {
13786			inline = $target.children("." + this._inlineClass);
13787			inline.children().addClass("ui-state-disabled");
13788			inline.find("select.ui-datepicker-month, select.ui-datepicker-year").
13789				prop("disabled", true);
13790		}
13791		this._disabledInputs = $.map(this._disabledInputs,
13792			function(value) { return (value === target ? null : value); }); // delete entry
13793		this._disabledInputs[this._disabledInputs.length] = target;
13794	},
13795
13796	/* Is the first field in a jQuery collection disabled as a datepicker?
13797	 * @param  target	element - the target input field or division or span
13798	 * @return boolean - true if disabled, false if enabled
13799	 */
13800	_isDisabledDatepicker: function(target) {
13801		if (!target) {
13802			return false;
13803		}
13804		for (var i = 0; i < this._disabledInputs.length; i++) {
13805			if (this._disabledInputs[i] === target) {
13806				return true;
13807			}
13808		}
13809		return false;
13810	},
13811
13812	/* Retrieve the instance data for the target control.
13813	 * @param  target  element - the target input field or division or span
13814	 * @return  object - the associated instance data
13815	 * @throws  error if a jQuery problem getting data
13816	 */
13817	_getInst: function(target) {
13818		try {
13819			return $.data(target, PROP_NAME);
13820		}
13821		catch (err) {
13822			throw "Missing instance data for this datepicker";
13823		}
13824	},
13825
13826	/* Update or retrieve the settings for a date picker attached to an input field or division.
13827	 * @param  target  element - the target input field or division or span
13828	 * @param  name	object - the new settings to update or
13829	 *				string - the name of the setting to change or retrieve,
13830	 *				when retrieving also "all" for all instance settings or
13831	 *				"defaults" for all global defaults
13832	 * @param  value   any - the new value for the setting
13833	 *				(omit if above is an object or to retrieve a value)
13834	 */
13835	_optionDatepicker: function(target, name, value) {
13836		var settings, date, minDate, maxDate,
13837			inst = this._getInst(target);
13838
13839		if (arguments.length === 2 && typeof name === "string") {
13840			return (name === "defaults" ? $.extend({}, $.datepicker._defaults) :
13841				(inst ? (name === "all" ? $.extend({}, inst.settings) :
13842				this._get(inst, name)) : null));
13843		}
13844
13845		settings = name || {};
13846		if (typeof name === "string") {
13847			settings = {};
13848			settings[name] = value;
13849		}
13850
13851		if (inst) {
13852			if (this._curInst === inst) {
13853				this._hideDatepicker();
13854			}
13855
13856			date = this._getDateDatepicker(target, true);
13857			minDate = this._getMinMaxDate(inst, "min");
13858			maxDate = this._getMinMaxDate(inst, "max");
13859			extendRemove(inst.settings, settings);
13860			// reformat the old minDate/maxDate values if dateFormat changes and a new minDate/maxDate isn't provided
13861			if (minDate !== null && settings.dateFormat !== undefined && settings.minDate === undefined) {
13862				inst.settings.minDate = this._formatDate(inst, minDate);
13863			}
13864			if (maxDate !== null && settings.dateFormat !== undefined && settings.maxDate === undefined) {
13865				inst.settings.maxDate = this._formatDate(inst, maxDate);
13866			}
13867			if ( "disabled" in settings ) {
13868				if ( settings.disabled ) {
13869					this._disableDatepicker(target);
13870				} else {
13871					this._enableDatepicker(target);
13872				}
13873			}
13874			this._attachments($(target), inst);
13875			this._autoSize(inst);
13876			this._setDate(inst, date);
13877			this._updateAlternate(inst);
13878			this._updateDatepicker(inst);
13879		}
13880	},
13881
13882	// change method deprecated
13883	_changeDatepicker: function(target, name, value) {
13884		this._optionDatepicker(target, name, value);
13885	},
13886
13887	/* Redraw the date picker attached to an input field or division.
13888	 * @param  target  element - the target input field or division or span
13889	 */
13890	_refreshDatepicker: function(target) {
13891		var inst = this._getInst(target);
13892		if (inst) {
13893			this._updateDatepicker(inst);
13894		}
13895	},
13896
13897	/* Set the dates for a jQuery selection.
13898	 * @param  target element - the target input field or division or span
13899	 * @param  date	Date - the new date
13900	 */
13901	_setDateDatepicker: function(target, date) {
13902		var inst = this._getInst(target);
13903		if (inst) {
13904			this._setDate(inst, date);
13905			this._updateDatepicker(inst);
13906			this._updateAlternate(inst);
13907		}
13908	},
13909
13910	/* Get the date(s) for the first entry in a jQuery selection.
13911	 * @param  target element - the target input field or division or span
13912	 * @param  noDefault boolean - true if no default date is to be used
13913	 * @return Date - the current date
13914	 */
13915	_getDateDatepicker: function(target, noDefault) {
13916		var inst = this._getInst(target);
13917		if (inst && !inst.inline) {
13918			this._setDateFromField(inst, noDefault);
13919		}
13920		return (inst ? this._getDate(inst) : null);
13921	},
13922
13923	/* Handle keystrokes. */
13924	_doKeyDown: function(event) {
13925		var onSelect, dateStr, sel,
13926			inst = $.datepicker._getInst(event.target),
13927			handled = true,
13928			isRTL = inst.dpDiv.is(".ui-datepicker-rtl");
13929
13930		inst._keyEvent = true;
13931		if ($.datepicker._datepickerShowing) {
13932			switch (event.keyCode) {
13933				case 9: $.datepicker._hideDatepicker();
13934						handled = false;
13935						break; // hide on tab out
13936				case 13: sel = $("td." + $.datepicker._dayOverClass + ":not(." +
13937									$.datepicker._currentClass + ")", inst.dpDiv);
13938						if (sel[0]) {
13939							$.datepicker._selectDay(event.target, inst.selectedMonth, inst.selectedYear, sel[0]);
13940						}
13941
13942						onSelect = $.datepicker._get(inst, "onSelect");
13943						if (onSelect) {
13944							dateStr = $.datepicker._formatDate(inst);
13945
13946							// trigger custom callback
13947							onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);
13948						} else {
13949							$.datepicker._hideDatepicker();
13950						}
13951
13952						return false; // don't submit the form
13953				case 27: $.datepicker._hideDatepicker();
13954						break; // hide on escape
13955				case 33: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
13956							-$.datepicker._get(inst, "stepBigMonths") :
13957							-$.datepicker._get(inst, "stepMonths")), "M");
13958						break; // previous month/year on page up/+ ctrl
13959				case 34: $.datepicker._adjustDate(event.target, (event.ctrlKey ?
13960							+$.datepicker._get(inst, "stepBigMonths") :
13961							+$.datepicker._get(inst, "stepMonths")), "M");
13962						break; // next month/year on page down/+ ctrl
13963				case 35: if (event.ctrlKey || event.metaKey) {
13964							$.datepicker._clearDate(event.target);
13965						}
13966						handled = event.ctrlKey || event.metaKey;
13967						break; // clear on ctrl or command +end
13968				case 36: if (event.ctrlKey || event.metaKey) {
13969							$.datepicker._gotoToday(event.target);
13970						}
13971						handled = event.ctrlKey || event.metaKey;
13972						break; // current on ctrl or command +home
13973				case 37: if (event.ctrlKey || event.metaKey) {
13974							$.datepicker._adjustDate(event.target, (isRTL ? +1 : -1), "D");
13975						}
13976						handled = event.ctrlKey || event.metaKey;
13977						// -1 day on ctrl or command +left
13978						if (event.originalEvent.altKey) {
13979							$.datepicker._adjustDate(event.target, (event.ctrlKey ?
13980								-$.datepicker._get(inst, "stepBigMonths") :
13981								-$.datepicker._get(inst, "stepMonths")), "M");
13982						}
13983						// next month/year on alt +left on Mac
13984						break;
13985				case 38: if (event.ctrlKey || event.metaKey) {
13986							$.datepicker._adjustDate(event.target, -7, "D");
13987						}
13988						handled = event.ctrlKey || event.metaKey;
13989						break; // -1 week on ctrl or command +up
13990				case 39: if (event.ctrlKey || event.metaKey) {
13991							$.datepicker._adjustDate(event.target, (isRTL ? -1 : +1), "D");
13992						}
13993						handled = event.ctrlKey || event.metaKey;
13994						// +1 day on ctrl or command +right
13995						if (event.originalEvent.altKey) {
13996							$.datepicker._adjustDate(event.target, (event.ctrlKey ?
13997								+$.datepicker._get(inst, "stepBigMonths") :
13998								+$.datepicker._get(inst, "stepMonths")), "M");
13999						}
14000						// next month/year on alt +right
14001						break;
14002				case 40: if (event.ctrlKey || event.metaKey) {
14003							$.datepicker._adjustDate(event.target, +7, "D");
14004						}
14005						handled = event.ctrlKey || event.metaKey;
14006						break; // +1 week on ctrl or command +down
14007				default: handled = false;
14008			}
14009		} else if (event.keyCode === 36 && event.ctrlKey) { // display the date picker on ctrl+home
14010			$.datepicker._showDatepicker(this);
14011		} else {
14012			handled = false;
14013		}
14014
14015		if (handled) {
14016			event.preventDefault();
14017			event.stopPropagation();
14018		}
14019	},
14020
14021	/* Filter entered characters - based on date format. */
14022	_doKeyPress: function(event) {
14023		var chars, chr,
14024			inst = $.datepicker._getInst(event.target);
14025
14026		if ($.datepicker._get(inst, "constrainInput")) {
14027			chars = $.datepicker._possibleChars($.datepicker._get(inst, "dateFormat"));
14028			chr = String.fromCharCode(event.charCode == null ? event.keyCode : event.charCode);
14029			return event.ctrlKey || event.metaKey || (chr < " " || !chars || chars.indexOf(chr) > -1);
14030		}
14031	},
14032
14033	/* Synchronise manual entry and field/alternate field. */
14034	_doKeyUp: function(event) {
14035		var date,
14036			inst = $.datepicker._getInst(event.target);
14037
14038		if (inst.input.val() !== inst.lastVal) {
14039			try {
14040				date = $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"),
14041					(inst.input ? inst.input.val() : null),
14042					$.datepicker._getFormatConfig(inst));
14043
14044				if (date) { // only if valid
14045					$.datepicker._setDateFromField(inst);
14046					$.datepicker._updateAlternate(inst);
14047					$.datepicker._updateDatepicker(inst);
14048				}
14049			}
14050			catch (err) {
14051			}
14052		}
14053		return true;
14054	},
14055
14056	/* Pop-up the date picker for a given input field.
14057	 * If false returned from beforeShow event handler do not show.
14058	 * @param  input  element - the input field attached to the date picker or
14059	 *					event - if triggered by focus
14060	 */
14061	_showDatepicker: function(input) {
14062		input = input.target || input;
14063		if (input.nodeName.toLowerCase() !== "input") { // find from button/image trigger
14064			input = $("input", input.parentNode)[0];
14065		}
14066
14067		if ($.datepicker._isDisabledDatepicker(input) || $.datepicker._lastInput === input) { // already here
14068			return;
14069		}
14070
14071		var inst, beforeShow, beforeShowSettings, isFixed,
14072			offset, showAnim, duration;
14073
14074		inst = $.datepicker._getInst(input);
14075		if ($.datepicker._curInst && $.datepicker._curInst !== inst) {
14076			$.datepicker._curInst.dpDiv.stop(true, true);
14077			if ( inst && $.datepicker._datepickerShowing ) {
14078				$.datepicker._hideDatepicker( $.datepicker._curInst.input[0] );
14079			}
14080		}
14081
14082		beforeShow = $.datepicker._get(inst, "beforeShow");
14083		beforeShowSettings = beforeShow ? beforeShow.apply(input, [input, inst]) : {};
14084		if(beforeShowSettings === false){
14085			return;
14086		}
14087		extendRemove(inst.settings, beforeShowSettings);
14088
14089		inst.lastVal = null;
14090		$.datepicker._lastInput = input;
14091		$.datepicker._setDateFromField(inst);
14092
14093		if ($.datepicker._inDialog) { // hide cursor
14094			input.value = "";
14095		}
14096		if (!$.datepicker._pos) { // position below input
14097			$.datepicker._pos = $.datepicker._findPos(input);
14098			$.datepicker._pos[1] += input.offsetHeight; // add the height
14099		}
14100
14101		isFixed = false;
14102		$(input).parents().each(function() {
14103			isFixed |= $(this).css("position") === "fixed";
14104			return !isFixed;
14105		});
14106
14107		offset = {left: $.datepicker._pos[0], top: $.datepicker._pos[1]};
14108		$.datepicker._pos = null;
14109		//to avoid flashes on Firefox
14110		inst.dpDiv.empty();
14111		// determine sizing offscreen
14112		inst.dpDiv.css({position: "absolute", display: "block", top: "-1000px"});
14113		$.datepicker._updateDatepicker(inst);
14114		// fix width for dynamic number of date pickers
14115		// and adjust position before showing
14116		offset = $.datepicker._checkOffset(inst, offset, isFixed);
14117		inst.dpDiv.css({position: ($.datepicker._inDialog && $.blockUI ?
14118			"static" : (isFixed ? "fixed" : "absolute")), display: "none",
14119			left: offset.left + "px", top: offset.top + "px"});
14120
14121		if (!inst.inline) {
14122			showAnim = $.datepicker._get(inst, "showAnim");
14123			duration = $.datepicker._get(inst, "duration");
14124			inst.dpDiv.zIndex($(input).zIndex()+1);
14125			$.datepicker._datepickerShowing = true;
14126
14127			if ( $.effects && $.effects.effect[ showAnim ] ) {
14128				inst.dpDiv.show(showAnim, $.datepicker._get(inst, "showOptions"), duration);
14129			} else {
14130				inst.dpDiv[showAnim || "show"](showAnim ? duration : null);
14131			}
14132
14133			if ( $.datepicker._shouldFocusInput( inst ) ) {
14134				inst.input.focus();
14135			}
14136
14137			$.datepicker._curInst = inst;
14138		}
14139	},
14140
14141	/* Generate the date picker content. */
14142	_updateDatepicker: function(inst) {
14143		this.maxRows = 4; //Reset the max number of rows being displayed (see #7043)
14144		instActive = inst; // for delegate hover events
14145		inst.dpDiv.empty().append(this._generateHTML(inst));
14146		this._attachHandlers(inst);
14147		inst.dpDiv.find("." + this._dayOverClass + " a").mouseover();
14148
14149		var origyearshtml,
14150			numMonths = this._getNumberOfMonths(inst),
14151			cols = numMonths[1],
14152			width = 17;
14153
14154		inst.dpDiv.removeClass("ui-datepicker-multi-2 ui-datepicker-multi-3 ui-datepicker-multi-4").width("");
14155		if (cols > 1) {
14156			inst.dpDiv.addClass("ui-datepicker-multi-" + cols).css("width", (width * cols) + "em");
14157		}
14158		inst.dpDiv[(numMonths[0] !== 1 || numMonths[1] !== 1 ? "add" : "remove") +
14159			"Class"]("ui-datepicker-multi");
14160		inst.dpDiv[(this._get(inst, "isRTL") ? "add" : "remove") +
14161			"Class"]("ui-datepicker-rtl");
14162
14163		if (inst === $.datepicker._curInst && $.datepicker._datepickerShowing && $.datepicker._shouldFocusInput( inst ) ) {
14164			inst.input.focus();
14165		}
14166
14167		// deffered render of the years select (to avoid flashes on Firefox)
14168		if( inst.yearshtml ){
14169			origyearshtml = inst.yearshtml;
14170			setTimeout(function(){
14171				//assure that inst.yearshtml didn't change.
14172				if( origyearshtml === inst.yearshtml && inst.yearshtml ){
14173					inst.dpDiv.find("select.ui-datepicker-year:first").replaceWith(inst.yearshtml);
14174				}
14175				origyearshtml = inst.yearshtml = null;
14176			}, 0);
14177		}
14178	},
14179
14180	// #6694 - don't focus the input if it's already focused
14181	// this breaks the change event in IE
14182	// Support: IE and jQuery <1.9
14183	_shouldFocusInput: function( inst ) {
14184		return inst.input && inst.input.is( ":visible" ) && !inst.input.is( ":disabled" ) && !inst.input.is( ":focus" );
14185	},
14186
14187	/* Check positioning to remain on screen. */
14188	_checkOffset: function(inst, offset, isFixed) {
14189		var dpWidth = inst.dpDiv.outerWidth(),
14190			dpHeight = inst.dpDiv.outerHeight(),
14191			inputWidth = inst.input ? inst.input.outerWidth() : 0,
14192			inputHeight = inst.input ? inst.input.outerHeight() : 0,
14193			viewWidth = document.documentElement.clientWidth + (isFixed ? 0 : $(document).scrollLeft()),
14194			viewHeight = document.documentElement.clientHeight + (isFixed ? 0 : $(document).scrollTop());
14195
14196		offset.left -= (this._get(inst, "isRTL") ? (dpWidth - inputWidth) : 0);
14197		offset.left -= (isFixed && offset.left === inst.input.offset().left) ? $(document).scrollLeft() : 0;
14198		offset.top -= (isFixed && offset.top === (inst.input.offset().top + inputHeight)) ? $(document).scrollTop() : 0;
14199
14200		// now check if datepicker is showing outside window viewport - move to a better place if so.
14201		offset.left -= Math.min(offset.left, (offset.left + dpWidth > viewWidth && viewWidth > dpWidth) ?
14202			Math.abs(offset.left + dpWidth - viewWidth) : 0);
14203		offset.top -= Math.min(offset.top, (offset.top + dpHeight > viewHeight && viewHeight > dpHeight) ?
14204			Math.abs(dpHeight + inputHeight) : 0);
14205
14206		return offset;
14207	},
14208
14209	/* Find an object's position on the screen. */
14210	_findPos: function(obj) {
14211		var position,
14212			inst = this._getInst(obj),
14213			isRTL = this._get(inst, "isRTL");
14214
14215		while (obj && (obj.type === "hidden" || obj.nodeType !== 1 || $.expr.filters.hidden(obj))) {
14216			obj = obj[isRTL ? "previousSibling" : "nextSibling"];
14217		}
14218
14219		position = $(obj).offset();
14220		return [position.left, position.top];
14221	},
14222
14223	/* Hide the date picker from view.
14224	 * @param  input  element - the input field attached to the date picker
14225	 */
14226	_hideDatepicker: function(input) {
14227		var showAnim, duration, postProcess, onClose,
14228			inst = this._curInst;
14229
14230		if (!inst || (input && inst !== $.data(input, PROP_NAME))) {
14231			return;
14232		}
14233
14234		if (this._datepickerShowing) {
14235			showAnim = this._get(inst, "showAnim");
14236			duration = this._get(inst, "duration");
14237			postProcess = function() {
14238				$.datepicker._tidyDialog(inst);
14239			};
14240
14241			// DEPRECATED: after BC for 1.8.x $.effects[ showAnim ] is not needed
14242			if ( $.effects && ( $.effects.effect[ showAnim ] || $.effects[ showAnim ] ) ) {
14243				inst.dpDiv.hide(showAnim, $.datepicker._get(inst, "showOptions"), duration, postProcess);
14244			} else {
14245				inst.dpDiv[(showAnim === "slideDown" ? "slideUp" :
14246					(showAnim === "fadeIn" ? "fadeOut" : "hide"))]((showAnim ? duration : null), postProcess);
14247			}
14248
14249			if (!showAnim) {
14250				postProcess();
14251			}
14252			this._datepickerShowing = false;
14253
14254			onClose = this._get(inst, "onClose");
14255			if (onClose) {
14256				onClose.apply((inst.input ? inst.input[0] : null), [(inst.input ? inst.input.val() : ""), inst]);
14257			}
14258
14259			this._lastInput = null;
14260			if (this._inDialog) {
14261				this._dialogInput.css({ position: "absolute", left: "0", top: "-100px" });
14262				if ($.blockUI) {
14263					$.unblockUI();
14264					$("body").append(this.dpDiv);
14265				}
14266			}
14267			this._inDialog = false;
14268		}
14269	},
14270
14271	/* Tidy up after a dialog display. */
14272	_tidyDialog: function(inst) {
14273		inst.dpDiv.removeClass(this._dialogClass).unbind(".ui-datepicker-calendar");
14274	},
14275
14276	/* Close date picker if clicked elsewhere. */
14277	_checkExternalClick: function(event) {
14278		if (!$.datepicker._curInst) {
14279			return;
14280		}
14281
14282		var $target = $(event.target),
14283			inst = $.datepicker._getInst($target[0]);
14284
14285		if ( ( ( $target[0].id !== $.datepicker._mainDivId &&
14286				$target.parents("#" + $.datepicker._mainDivId).length === 0 &&
14287				!$target.hasClass($.datepicker.markerClassName) &&
14288				!$target.closest("." + $.datepicker._triggerClass).length &&
14289				$.datepicker._datepickerShowing && !($.datepicker._inDialog && $.blockUI) ) ) ||
14290			( $target.hasClass($.datepicker.markerClassName) && $.datepicker._curInst !== inst ) ) {
14291				$.datepicker._hideDatepicker();
14292		}
14293	},
14294
14295	/* Adjust one of the date sub-fields. */
14296	_adjustDate: function(id, offset, period) {
14297		var target = $(id),
14298			inst = this._getInst(target[0]);
14299
14300		if (this._isDisabledDatepicker(target[0])) {
14301			return;
14302		}
14303		this._adjustInstDate(inst, offset +
14304			(period === "M" ? this._get(inst, "showCurrentAtPos") : 0), // undo positioning
14305			period);
14306		this._updateDatepicker(inst);
14307	},
14308
14309	/* Action for current link. */
14310	_gotoToday: function(id) {
14311		var date,
14312			target = $(id),
14313			inst = this._getInst(target[0]);
14314
14315		if (this._get(inst, "gotoCurrent") && inst.currentDay) {
14316			inst.selectedDay = inst.currentDay;
14317			inst.drawMonth = inst.selectedMonth = inst.currentMonth;
14318			inst.drawYear = inst.selectedYear = inst.currentYear;
14319		} else {
14320			date = new Date();
14321			inst.selectedDay = date.getDate();
14322			inst.drawMonth = inst.selectedMonth = date.getMonth();
14323			inst.drawYear = inst.selectedYear = date.getFullYear();
14324		}
14325		this._notifyChange(inst);
14326		this._adjustDate(target);
14327	},
14328
14329	/* Action for selecting a new month/year. */
14330	_selectMonthYear: function(id, select, period) {
14331		var target = $(id),
14332			inst = this._getInst(target[0]);
14333
14334		inst["selected" + (period === "M" ? "Month" : "Year")] =
14335		inst["draw" + (period === "M" ? "Month" : "Year")] =
14336			parseInt(select.options[select.selectedIndex].value,10);
14337
14338		this._notifyChange(inst);
14339		this._adjustDate(target);
14340	},
14341
14342	/* Action for selecting a day. */
14343	_selectDay: function(id, month, year, td) {
14344		var inst,
14345			target = $(id);
14346
14347		if ($(td).hasClass(this._unselectableClass) || this._isDisabledDatepicker(target[0])) {
14348			return;
14349		}
14350
14351		inst = this._getInst(target[0]);
14352		inst.selectedDay = inst.currentDay = $("a", td).html();
14353		inst.selectedMonth = inst.currentMonth = month;
14354		inst.selectedYear = inst.currentYear = year;
14355		this._selectDate(id, this._formatDate(inst,
14356			inst.currentDay, inst.currentMonth, inst.currentYear));
14357	},
14358
14359	/* Erase the input field and hide the date picker. */
14360	_clearDate: function(id) {
14361		var target = $(id);
14362		this._selectDate(target, "");
14363	},
14364
14365	/* Update the input field with the selected date. */
14366	_selectDate: function(id, dateStr) {
14367		var onSelect,
14368			target = $(id),
14369			inst = this._getInst(target[0]);
14370
14371		dateStr = (dateStr != null ? dateStr : this._formatDate(inst));
14372		if (inst.input) {
14373			inst.input.val(dateStr);
14374		}
14375		this._updateAlternate(inst);
14376
14377		onSelect = this._get(inst, "onSelect");
14378		if (onSelect) {
14379			onSelect.apply((inst.input ? inst.input[0] : null), [dateStr, inst]);  // trigger custom callback
14380		} else if (inst.input) {
14381			inst.input.trigger("change"); // fire the change event
14382		}
14383
14384		if (inst.inline){
14385			this._updateDatepicker(inst);
14386		} else {
14387			this._hideDatepicker();
14388			this._lastInput = inst.input[0];
14389			if (typeof(inst.input[0]) !== "object") {
14390				inst.input.focus(); // restore focus
14391			}
14392			this._lastInput = null;
14393		}
14394	},
14395
14396	/* Update any alternate field to synchronise with the main field. */
14397	_updateAlternate: function(inst) {
14398		var altFormat, date, dateStr,
14399			altField = this._get(inst, "altField");
14400
14401		if (altField) { // update alternate field too
14402			altFormat = this._get(inst, "altFormat") || this._get(inst, "dateFormat");
14403			date = this._getDate(inst);
14404			dateStr = this.formatDate(altFormat, date, this._getFormatConfig(inst));
14405			$(altField).each(function() { $(this).val(dateStr); });
14406		}
14407	},
14408
14409	/* Set as beforeShowDay function to prevent selection of weekends.
14410	 * @param  date  Date - the date to customise
14411	 * @return [boolean, string] - is this date selectable?, what is its CSS class?
14412	 */
14413	noWeekends: function(date) {
14414		var day = date.getDay();
14415		return [(day > 0 && day < 6), ""];
14416	},
14417
14418	/* Set as calculateWeek to determine the week of the year based on the ISO 8601 definition.
14419	 * @param  date  Date - the date to get the week for
14420	 * @return  number - the number of the week within the year that contains this date
14421	 */
14422	iso8601Week: function(date) {
14423		var time,
14424			checkDate = new Date(date.getTime());
14425
14426		// Find Thursday of this week starting on Monday
14427		checkDate.setDate(checkDate.getDate() + 4 - (checkDate.getDay() || 7));
14428
14429		time = checkDate.getTime();
14430		checkDate.setMonth(0); // Compare with Jan 1
14431		checkDate.setDate(1);
14432		return Math.floor(Math.round((time - checkDate) / 86400000) / 7) + 1;
14433	},
14434
14435	/* Parse a string value into a date object.
14436	 * See formatDate below for the possible formats.
14437	 *
14438	 * @param  format string - the expected format of the date
14439	 * @param  value string - the date in the above format
14440	 * @param  settings Object - attributes include:
14441	 *					shortYearCutoff  number - the cutoff year for determining the century (optional)
14442	 *					dayNamesShort	string[7] - abbreviated names of the days from Sunday (optional)
14443	 *					dayNames		string[7] - names of the days from Sunday (optional)
14444	 *					monthNamesShort string[12] - abbreviated names of the months (optional)
14445	 *					monthNames		string[12] - names of the months (optional)
14446	 * @return  Date - the extracted date value or null if value is blank
14447	 */
14448	parseDate: function (format, value, settings) {
14449		if (format == null || value == null) {
14450			throw "Invalid arguments";
14451		}
14452
14453		value = (typeof value === "object" ? value.toString() : value + "");
14454		if (value === "") {
14455			return null;
14456		}
14457
14458		var iFormat, dim, extra,
14459			iValue = 0,
14460			shortYearCutoffTemp = (settings ? settings.shortYearCutoff : null) || this._defaults.shortYearCutoff,
14461			shortYearCutoff = (typeof shortYearCutoffTemp !== "string" ? shortYearCutoffTemp :
14462				new Date().getFullYear() % 100 + parseInt(shortYearCutoffTemp, 10)),
14463			dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort,
14464			dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames,
14465			monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort,
14466			monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames,
14467			year = -1,
14468			month = -1,
14469			day = -1,
14470			doy = -1,
14471			literal = false,
14472			date,
14473			// Check whether a format character is doubled
14474			lookAhead = function(match) {
14475				var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
14476				if (matches) {
14477					iFormat++;
14478				}
14479				return matches;
14480			},
14481			// Extract a number from the string value
14482			getNumber = function(match) {
14483				var isDoubled = lookAhead(match),
14484					size = (match === "@" ? 14 : (match === "!" ? 20 :
14485					(match === "y" && isDoubled ? 4 : (match === "o" ? 3 : 2)))),
14486					digits = new RegExp("^\\d{1," + size + "}"),
14487					num = value.substring(iValue).match(digits);
14488				if (!num) {
14489					throw "Missing number at position " + iValue;
14490				}
14491				iValue += num[0].length;
14492				return parseInt(num[0], 10);
14493			},
14494			// Extract a name from the string value and convert to an index
14495			getName = function(match, shortNames, longNames) {
14496				var index = -1,
14497					names = $.map(lookAhead(match) ? longNames : shortNames, function (v, k) {
14498						return [ [k, v] ];
14499					}).sort(function (a, b) {
14500						return -(a[1].length - b[1].length);
14501					});
14502
14503				$.each(names, function (i, pair) {
14504					var name = pair[1];
14505					if (value.substr(iValue, name.length).toLowerCase() === name.toLowerCase()) {
14506						index = pair[0];
14507						iValue += name.length;
14508						return false;
14509					}
14510				});
14511				if (index !== -1) {
14512					return index + 1;
14513				} else {
14514					throw "Unknown name at position " + iValue;
14515				}
14516			},
14517			// Confirm that a literal character matches the string value
14518			checkLiteral = function() {
14519				if (value.charAt(iValue) !== format.charAt(iFormat)) {
14520					throw "Unexpected literal at position " + iValue;
14521				}
14522				iValue++;
14523			};
14524
14525		for (iFormat = 0; iFormat < format.length; iFormat++) {
14526			if (literal) {
14527				if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
14528					literal = false;
14529				} else {
14530					checkLiteral();
14531				}
14532			} else {
14533				switch (format.charAt(iFormat)) {
14534					case "d":
14535						day = getNumber("d");
14536						break;
14537					case "D":
14538						getName("D", dayNamesShort, dayNames);
14539						break;
14540					case "o":
14541						doy = getNumber("o");
14542						break;
14543					case "m":
14544						month = getNumber("m");
14545						break;
14546					case "M":
14547						month = getName("M", monthNamesShort, monthNames);
14548						break;
14549					case "y":
14550						year = getNumber("y");
14551						break;
14552					case "@":
14553						date = new Date(getNumber("@"));
14554						year = date.getFullYear();
14555						month = date.getMonth() + 1;
14556						day = date.getDate();
14557						break;
14558					case "!":
14559						date = new Date((getNumber("!") - this._ticksTo1970) / 10000);
14560						year = date.getFullYear();
14561						month = date.getMonth() + 1;
14562						day = date.getDate();
14563						break;
14564					case "'":
14565						if (lookAhead("'")){
14566							checkLiteral();
14567						} else {
14568							literal = true;
14569						}
14570						break;
14571					default:
14572						checkLiteral();
14573				}
14574			}
14575		}
14576
14577		if (iValue < value.length){
14578			extra = value.substr(iValue);
14579			if (!/^\s+/.test(extra)) {
14580				throw "Extra/unparsed characters found in date: " + extra;
14581			}
14582		}
14583
14584		if (year === -1) {
14585			year = new Date().getFullYear();
14586		} else if (year < 100) {
14587			year += new Date().getFullYear() - new Date().getFullYear() % 100 +
14588				(year <= shortYearCutoff ? 0 : -100);
14589		}
14590
14591		if (doy > -1) {
14592			month = 1;
14593			day = doy;
14594			do {
14595				dim = this._getDaysInMonth(year, month - 1);
14596				if (day <= dim) {
14597					break;
14598				}
14599				month++;
14600				day -= dim;
14601			} while (true);
14602		}
14603
14604		date = this._daylightSavingAdjust(new Date(year, month - 1, day));
14605		if (date.getFullYear() !== year || date.getMonth() + 1 !== month || date.getDate() !== day) {
14606			throw "Invalid date"; // E.g. 31/02/00
14607		}
14608		return date;
14609	},
14610
14611	/* Standard date formats. */
14612	ATOM: "yy-mm-dd", // RFC 3339 (ISO 8601)
14613	COOKIE: "D, dd M yy",
14614	ISO_8601: "yy-mm-dd",
14615	RFC_822: "D, d M y",
14616	RFC_850: "DD, dd-M-y",
14617	RFC_1036: "D, d M y",
14618	RFC_1123: "D, d M yy",
14619	RFC_2822: "D, d M yy",
14620	RSS: "D, d M y", // RFC 822
14621	TICKS: "!",
14622	TIMESTAMP: "@",
14623	W3C: "yy-mm-dd", // ISO 8601
14624
14625	_ticksTo1970: (((1970 - 1) * 365 + Math.floor(1970 / 4) - Math.floor(1970 / 100) +
14626		Math.floor(1970 / 400)) * 24 * 60 * 60 * 10000000),
14627
14628	/* Format a date object into a string value.
14629	 * The format can be combinations of the following:
14630	 * d  - day of month (no leading zero)
14631	 * dd - day of month (two digit)
14632	 * o  - day of year (no leading zeros)
14633	 * oo - day of year (three digit)
14634	 * D  - day name short
14635	 * DD - day name long
14636	 * m  - month of year (no leading zero)
14637	 * mm - month of year (two digit)
14638	 * M  - month name short
14639	 * MM - month name long
14640	 * y  - year (two digit)
14641	 * yy - year (four digit)
14642	 * @ - Unix timestamp (ms since 01/01/1970)
14643	 * ! - Windows ticks (100ns since 01/01/0001)
14644	 * "..." - literal text
14645	 * '' - single quote
14646	 *
14647	 * @param  format string - the desired format of the date
14648	 * @param  date Date - the date value to format
14649	 * @param  settings Object - attributes include:
14650	 *					dayNamesShort	string[7] - abbreviated names of the days from Sunday (optional)
14651	 *					dayNames		string[7] - names of the days from Sunday (optional)
14652	 *					monthNamesShort string[12] - abbreviated names of the months (optional)
14653	 *					monthNames		string[12] - names of the months (optional)
14654	 * @return  string - the date in the above format
14655	 */
14656	formatDate: function (format, date, settings) {
14657		if (!date) {
14658			return "";
14659		}
14660
14661		var iFormat,
14662			dayNamesShort = (settings ? settings.dayNamesShort : null) || this._defaults.dayNamesShort,
14663			dayNames = (settings ? settings.dayNames : null) || this._defaults.dayNames,
14664			monthNamesShort = (settings ? settings.monthNamesShort : null) || this._defaults.monthNamesShort,
14665			monthNames = (settings ? settings.monthNames : null) || this._defaults.monthNames,
14666			// Check whether a format character is doubled
14667			lookAhead = function(match) {
14668				var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
14669				if (matches) {
14670					iFormat++;
14671				}
14672				return matches;
14673			},
14674			// Format a number, with leading zero if necessary
14675			formatNumber = function(match, value, len) {
14676				var num = "" + value;
14677				if (lookAhead(match)) {
14678					while (num.length < len) {
14679						num = "0" + num;
14680					}
14681				}
14682				return num;
14683			},
14684			// Format a name, short or long as requested
14685			formatName = function(match, value, shortNames, longNames) {
14686				return (lookAhead(match) ? longNames[value] : shortNames[value]);
14687			},
14688			output = "",
14689			literal = false;
14690
14691		if (date) {
14692			for (iFormat = 0; iFormat < format.length; iFormat++) {
14693				if (literal) {
14694					if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
14695						literal = false;
14696					} else {
14697						output += format.charAt(iFormat);
14698					}
14699				} else {
14700					switch (format.charAt(iFormat)) {
14701						case "d":
14702							output += formatNumber("d", date.getDate(), 2);
14703							break;
14704						case "D":
14705							output += formatName("D", date.getDay(), dayNamesShort, dayNames);
14706							break;
14707						case "o":
14708							output += formatNumber("o",
14709								Math.round((new Date(date.getFullYear(), date.getMonth(), date.getDate()).getTime() - new Date(date.getFullYear(), 0, 0).getTime()) / 86400000), 3);
14710							break;
14711						case "m":
14712							output += formatNumber("m", date.getMonth() + 1, 2);
14713							break;
14714						case "M":
14715							output += formatName("M", date.getMonth(), monthNamesShort, monthNames);
14716							break;
14717						case "y":
14718							output += (lookAhead("y") ? date.getFullYear() :
14719								(date.getYear() % 100 < 10 ? "0" : "") + date.getYear() % 100);
14720							break;
14721						case "@":
14722							output += date.getTime();
14723							break;
14724						case "!":
14725							output += date.getTime() * 10000 + this._ticksTo1970;
14726							break;
14727						case "'":
14728							if (lookAhead("'")) {
14729								output += "'";
14730							} else {
14731								literal = true;
14732							}
14733							break;
14734						default:
14735							output += format.charAt(iFormat);
14736					}
14737				}
14738			}
14739		}
14740		return output;
14741	},
14742
14743	/* Extract all possible characters from the date format. */
14744	_possibleChars: function (format) {
14745		var iFormat,
14746			chars = "",
14747			literal = false,
14748			// Check whether a format character is doubled
14749			lookAhead = function(match) {
14750				var matches = (iFormat + 1 < format.length && format.charAt(iFormat + 1) === match);
14751				if (matches) {
14752					iFormat++;
14753				}
14754				return matches;
14755			};
14756
14757		for (iFormat = 0; iFormat < format.length; iFormat++) {
14758			if (literal) {
14759				if (format.charAt(iFormat) === "'" && !lookAhead("'")) {
14760					literal = false;
14761				} else {
14762					chars += format.charAt(iFormat);
14763				}
14764			} else {
14765				switch (format.charAt(iFormat)) {
14766					case "d": case "m": case "y": case "@":
14767						chars += "0123456789";
14768						break;
14769					case "D": case "M":
14770						return null; // Accept anything
14771					case "'":
14772						if (lookAhead("'")) {
14773							chars += "'";
14774						} else {
14775							literal = true;
14776						}
14777						break;
14778					default:
14779						chars += format.charAt(iFormat);
14780				}
14781			}
14782		}
14783		return chars;
14784	},
14785
14786	/* Get a setting value, defaulting if necessary. */
14787	_get: function(inst, name) {
14788		return inst.settings[name] !== undefined ?
14789			inst.settings[name] : this._defaults[name];
14790	},
14791
14792	/* Parse existing date and initialise date picker. */
14793	_setDateFromField: function(inst, noDefault) {
14794		if (inst.input.val() === inst.lastVal) {
14795			return;
14796		}
14797
14798		var dateFormat = this._get(inst, "dateFormat"),
14799			dates = inst.lastVal = inst.input ? inst.input.val() : null,
14800			defaultDate = this._getDefaultDate(inst),
14801			date = defaultDate,
14802			settings = this._getFormatConfig(inst);
14803
14804		try {
14805			date = this.parseDate(dateFormat, dates, settings) || defaultDate;
14806		} catch (event) {
14807			dates = (noDefault ? "" : dates);
14808		}
14809		inst.selectedDay = date.getDate();
14810		inst.drawMonth = inst.selectedMonth = date.getMonth();
14811		inst.drawYear = inst.selectedYear = date.getFullYear();
14812		inst.currentDay = (dates ? date.getDate() : 0);
14813		inst.currentMonth = (dates ? date.getMonth() : 0);
14814		inst.currentYear = (dates ? date.getFullYear() : 0);
14815		this._adjustInstDate(inst);
14816	},
14817
14818	/* Retrieve the default date shown on opening. */
14819	_getDefaultDate: function(inst) {
14820		return this._restrictMinMax(inst,
14821			this._determineDate(inst, this._get(inst, "defaultDate"), new Date()));
14822	},
14823
14824	/* A date may be specified as an exact value or a relative one. */
14825	_determineDate: function(inst, date, defaultDate) {
14826		var offsetNumeric = function(offset) {
14827				var date = new Date();
14828				date.setDate(date.getDate() + offset);
14829				return date;
14830			},
14831			offsetString = function(offset) {
14832				try {
14833					return $.datepicker.parseDate($.datepicker._get(inst, "dateFormat"),
14834						offset, $.datepicker._getFormatConfig(inst));
14835				}
14836				catch (e) {
14837					// Ignore
14838				}
14839
14840				var date = (offset.toLowerCase().match(/^c/) ?
14841					$.datepicker._getDate(inst) : null) || new Date(),
14842					year = date.getFullYear(),
14843					month = date.getMonth(),
14844					day = date.getDate(),
14845					pattern = /([+\-]?[0-9]+)\s*(d|D|w|W|m|M|y|Y)?/g,
14846					matches = pattern.exec(offset);
14847
14848				while (matches) {
14849					switch (matches[2] || "d") {
14850						case "d" : case "D" :
14851							day += parseInt(matches[1],10); break;
14852						case "w" : case "W" :
14853							day += parseInt(matches[1],10) * 7; break;
14854						case "m" : case "M" :
14855							month += parseInt(matches[1],10);
14856							day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
14857							break;
14858						case "y": case "Y" :
14859							year += parseInt(matches[1],10);
14860							day = Math.min(day, $.datepicker._getDaysInMonth(year, month));
14861							break;
14862					}
14863					matches = pattern.exec(offset);
14864				}
14865				return new Date(year, month, day);
14866			},
14867			newDate = (date == null || date === "" ? defaultDate : (typeof date === "string" ? offsetString(date) :
14868				(typeof date === "number" ? (isNaN(date) ? defaultDate : offsetNumeric(date)) : new Date(date.getTime()))));
14869
14870		newDate = (newDate && newDate.toString() === "Invalid Date" ? defaultDate : newDate);
14871		if (newDate) {
14872			newDate.setHours(0);
14873			newDate.setMinutes(0);
14874			newDate.setSeconds(0);
14875			newDate.setMilliseconds(0);
14876		}
14877		return this._daylightSavingAdjust(newDate);
14878	},
14879
14880	/* Handle switch to/from daylight saving.
14881	 * Hours may be non-zero on daylight saving cut-over:
14882	 * > 12 when midnight changeover, but then cannot generate
14883	 * midnight datetime, so jump to 1AM, otherwise reset.
14884	 * @param  date  (Date) the date to check
14885	 * @return  (Date) the corrected date
14886	 */
14887	_daylightSavingAdjust: function(date) {
14888		if (!date) {
14889			return null;
14890		}
14891		date.setHours(date.getHours() > 12 ? date.getHours() + 2 : 0);
14892		return date;
14893	},
14894
14895	/* Set the date(s) directly. */
14896	_setDate: function(inst, date, noChange) {
14897		var clear = !date,
14898			origMonth = inst.selectedMonth,
14899			origYear = inst.selectedYear,
14900			newDate = this._restrictMinMax(inst, this._determineDate(inst, date, new Date()));
14901
14902		inst.selectedDay = inst.currentDay = newDate.getDate();
14903		inst.drawMonth = inst.selectedMonth = inst.currentMonth = newDate.getMonth();
14904		inst.drawYear = inst.selectedYear = inst.currentYear = newDate.getFullYear();
14905		if ((origMonth !== inst.selectedMonth || origYear !== inst.selectedYear) && !noChange) {
14906			this._notifyChange(inst);
14907		}
14908		this._adjustInstDate(inst);
14909		if (inst.input) {
14910			inst.input.val(clear ? "" : this._formatDate(inst));
14911		}
14912	},
14913
14914	/* Retrieve the date(s) directly. */
14915	_getDate: function(inst) {
14916		var startDate = (!inst.currentYear || (inst.input && inst.input.val() === "") ? null :
14917			this._daylightSavingAdjust(new Date(
14918			inst.currentYear, inst.currentMonth, inst.currentDay)));
14919			return startDate;
14920	},
14921
14922	/* Attach the onxxx handlers.  These are declared statically so
14923	 * they work with static code transformers like Caja.
14924	 */
14925	_attachHandlers: function(inst) {
14926		var stepMonths = this._get(inst, "stepMonths"),
14927			id = "#" + inst.id.replace( /\\\\/g, "\\" );
14928		inst.dpDiv.find("[data-handler]").map(function () {
14929			var handler = {
14930				prev: function () {
14931					$.datepicker._adjustDate(id, -stepMonths, "M");
14932				},
14933				next: function () {
14934					$.datepicker._adjustDate(id, +stepMonths, "M");
14935				},
14936				hide: function () {
14937					$.datepicker._hideDatepicker();
14938				},
14939				today: function () {
14940					$.datepicker._gotoToday(id);
14941				},
14942				selectDay: function () {
14943					$.datepicker._selectDay(id, +this.getAttribute("data-month"), +this.getAttribute("data-year"), this);
14944					return false;
14945				},
14946				selectMonth: function () {
14947					$.datepicker._selectMonthYear(id, this, "M");
14948					return false;
14949				},
14950				selectYear: function () {
14951					$.datepicker._selectMonthYear(id, this, "Y");
14952					return false;
14953				}
14954			};
14955			$(this).bind(this.getAttribute("data-event"), handler[this.getAttribute("data-handler")]);
14956		});
14957	},
14958
14959	/* Generate the HTML for the current state of the date picker. */
14960	_generateHTML: function(inst) {
14961		var maxDraw, prevText, prev, nextText, next, currentText, gotoDate,
14962			controls, buttonPanel, firstDay, showWeek, dayNames, dayNamesMin,
14963			monthNames, monthNamesShort, beforeShowDay, showOtherMonths,
14964			selectOtherMonths, defaultDate, html, dow, row, group, col, selectedDate,
14965			cornerClass, calender, thead, day, daysInMonth, leadDays, curRows, numRows,
14966			printDate, dRow, tbody, daySettings, otherMonth, unselectable,
14967			tempDate = new Date(),
14968			today = this._daylightSavingAdjust(
14969				new Date(tempDate.getFullYear(), tempDate.getMonth(), tempDate.getDate())), // clear time
14970			isRTL = this._get(inst, "isRTL"),
14971			showButtonPanel = this._get(inst, "showButtonPanel"),
14972			hideIfNoPrevNext = this._get(inst, "hideIfNoPrevNext"),
14973			navigationAsDateFormat = this._get(inst, "navigationAsDateFormat"),
14974			numMonths = this._getNumberOfMonths(inst),
14975			showCurrentAtPos = this._get(inst, "showCurrentAtPos"),
14976			stepMonths = this._get(inst, "stepMonths"),
14977			isMultiMonth = (numMonths[0] !== 1 || numMonths[1] !== 1),
14978			currentDate = this._daylightSavingAdjust((!inst.currentDay ? new Date(9999, 9, 9) :
14979				new Date(inst.currentYear, inst.currentMonth, inst.currentDay))),
14980			minDate = this._getMinMaxDate(inst, "min"),
14981			maxDate = this._getMinMaxDate(inst, "max"),
14982			drawMonth = inst.drawMonth - showCurrentAtPos,
14983			drawYear = inst.drawYear;
14984
14985		if (drawMonth < 0) {
14986			drawMonth += 12;
14987			drawYear--;
14988		}
14989		if (maxDate) {
14990			maxDraw = this._daylightSavingAdjust(new Date(maxDate.getFullYear(),
14991				maxDate.getMonth() - (numMonths[0] * numMonths[1]) + 1, maxDate.getDate()));
14992			maxDraw = (minDate && maxDraw < minDate ? minDate : maxDraw);
14993			while (this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1)) > maxDraw) {
14994				drawMonth--;
14995				if (drawMonth < 0) {
14996					drawMonth = 11;
14997					drawYear--;
14998				}
14999			}
15000		}
15001		inst.drawMonth = drawMonth;
15002		inst.drawYear = drawYear;
15003
15004		prevText = this._get(inst, "prevText");
15005		prevText = (!navigationAsDateFormat ? prevText : this.formatDate(prevText,
15006			this._daylightSavingAdjust(new Date(drawYear, drawMonth - stepMonths, 1)),
15007			this._getFormatConfig(inst)));
15008
15009		prev = (this._canAdjustMonth(inst, -1, drawYear, drawMonth) ?
15010			"<a class='ui-datepicker-prev ui-corner-all' data-handler='prev' data-event='click'" +
15011			" title='" + prevText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>" :
15012			(hideIfNoPrevNext ? "" : "<a class='ui-datepicker-prev ui-corner-all ui-state-disabled' title='"+ prevText +"'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "e" : "w") + "'>" + prevText + "</span></a>"));
15013
15014		nextText = this._get(inst, "nextText");
15015		nextText = (!navigationAsDateFormat ? nextText : this.formatDate(nextText,
15016			this._daylightSavingAdjust(new Date(drawYear, drawMonth + stepMonths, 1)),
15017			this._getFormatConfig(inst)));
15018
15019		next = (this._canAdjustMonth(inst, +1, drawYear, drawMonth) ?
15020			"<a class='ui-datepicker-next ui-corner-all' data-handler='next' data-event='click'" +
15021			" title='" + nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>" :
15022			(hideIfNoPrevNext ? "" : "<a class='ui-datepicker-next ui-corner-all ui-state-disabled' title='"+ nextText + "'><span class='ui-icon ui-icon-circle-triangle-" + ( isRTL ? "w" : "e") + "'>" + nextText + "</span></a>"));
15023
15024		currentText = this._get(inst, "currentText");
15025		gotoDate = (this._get(inst, "gotoCurrent") && inst.currentDay ? currentDate : today);
15026		currentText = (!navigationAsDateFormat ? currentText :
15027			this.formatDate(currentText, gotoDate, this._getFormatConfig(inst)));
15028
15029		controls = (!inst.inline ? "<button type='button' class='ui-datepicker-close ui-state-default ui-priority-primary ui-corner-all' data-handler='hide' data-event='click'>" +
15030			this._get(inst, "closeText") + "</button>" : "");
15031
15032		buttonPanel = (showButtonPanel) ? "<div class='ui-datepicker-buttonpane ui-widget-content'>" + (isRTL ? controls : "") +
15033			(this._isInRange(inst, gotoDate) ? "<button type='button' class='ui-datepicker-current ui-state-default ui-priority-secondary ui-corner-all' data-handler='today' data-event='click'" +
15034			">" + currentText + "</button>" : "") + (isRTL ? "" : controls) + "</div>" : "";
15035
15036		firstDay = parseInt(this._get(inst, "firstDay"),10);
15037		firstDay = (isNaN(firstDay) ? 0 : firstDay);
15038
15039		showWeek = this._get(inst, "showWeek");
15040		dayNames = this._get(inst, "dayNames");
15041		dayNamesMin = this._get(inst, "dayNamesMin");
15042		monthNames = this._get(inst, "monthNames");
15043		monthNamesShort = this._get(inst, "monthNamesShort");
15044		beforeShowDay = this._get(inst, "beforeShowDay");
15045		showOtherMonths = this._get(inst, "showOtherMonths");
15046		selectOtherMonths = this._get(inst, "selectOtherMonths");
15047		defaultDate = this._getDefaultDate(inst);
15048		html = "";
15049		dow;
15050		for (row = 0; row < numMonths[0]; row++) {
15051			group = "";
15052			this.maxRows = 4;
15053			for (col = 0; col < numMonths[1]; col++) {
15054				selectedDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, inst.selectedDay));
15055				cornerClass = " ui-corner-all";
15056				calender = "";
15057				if (isMultiMonth) {
15058					calender += "<div class='ui-datepicker-group";
15059					if (numMonths[1] > 1) {
15060						switch (col) {
15061							case 0: calender += " ui-datepicker-group-first";
15062								cornerClass = " ui-corner-" + (isRTL ? "right" : "left"); break;
15063							case numMonths[1]-1: calender += " ui-datepicker-group-last";
15064								cornerClass = " ui-corner-" + (isRTL ? "left" : "right"); break;
15065							default: calender += " ui-datepicker-group-middle"; cornerClass = ""; break;
15066						}
15067					}
15068					calender += "'>";
15069				}
15070				calender += "<div class='ui-datepicker-header ui-widget-header ui-helper-clearfix" + cornerClass + "'>" +
15071					(/all|left/.test(cornerClass) && row === 0 ? (isRTL ? next : prev) : "") +
15072					(/all|right/.test(cornerClass) && row === 0 ? (isRTL ? prev : next) : "") +
15073					this._generateMonthYearHeader(inst, drawMonth, drawYear, minDate, maxDate,
15074					row > 0 || col > 0, monthNames, monthNamesShort) + // draw month headers
15075					"</div><table class='ui-datepicker-calendar'><thead>" +
15076					"<tr>";
15077				thead = (showWeek ? "<th class='ui-datepicker-week-col'>" + this._get(inst, "weekHeader") + "</th>" : "");
15078				for (dow = 0; dow < 7; dow++) { // days of the week
15079					day = (dow + firstDay) % 7;
15080					thead += "<th" + ((dow + firstDay + 6) % 7 >= 5 ? " class='ui-datepicker-week-end'" : "") + ">" +
15081						"<span title='" + dayNames[day] + "'>" + dayNamesMin[day] + "</span></th>";
15082				}
15083				calender += thead + "</tr></thead><tbody>";
15084				daysInMonth = this._getDaysInMonth(drawYear, drawMonth);
15085				if (drawYear === inst.selectedYear && drawMonth === inst.selectedMonth) {
15086					inst.selectedDay = Math.min(inst.selectedDay, daysInMonth);
15087				}
15088				leadDays = (this._getFirstDayOfMonth(drawYear, drawMonth) - firstDay + 7) % 7;
15089				curRows = Math.ceil((leadDays + daysInMonth) / 7); // calculate the number of rows to generate
15090				numRows = (isMultiMonth ? this.maxRows > curRows ? this.maxRows : curRows : curRows); //If multiple months, use the higher number of rows (see #7043)
15091				this.maxRows = numRows;
15092				printDate = this._daylightSavingAdjust(new Date(drawYear, drawMonth, 1 - leadDays));
15093				for (dRow = 0; dRow < numRows; dRow++) { // create date picker rows
15094					calender += "<tr>";
15095					tbody = (!showWeek ? "" : "<td class='ui-datepicker-week-col'>" +
15096						this._get(inst, "calculateWeek")(printDate) + "</td>");
15097					for (dow = 0; dow < 7; dow++) { // create date picker days
15098						daySettings = (beforeShowDay ?
15099							beforeShowDay.apply((inst.input ? inst.input[0] : null), [printDate]) : [true, ""]);
15100						otherMonth = (printDate.getMonth() !== drawMonth);
15101						unselectable = (otherMonth && !selectOtherMonths) || !daySettings[0] ||
15102							(minDate && printDate < minDate) || (maxDate && printDate > maxDate);
15103						tbody += "<td class='" +
15104							((dow + firstDay + 6) % 7 >= 5 ? " ui-datepicker-week-end" : "") + // highlight weekends
15105							(otherMonth ? " ui-datepicker-other-month" : "") + // highlight days from other months
15106							((printDate.getTime() === selectedDate.getTime() && drawMonth === inst.selectedMonth && inst._keyEvent) || // user pressed key
15107							(defaultDate.getTime() === printDate.getTime() && defaultDate.getTime() === selectedDate.getTime()) ?
15108							// or defaultDate is current printedDate and defaultDate is selectedDate
15109							" " + this._dayOverClass : "") + // highlight selected day
15110							(unselectable ? " " + this._unselectableClass + " ui-state-disabled": "") +  // highlight unselectable days
15111							(otherMonth && !showOtherMonths ? "" : " " + daySettings[1] + // highlight custom dates
15112							(printDate.getTime() === currentDate.getTime() ? " " + this._currentClass : "") + // highlight selected day
15113							(printDate.getTime() === today.getTime() ? " ui-datepicker-today" : "")) + "'" + // highlight today (if different)
15114							((!otherMonth || showOtherMonths) && daySettings[2] ? " title='" + daySettings[2].replace(/'/g, "&#39;") + "'" : "") + // cell title
15115							(unselectable ? "" : " data-handler='selectDay' data-event='click' data-month='" + printDate.getMonth() + "' data-year='" + printDate.getFullYear() + "'") + ">" + // actions
15116							(otherMonth && !showOtherMonths ? "&#xa0;" : // display for other months
15117							(unselectable ? "<span class='ui-state-default'>" + printDate.getDate() + "</span>" : "<a class='ui-state-default" +
15118							(printDate.getTime() === today.getTime() ? " ui-state-highlight" : "") +
15119							(printDate.getTime() === currentDate.getTime() ? " ui-state-active" : "") + // highlight selected day
15120							(otherMonth ? " ui-priority-secondary" : "") + // distinguish dates from other months
15121							"' href='#'>" + printDate.getDate() + "</a>")) + "</td>"; // display selectable date
15122						printDate.setDate(printDate.getDate() + 1);
15123						printDate = this._daylightSavingAdjust(printDate);
15124					}
15125					calender += tbody + "</tr>";
15126				}
15127				drawMonth++;
15128				if (drawMonth > 11) {
15129					drawMonth = 0;
15130					drawYear++;
15131				}
15132				calender += "</tbody></table>" + (isMultiMonth ? "</div>" +
15133							((numMonths[0] > 0 && col === numMonths[1]-1) ? "<div class='ui-datepicker-row-break'></div>" : "") : "");
15134				group += calender;
15135			}
15136			html += group;
15137		}
15138		html += buttonPanel;
15139		inst._keyEvent = false;
15140		return html;
15141	},
15142
15143	/* Generate the month and year header. */
15144	_generateMonthYearHeader: function(inst, drawMonth, drawYear, minDate, maxDate,
15145			secondary, monthNames, monthNamesShort) {
15146
15147		var inMinYear, inMaxYear, month, years, thisYear, determineYear, year, endYear,
15148			changeMonth = this._get(inst, "changeMonth"),
15149			changeYear = this._get(inst, "changeYear"),
15150			showMonthAfterYear = this._get(inst, "showMonthAfterYear"),
15151			html = "<div class='ui-datepicker-title'>",
15152			monthHtml = "";
15153
15154		// month selection
15155		if (secondary || !changeMonth) {
15156			monthHtml += "<span class='ui-datepicker-month'>" + monthNames[drawMonth] + "</span>";
15157		} else {
15158			inMinYear = (minDate && minDate.getFullYear() === drawYear);
15159			inMaxYear = (maxDate && maxDate.getFullYear() === drawYear);
15160			monthHtml += "<select class='ui-datepicker-month' data-handler='selectMonth' data-event='change'>";
15161			for ( month = 0; month < 12; month++) {
15162				if ((!inMinYear || month >= minDate.getMonth()) && (!inMaxYear || month <= maxDate.getMonth())) {
15163					monthHtml += "<option value='" + month + "'" +
15164						(month === drawMonth ? " selected='selected'" : "") +
15165						">" + monthNamesShort[month] + "</option>";
15166				}
15167			}
15168			monthHtml += "</select>";
15169		}
15170
15171		if (!showMonthAfterYear) {
15172			html += monthHtml + (secondary || !(changeMonth && changeYear) ? "&#xa0;" : "");
15173		}
15174
15175		// year selection
15176		if ( !inst.yearshtml ) {
15177			inst.yearshtml = "";
15178			if (secondary || !changeYear) {
15179				html += "<span class='ui-datepicker-year'>" + drawYear + "</span>";
15180			} else {
15181				// determine range of years to display
15182				years = this._get(inst, "yearRange").split(":");
15183				thisYear = new Date().getFullYear();
15184				determineYear = function(value) {
15185					var year = (value.match(/c[+\-].*/) ? drawYear + parseInt(value.substring(1), 10) :
15186						(value.match(/[+\-].*/) ? thisYear + parseInt(value, 10) :
15187						parseInt(value, 10)));
15188					return (isNaN(year) ? thisYear : year);
15189				};
15190				year = determineYear(years[0]);
15191				endYear = Math.max(year, determineYear(years[1] || ""));
15192				year = (minDate ? Math.max(year, minDate.getFullYear()) : year);
15193				endYear = (maxDate ? Math.min(endYear, maxDate.getFullYear()) : endYear);
15194				inst.yearshtml += "<select class='ui-datepicker-year' data-handler='selectYear' data-event='change'>";
15195				for (; year <= endYear; year++) {
15196					inst.yearshtml += "<option value='" + year + "'" +
15197						(year === drawYear ? " selected='selected'" : "") +
15198						">" + year + "</option>";
15199				}
15200				inst.yearshtml += "</select>";
15201
15202				html += inst.yearshtml;
15203				inst.yearshtml = null;
15204			}
15205		}
15206
15207		html += this._get(inst, "yearSuffix");
15208		if (showMonthAfterYear) {
15209			html += (secondary || !(changeMonth && changeYear) ? "&#xa0;" : "") + monthHtml;
15210		}
15211		html += "</div>"; // Close datepicker_header
15212		return html;
15213	},
15214
15215	/* Adjust one of the date sub-fields. */
15216	_adjustInstDate: function(inst, offset, period) {
15217		var year = inst.drawYear + (period === "Y" ? offset : 0),
15218			month = inst.drawMonth + (period === "M" ? offset : 0),
15219			day = Math.min(inst.selectedDay, this._getDaysInMonth(year, month)) + (period === "D" ? offset : 0),
15220			date = this._restrictMinMax(inst, this._daylightSavingAdjust(new Date(year, month, day)));
15221
15222		inst.selectedDay = date.getDate();
15223		inst.drawMonth = inst.selectedMonth = date.getMonth();
15224		inst.drawYear = inst.selectedYear = date.getFullYear();
15225		if (period === "M" || period === "Y") {
15226			this._notifyChange(inst);
15227		}
15228	},
15229
15230	/* Ensure a date is within any min/max bounds. */
15231	_restrictMinMax: function(inst, date) {
15232		var minDate = this._getMinMaxDate(inst, "min"),
15233			maxDate = this._getMinMaxDate(inst, "max"),
15234			newDate = (minDate && date < minDate ? minDate : date);
15235		return (maxDate && newDate > maxDate ? maxDate : newDate);
15236	},
15237
15238	/* Notify change of month/year. */
15239	_notifyChange: function(inst) {
15240		var onChange = this._get(inst, "onChangeMonthYear");
15241		if (onChange) {
15242			onChange.apply((inst.input ? inst.input[0] : null),
15243				[inst.selectedYear, inst.selectedMonth + 1, inst]);
15244		}
15245	},
15246
15247	/* Determine the number of months to show. */
15248	_getNumberOfMonths: function(inst) {
15249		var numMonths = this._get(inst, "numberOfMonths");
15250		return (numMonths == null ? [1, 1] : (typeof numMonths === "number" ? [1, numMonths] : numMonths));
15251	},
15252
15253	/* Determine the current maximum date - ensure no time components are set. */
15254	_getMinMaxDate: function(inst, minMax) {
15255		return this._determineDate(inst, this._get(inst, minMax + "Date"), null);
15256	},
15257
15258	/* Find the number of days in a given month. */
15259	_getDaysInMonth: function(year, month) {
15260		return 32 - this._daylightSavingAdjust(new Date(year, month, 32)).getDate();
15261	},
15262
15263	/* Find the day of the week of the first of a month. */
15264	_getFirstDayOfMonth: function(year, month) {
15265		return new Date(year, month, 1).getDay();
15266	},
15267
15268	/* Determines if we should allow a "next/prev" month display change. */
15269	_canAdjustMonth: function(inst, offset, curYear, curMonth) {
15270		var numMonths = this._getNumberOfMonths(inst),
15271			date = this._daylightSavingAdjust(new Date(curYear,
15272			curMonth + (offset < 0 ? offset : numMonths[0] * numMonths[1]), 1));
15273
15274		if (offset < 0) {
15275			date.setDate(this._getDaysInMonth(date.getFullYear(), date.getMonth()));
15276		}
15277		return this._isInRange(inst, date);
15278	},
15279
15280	/* Is the given date in the accepted range? */
15281	_isInRange: function(inst, date) {
15282		var yearSplit, currentYear,
15283			minDate = this._getMinMaxDate(inst, "min"),
15284			maxDate = this._getMinMaxDate(inst, "max"),
15285			minYear = null,
15286			maxYear = null,
15287			years = this._get(inst, "yearRange");
15288			if (years){
15289				yearSplit = years.split(":");
15290				currentYear = new Date().getFullYear();
15291				minYear = parseInt(yearSplit[0], 10);
15292				maxYear = parseInt(yearSplit[1], 10);
15293				if ( yearSplit[0].match(/[+\-].*/) ) {
15294					minYear += currentYear;
15295				}
15296				if ( yearSplit[1].match(/[+\-].*/) ) {
15297					maxYear += currentYear;
15298				}
15299			}
15300
15301		return ((!minDate || date.getTime() >= minDate.getTime()) &&
15302			(!maxDate || date.getTime() <= maxDate.getTime()) &&
15303			(!minYear || date.getFullYear() >= minYear) &&
15304			(!maxYear || date.getFullYear() <= maxYear));
15305	},
15306
15307	/* Provide the configuration settings for formatting/parsing. */
15308	_getFormatConfig: function(inst) {
15309		var shortYearCutoff = this._get(inst, "shortYearCutoff");
15310		shortYearCutoff = (typeof shortYearCutoff !== "string" ? shortYearCutoff :
15311			new Date().getFullYear() % 100 + parseInt(shortYearCutoff, 10));
15312		return {shortYearCutoff: shortYearCutoff,
15313			dayNamesShort: this._get(inst, "dayNamesShort"), dayNames: this._get(inst, "dayNames"),
15314			monthNamesShort: this._get(inst, "monthNamesShort"), monthNames: this._get(inst, "monthNames")};
15315	},
15316
15317	/* Format the given date for display. */
15318	_formatDate: function(inst, day, month, year) {
15319		if (!day) {
15320			inst.currentDay = inst.selectedDay;
15321			inst.currentMonth = inst.selectedMonth;
15322			inst.currentYear = inst.selectedYear;
15323		}
15324		var date = (day ? (typeof day === "object" ? day :
15325			this._daylightSavingAdjust(new Date(year, month, day))) :
15326			this._daylightSavingAdjust(new Date(inst.currentYear, inst.currentMonth, inst.currentDay)));
15327		return this.formatDate(this._get(inst, "dateFormat"), date, this._getFormatConfig(inst));
15328	}
15329});
15330
15331/*
15332 * Bind hover events for datepicker elements.
15333 * Done via delegate so the binding only occurs once in the lifetime of the parent div.
15334 * Global instActive, set by _updateDatepicker allows the handlers to find their way back to the active picker.
15335 */
15336function bindHover(dpDiv) {
15337	var selector = "button, .ui-datepicker-prev, .ui-datepicker-next, .ui-datepicker-calendar td a";
15338	return dpDiv.delegate(selector, "mouseout", function() {
15339			$(this).removeClass("ui-state-hover");
15340			if (this.className.indexOf("ui-datepicker-prev") !== -1) {
15341				$(this).removeClass("ui-datepicker-prev-hover");
15342			}
15343			if (this.className.indexOf("ui-datepicker-next") !== -1) {
15344				$(this).removeClass("ui-datepicker-next-hover");
15345			}
15346		})
15347		.delegate(selector, "mouseover", function(){
15348			if (!$.datepicker._isDisabledDatepicker( instActive.inline ? dpDiv.parent()[0] : instActive.input[0])) {
15349				$(this).parents(".ui-datepicker-calendar").find("a").removeClass("ui-state-hover");
15350				$(this).addClass("ui-state-hover");
15351				if (this.className.indexOf("ui-datepicker-prev") !== -1) {
15352					$(this).addClass("ui-datepicker-prev-hover");
15353				}
15354				if (this.className.indexOf("ui-datepicker-next") !== -1) {
15355					$(this).addClass("ui-datepicker-next-hover");
15356				}
15357			}
15358		});
15359}
15360
15361/* jQuery extend now ignores nulls! */
15362function extendRemove(target, props) {
15363	$.extend(target, props);
15364	for (var name in props) {
15365		if (props[name] == null) {
15366			target[name] = props[name];
15367		}
15368	}
15369	return target;
15370}
15371
15372/* Invoke the datepicker functionality.
15373   @param  options  string - a command, optionally followed by additional parameters or
15374					Object - settings for attaching new datepicker functionality
15375   @return  jQuery object */
15376$.fn.datepicker = function(options){
15377
15378	/* Verify an empty collection wasn't passed - Fixes #6976 */
15379	if ( !this.length ) {
15380		return this;
15381	}
15382
15383	/* Initialise the date picker. */
15384	if (!$.datepicker.initialized) {
15385		$(document).mousedown($.datepicker._checkExternalClick);
15386		$.datepicker.initialized = true;
15387	}
15388
15389	/* Append datepicker main container to body if not exist. */
15390	if ($("#"+$.datepicker._mainDivId).length === 0) {
15391		$("body").append($.datepicker.dpDiv);
15392	}
15393
15394	var otherArgs = Array.prototype.slice.call(arguments, 1);
15395	if (typeof options === "string" && (options === "isDisabled" || options === "getDate" || options === "widget")) {
15396		return $.datepicker["_" + options + "Datepicker"].
15397			apply($.datepicker, [this[0]].concat(otherArgs));
15398	}
15399	if (options === "option" && arguments.length === 2 && typeof arguments[1] === "string") {
15400		return $.datepicker["_" + options + "Datepicker"].
15401			apply($.datepicker, [this[0]].concat(otherArgs));
15402	}
15403	return this.each(function() {
15404		typeof options === "string" ?
15405			$.datepicker["_" + options + "Datepicker"].
15406				apply($.datepicker, [this].concat(otherArgs)) :
15407			$.datepicker._attachDatepicker(this, options);
15408	});
15409};
15410
15411$.datepicker = new Datepicker(); // singleton instance
15412$.datepicker.initialized = false;
15413$.datepicker.uuid = new Date().getTime();
15414$.datepicker.version = "1.10.3";
15415
15416})(jQuery);
vendor: 55,650 bytes, lines 15417-17269
15417
15418
15419
15420/*!
15421 * jQuery UI Mouse 1.10.3
15422 * http://jqueryui.com
15423 *
15424 * Copyright 2013 jQuery Foundation and other contributors
15425 * Released under the MIT license.
15426 * http://jquery.org/license
15427 *
15428 * http://api.jqueryui.com/mouse/
15429 *
15430 * Depends:
15431 *	jquery.ui.widget.js
15432 */
15433
15434(function( $, undefined ) {
15435
15436var mouseHandled = false;
15437$( document ).mouseup( function() {
15438	mouseHandled = false;
15439});
15440
15441$.widget("ui.mouse", {
15442	version: "1.10.3",
15443	options: {
15444		cancel: "input,textarea,button,select,option",
15445		distance: 1,
15446		delay: 0
15447	},
15448	_mouseInit: function() {
15449		var that = this;
15450
15451		this.element
15452			.bind("mousedown."+this.widgetName, function(event) {
15453				return that._mouseDown(event);
15454			})
15455			.bind("click."+this.widgetName, function(event) {
15456				if (true === $.data(event.target, that.widgetName + ".preventClickEvent")) {
15457					$.removeData(event.target, that.widgetName + ".preventClickEvent");
15458					event.stopImmediatePropagation();
15459					return false;
15460				}
15461			});
15462
15463		this.started = false;
15464	},
15465
15466	// TODO: make sure destroying one instance of mouse doesn't mess with
15467	// other instances of mouse
15468	_mouseDestroy: function() {
15469		this.element.unbind("."+this.widgetName);
15470		if ( this._mouseMoveDelegate ) {
15471			$(document)
15472				.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
15473				.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
15474		}
15475	},
15476
15477	_mouseDown: function(event) {
15478		// don't let more than one widget handle mouseStart
15479		if( mouseHandled ) { return; }
15480
15481		// we may have missed mouseup (out of window)
15482		(this._mouseStarted && this._mouseUp(event));
15483
15484		this._mouseDownEvent = event;
15485
15486		var that = this,
15487			btnIsLeft = (event.which === 1),
15488			// event.target.nodeName works around a bug in IE 8 with
15489			// disabled inputs (#7620)
15490			elIsCancel = (typeof this.options.cancel === "string" && event.target.nodeName ? $(event.target).closest(this.options.cancel).length : false);
15491		if (!btnIsLeft || elIsCancel || !this._mouseCapture(event)) {
15492			return true;
15493		}
15494
15495		this.mouseDelayMet = !this.options.delay;
15496		if (!this.mouseDelayMet) {
15497			this._mouseDelayTimer = setTimeout(function() {
15498				that.mouseDelayMet = true;
15499			}, this.options.delay);
15500		}
15501
15502		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
15503			this._mouseStarted = (this._mouseStart(event) !== false);
15504			if (!this._mouseStarted) {
15505				event.preventDefault();
15506				return true;
15507			}
15508		}
15509
15510		// Click event may never have fired (Gecko & Opera)
15511		if (true === $.data(event.target, this.widgetName + ".preventClickEvent")) {
15512			$.removeData(event.target, this.widgetName + ".preventClickEvent");
15513		}
15514
15515		// these delegates are required to keep context
15516		this._mouseMoveDelegate = function(event) {
15517			return that._mouseMove(event);
15518		};
15519		this._mouseUpDelegate = function(event) {
15520			return that._mouseUp(event);
15521		};
15522		$(document)
15523			.bind("mousemove."+this.widgetName, this._mouseMoveDelegate)
15524			.bind("mouseup."+this.widgetName, this._mouseUpDelegate);
15525
15526		event.preventDefault();
15527
15528		mouseHandled = true;
15529		return true;
15530	},
15531
15532	_mouseMove: function(event) {
15533		// IE mouseup check - mouseup happened when mouse was out of window
15534		if ($.ui.ie && ( !document.documentMode || document.documentMode < 9 ) && !event.button) {
15535			return this._mouseUp(event);
15536		}
15537
15538		if (this._mouseStarted) {
15539			this._mouseDrag(event);
15540			return event.preventDefault();
15541		}
15542
15543		if (this._mouseDistanceMet(event) && this._mouseDelayMet(event)) {
15544			this._mouseStarted =
15545				(this._mouseStart(this._mouseDownEvent, event) !== false);
15546			(this._mouseStarted ? this._mouseDrag(event) : this._mouseUp(event));
15547		}
15548
15549		return !this._mouseStarted;
15550	},
15551
15552	_mouseUp: function(event) {
15553		$(document)
15554			.unbind("mousemove."+this.widgetName, this._mouseMoveDelegate)
15555			.unbind("mouseup."+this.widgetName, this._mouseUpDelegate);
15556
15557		if (this._mouseStarted) {
15558			this._mouseStarted = false;
15559
15560			if (event.target === this._mouseDownEvent.target) {
15561				$.data(event.target, this.widgetName + ".preventClickEvent", true);
15562			}
15563
15564			this._mouseStop(event);
15565		}
15566
15567		return false;
15568	},
15569
15570	_mouseDistanceMet: function(event) {
15571		return (Math.max(
15572				Math.abs(this._mouseDownEvent.pageX - event.pageX),
15573				Math.abs(this._mouseDownEvent.pageY - event.pageY)
15574			) >= this.options.distance
15575		);
15576	},
15577
15578	_mouseDelayMet: function(/* event */) {
15579		return this.mouseDelayMet;
15580	},
15581
15582	// These are placeholder methods, to be overriden by extending plugin
15583	_mouseStart: function(/* event */) {},
15584	_mouseDrag: function(/* event */) {},
15585	_mouseStop: function(/* event */) {},
15586	_mouseCapture: function(/* event */) { return true; }
15587});
15588
15589})(jQuery);
15590
15591
15592
15593
15594/*!
15595 * jQuery UI Draggable 1.10.3
15596 * http://jqueryui.com
15597 *
15598 * Copyright 2013 jQuery Foundation and other contributors
15599 * Released under the MIT license.
15600 * http://jquery.org/license
15601 *
15602 * http://api.jqueryui.com/draggable/
15603 *
15604 * Depends:
15605 *	jquery.ui.core.js
15606 *	jquery.ui.mouse.js
15607 *	jquery.ui.widget.js
15608 */
15609
15610(function( $, undefined ) {
15611
15612$.widget("ui.draggable", $.ui.mouse, {
15613	version: "1.10.3",
15614	widgetEventPrefix: "drag",
15615	options: {
15616		addClasses: true,
15617		appendTo: "parent",
15618		axis: false,
15619		connectToSortable: false,
15620		containment: false,
15621		cursor: "auto",
15622		cursorAt: false,
15623		grid: false,
15624		handle: false,
15625		helper: "original",
15626		iframeFix: false,
15627		opacity: false,
15628		refreshPositions: false,
15629		revert: false,
15630		revertDuration: 500,
15631		scope: "default",
15632		scroll: true,
15633		scrollSensitivity: 20,
15634		scrollSpeed: 20,
15635		snap: false,
15636		snapMode: "both",
15637		snapTolerance: 20,
15638		stack: false,
15639		zIndex: false,
15640
15641		// callbacks
15642		drag: null,
15643		start: null,
15644		stop: null
15645	},
15646	_create: function() {
15647
15648		if (this.options.helper === "original" && !(/^(?:r|a|f)/).test(this.element.css("position"))) {
15649			this.element[0].style.position = "relative";
15650		}
15651		if (this.options.addClasses){
15652			this.element.addClass("ui-draggable");
15653		}
15654		if (this.options.disabled){
15655			this.element.addClass("ui-draggable-disabled");
15656		}
15657
15658		this._mouseInit();
15659
15660	},
15661
15662	_destroy: function() {
15663		this.element.removeClass( "ui-draggable ui-draggable-dragging ui-draggable-disabled" );
15664		this._mouseDestroy();
15665	},
15666
15667	_mouseCapture: function(event) {
15668
15669		var o = this.options;
15670
15671		// among others, prevent a drag on a resizable-handle
15672		if (this.helper || o.disabled || $(event.target).closest(".ui-resizable-handle").length > 0) {
15673			return false;
15674		}
15675
15676		//Quit if we're not on a valid handle
15677		this.handle = this._getHandle(event);
15678		if (!this.handle) {
15679			return false;
15680		}
15681
15682		$(o.iframeFix === true ? "iframe" : o.iframeFix).each(function() {
15683			$("<div class='ui-draggable-iframeFix' style='background: #fff;'></div>")
15684			.css({
15685				width: this.offsetWidth+"px", height: this.offsetHeight+"px",
15686				position: "absolute", opacity: "0.001", zIndex: 1000
15687			})
15688			.css($(this).offset())
15689			.appendTo("body");
15690		});
15691
15692		return true;
15693
15694	},
15695
15696	_mouseStart: function(event) {
15697
15698		var o = this.options;
15699
15700		//Create and append the visible helper
15701		this.helper = this._createHelper(event);
15702
15703		this.helper.addClass("ui-draggable-dragging");
15704
15705		//Cache the helper size
15706		this._cacheHelperProportions();
15707
15708		//If ddmanager is used for droppables, set the global draggable
15709		if($.ui.ddmanager) {
15710			$.ui.ddmanager.current = this;
15711		}
15712
15713		/*
15714		 * - Position generation -
15715		 * This block generates everything position related - it's the core of draggables.
15716		 */
15717
15718		//Cache the margins of the original element
15719		this._cacheMargins();
15720
15721		//Store the helper's css position
15722		this.cssPosition = this.helper.css( "position" );
15723		this.scrollParent = this.helper.scrollParent();
15724		this.offsetParent = this.helper.offsetParent();
15725		this.offsetParentCssPosition = this.offsetParent.css( "position" );
15726
15727		//The element's absolute position on the page minus margins
15728		this.offset = this.positionAbs = this.element.offset();
15729		this.offset = {
15730			top: this.offset.top - this.margins.top,
15731			left: this.offset.left - this.margins.left
15732		};
15733
15734		//Reset scroll cache
15735		this.offset.scroll = false;
15736
15737		$.extend(this.offset, {
15738			click: { //Where the click happened, relative to the element
15739				left: event.pageX - this.offset.left,
15740				top: event.pageY - this.offset.top
15741			},
15742			parent: this._getParentOffset(),
15743			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
15744		});
15745
15746		//Generate the original position
15747		this.originalPosition = this.position = this._generatePosition(event);
15748		this.originalPageX = event.pageX;
15749		this.originalPageY = event.pageY;
15750
15751		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
15752		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
15753
15754		//Set a containment if given in the options
15755		this._setContainment();
15756
15757		//Trigger event + callbacks
15758		if(this._trigger("start", event) === false) {
15759			this._clear();
15760			return false;
15761		}
15762
15763		//Recache the helper size
15764		this._cacheHelperProportions();
15765
15766		//Prepare the droppable offsets
15767		if ($.ui.ddmanager && !o.dropBehaviour) {
15768			$.ui.ddmanager.prepareOffsets(this, event);
15769		}
15770
15771
15772		this._mouseDrag(event, true); //Execute the drag once - this causes the helper not to be visible before getting its correct position
15773
15774		//If the ddmanager is used for droppables, inform the manager that dragging has started (see #5003)
15775		if ( $.ui.ddmanager ) {
15776			$.ui.ddmanager.dragStart(this, event);
15777		}
15778
15779		return true;
15780	},
15781
15782	_mouseDrag: function(event, noPropagation) {
15783		// reset any necessary cached properties (see #5009)
15784		if ( this.offsetParentCssPosition === "fixed" ) {
15785			this.offset.parent = this._getParentOffset();
15786		}
15787
15788		//Compute the helpers position
15789		this.position = this._generatePosition(event);
15790		this.positionAbs = this._convertPositionTo("absolute");
15791
15792		//Call plugins and callbacks and use the resulting position if something is returned
15793		if (!noPropagation) {
15794			var ui = this._uiHash();
15795			if(this._trigger("drag", event, ui) === false) {
15796				this._mouseUp({});
15797				return false;
15798			}
15799			this.position = ui.position;
15800		}
15801
15802		if(!this.options.axis || this.options.axis !== "y") {
15803			this.helper[0].style.left = this.position.left+"px";
15804		}
15805		if(!this.options.axis || this.options.axis !== "x") {
15806			this.helper[0].style.top = this.position.top+"px";
15807		}
15808		if($.ui.ddmanager) {
15809			$.ui.ddmanager.drag(this, event);
15810		}
15811
15812		return false;
15813	},
15814
15815	_mouseStop: function(event) {
15816
15817		//If we are using droppables, inform the manager about the drop
15818		var that = this,
15819			dropped = false;
15820		if ($.ui.ddmanager && !this.options.dropBehaviour) {
15821			dropped = $.ui.ddmanager.drop(this, event);
15822		}
15823
15824		//if a drop comes from outside (a sortable)
15825		if(this.dropped) {
15826			dropped = this.dropped;
15827			this.dropped = false;
15828		}
15829
15830		//if the original element is no longer in the DOM don't bother to continue (see #8269)
15831		if ( this.options.helper === "original" && !$.contains( this.element[ 0 ].ownerDocument, this.element[ 0 ] ) ) {
15832			return false;
15833		}
15834
15835		if((this.options.revert === "invalid" && !dropped) || (this.options.revert === "valid" && dropped) || this.options.revert === true || ($.isFunction(this.options.revert) && this.options.revert.call(this.element, dropped))) {
15836			$(this.helper).animate(this.originalPosition, parseInt(this.options.revertDuration, 10), function() {
15837				if(that._trigger("stop", event) !== false) {
15838					that._clear();
15839				}
15840			});
15841		} else {
15842			if(this._trigger("stop", event) !== false) {
15843				this._clear();
15844			}
15845		}
15846
15847		return false;
15848	},
15849
15850	_mouseUp: function(event) {
15851		//Remove frame helpers
15852		$("div.ui-draggable-iframeFix").each(function() {
15853			this.parentNode.removeChild(this);
15854		});
15855
15856		//If the ddmanager is used for droppables, inform the manager that dragging has stopped (see #5003)
15857		if( $.ui.ddmanager ) {
15858			$.ui.ddmanager.dragStop(this, event);
15859		}
15860
15861		return $.ui.mouse.prototype._mouseUp.call(this, event);
15862	},
15863
15864	cancel: function() {
15865
15866		if(this.helper.is(".ui-draggable-dragging")) {
15867			this._mouseUp({});
15868		} else {
15869			this._clear();
15870		}
15871
15872		return this;
15873
15874	},
15875
15876	_getHandle: function(event) {
15877		return this.options.handle ?
15878			!!$( event.target ).closest( this.element.find( this.options.handle ) ).length :
15879			true;
15880	},
15881
15882	_createHelper: function(event) {
15883
15884		var o = this.options,
15885			helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event])) : (o.helper === "clone" ? this.element.clone().removeAttr("id") : this.element);
15886
15887		if(!helper.parents("body").length) {
15888			helper.appendTo((o.appendTo === "parent" ? this.element[0].parentNode : o.appendTo));
15889		}
15890
15891		if(helper[0] !== this.element[0] && !(/(fixed|absolute)/).test(helper.css("position"))) {
15892			helper.css("position", "absolute");
15893		}
15894
15895		return helper;
15896
15897	},
15898
15899	_adjustOffsetFromHelper: function(obj) {
15900		if (typeof obj === "string") {
15901			obj = obj.split(" ");
15902		}
15903		if ($.isArray(obj)) {
15904			obj = {left: +obj[0], top: +obj[1] || 0};
15905		}
15906		if ("left" in obj) {
15907			this.offset.click.left = obj.left + this.margins.left;
15908		}
15909		if ("right" in obj) {
15910			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
15911		}
15912		if ("top" in obj) {
15913			this.offset.click.top = obj.top + this.margins.top;
15914		}
15915		if ("bottom" in obj) {
15916			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
15917		}
15918	},
15919
15920	_getParentOffset: function() {
15921
15922		//Get the offsetParent and cache its position
15923		var po = this.offsetParent.offset();
15924
15925		// This is a special case where we need to modify a offset calculated on start, since the following happened:
15926		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
15927		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
15928		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
15929		if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
15930			po.left += this.scrollParent.scrollLeft();
15931			po.top += this.scrollParent.scrollTop();
15932		}
15933
15934		//This needs to be actually done for all browsers, since pageX/pageY includes this information
15935		//Ugly IE fix
15936		if((this.offsetParent[0] === document.body) ||
15937			(this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
15938			po = { top: 0, left: 0 };
15939		}
15940
15941		return {
15942			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
15943			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
15944		};
15945
15946	},
15947
15948	_getRelativeOffset: function() {
15949
15950		if(this.cssPosition === "relative") {
15951			var p = this.element.position();
15952			return {
15953				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
15954				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
15955			};
15956		} else {
15957			return { top: 0, left: 0 };
15958		}
15959
15960	},
15961
15962	_cacheMargins: function() {
15963		this.margins = {
15964			left: (parseInt(this.element.css("marginLeft"),10) || 0),
15965			top: (parseInt(this.element.css("marginTop"),10) || 0),
15966			right: (parseInt(this.element.css("marginRight"),10) || 0),
15967			bottom: (parseInt(this.element.css("marginBottom"),10) || 0)
15968		};
15969	},
15970
15971	_cacheHelperProportions: function() {
15972		this.helperProportions = {
15973			width: this.helper.outerWidth(),
15974			height: this.helper.outerHeight()
15975		};
15976	},
15977
15978	_setContainment: function() {
15979
15980		var over, c, ce,
15981			o = this.options;
15982
15983		if ( !o.containment ) {
15984			this.containment = null;
15985			return;
15986		}
15987
15988		if ( o.containment === "window" ) {
15989			this.containment = [
15990				$( window ).scrollLeft() - this.offset.relative.left - this.offset.parent.left,
15991				$( window ).scrollTop() - this.offset.relative.top - this.offset.parent.top,
15992				$( window ).scrollLeft() + $( window ).width() - this.helperProportions.width - this.margins.left,
15993				$( window ).scrollTop() + ( $( window ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
15994			];
15995			return;
15996		}
15997
15998		if ( o.containment === "document") {
15999			this.containment = [
16000				0,
16001				0,
16002				$( document ).width() - this.helperProportions.width - this.margins.left,
16003				( $( document ).height() || document.body.parentNode.scrollHeight ) - this.helperProportions.height - this.margins.top
16004			];
16005			return;
16006		}
16007
16008		if ( o.containment.constructor === Array ) {
16009			this.containment = o.containment;
16010			return;
16011		}
16012
16013		if ( o.containment === "parent" ) {
16014			o.containment = this.helper[ 0 ].parentNode;
16015		}
16016
16017		c = $( o.containment );
16018		ce = c[ 0 ];
16019
16020		if( !ce ) {
16021			return;
16022		}
16023
16024		over = c.css( "overflow" ) !== "hidden";
16025
16026		this.containment = [
16027			( parseInt( c.css( "borderLeftWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingLeft" ), 10 ) || 0 ),
16028			( parseInt( c.css( "borderTopWidth" ), 10 ) || 0 ) + ( parseInt( c.css( "paddingTop" ), 10 ) || 0 ) ,
16029			( over ? Math.max( ce.scrollWidth, ce.offsetWidth ) : ce.offsetWidth ) - ( parseInt( c.css( "borderRightWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingRight" ), 10 ) || 0 ) - this.helperProportions.width - this.margins.left - this.margins.right,
16030			( over ? Math.max( ce.scrollHeight, ce.offsetHeight ) : ce.offsetHeight ) - ( parseInt( c.css( "borderBottomWidth" ), 10 ) || 0 ) - ( parseInt( c.css( "paddingBottom" ), 10 ) || 0 ) - this.helperProportions.height - this.margins.top  - this.margins.bottom
16031		];
16032		this.relative_container = c;
16033	},
16034
16035	_convertPositionTo: function(d, pos) {
16036
16037		if(!pos) {
16038			pos = this.position;
16039		}
16040
16041		var mod = d === "absolute" ? 1 : -1,
16042			scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent;
16043
16044		//Cache the scroll
16045		if (!this.offset.scroll) {
16046			this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()};
16047		}
16048
16049		return {
16050			top: (
16051				pos.top	+																// The absolute mouse position
16052				this.offset.relative.top * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
16053				this.offset.parent.top * mod -										// The offsetParent's offset without borders (offset + border)
16054				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top ) * mod )
16055			),
16056			left: (
16057				pos.left +																// The absolute mouse position
16058				this.offset.relative.left * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
16059				this.offset.parent.left * mod	-										// The offsetParent's offset without borders (offset + border)
16060				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left ) * mod )
16061			)
16062		};
16063
16064	},
16065
16066	_generatePosition: function(event) {
16067
16068		var containment, co, top, left,
16069			o = this.options,
16070			scroll = this.cssPosition === "absolute" && !( this.scrollParent[ 0 ] !== document && $.contains( this.scrollParent[ 0 ], this.offsetParent[ 0 ] ) ) ? this.offsetParent : this.scrollParent,
16071			pageX = event.pageX,
16072			pageY = event.pageY;
16073
16074		//Cache the scroll
16075		if (!this.offset.scroll) {
16076			this.offset.scroll = {top : scroll.scrollTop(), left : scroll.scrollLeft()};
16077		}
16078
16079		/*
16080		 * - Position constraining -
16081		 * Constrain the position to a mix of grid, containment.
16082		 */
16083
16084		// If we are not dragging yet, we won't check for options
16085		if ( this.originalPosition ) {
16086			if ( this.containment ) {
16087				if ( this.relative_container ){
16088					co = this.relative_container.offset();
16089					containment = [
16090						this.containment[ 0 ] + co.left,
16091						this.containment[ 1 ] + co.top,
16092						this.containment[ 2 ] + co.left,
16093						this.containment[ 3 ] + co.top
16094					];
16095				}
16096				else {
16097					containment = this.containment;
16098				}
16099
16100				if(event.pageX - this.offset.click.left < containment[0]) {
16101					pageX = containment[0] + this.offset.click.left;
16102				}
16103				if(event.pageY - this.offset.click.top < containment[1]) {
16104					pageY = containment[1] + this.offset.click.top;
16105				}
16106				if(event.pageX - this.offset.click.left > containment[2]) {
16107					pageX = containment[2] + this.offset.click.left;
16108				}
16109				if(event.pageY - this.offset.click.top > containment[3]) {
16110					pageY = containment[3] + this.offset.click.top;
16111				}
16112			}
16113
16114			if(o.grid) {
16115				//Check for grid elements set to 0 to prevent divide by 0 error causing invalid argument errors in IE (see ticket #6950)
16116				top = o.grid[1] ? this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1] : this.originalPageY;
16117				pageY = containment ? ((top - this.offset.click.top >= containment[1] || top - this.offset.click.top > containment[3]) ? top : ((top - this.offset.click.top >= containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
16118
16119				left = o.grid[0] ? this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0] : this.originalPageX;
16120				pageX = containment ? ((left - this.offset.click.left >= containment[0] || left - this.offset.click.left > containment[2]) ? left : ((left - this.offset.click.left >= containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
16121			}
16122
16123		}
16124
16125		return {
16126			top: (
16127				pageY -																	// The absolute mouse position
16128				this.offset.click.top	-												// Click offset (relative to the element)
16129				this.offset.relative.top -												// Only for relative positioned nodes: Relative offset from element to offset parent
16130				this.offset.parent.top +												// The offsetParent's offset without borders (offset + border)
16131				( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : this.offset.scroll.top )
16132			),
16133			left: (
16134				pageX -																	// The absolute mouse position
16135				this.offset.click.left -												// Click offset (relative to the element)
16136				this.offset.relative.left -												// Only for relative positioned nodes: Relative offset from element to offset parent
16137				this.offset.parent.left +												// The offsetParent's offset without borders (offset + border)
16138				( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : this.offset.scroll.left )
16139			)
16140		};
16141
16142	},
16143
16144	_clear: function() {
16145		this.helper.removeClass("ui-draggable-dragging");
16146		if(this.helper[0] !== this.element[0] && !this.cancelHelperRemoval) {
16147			this.helper.remove();
16148		}
16149		this.helper = null;
16150		this.cancelHelperRemoval = false;
16151	},
16152
16153	// From now on bulk stuff - mainly helpers
16154
16155	_trigger: function(type, event, ui) {
16156		ui = ui || this._uiHash();
16157		$.ui.plugin.call(this, type, [event, ui]);
16158		//The absolute position has to be recalculated after plugins
16159		if(type === "drag") {
16160			this.positionAbs = this._convertPositionTo("absolute");
16161		}
16162		return $.Widget.prototype._trigger.call(this, type, event, ui);
16163	},
16164
16165	plugins: {},
16166
16167	_uiHash: function() {
16168		return {
16169			helper: this.helper,
16170			position: this.position,
16171			originalPosition: this.originalPosition,
16172			offset: this.positionAbs
16173		};
16174	}
16175
16176});
16177
16178$.ui.plugin.add("draggable", "connectToSortable", {
16179	start: function(event, ui) {
16180
16181		var inst = $(this).data("ui-draggable"), o = inst.options,
16182			uiSortable = $.extend({}, ui, { item: inst.element });
16183		inst.sortables = [];
16184		$(o.connectToSortable).each(function() {
16185			var sortable = $.data(this, "ui-sortable");
16186			if (sortable && !sortable.options.disabled) {
16187				inst.sortables.push({
16188					instance: sortable,
16189					shouldRevert: sortable.options.revert
16190				});
16191				sortable.refreshPositions();	// Call the sortable's refreshPositions at drag start to refresh the containerCache since the sortable container cache is used in drag and needs to be up to date (this will ensure it's initialised as well as being kept in step with any changes that might have happened on the page).
16192				sortable._trigger("activate", event, uiSortable);
16193			}
16194		});
16195
16196	},
16197	stop: function(event, ui) {
16198
16199		//If we are still over the sortable, we fake the stop event of the sortable, but also remove helper
16200		var inst = $(this).data("ui-draggable"),
16201			uiSortable = $.extend({}, ui, { item: inst.element });
16202
16203		$.each(inst.sortables, function() {
16204			if(this.instance.isOver) {
16205
16206				this.instance.isOver = 0;
16207
16208				inst.cancelHelperRemoval = true; //Don't remove the helper in the draggable instance
16209				this.instance.cancelHelperRemoval = false; //Remove it in the sortable instance (so sortable plugins like revert still work)
16210
16211				//The sortable revert is supported, and we have to set a temporary dropped variable on the draggable to support revert: "valid/invalid"
16212				if(this.shouldRevert) {
16213					this.instance.options.revert = this.shouldRevert;
16214				}
16215
16216				//Trigger the stop of the sortable
16217				this.instance._mouseStop(event);
16218
16219				this.instance.options.helper = this.instance.options._helper;
16220
16221				//If the helper has been the original item, restore properties in the sortable
16222				if(inst.options.helper === "original") {
16223					this.instance.currentItem.css({ top: "auto", left: "auto" });
16224				}
16225
16226			} else {
16227				this.instance.cancelHelperRemoval = false; //Remove the helper in the sortable instance
16228				this.instance._trigger("deactivate", event, uiSortable);
16229			}
16230
16231		});
16232
16233	},
16234	drag: function(event, ui) {
16235
16236		var inst = $(this).data("ui-draggable"), that = this;
16237
16238		$.each(inst.sortables, function() {
16239
16240			var innermostIntersecting = false,
16241				thisSortable = this;
16242
16243			//Copy over some variables to allow calling the sortable's native _intersectsWith
16244			this.instance.positionAbs = inst.positionAbs;
16245			this.instance.helperProportions = inst.helperProportions;
16246			this.instance.offset.click = inst.offset.click;
16247
16248			if(this.instance._intersectsWith(this.instance.containerCache)) {
16249				innermostIntersecting = true;
16250				$.each(inst.sortables, function () {
16251					this.instance.positionAbs = inst.positionAbs;
16252					this.instance.helperProportions = inst.helperProportions;
16253					this.instance.offset.click = inst.offset.click;
16254					if (this !== thisSortable &&
16255						this.instance._intersectsWith(this.instance.containerCache) &&
16256						$.contains(thisSortable.instance.element[0], this.instance.element[0])
16257					) {
16258						innermostIntersecting = false;
16259					}
16260					return innermostIntersecting;
16261				});
16262			}
16263
16264
16265			if(innermostIntersecting) {
16266				//If it intersects, we use a little isOver variable and set it once, so our move-in stuff gets fired only once
16267				if(!this.instance.isOver) {
16268
16269					this.instance.isOver = 1;
16270					//Now we fake the start of dragging for the sortable instance,
16271					//by cloning the list group item, appending it to the sortable and using it as inst.currentItem
16272					//We can then fire the start event of the sortable with our passed browser event, and our own helper (so it doesn't create a new one)
16273					this.instance.currentItem = $(that).clone().removeAttr("id").appendTo(this.instance.element).data("ui-sortable-item", true);
16274					this.instance.options._helper = this.instance.options.helper; //Store helper option to later restore it
16275					this.instance.options.helper = function() { return ui.helper[0]; };
16276
16277					event.target = this.instance.currentItem[0];
16278					this.instance._mouseCapture(event, true);
16279					this.instance._mouseStart(event, true, true);
16280
16281					//Because the browser event is way off the new appended portlet, we modify a couple of variables to reflect the changes
16282					this.instance.offset.click.top = inst.offset.click.top;
16283					this.instance.offset.click.left = inst.offset.click.left;
16284					this.instance.offset.parent.left -= inst.offset.parent.left - this.instance.offset.parent.left;
16285					this.instance.offset.parent.top -= inst.offset.parent.top - this.instance.offset.parent.top;
16286
16287					inst._trigger("toSortable", event);
16288					inst.dropped = this.instance.element; //draggable revert needs that
16289					//hack so receive/update callbacks work (mostly)
16290					inst.currentItem = inst.element;
16291					this.instance.fromOutside = inst;
16292
16293				}
16294
16295				//Provided we did all the previous steps, we can fire the drag event of the sortable on every draggable drag, when it intersects with the sortable
16296				if(this.instance.currentItem) {
16297					this.instance._mouseDrag(event);
16298				}
16299
16300			} else {
16301
16302				//If it doesn't intersect with the sortable, and it intersected before,
16303				//we fake the drag stop of the sortable, but make sure it doesn't remove the helper by using cancelHelperRemoval
16304				if(this.instance.isOver) {
16305
16306					this.instance.isOver = 0;
16307					this.instance.cancelHelperRemoval = true;
16308
16309					//Prevent reverting on this forced stop
16310					this.instance.options.revert = false;
16311
16312					// The out event needs to be triggered independently
16313					this.instance._trigger("out", event, this.instance._uiHash(this.instance));
16314
16315					this.instance._mouseStop(event, true);
16316					this.instance.options.helper = this.instance.options._helper;
16317
16318					//Now we remove our currentItem, the list group clone again, and the placeholder, and animate the helper back to it's original size
16319					this.instance.currentItem.remove();
16320					if(this.instance.placeholder) {
16321						this.instance.placeholder.remove();
16322					}
16323
16324					inst._trigger("fromSortable", event);
16325					inst.dropped = false; //draggable revert needs that
16326				}
16327
16328			}
16329
16330		});
16331
16332	}
16333});
16334
16335$.ui.plugin.add("draggable", "cursor", {
16336	start: function() {
16337		var t = $("body"), o = $(this).data("ui-draggable").options;
16338		if (t.css("cursor")) {
16339			o._cursor = t.css("cursor");
16340		}
16341		t.css("cursor", o.cursor);
16342	},
16343	stop: function() {
16344		var o = $(this).data("ui-draggable").options;
16345		if (o._cursor) {
16346			$("body").css("cursor", o._cursor);
16347		}
16348	}
16349});
16350
16351$.ui.plugin.add("draggable", "opacity", {
16352	start: function(event, ui) {
16353		var t = $(ui.helper), o = $(this).data("ui-draggable").options;
16354		if(t.css("opacity")) {
16355			o._opacity = t.css("opacity");
16356		}
16357		t.css("opacity", o.opacity);
16358	},
16359	stop: function(event, ui) {
16360		var o = $(this).data("ui-draggable").options;
16361		if(o._opacity) {
16362			$(ui.helper).css("opacity", o._opacity);
16363		}
16364	}
16365});
16366
16367$.ui.plugin.add("draggable", "scroll", {
16368	start: function() {
16369		var i = $(this).data("ui-draggable");
16370		if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {
16371			i.overflowOffset = i.scrollParent.offset();
16372		}
16373	},
16374	drag: function( event ) {
16375
16376		var i = $(this).data("ui-draggable"), o = i.options, scrolled = false;
16377
16378		if(i.scrollParent[0] !== document && i.scrollParent[0].tagName !== "HTML") {
16379
16380			if(!o.axis || o.axis !== "x") {
16381				if((i.overflowOffset.top + i.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
16382					i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop + o.scrollSpeed;
16383				} else if(event.pageY - i.overflowOffset.top < o.scrollSensitivity) {
16384					i.scrollParent[0].scrollTop = scrolled = i.scrollParent[0].scrollTop - o.scrollSpeed;
16385				}
16386			}
16387
16388			if(!o.axis || o.axis !== "y") {
16389				if((i.overflowOffset.left + i.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
16390					i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft + o.scrollSpeed;
16391				} else if(event.pageX - i.overflowOffset.left < o.scrollSensitivity) {
16392					i.scrollParent[0].scrollLeft = scrolled = i.scrollParent[0].scrollLeft - o.scrollSpeed;
16393				}
16394			}
16395
16396		} else {
16397
16398			if(!o.axis || o.axis !== "x") {
16399				if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
16400					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
16401				} else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
16402					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
16403				}
16404			}
16405
16406			if(!o.axis || o.axis !== "y") {
16407				if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
16408					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
16409				} else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
16410					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
16411				}
16412			}
16413
16414		}
16415
16416		if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
16417			$.ui.ddmanager.prepareOffsets(i, event);
16418		}
16419
16420	}
16421});
16422
16423$.ui.plugin.add("draggable", "snap", {
16424	start: function() {
16425
16426		var i = $(this).data("ui-draggable"),
16427			o = i.options;
16428
16429		i.snapElements = [];
16430
16431		$(o.snap.constructor !== String ? ( o.snap.items || ":data(ui-draggable)" ) : o.snap).each(function() {
16432			var $t = $(this),
16433				$o = $t.offset();
16434			if(this !== i.element[0]) {
16435				i.snapElements.push({
16436					item: this,
16437					width: $t.outerWidth(), height: $t.outerHeight(),
16438					top: $o.top, left: $o.left
16439				});
16440			}
16441		});
16442
16443	},
16444	drag: function(event, ui) {
16445
16446		var ts, bs, ls, rs, l, r, t, b, i, first,
16447			inst = $(this).data("ui-draggable"),
16448			o = inst.options,
16449			d = o.snapTolerance,
16450			x1 = ui.offset.left, x2 = x1 + inst.helperProportions.width,
16451			y1 = ui.offset.top, y2 = y1 + inst.helperProportions.height;
16452
16453		for (i = inst.snapElements.length - 1; i >= 0; i--){
16454
16455			l = inst.snapElements[i].left;
16456			r = l + inst.snapElements[i].width;
16457			t = inst.snapElements[i].top;
16458			b = t + inst.snapElements[i].height;
16459
16460			if ( x2 < l - d || x1 > r + d || y2 < t - d || y1 > b + d || !$.contains( inst.snapElements[ i ].item.ownerDocument, inst.snapElements[ i ].item ) ) {
16461				if(inst.snapElements[i].snapping) {
16462					(inst.options.snap.release && inst.options.snap.release.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
16463				}
16464				inst.snapElements[i].snapping = false;
16465				continue;
16466			}
16467
16468			if(o.snapMode !== "inner") {
16469				ts = Math.abs(t - y2) <= d;
16470				bs = Math.abs(b - y1) <= d;
16471				ls = Math.abs(l - x2) <= d;
16472				rs = Math.abs(r - x1) <= d;
16473				if(ts) {
16474					ui.position.top = inst._convertPositionTo("relative", { top: t - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
16475				}
16476				if(bs) {
16477					ui.position.top = inst._convertPositionTo("relative", { top: b, left: 0 }).top - inst.margins.top;
16478				}
16479				if(ls) {
16480					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l - inst.helperProportions.width }).left - inst.margins.left;
16481				}
16482				if(rs) {
16483					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r }).left - inst.margins.left;
16484				}
16485			}
16486
16487			first = (ts || bs || ls || rs);
16488
16489			if(o.snapMode !== "outer") {
16490				ts = Math.abs(t - y1) <= d;
16491				bs = Math.abs(b - y2) <= d;
16492				ls = Math.abs(l - x1) <= d;
16493				rs = Math.abs(r - x2) <= d;
16494				if(ts) {
16495					ui.position.top = inst._convertPositionTo("relative", { top: t, left: 0 }).top - inst.margins.top;
16496				}
16497				if(bs) {
16498					ui.position.top = inst._convertPositionTo("relative", { top: b - inst.helperProportions.height, left: 0 }).top - inst.margins.top;
16499				}
16500				if(ls) {
16501					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: l }).left - inst.margins.left;
16502				}
16503				if(rs) {
16504					ui.position.left = inst._convertPositionTo("relative", { top: 0, left: r - inst.helperProportions.width }).left - inst.margins.left;
16505				}
16506			}
16507
16508			if(!inst.snapElements[i].snapping && (ts || bs || ls || rs || first)) {
16509				(inst.options.snap.snap && inst.options.snap.snap.call(inst.element, event, $.extend(inst._uiHash(), { snapItem: inst.snapElements[i].item })));
16510			}
16511			inst.snapElements[i].snapping = (ts || bs || ls || rs || first);
16512
16513		}
16514
16515	}
16516});
16517
16518$.ui.plugin.add("draggable", "stack", {
16519	start: function() {
16520		var min,
16521			o = this.data("ui-draggable").options,
16522			group = $.makeArray($(o.stack)).sort(function(a,b) {
16523				return (parseInt($(a).css("zIndex"),10) || 0) - (parseInt($(b).css("zIndex"),10) || 0);
16524			});
16525
16526		if (!group.length) { return; }
16527
16528		min = parseInt($(group[0]).css("zIndex"), 10) || 0;
16529		$(group).each(function(i) {
16530			$(this).css("zIndex", min + i);
16531		});
16532		this.css("zIndex", (min + group.length));
16533	}
16534});
16535
16536$.ui.plugin.add("draggable", "zIndex", {
16537	start: function(event, ui) {
16538		var t = $(ui.helper), o = $(this).data("ui-draggable").options;
16539		if(t.css("zIndex")) {
16540			o._zIndex = t.css("zIndex");
16541		}
16542		t.css("zIndex", o.zIndex);
16543	},
16544	stop: function(event, ui) {
16545		var o = $(this).data("ui-draggable").options;
16546		if(o._zIndex) {
16547			$(ui.helper).css("zIndex", o._zIndex);
16548		}
16549	}
16550});
16551
16552})(jQuery);
16553
16554
16555
16556
16557/*!
16558 * jQuery UI Resizable 1.10.3
16559 * http://jqueryui.com
16560 *
16561 * Copyright 2013 jQuery Foundation and other contributors
16562 * Released under the MIT license.
16563 * http://jquery.org/license
16564 *
16565 * http://api.jqueryui.com/resizable/
16566 *
16567 * Depends:
16568 *	jquery.ui.core.js
16569 *	jquery.ui.mouse.js
16570 *	jquery.ui.widget.js
16571 */
16572
16573(function( $, undefined ) {
16574
16575function num(v) {
16576	return parseInt(v, 10) || 0;
16577}
16578
16579function isNumber(value) {
16580	return !isNaN(parseInt(value, 10));
16581}
16582
16583$.widget("ui.resizable", $.ui.mouse, {
16584	version: "1.10.3",
16585	widgetEventPrefix: "resize",
16586	options: {
16587		alsoResize: false,
16588		animate: false,
16589		animateDuration: "slow",
16590		animateEasing: "swing",
16591		aspectRatio: false,
16592		autoHide: false,
16593		containment: false,
16594		ghost: false,
16595		grid: false,
16596		handles: "e,s,se",
16597		helper: false,
16598		maxHeight: null,
16599		maxWidth: null,
16600		minHeight: 10,
16601		minWidth: 10,
16602		// See #7960
16603		zIndex: 90,
16604
16605		// callbacks
16606		resize: null,
16607		start: null,
16608		stop: null
16609	},
16610	_create: function() {
16611
16612		var n, i, handle, axis, hname,
16613			that = this,
16614			o = this.options;
16615		this.element.addClass("ui-resizable");
16616
16617		$.extend(this, {
16618			_aspectRatio: !!(o.aspectRatio),
16619			aspectRatio: o.aspectRatio,
16620			originalElement: this.element,
16621			_proportionallyResizeElements: [],
16622			_helper: o.helper || o.ghost || o.animate ? o.helper || "ui-resizable-helper" : null
16623		});
16624
16625		//Wrap the element if it cannot hold child nodes
16626		if(this.element[0].nodeName.match(/canvas|textarea|input|select|button|img/i)) {
16627
16628			//Create a wrapper element and set the wrapper to the new current internal element
16629			this.element.wrap(
16630				$("<div class='ui-wrapper' style='overflow: hidden;'></div>").css({
16631					position: this.element.css("position"),
16632					width: this.element.outerWidth(),
16633					height: this.element.outerHeight(),
16634					top: this.element.css("top"),
16635					left: this.element.css("left")
16636				})
16637			);
16638
16639			//Overwrite the original this.element
16640			this.element = this.element.parent().data(
16641				"ui-resizable", this.element.data("ui-resizable")
16642			);
16643
16644			this.elementIsWrapper = true;
16645
16646			//Move margins to the wrapper
16647			this.element.css({ marginLeft: this.originalElement.css("marginLeft"), marginTop: this.originalElement.css("marginTop"), marginRight: this.originalElement.css("marginRight"), marginBottom: this.originalElement.css("marginBottom") });
16648			this.originalElement.css({ marginLeft: 0, marginTop: 0, marginRight: 0, marginBottom: 0});
16649
16650			//Prevent Safari textarea resize
16651			this.originalResizeStyle = this.originalElement.css("resize");
16652			this.originalElement.css("resize", "none");
16653
16654			//Push the actual element to our proportionallyResize internal array
16655			this._proportionallyResizeElements.push(this.originalElement.css({ position: "static", zoom: 1, display: "block" }));
16656
16657			// avoid IE jump (hard set the margin)
16658			this.originalElement.css({ margin: this.originalElement.css("margin") });
16659
16660			// fix handlers offset
16661			this._proportionallyResize();
16662
16663		}
16664
16665		this.handles = o.handles || (!$(".ui-resizable-handle", this.element).length ? "e,s,se" : { n: ".ui-resizable-n", e: ".ui-resizable-e", s: ".ui-resizable-s", w: ".ui-resizable-w", se: ".ui-resizable-se", sw: ".ui-resizable-sw", ne: ".ui-resizable-ne", nw: ".ui-resizable-nw" });
16666		if(this.handles.constructor === String) {
16667
16668			if ( this.handles === "all") {
16669				this.handles = "n,e,s,w,se,sw,ne,nw";
16670			}
16671
16672			n = this.handles.split(",");
16673			this.handles = {};
16674
16675			for(i = 0; i < n.length; i++) {
16676
16677				handle = $.trim(n[i]);
16678				hname = "ui-resizable-"+handle;
16679				axis = $("<div class='ui-resizable-handle " + hname + "'></div>");
16680
16681				// Apply zIndex to all handles - see #7960
16682				axis.css({ zIndex: o.zIndex });
16683
16684				//TODO : What's going on here?
16685				if ("se" === handle) {
16686					axis.addClass("ui-icon ui-icon-gripsmall-diagonal-se");
16687				}
16688
16689				//Insert into internal handles object and append to element
16690				this.handles[handle] = ".ui-resizable-"+handle;
16691				this.element.append(axis);
16692			}
16693
16694		}
16695
16696		this._renderAxis = function(target) {
16697
16698			var i, axis, padPos, padWrapper;
16699
16700			target = target || this.element;
16701
16702			for(i in this.handles) {
16703
16704				if(this.handles[i].constructor === String) {
16705					this.handles[i] = $(this.handles[i], this.element).show();
16706				}
16707
16708				//Apply pad to wrapper element, needed to fix axis position (textarea, inputs, scrolls)
16709				if (this.elementIsWrapper && this.originalElement[0].nodeName.match(/textarea|input|select|button/i)) {
16710
16711					axis = $(this.handles[i], this.element);
16712
16713					//Checking the correct pad and border
16714					padWrapper = /sw|ne|nw|se|n|s/.test(i) ? axis.outerHeight() : axis.outerWidth();
16715
16716					//The padding type i have to apply...
16717					padPos = [ "padding",
16718						/ne|nw|n/.test(i) ? "Top" :
16719						/se|sw|s/.test(i) ? "Bottom" :
16720						/^e$/.test(i) ? "Right" : "Left" ].join("");
16721
16722					target.css(padPos, padWrapper);
16723
16724					this._proportionallyResize();
16725
16726				}
16727
16728				//TODO: What's that good for? There's not anything to be executed left
16729				if(!$(this.handles[i]).length) {
16730					continue;
16731				}
16732			}
16733		};
16734
16735		//TODO: make renderAxis a prototype function
16736		this._renderAxis(this.element);
16737
16738		this._handles = $(".ui-resizable-handle", this.element)
16739			.disableSelection();
16740
16741		//Matching axis name
16742		this._handles.mouseover(function() {
16743			if (!that.resizing) {
16744				if (this.className) {
16745					axis = this.className.match(/ui-resizable-(se|sw|ne|nw|n|e|s|w)/i);
16746				}
16747				//Axis, default = se
16748				that.axis = axis && axis[1] ? axis[1] : "se";
16749			}
16750		});
16751
16752		//If we want to auto hide the elements
16753		if (o.autoHide) {
16754			this._handles.hide();
16755			$(this.element)
16756				.addClass("ui-resizable-autohide")
16757				.mouseenter(function() {
16758					if (o.disabled) {
16759						return;
16760					}
16761					$(this).removeClass("ui-resizable-autohide");
16762					that._handles.show();
16763				})
16764				.mouseleave(function(){
16765					if (o.disabled) {
16766						return;
16767					}
16768					if (!that.resizing) {
16769						$(this).addClass("ui-resizable-autohide");
16770						that._handles.hide();
16771					}
16772				});
16773		}
16774
16775		//Initialize the mouse interaction
16776		this._mouseInit();
16777
16778	},
16779
16780	_destroy: function() {
16781
16782		this._mouseDestroy();
16783
16784		var wrapper,
16785			_destroy = function(exp) {
16786				$(exp).removeClass("ui-resizable ui-resizable-disabled ui-resizable-resizing")
16787					.removeData("resizable").removeData("ui-resizable").unbind(".resizable").find(".ui-resizable-handle").remove();
16788			};
16789
16790		//TODO: Unwrap at same DOM position
16791		if (this.elementIsWrapper) {
16792			_destroy(this.element);
16793			wrapper = this.element;
16794			this.originalElement.css({
16795				position: wrapper.css("position"),
16796				width: wrapper.outerWidth(),
16797				height: wrapper.outerHeight(),
16798				top: wrapper.css("top"),
16799				left: wrapper.css("left")
16800			}).insertAfter( wrapper );
16801			wrapper.remove();
16802		}
16803
16804		this.originalElement.css("resize", this.originalResizeStyle);
16805		_destroy(this.originalElement);
16806
16807		return this;
16808	},
16809
16810	_mouseCapture: function(event) {
16811		var i, handle,
16812			capture = false;
16813
16814		for (i in this.handles) {
16815			handle = $(this.handles[i])[0];
16816			if (handle === event.target || $.contains(handle, event.target)) {
16817				capture = true;
16818			}
16819		}
16820
16821		return !this.options.disabled && capture;
16822	},
16823
16824	_mouseStart: function(event) {
16825
16826		var curleft, curtop, cursor,
16827			o = this.options,
16828			iniPos = this.element.position(),
16829			el = this.element;
16830
16831		this.resizing = true;
16832
16833		// bugfix for http://dev.jquery.com/ticket/1749
16834		if ( (/absolute/).test( el.css("position") ) ) {
16835			el.css({ position: "absolute", top: el.css("top"), left: el.css("left") });
16836		} else if (el.is(".ui-draggable")) {
16837			el.css({ position: "absolute", top: iniPos.top, left: iniPos.left });
16838		}
16839
16840		this._renderProxy();
16841
16842		curleft = num(this.helper.css("left"));
16843		curtop = num(this.helper.css("top"));
16844
16845		if (o.containment) {
16846			curleft += $(o.containment).scrollLeft() || 0;
16847			curtop += $(o.containment).scrollTop() || 0;
16848		}
16849
16850		//Store needed variables
16851		this.offset = this.helper.offset();
16852		this.position = { left: curleft, top: curtop };
16853		this.size = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
16854		this.originalSize = this._helper ? { width: el.outerWidth(), height: el.outerHeight() } : { width: el.width(), height: el.height() };
16855		this.originalPosition = { left: curleft, top: curtop };
16856		this.sizeDiff = { width: el.outerWidth() - el.width(), height: el.outerHeight() - el.height() };
16857		this.originalMousePosition = { left: event.pageX, top: event.pageY };
16858
16859		//Aspect Ratio
16860		this.aspectRatio = (typeof o.aspectRatio === "number") ? o.aspectRatio : ((this.originalSize.width / this.originalSize.height) || 1);
16861
16862		cursor = $(".ui-resizable-" + this.axis).css("cursor");
16863		$("body").css("cursor", cursor === "auto" ? this.axis + "-resize" : cursor);
16864
16865		el.addClass("ui-resizable-resizing");
16866		this._propagate("start", event);
16867		return true;
16868	},
16869
16870	_mouseDrag: function(event) {
16871
16872		//Increase performance, avoid regex
16873		var data,
16874			el = this.helper, props = {},
16875			smp = this.originalMousePosition,
16876			a = this.axis,
16877			prevTop = this.position.top,
16878			prevLeft = this.position.left,
16879			prevWidth = this.size.width,
16880			prevHeight = this.size.height,
16881			dx = (event.pageX-smp.left)||0,
16882			dy = (event.pageY-smp.top)||0,
16883			trigger = this._change[a];
16884
16885		if (!trigger) {
16886			return false;
16887		}
16888
16889		// Calculate the attrs that will be change
16890		data = trigger.apply(this, [event, dx, dy]);
16891
16892		// Put this in the mouseDrag handler since the user can start pressing shift while resizing
16893		this._updateVirtualBoundaries(event.shiftKey);
16894		if (this._aspectRatio || event.shiftKey) {
16895			data = this._updateRatio(data, event);
16896		}
16897
16898		data = this._respectSize(data, event);
16899
16900		this._updateCache(data);
16901
16902		// plugins callbacks need to be called first
16903		this._propagate("resize", event);
16904
16905		if (this.position.top !== prevTop) {
16906			props.top = this.position.top + "px";
16907		}
16908		if (this.position.left !== prevLeft) {
16909			props.left = this.position.left + "px";
16910		}
16911		if (this.size.width !== prevWidth) {
16912			props.width = this.size.width + "px";
16913		}
16914		if (this.size.height !== prevHeight) {
16915			props.height = this.size.height + "px";
16916		}
16917		el.css(props);
16918
16919		if (!this._helper && this._proportionallyResizeElements.length) {
16920			this._proportionallyResize();
16921		}
16922
16923		// Call the user callback if the element was resized
16924		if ( ! $.isEmptyObject(props) ) {
16925			this._trigger("resize", event, this.ui());
16926		}
16927
16928		return false;
16929	},
16930
16931	_mouseStop: function(event) {
16932
16933		this.resizing = false;
16934		var pr, ista, soffseth, soffsetw, s, left, top,
16935			o = this.options, that = this;
16936
16937		if(this._helper) {
16938
16939			pr = this._proportionallyResizeElements;
16940			ista = pr.length && (/textarea/i).test(pr[0].nodeName);
16941			soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height;
16942			soffsetw = ista ? 0 : that.sizeDiff.width;
16943
16944			s = { width: (that.helper.width()  - soffsetw), height: (that.helper.height() - soffseth) };
16945			left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null;
16946			top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null;
16947
16948			if (!o.animate) {
16949				this.element.css($.extend(s, { top: top, left: left }));
16950			}
16951
16952			that.helper.height(that.size.height);
16953			that.helper.width(that.size.width);
16954
16955			if (this._helper && !o.animate) {
16956				this._proportionallyResize();
16957			}
16958		}
16959
16960		$("body").css("cursor", "auto");
16961
16962		this.element.removeClass("ui-resizable-resizing");
16963
16964		this._propagate("stop", event);
16965
16966		if (this._helper) {
16967			this.helper.remove();
16968		}
16969
16970		return false;
16971
16972	},
16973
16974	_updateVirtualBoundaries: function(forceAspectRatio) {
16975		var pMinWidth, pMaxWidth, pMinHeight, pMaxHeight, b,
16976			o = this.options;
16977
16978		b = {
16979			minWidth: isNumber(o.minWidth) ? o.minWidth : 0,
16980			maxWidth: isNumber(o.maxWidth) ? o.maxWidth : Infinity,
16981			minHeight: isNumber(o.minHeight) ? o.minHeight : 0,
16982			maxHeight: isNumber(o.maxHeight) ? o.maxHeight : Infinity
16983		};
16984
16985		if(this._aspectRatio || forceAspectRatio) {
16986			// We want to create an enclosing box whose aspect ration is the requested one
16987			// First, compute the "projected" size for each dimension based on the aspect ratio and other dimension
16988			pMinWidth = b.minHeight * this.aspectRatio;
16989			pMinHeight = b.minWidth / this.aspectRatio;
16990			pMaxWidth = b.maxHeight * this.aspectRatio;
16991			pMaxHeight = b.maxWidth / this.aspectRatio;
16992
16993			if(pMinWidth > b.minWidth) {
16994				b.minWidth = pMinWidth;
16995			}
16996			if(pMinHeight > b.minHeight) {
16997				b.minHeight = pMinHeight;
16998			}
16999			if(pMaxWidth < b.maxWidth) {
17000				b.maxWidth = pMaxWidth;
17001			}
17002			if(pMaxHeight < b.maxHeight) {
17003				b.maxHeight = pMaxHeight;
17004			}
17005		}
17006		this._vBoundaries = b;
17007	},
17008
17009	_updateCache: function(data) {
17010		this.offset = this.helper.offset();
17011		if (isNumber(data.left)) {
17012			this.position.left = data.left;
17013		}
17014		if (isNumber(data.top)) {
17015			this.position.top = data.top;
17016		}
17017		if (isNumber(data.height)) {
17018			this.size.height = data.height;
17019		}
17020		if (isNumber(data.width)) {
17021			this.size.width = data.width;
17022		}
17023	},
17024
17025	_updateRatio: function( data ) {
17026
17027		var cpos = this.position,
17028			csize = this.size,
17029			a = this.axis;
17030
17031		if (isNumber(data.height)) {
17032			data.width = (data.height * this.aspectRatio);
17033		} else if (isNumber(data.width)) {
17034			data.height = (data.width / this.aspectRatio);
17035		}
17036
17037		if (a === "sw") {
17038			data.left = cpos.left + (csize.width - data.width);
17039			data.top = null;
17040		}
17041		if (a === "nw") {
17042			data.top = cpos.top + (csize.height - data.height);
17043			data.left = cpos.left + (csize.width - data.width);
17044		}
17045
17046		return data;
17047	},
17048
17049	_respectSize: function( data ) {
17050
17051		var o = this._vBoundaries,
17052			a = this.axis,
17053			ismaxw = isNumber(data.width) && o.maxWidth && (o.maxWidth < data.width), ismaxh = isNumber(data.height) && o.maxHeight && (o.maxHeight < data.height),
17054			isminw = isNumber(data.width) && o.minWidth && (o.minWidth > data.width), isminh = isNumber(data.height) && o.minHeight && (o.minHeight > data.height),
17055			dw = this.originalPosition.left + this.originalSize.width,
17056			dh = this.position.top + this.size.height,
17057			cw = /sw|nw|w/.test(a), ch = /nw|ne|n/.test(a);
17058		if (isminw) {
17059			data.width = o.minWidth;
17060		}
17061		if (isminh) {
17062			data.height = o.minHeight;
17063		}
17064		if (ismaxw) {
17065			data.width = o.maxWidth;
17066		}
17067		if (ismaxh) {
17068			data.height = o.maxHeight;
17069		}
17070
17071		if (isminw && cw) {
17072			data.left = dw - o.minWidth;
17073		}
17074		if (ismaxw && cw) {
17075			data.left = dw - o.maxWidth;
17076		}
17077		if (isminh && ch) {
17078			data.top = dh - o.minHeight;
17079		}
17080		if (ismaxh && ch) {
17081			data.top = dh - o.maxHeight;
17082		}
17083
17084		// fixing jump error on top/left - bug #2330
17085		if (!data.width && !data.height && !data.left && data.top) {
17086			data.top = null;
17087		} else if (!data.width && !data.height && !data.top && data.left) {
17088			data.left = null;
17089		}
17090
17091		return data;
17092	},
17093
17094	_proportionallyResize: function() {
17095
17096		if (!this._proportionallyResizeElements.length) {
17097			return;
17098		}
17099
17100		var i, j, borders, paddings, prel,
17101			element = this.helper || this.element;
17102
17103		for ( i=0; i < this._proportionallyResizeElements.length; i++) {
17104
17105			prel = this._proportionallyResizeElements[i];
17106
17107			if (!this.borderDif) {
17108				this.borderDif = [];
17109				borders = [prel.css("borderTopWidth"), prel.css("borderRightWidth"), prel.css("borderBottomWidth"), prel.css("borderLeftWidth")];
17110				paddings = [prel.css("paddingTop"), prel.css("paddingRight"), prel.css("paddingBottom"), prel.css("paddingLeft")];
17111
17112				for ( j = 0; j < borders.length; j++ ) {
17113					this.borderDif[ j ] = ( parseInt( borders[ j ], 10 ) || 0 ) + ( parseInt( paddings[ j ], 10 ) || 0 );
17114				}
17115			}
17116
17117			prel.css({
17118				height: (element.height() - this.borderDif[0] - this.borderDif[2]) || 0,
17119				width: (element.width() - this.borderDif[1] - this.borderDif[3]) || 0
17120			});
17121
17122		}
17123
17124	},
17125
17126	_renderProxy: function() {
17127
17128		var el = this.element, o = this.options;
17129		this.elementOffset = el.offset();
17130
17131		if(this._helper) {
17132
17133			this.helper = this.helper || $("<div style='overflow:hidden;'></div>");
17134
17135			this.helper.addClass(this._helper).css({
17136				width: this.element.outerWidth() - 1,
17137				height: this.element.outerHeight() - 1,
17138				position: "absolute",
17139				left: this.elementOffset.left +"px",
17140				top: this.elementOffset.top +"px",
17141				zIndex: ++o.zIndex //TODO: Don't modify option
17142			});
17143
17144			this.helper
17145				.appendTo("body")
17146				.disableSelection();
17147
17148		} else {
17149			this.helper = this.element;
17150		}
17151
17152	},
17153
17154	_change: {
17155		e: function(event, dx) {
17156			return { width: this.originalSize.width + dx };
17157		},
17158		w: function(event, dx) {
17159			var cs = this.originalSize, sp = this.originalPosition;
17160			return { left: sp.left + dx, width: cs.width - dx };
17161		},
17162		n: function(event, dx, dy) {
17163			var cs = this.originalSize, sp = this.originalPosition;
17164			return { top: sp.top + dy, height: cs.height - dy };
17165		},
17166		s: function(event, dx, dy) {
17167			return { height: this.originalSize.height + dy };
17168		},
17169		se: function(event, dx, dy) {
17170			return $.extend(this._change.s.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
17171		},
17172		sw: function(event, dx, dy) {
17173			return $.extend(this._change.s.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
17174		},
17175		ne: function(event, dx, dy) {
17176			return $.extend(this._change.n.apply(this, arguments), this._change.e.apply(this, [event, dx, dy]));
17177		},
17178		nw: function(event, dx, dy) {
17179			return $.extend(this._change.n.apply(this, arguments), this._change.w.apply(this, [event, dx, dy]));
17180		}
17181	},
17182
17183	_propagate: function(n, event) {
17184		$.ui.plugin.call(this, n, [event, this.ui()]);
17185		(n !== "resize" && this._trigger(n, event, this.ui()));
17186	},
17187
17188	plugins: {},
17189
17190	ui: function() {
17191		return {
17192			originalElement: this.originalElement,
17193			element: this.element,
17194			helper: this.helper,
17195			position: this.position,
17196			size: this.size,
17197			originalSize: this.originalSize,
17198			originalPosition: this.originalPosition
17199		};
17200	}
17201
17202});
17203
17204/*
17205 * Resizable Extensions
17206 */
17207
17208$.ui.plugin.add("resizable", "animate", {
17209
17210	stop: function( event ) {
17211		var that = $(this).data("ui-resizable"),
17212			o = that.options,
17213			pr = that._proportionallyResizeElements,
17214			ista = pr.length && (/textarea/i).test(pr[0].nodeName),
17215			soffseth = ista && $.ui.hasScroll(pr[0], "left") /* TODO - jump height */ ? 0 : that.sizeDiff.height,
17216			soffsetw = ista ? 0 : that.sizeDiff.width,
17217			style = { width: (that.size.width - soffsetw), height: (that.size.height - soffseth) },
17218			left = (parseInt(that.element.css("left"), 10) + (that.position.left - that.originalPosition.left)) || null,
17219			top = (parseInt(that.element.css("top"), 10) + (that.position.top - that.originalPosition.top)) || null;
17220
17221		that.element.animate(
17222			$.extend(style, top && left ? { top: top, left: left } : {}), {
17223				duration: o.animateDuration,
17224				easing: o.animateEasing,
17225				step: function() {
17226
17227					var data = {
17228						width: parseInt(that.element.css("width"), 10),
17229						height: parseInt(that.element.css("height"), 10),
17230						top: parseInt(that.element.css("top"), 10),
17231						left: parseInt(that.element.css("left"), 10)
17232					};
17233
17234					if (pr && pr.length) {
17235						$(pr[0]).css({ width: data.width, height: data.height });
17236					}
17237
17238					// propagating resize, and updating values for each animation step
17239					that._updateCache(data);
17240					that._propagate("resize", event);
17241
17242				}
17243			}
17244		);
17245	}
17246
17247});
17248
17249$.ui.plugin.add("resizable", "containment", {
17250
17251	start: function() {
17252		var element, p, co, ch, cw, width, height,
17253			that = $(this).data("ui-resizable"),
17254			o = that.options,
17255			el = that.element,
17256			oc = o.containment,
17257			ce = (oc instanceof $) ? oc.get(0) : (/parent/.test(oc)) ? el.parent().get(0) : oc;
17258
17259		if (!ce) {
17260			return;
17261		}
17262
17263		that.containerElement = $(ce);
17264
17265		if (/document/.test(oc) || oc === document) {
17266			that.containerOffset = { left: 0, top: 0 };
17267			that.containerPosition = { left: 0, top: 0 };
17268
17269			that.parentData = {
17270				element: $(document), left: 0, top: 0,
17271				width: $(document).width(), height: $(document).height() || document.body.parentNode.scrollHeight
17272			};
17273		}
17274
17275		// i'm a node, so compute top, left, right, bottom
17276		else {
17277			element = $(ce);
17278			p = [];
17279			$([ "Top", "Right", "Left", "Bottom" ]).each(function(i, name) { p[i] = num(element.css("padding" + name)); });
17280
17281			that.containerOffset = element.offset();
17282			that.containerPosition = element.position();
17283			that.containerSize = { height: (element.innerHeight() - p[3]), width: (element.innerWidth() - p[1]) };
17284
17285			co = that.containerOffset;
17286			ch = that.containerSize.height;
17287			cw = that.containerSize.width;
17288			width = ($.ui.hasScroll(ce, "left") ? ce.scrollWidth : cw );
17289			height = ($.ui.hasScroll(ce) ? ce.scrollHeight : ch);
17290
17291			that.parentData = {
17292				element: ce, left: co.left, top: co.top, width: width, height: height
17293			};
17294		}
17295	},
17296
17297	resize: function( event ) {
17298		var woset, hoset, isParent, isOffsetRelative,
17299			that = $(this).data("ui-resizable"),
17300			o = that.options,
17301			co = that.containerOffset, cp = that.position,
17302			pRatio = that._aspectRatio || event.shiftKey,
17303			cop = { top:0, left:0 }, ce = that.containerElement;
17304
17305		if (ce[0] !== document && (/static/).test(ce.css("position"))) {
17306			cop = co;
17307		}
17308
17309		if (cp.left < (that._helper ? co.left : 0)) {
17310			that.size.width = that.size.width + (that._helper ? (that.position.left - co.left) : (that.position.left - cop.left));
17311			if (pRatio) {
17312				that.size.height = that.size.width / that.aspectRatio;
17313			}
17314			that.position.left = o.helper ? co.left : 0;
17315		}
17316
17317		if (cp.top < (that._helper ? co.top : 0)) {
17318			that.size.height = that.size.height + (that._helper ? (that.position.top - co.top) : that.position.top);
17319			if (pRatio) {
17320				that.size.width = that.size.height * that.aspectRatio;
17321			}
17322			that.position.top = that._helper ? co.top : 0;
17323		}
17324
17325		that.offset.left = that.parentData.left+that.position.left;
17326		that.offset.top = that.parentData.top+that.position.top;
17327
17328		woset = Math.abs( (that._helper ? that.offset.left - cop.left : (that.offset.left - cop.left)) + that.sizeDiff.width );
17329		hoset = Math.abs( (that._helper ? that.offset.top - cop.top : (that.offset.top - co.top)) + that.sizeDiff.height );
17330
17331		isParent = that.containerElement.get(0) === that.element.parent().get(0);
17332		isOffsetRelative = /relative|absolute/.test(that.containerElement.css("position"));
17333
17334		if(isParent && isOffsetRelative) {
17335			woset -= that.parentData.left;
17336		}
17337
17338		if (woset + that.size.width >= that.parentData.width) {
17339			that.size.width = that.parentData.width - woset;
17340			if (pRatio) {
17341				that.size.height = that.size.width / that.aspectRatio;
17342			}
17343		}
17344
17345		if (hoset + that.size.height >= that.parentData.height) {
17346			that.size.height = that.parentData.height - hoset;
17347			if (pRatio) {
17348				that.size.width = that.size.height * that.aspectRatio;
17349			}
17350		}
17351	},
17352
17353	stop: function(){
17354		var that = $(this).data("ui-resizable"),
17355			o = that.options,
17356			co = that.containerOffset,
17357			cop = that.containerPosition,
17358			ce = that.containerElement,
17359			helper = $(that.helper),
17360			ho = helper.offset(),
17361			w = helper.outerWidth() - that.sizeDiff.width,
17362			h = helper.outerHeight() - that.sizeDiff.height;
17363
17364		if (that._helper && !o.animate && (/relative/).test(ce.css("position"))) {
17365			$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
17366		}
17367
17368		if (that._helper && !o.animate && (/static/).test(ce.css("position"))) {
17369			$(this).css({ left: ho.left - cop.left - co.left, width: w, height: h });
17370		}
17371
17372	}
17373});
17374
17375$.ui.plugin.add("resizable", "alsoResize", {
17376
17377	start: function () {
17378		var that = $(this).data("ui-resizable"),
17379			o = that.options,
17380			_store = function (exp) {
17381				$(exp).each(function() {
17382					var el = $(this);
17383					el.data("ui-resizable-alsoresize", {
17384						width: parseInt(el.width(), 10), height: parseInt(el.height(), 10),
17385						left: parseInt(el.css("left"), 10), top: parseInt(el.css("top"), 10)
17386					});
17387				});
17388			};
17389
17390		if (typeof(o.alsoResize) === "object" && !o.alsoResize.parentNode) {
17391			if (o.alsoResize.length) { o.alsoResize = o.alsoResize[0]; _store(o.alsoResize); }
17392			else { $.each(o.alsoResize, function (exp) { _store(exp); }); }
17393		}else{
17394			_store(o.alsoResize);
17395		}
17396	},
17397
17398	resize: function (event, ui) {
17399		var that = $(this).data("ui-resizable"),
17400			o = that.options,
17401			os = that.originalSize,
17402			op = that.originalPosition,
17403			delta = {
17404				height: (that.size.height - os.height) || 0, width: (that.size.width - os.width) || 0,
17405				top: (that.position.top - op.top) || 0, left: (that.position.left - op.left) || 0
17406			},
17407
17408			_alsoResize = function (exp, c) {
17409				$(exp).each(function() {
17410					var el = $(this), start = $(this).data("ui-resizable-alsoresize"), style = {},
17411						css = c && c.length ? c : el.parents(ui.originalElement[0]).length ? ["width", "height"] : ["width", "height", "top", "left"];
17412
vendor: 1,574 bytes, lines 17413-17477
17413					$.each(css, function (i, prop) {
17414						var sum = (start[prop]||0) + (delta[prop]||0);
17415						if (sum && sum >= 0) {
17416							style[prop] = sum || null;
17417						}
17418					});
17419
17420					el.css(style);
17421				});
17422			};
17423
17424		if (typeof(o.alsoResize) === "object" && !o.alsoResize.nodeType) {
17425			$.each(o.alsoResize, function (exp, c) { _alsoResize(exp, c); });
17426		}else{
17427			_alsoResize(o.alsoResize);
17428		}
17429	},
17430
17431	stop: function () {
17432		$(this).removeData("resizable-alsoresize");
17433	}
17434});
17435
17436$.ui.plugin.add("resizable", "ghost", {
17437
17438	start: function() {
17439
17440		var that = $(this).data("ui-resizable"), o = that.options, cs = that.size;
17441
17442		that.ghost = that.originalElement.clone();
17443		that.ghost
17444			.css({ opacity: 0.25, display: "block", position: "relative", height: cs.height, width: cs.width, margin: 0, left: 0, top: 0 })
17445			.addClass("ui-resizable-ghost")
17446			.addClass(typeof o.ghost === "string" ? o.ghost : "");
17447
17448		that.ghost.appendTo(that.helper);
17449
17450	},
17451
17452	resize: function(){
17453		var that = $(this).data("ui-resizable");
17454		if (that.ghost) {
17455			that.ghost.css({ position: "relative", height: that.size.height, width: that.size.width });
17456		}
17457	},
17458
17459	stop: function() {
17460		var that = $(this).data("ui-resizable");
17461		if (that.ghost && that.helper) {
17462			that.helper.get(0).removeChild(that.ghost.get(0));
17463		}
17464	}
17465
17466});
17467
17468$.ui.plugin.add("resizable", "grid", {
17469
17470	resize: function() {
17471		var that = $(this).data("ui-resizable"),
17472			o = that.options,
17473			cs = that.size,
17474			os = that.originalSize,
17475			op = that.originalPosition,
17476			a = that.axis,
17477			grid = typeof o.grid === "number" ? [o.grid, o.grid] : o.grid,
17478			gridX = (grid[0]||1),
17479			gridY = (grid[1]||1),
17480			ox = Math.round((cs.width - os.width) / gridX) * gridX,
17481			oy = Math.round((cs.height - os.height) / gridY) * gridY,
17482			newWidth = os.width + ox,
17483			newHeight = os.height + oy,
17484			isMaxWidth = o.maxWidth && (o.maxWidth < newWidth),
17485			isMaxHeight = o.maxHeight && (o.maxHeight < newHeight),
17486			isMinWidth = o.minWidth && (o.minWidth > newWidth),
17487			isMinHeight = o.minHeight && (o.minHeight > newHeight);
17488
17489		o.grid = grid;
17490
17491		if (isMinWidth) {
17492			newWidth = newWidth + gridX;
17493		}
17494		if (isMinHeight) {
17495			newHeight = newHeight + gridY;
17496		}
17497		if (isMaxWidth) {
17498			newWidth = newWidth - gridX;
17499		}
17500		if (isMaxHeight) {
17501			newHeight = newHeight - gridY;
17502		}
17503
17504		if (/^(se|s|e)$/.test(a)) {
17505			that.size.width = newWidth;
17506			that.size.height = newHeight;
17507		} else if (/^(ne)$/.test(a)) {
17508			that.size.width = newWidth;
17509			that.size.height = newHeight;
17510			that.position.top = op.top - oy;
17511		} else if (/^(sw)$/.test(a)) {
17512			that.size.width = newWidth;
17513			that.size.height = newHeight;
17514			that.position.left = op.left - ox;
17515		} else {
17516			that.size.width = newWidth;
17517			that.size.height = newHeight;
17518			that.position.top = op.top - oy;
17519			that.position.left = op.left - ox;
17520		}
17521	}
17522
17523});
17524
17525})(jQuery);
17526
17527
17528
17529
17530
17531
17532
17533/*!
17534 * jQuery UI Dialog 1.10.3
17535 * http://jqueryui.com
17536 *
17537 * Copyright 2013 jQuery Foundation and other contributors
17538 * Released under the MIT license.
17539 * http://jquery.org/license
17540 *
17541 * http://api.jqueryui.com/dialog/
17542 *
17543 * Depends:
17544 *	jquery.ui.core.js
17545 *	jquery.ui.widget.js
17546 *  jquery.ui.button.js
17547 *	jquery.ui.draggable.js
17548 *	jquery.ui.mouse.js
17549 *	jquery.ui.position.js
17550 *	jquery.ui.resizable.js
17551 */
17552
17553(function( $, undefined ) {
17554
17555var sizeRelatedOptions = {
17556		buttons: true,
17557		height: true,
17558		maxHeight: true,
17559		maxWidth: true,
17560		minHeight: true,
17561		minWidth: true,
17562		width: true
17563	},
17564	resizableRelatedOptions = {
17565		maxHeight: true,
17566		maxWidth: true,
17567		minHeight: true,
17568		minWidth: true
17569	};
17570
17571$.widget( "ui.dialog", {
17572	version: "1.10.3",
17573	options: {
17574		appendTo: "body",
17575		autoOpen: true,
17576		buttons: [],
17577		closeOnEscape: true,
17578		closeText: "close",
17579		dialogClass: "",
17580		draggable: true,
17581		hide: null,
17582		height: "auto",
17583		maxHeight: null,
17584		maxWidth: null,
17585		minHeight: 150,
17586		minWidth: 150,
17587		modal: false,
17588		position: {
17589			my: "center",
17590			at: "center",
17591			of: window,
17592			collision: "fit",
17593			// Ensure the titlebar is always visible
17594			using: function( pos ) {
17595				var topOffset = $( this ).css( pos ).offset().top;
17596				if ( topOffset < 0 ) {
17597					$( this ).css( "top", pos.top - topOffset );
17598				}
17599			}
17600		},
17601		resizable: true,
17602		show: null,
17603		title: null,
17604		width: 300,
17605
17606		// callbacks
17607		beforeClose: null,
17608		close: null,
17609		drag: null,
17610		dragStart: null,
17611		dragStop: null,
17612		focus: null,
17613		open: null,
17614		resize: null,
17615		resizeStart: null,
17616		resizeStop: null
17617	},
17618
17619	_create: function() {
17620		this.originalCss = {
17621			display: this.element[0].style.display,
17622			width: this.element[0].style.width,
17623			minHeight: this.element[0].style.minHeight,
17624			maxHeight: this.element[0].style.maxHeight,
17625			height: this.element[0].style.height
17626		};
17627		this.originalPosition = {
17628			parent: this.element.parent(),
17629			index: this.element.parent().children().index( this.element )
17630		};
17631		this.originalTitle = this.element.attr("title");
17632		this.options.title = this.options.title || this.originalTitle;
17633
17634		this._createWrapper();
17635
17636		this.element
17637			.show()
17638			.removeAttr("title")
17639			.addClass("ui-dialog-content ui-widget-content")
17640			.appendTo( this.uiDialog );
17641
17642		this._createTitlebar();
17643		this._createButtonPane();
17644
17645		if ( this.options.draggable && $.fn.draggable ) {
17646			this._makeDraggable();
17647		}
17648		if ( this.options.resizable && $.fn.resizable ) {
17649			this._makeResizable();
17650		}
17651
17652		this._isOpen = false;
17653	},
17654
17655	_init: function() {
17656		if ( this.options.autoOpen ) {
17657			this.open();
17658		}
17659	},
17660
17661	_appendTo: function() {
17662		var element = this.options.appendTo;
17663		if ( element && (element.jquery || element.nodeType) ) {
17664			return $( element );
17665		}
17666		return this.document.find( element || "body" ).eq( 0 );
17667	},
17668
17669	_destroy: function() {
17670		var next,
17671			originalPosition = this.originalPosition;
17672
17673		this._destroyOverlay();
17674
17675		this.element
17676			.removeUniqueId()
17677			.removeClass("ui-dialog-content ui-widget-content")
17678			.css( this.originalCss )
17679			// Without detaching first, the following becomes really slow
17680			.detach();
17681
17682		this.uiDialog.stop( true, true ).remove();
17683
17684		if ( this.originalTitle ) {
17685			this.element.attr( "title", this.originalTitle );
17686		}
17687
17688		next = originalPosition.parent.children().eq( originalPosition.index );
17689		// Don't try to place the dialog next to itself (#8613)
17690		if ( next.length && next[0] !== this.element[0] ) {
17691			next.before( this.element );
17692		} else {
17693			originalPosition.parent.append( this.element );
17694		}
17695	},
17696
17697	widget: function() {
17698		return this.uiDialog;
17699	},
17700
17701	disable: $.noop,
17702	enable: $.noop,
17703
17704	close: function( event ) {
17705		var that = this;
17706
17707		if ( !this._isOpen || this._trigger( "beforeClose", event ) === false ) {
17708			return;
17709		}
17710
17711		this._isOpen = false;
vendor: 14,295 bytes, lines 17712-18262
17712		this._destroyOverlay();
17713
17714		if ( !this.opener.filter(":focusable").focus().length ) {
17715			// Hiding a focused element doesn't trigger blur in WebKit
17716			// so in case we have nothing to focus on, explicitly blur the active element
17717			// https://bugs.webkit.org/show_bug.cgi?id=47182
17718			$( this.document[0].activeElement ).blur();
17719		}
17720
17721		this._hide( this.uiDialog, this.options.hide, function() {
17722			that._trigger( "close", event );
17723		});
17724	},
17725
17726	isOpen: function() {
17727		return this._isOpen;
17728	},
17729
17730	moveToTop: function() {
17731		this._moveToTop();
17732	},
17733
17734	_moveToTop: function( event, silent ) {
17735		var moved = !!this.uiDialog.nextAll(":visible").insertBefore( this.uiDialog ).length;
17736		if ( moved && !silent ) {
17737			this._trigger( "focus", event );
17738		}
17739		return moved;
17740	},
17741
17742	open: function() {
17743		var that = this;
17744		if ( this._isOpen ) {
17745			if ( this._moveToTop() ) {
17746				this._focusTabbable();
17747			}
17748			return;
17749		}
17750
17751		this._isOpen = true;
17752		this.opener = $( this.document[0].activeElement );
17753
17754		this._size();
17755		this._position();
17756		this._createOverlay();
17757		this._moveToTop( null, true );
17758		this._show( this.uiDialog, this.options.show, function() {
17759			that._focusTabbable();
17760			that._trigger("focus");
17761		});
17762
17763		this._trigger("open");
17764	},
17765
17766	_focusTabbable: function() {
17767		// Set focus to the first match:
17768		// 1. First element inside the dialog matching [autofocus]
17769		// 2. Tabbable element inside the content element
17770		// 3. Tabbable element inside the buttonpane
17771		// 4. The close button
17772		// 5. The dialog itself
17773		var hasFocus = this.element.find("[autofocus]");
17774		if ( !hasFocus.length ) {
17775			hasFocus = this.element.find(":tabbable");
17776		}
17777		if ( !hasFocus.length ) {
17778			hasFocus = this.uiDialogButtonPane.find(":tabbable");
17779		}
17780		if ( !hasFocus.length ) {
17781			hasFocus = this.uiDialogTitlebarClose.filter(":tabbable");
17782		}
17783		if ( !hasFocus.length ) {
17784			hasFocus = this.uiDialog;
17785		}
17786		hasFocus.eq( 0 ).focus();
17787	},
17788
17789	_keepFocus: function( event ) {
17790		function checkFocus() {
17791			var activeElement = this.document[0].activeElement,
17792				isActive = this.uiDialog[0] === activeElement ||
17793					$.contains( this.uiDialog[0], activeElement );
17794			if ( !isActive ) {
17795				this._focusTabbable();
17796			}
17797		}
17798		event.preventDefault();
17799		checkFocus.call( this );
17800		// support: IE
17801		// IE <= 8 doesn't prevent moving focus even with event.preventDefault()
17802		// so we check again later
17803		this._delay( checkFocus );
17804	},
17805
17806	_createWrapper: function() {
17807		this.uiDialog = $("<div>")
17808			.addClass( "ui-dialog ui-widget ui-widget-content ui-corner-all ui-front " +
17809				this.options.dialogClass )
17810			.hide()
17811			.attr({
17812				// Setting tabIndex makes the div focusable
17813				tabIndex: -1,
17814				role: "dialog"
17815			})
17816			.appendTo( this._appendTo() );
17817
17818		this._on( this.uiDialog, {
17819			keydown: function( event ) {
17820				if ( this.options.closeOnEscape && !event.isDefaultPrevented() && event.keyCode &&
17821						event.keyCode === $.ui.keyCode.ESCAPE ) {
17822					event.preventDefault();
17823					this.close( event );
17824					return;
17825				}
17826
17827				// prevent tabbing out of dialogs
17828				if ( event.keyCode !== $.ui.keyCode.TAB ) {
17829					return;
17830				}
17831				var tabbables = this.uiDialog.find(":tabbable"),
17832					first = tabbables.filter(":first"),
17833					last  = tabbables.filter(":last");
17834
17835				if ( ( event.target === last[0] || event.target === this.uiDialog[0] ) && !event.shiftKey ) {
17836					first.focus( 1 );
17837					event.preventDefault();
17838				} else if ( ( event.target === first[0] || event.target === this.uiDialog[0] ) && event.shiftKey ) {
17839					last.focus( 1 );
17840					event.preventDefault();
17841				}
17842			},
17843			mousedown: function( event ) {
17844				if ( this._moveToTop( event ) ) {
17845					this._focusTabbable();
17846				}
17847			}
17848		});
17849
17850		// We assume that any existing aria-describedby attribute means
17851		// that the dialog content is marked up properly
17852		// otherwise we brute force the content as the description
17853		if ( !this.element.find("[aria-describedby]").length ) {
17854			this.uiDialog.attr({
17855				"aria-describedby": this.element.uniqueId().attr("id")
17856			});
17857		}
17858	},
17859
17860	_createTitlebar: function() {
17861		var uiDialogTitle;
17862
17863		this.uiDialogTitlebar = $("<div>")
17864			.addClass("ui-dialog-titlebar ui-widget-header ui-corner-all ui-helper-clearfix")
17865			.prependTo( this.uiDialog );
17866		this._on( this.uiDialogTitlebar, {
17867			mousedown: function( event ) {
17868				// Don't prevent click on close button (#8838)
17869				// Focusing a dialog that is partially scrolled out of view
17870				// causes the browser to scroll it into view, preventing the click event
17871				if ( !$( event.target ).closest(".ui-dialog-titlebar-close") ) {
17872					// Dialog isn't getting focus when dragging (#8063)
17873					this.uiDialog.focus();
17874				}
17875			}
17876		});
17877
17878		this.uiDialogTitlebarClose = $("<button></button>")
17879			.button({
17880				label: this.options.closeText,
17881				icons: {
17882					primary: "ui-icon-closethick"
17883				},
17884				text: false
17885			})
17886			.addClass("ui-dialog-titlebar-close")
17887			.appendTo( this.uiDialogTitlebar );
17888		this._on( this.uiDialogTitlebarClose, {
17889			click: function( event ) {
17890				event.preventDefault();
17891				this.close( event );
17892			}
17893		});
17894
17895		uiDialogTitle = $("<span>")
17896			.uniqueId()
17897			.addClass("ui-dialog-title")
17898			.prependTo( this.uiDialogTitlebar );
17899		this._title( uiDialogTitle );
17900
17901		this.uiDialog.attr({
17902			"aria-labelledby": uiDialogTitle.attr("id")
17903		});
17904	},
17905
17906	_title: function( title ) {
17907		if ( !this.options.title ) {
17908			title.html("&#160;");
17909		}
17910		title.text( this.options.title );
17911	},
17912
17913	_createButtonPane: function() {
17914		this.uiDialogButtonPane = $("<div>")
17915			.addClass("ui-dialog-buttonpane ui-widget-content ui-helper-clearfix");
17916
17917		this.uiButtonSet = $("<div>")
17918			.addClass("ui-dialog-buttonset")
17919			.appendTo( this.uiDialogButtonPane );
17920
17921		this._createButtons();
17922	},
17923
17924	_createButtons: function() {
17925		var that = this,
17926			buttons = this.options.buttons;
17927
17928		// if we already have a button pane, remove it
17929		this.uiDialogButtonPane.remove();
17930		this.uiButtonSet.empty();
17931
17932		if ( $.isEmptyObject( buttons ) || ($.isArray( buttons ) && !buttons.length) ) {
17933			this.uiDialog.removeClass("ui-dialog-buttons");
17934			return;
17935		}
17936
17937		$.each( buttons, function( name, props ) {
17938			var click, buttonOptions;
17939			props = $.isFunction( props ) ?
17940				{ click: props, text: name } :
17941				props;
17942			// Default to a non-submitting button
17943			props = $.extend( { type: "button" }, props );
17944			// Change the context for the click callback to be the main element
17945			click = props.click;
17946			props.click = function() {
17947				click.apply( that.element[0], arguments );
17948			};
17949			buttonOptions = {
17950				icons: props.icons,
17951				text: props.showText
17952			};
17953			delete props.icons;
17954			delete props.showText;
17955			$( "<button></button>", props )
17956				.button( buttonOptions )
17957				.appendTo( that.uiButtonSet );
17958		});
17959		this.uiDialog.addClass("ui-dialog-buttons");
17960		this.uiDialogButtonPane.appendTo( this.uiDialog );
17961	},
17962
17963	_makeDraggable: function() {
17964		var that = this,
17965			options = this.options;
17966
17967		function filteredUi( ui ) {
17968			return {
17969				position: ui.position,
17970				offset: ui.offset
17971			};
17972		}
17973
17974		this.uiDialog.draggable({
17975			cancel: ".ui-dialog-content, .ui-dialog-titlebar-close",
17976			handle: ".ui-dialog-titlebar",
17977			containment: "document",
17978			start: function( event, ui ) {
17979				$( this ).addClass("ui-dialog-dragging");
17980				that._blockFrames();
17981				that._trigger( "dragStart", event, filteredUi( ui ) );
17982			},
17983			drag: function( event, ui ) {
17984				that._trigger( "drag", event, filteredUi( ui ) );
17985			},
17986			stop: function( event, ui ) {
17987				options.position = [
17988					ui.position.left - that.document.scrollLeft(),
17989					ui.position.top - that.document.scrollTop()
17990				];
17991				$( this ).removeClass("ui-dialog-dragging");
17992				that._unblockFrames();
17993				that._trigger( "dragStop", event, filteredUi( ui ) );
17994			}
17995		});
17996	},
17997
17998	_makeResizable: function() {
17999		var that = this,
18000			options = this.options,
18001			handles = options.resizable,
18002			// .ui-resizable has position: relative defined in the stylesheet
18003			// but dialogs have to use absolute or fixed positioning
18004			position = this.uiDialog.css("position"),
18005			resizeHandles = typeof handles === "string" ?
18006				handles	:
18007				"n,e,s,w,se,sw,ne,nw";
18008
18009		function filteredUi( ui ) {
18010			return {
18011				originalPosition: ui.originalPosition,
18012				originalSize: ui.originalSize,
18013				position: ui.position,
18014				size: ui.size
18015			};
18016		}
18017
18018		this.uiDialog.resizable({
18019			cancel: ".ui-dialog-content",
18020			containment: "document",
18021			alsoResize: this.element,
18022			maxWidth: options.maxWidth,
18023			maxHeight: options.maxHeight,
18024			minWidth: options.minWidth,
18025			minHeight: this._minHeight(),
18026			handles: resizeHandles,
18027			start: function( event, ui ) {
18028				$( this ).addClass("ui-dialog-resizing");
18029				that._blockFrames();
18030				that._trigger( "resizeStart", event, filteredUi( ui ) );
18031			},
18032			resize: function( event, ui ) {
18033				that._trigger( "resize", event, filteredUi( ui ) );
18034			},
18035			stop: function( event, ui ) {
18036				options.height = $( this ).height();
18037				options.width = $( this ).width();
18038				$( this ).removeClass("ui-dialog-resizing");
18039				that._unblockFrames();
18040				that._trigger( "resizeStop", event, filteredUi( ui ) );
18041			}
18042		})
18043		.css( "position", position );
18044	},
18045
18046	_minHeight: function() {
18047		var options = this.options;
18048
18049		return options.height === "auto" ?
18050			options.minHeight :
18051			Math.min( options.minHeight, options.height );
18052	},
18053
18054	_position: function() {
18055		// Need to show the dialog to get the actual offset in the position plugin
18056		var isVisible = this.uiDialog.is(":visible");
18057		if ( !isVisible ) {
18058			this.uiDialog.show();
18059		}
18060		this.uiDialog.position( this.options.position );
18061		if ( !isVisible ) {
18062			this.uiDialog.hide();
18063		}
18064	},
18065
18066	_setOptions: function( options ) {
18067		var that = this,
18068			resize = false,
18069			resizableOptions = {};
18070
18071		$.each( options, function( key, value ) {
18072			that._setOption( key, value );
18073
18074			if ( key in sizeRelatedOptions ) {
18075				resize = true;
18076			}
18077			if ( key in resizableRelatedOptions ) {
18078				resizableOptions[ key ] = value;
18079			}
18080		});
18081
18082		if ( resize ) {
18083			this._size();
18084			this._position();
18085		}
18086		if ( this.uiDialog.is(":data(ui-resizable)") ) {
18087			this.uiDialog.resizable( "option", resizableOptions );
18088		}
18089	},
18090
18091	_setOption: function( key, value ) {
18092		/*jshint maxcomplexity:15*/
18093		var isDraggable, isResizable,
18094			uiDialog = this.uiDialog;
18095
18096		if ( key === "dialogClass" ) {
18097			uiDialog
18098				.removeClass( this.options.dialogClass )
18099				.addClass( value );
18100		}
18101
18102		if ( key === "disabled" ) {
18103			return;
18104		}
18105
18106		this._super( key, value );
18107
18108		if ( key === "appendTo" ) {
18109			this.uiDialog.appendTo( this._appendTo() );
18110		}
18111
18112		if ( key === "buttons" ) {
18113			this._createButtons();
18114		}
18115
18116		if ( key === "closeText" ) {
18117			this.uiDialogTitlebarClose.button({
18118				// Ensure that we always pass a string
18119				label: "" + value
18120			});
18121		}
18122
18123		if ( key === "draggable" ) {
18124			isDraggable = uiDialog.is(":data(ui-draggable)");
18125			if ( isDraggable && !value ) {
18126				uiDialog.draggable("destroy");
18127			}
18128
18129			if ( !isDraggable && value ) {
18130				this._makeDraggable();
18131			}
18132		}
18133
18134		if ( key === "position" ) {
18135			this._position();
18136		}
18137
18138		if ( key === "resizable" ) {
18139			// currently resizable, becoming non-resizable
18140			isResizable = uiDialog.is(":data(ui-resizable)");
18141			if ( isResizable && !value ) {
18142				uiDialog.resizable("destroy");
18143			}
18144
18145			// currently resizable, changing handles
18146			if ( isResizable && typeof value === "string" ) {
18147				uiDialog.resizable( "option", "handles", value );
18148			}
18149
18150			// currently non-resizable, becoming resizable
18151			if ( !isResizable && value !== false ) {
18152				this._makeResizable();
18153			}
18154		}
18155
18156		if ( key === "title" ) {
18157			this._title( this.uiDialogTitlebar.find(".ui-dialog-title") );
18158		}
18159	},
18160
18161	_size: function() {
18162		// If the user has resized the dialog, the .ui-dialog and .ui-dialog-content
18163		// divs will both have width and height set, so we need to reset them
18164		var nonContentHeight, minContentHeight, maxContentHeight,
18165			options = this.options;
18166
18167		// Reset content sizing
18168		this.element.show().css({
18169			width: "auto",
18170			minHeight: 0,
18171			maxHeight: "none",
18172			height: 0
18173		});
18174
18175		if ( options.minWidth > options.width ) {
18176			options.width = options.minWidth;
18177		}
18178
18179		// reset wrapper sizing
18180		// determine the height of all the non-content elements
18181		nonContentHeight = this.uiDialog.css({
18182				height: "auto",
18183				width: options.width
18184			})
18185			.outerHeight();
18186		minContentHeight = Math.max( 0, options.minHeight - nonContentHeight );
18187		maxContentHeight = typeof options.maxHeight === "number" ?
18188			Math.max( 0, options.maxHeight - nonContentHeight ) :
18189			"none";
18190
18191		if ( options.height === "auto" ) {
18192			this.element.css({
18193				minHeight: minContentHeight,
18194				maxHeight: maxContentHeight,
18195				height: "auto"
18196			});
18197		} else {
18198			this.element.height( Math.max( 0, options.height - nonContentHeight ) );
18199		}
18200
18201		if (this.uiDialog.is(":data(ui-resizable)") ) {
18202			this.uiDialog.resizable( "option", "minHeight", this._minHeight() );
18203		}
18204	},
18205
18206	_blockFrames: function() {
18207		this.iframeBlocks = this.document.find( "iframe" ).map(function() {
18208			var iframe = $( this );
18209
18210			return $( "<div>" )
18211				.css({
18212					position: "absolute",
18213					width: iframe.outerWidth(),
18214					height: iframe.outerHeight()
18215				})
18216				.appendTo( iframe.parent() )
18217				.offset( iframe.offset() )[0];
18218		});
18219	},
18220
18221	_unblockFrames: function() {
18222		if ( this.iframeBlocks ) {
18223			this.iframeBlocks.remove();
18224			delete this.iframeBlocks;
18225		}
18226	},
18227
18228	_allowInteraction: function( event ) {
18229		if ( $( event.target ).closest(".ui-dialog").length ) {
18230			return true;
18231		}
18232
18233		// TODO: Remove hack when datepicker implements
18234		// the .ui-front logic (#8989)
18235		return !!$( event.target ).closest(".ui-datepicker").length;
18236	},
18237
18238	_createOverlay: function() {
18239		if ( !this.options.modal ) {
18240			return;
18241		}
18242
18243		var that = this,
18244			widgetFullName = this.widgetFullName;
18245		if ( !$.ui.dialog.overlayInstances ) {
18246			// Prevent use of anchors and inputs.
18247			// We use a delay in case the overlay is created from an
18248			// event that we're going to be cancelling. (#2804)
18249			this._delay(function() {
18250				// Handle .dialog().dialog("close") (#4065)
18251				if ( $.ui.dialog.overlayInstances ) {
18252					this.document.bind( "focusin.dialog", function( event ) {
18253						if ( !that._allowInteraction( event ) ) {
18254							event.preventDefault();
18255							$(".ui-dialog:visible:last .ui-dialog-content")
18256								.data( widgetFullName )._focusTabbable();
18257						}
18258					});
18259				}
18260			});
18261		}
18262
18263		this.overlay = $("<div>")
18264			.addClass("ui-widget-overlay ui-front")
18265			.appendTo( this._appendTo() );
18266		this._on( this.overlay, {
18267			mousedown: "_keepFocus"
18268		});
18269		$.ui.dialog.overlayInstances++;
18270	},
18271
18272	_destroyOverlay: function() {
18273		if ( !this.options.modal ) {
18274			return;
18275		}
18276
18277		if ( this.overlay ) {
18278			$.ui.dialog.overlayInstances--;
18279
18280			if ( !$.ui.dialog.overlayInstances ) {
18281				this.document.unbind( "focusin.dialog" );
18282			}
18283			this.overlay.remove();
18284			this.overlay = null;
18285		}
18286	}
18287});
18288
18289$.ui.dialog.overlayInstances = 0;
18290
18291// DEPRECATED
18292if ( $.uiBackCompat !== false ) {
18293	// position option with array notation
18294	// just override with old implementation
18295	$.widget( "ui.dialog", $.ui.dialog, {
18296		_position: function() {
18297			var position = this.options.position,
18298				myAt = [],
18299				offset = [ 0, 0 ],
18300				isVisible;
18301
18302			if ( position ) {
18303				if ( typeof position === "string" || (typeof position === "object" && "0" in position ) ) {
18304					myAt = position.split ? position.split(" ") : [ position[0], position[1] ];
18305					if ( myAt.length === 1 ) {
18306						myAt[1] = myAt[0];
18307					}
18308
18309					$.each( [ "left", "top" ], function( i, offsetPosition ) {
18310						if ( +myAt[ i ] === myAt[ i ] ) {
18311							offset[ i ] = myAt[ i ];
18312							myAt[ i ] = offsetPosition;
18313						}
18314					});
18315
18316					position = {
18317						my: myAt[0] + (offset[0] < 0 ? offset[0] : "+" + offset[0]) + " " +
18318							myAt[1] + (offset[1] < 0 ? offset[1] : "+" + offset[1]),
18319						at: myAt.join(" ")
18320					};
18321				}
18322
18323				position = $.extend( {}, $.ui.dialog.prototype.options.position, position );
18324			} else {
18325				position = $.ui.dialog.prototype.options.position;
18326			}
18327
18328			// need to show the dialog to get the actual offset in the position plugin
18329			isVisible = this.uiDialog.is(":visible");
18330			if ( !isVisible ) {
18331				this.uiDialog.show();
18332			}
18333			this.uiDialog.position( position );
18334			if ( !isVisible ) {
18335				this.uiDialog.hide();
18336			}
18337		}
18338	});
18339}
18340
18341}( jQuery ) );
18342
18343
18344
18345
18346
18347/*!
18348 * jQuery UI Droppable 1.10.3
18349 * http://jqueryui.com
18350 *
18351 * Copyright 2013 jQuery Foundation and other contributors
18352 * Released under the MIT license.
18353 * http://jquery.org/license
18354 *
18355 * http://api.jqueryui.com/droppable/
18356 *
18357 * Depends:
18358 *	jquery.ui.core.js
18359 *	jquery.ui.widget.js
18360 *	jquery.ui.mouse.js
18361 *	jquery.ui.draggable.js
18362 */
18363
18364(function( $, undefined ) {
18365
18366function isOverAxis( x, reference, size ) {
18367	return ( x > reference ) && ( x < ( reference + size ) );
18368}
18369
18370$.widget("ui.droppable", {
18371	version: "1.10.3",
18372	widgetEventPrefix: "drop",
18373	options: {
18374		accept: "*",
18375		activeClass: false,
18376		addClasses: true,
18377		greedy: false,
18378		hoverClass: false,
18379		scope: "default",
18380		tolerance: "intersect",
18381
18382		// callbacks
18383		activate: null,
18384		deactivate: null,
18385		drop: null,
18386		out: null,
18387		over: null
18388	},
18389	_create: function() {
18390
18391		var o = this.options,
18392			accept = o.accept;
18393
18394		this.isover = false;
18395		this.isout = true;
18396
18397		this.accept = $.isFunction(accept) ? accept : function(d) {
18398			return d.is(accept);
18399		};
18400
18401		//Store the droppable's proportions
18402		this.proportions = { width: this.element[0].offsetWidth, height: this.element[0].offsetHeight };
18403
18404		// Add the reference and positions to the manager
18405		$.ui.ddmanager.droppables[o.scope] = $.ui.ddmanager.droppables[o.scope] || [];
18406		$.ui.ddmanager.droppables[o.scope].push(this);
18407
18408		(o.addClasses && this.element.addClass("ui-droppable"));
18409
18410	},
18411
18412	_destroy: function() {
18413		var i = 0,
18414			drop = $.ui.ddmanager.droppables[this.options.scope];
18415
18416		for ( ; i < drop.length; i++ ) {
18417			if ( drop[i] === this ) {
18418				drop.splice(i, 1);
18419			}
18420		}
18421
18422		this.element.removeClass("ui-droppable ui-droppable-disabled");
18423	},
18424
18425	_setOption: function(key, value) {
18426
18427		if(key === "accept") {
18428			this.accept = $.isFunction(value) ? value : function(d) {
18429				return d.is(value);
18430			};
18431		}
18432		$.Widget.prototype._setOption.apply(this, arguments);
18433	},
18434
18435	_activate: function(event) {
18436		var draggable = $.ui.ddmanager.current;
18437		if(this.options.activeClass) {
18438			this.element.addClass(this.options.activeClass);
18439		}
18440		if(draggable){
18441			this._trigger("activate", event, this.ui(draggable));
18442		}
18443	},
18444
18445	_deactivate: function(event) {
18446		var draggable = $.ui.ddmanager.current;
18447		if(this.options.activeClass) {
18448			this.element.removeClass(this.options.activeClass);
18449		}
18450		if(draggable){
18451			this._trigger("deactivate", event, this.ui(draggable));
18452		}
18453	},
18454
18455	_over: function(event) {
18456
18457		var draggable = $.ui.ddmanager.current;
18458
18459		// Bail if draggable and droppable are same element
18460		if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
18461			return;
18462		}
18463
18464		if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
18465			if(this.options.hoverClass) {
18466				this.element.addClass(this.options.hoverClass);
18467			}
18468			this._trigger("over", event, this.ui(draggable));
18469		}
18470
18471	},
18472
18473	_out: function(event) {
18474
18475		var draggable = $.ui.ddmanager.current;
18476
18477		// Bail if draggable and droppable are same element
18478		if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
18479			return;
18480		}
18481
18482		if (this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
18483			if(this.options.hoverClass) {
18484				this.element.removeClass(this.options.hoverClass);
18485			}
18486			this._trigger("out", event, this.ui(draggable));
18487		}
18488
18489	},
18490
18491	_drop: function(event,custom) {
18492
18493		var draggable = custom || $.ui.ddmanager.current,
18494			childrenIntersection = false;
18495
18496		// Bail if draggable and droppable are same element
18497		if (!draggable || (draggable.currentItem || draggable.element)[0] === this.element[0]) {
18498			return false;
18499		}
18500
18501		this.element.find(":data(ui-droppable)").not(".ui-draggable-dragging").each(function() {
18502			var inst = $.data(this, "ui-droppable");
18503			if(
18504				inst.options.greedy &&
18505				!inst.options.disabled &&
18506				inst.options.scope === draggable.options.scope &&
18507				inst.accept.call(inst.element[0], (draggable.currentItem || draggable.element)) &&
18508				$.ui.intersect(draggable, $.extend(inst, { offset: inst.element.offset() }), inst.options.tolerance)
18509			) { childrenIntersection = true; return false; }
18510		});
18511		if(childrenIntersection) {
18512			return false;
18513		}
18514
18515		if(this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
18516			if(this.options.activeClass) {
18517				this.element.removeClass(this.options.activeClass);
18518			}
18519			if(this.options.hoverClass) {
18520				this.element.removeClass(this.options.hoverClass);
18521			}
18522			this._trigger("drop", event, this.ui(draggable));
18523			return this.element;
18524		}
18525
18526		return false;
18527
18528	},
18529
18530	ui: function(c) {
18531		return {
18532			draggable: (c.currentItem || c.element),
18533			helper: c.helper,
18534			position: c.position,
18535			offset: c.positionAbs
18536		};
18537	}
18538
18539});
18540
18541$.ui.intersect = function(draggable, droppable, toleranceMode) {
18542
18543	if (!droppable.offset) {
18544		return false;
18545	}
18546
18547	var draggableLeft, draggableTop,
18548		x1 = (draggable.positionAbs || draggable.position.absolute).left, x2 = x1 + draggable.helperProportions.width,
18549		y1 = (draggable.positionAbs || draggable.position.absolute).top, y2 = y1 + draggable.helperProportions.height,
18550		l = droppable.offset.left, r = l + droppable.proportions.width,
18551		t = droppable.offset.top, b = t + droppable.proportions.height;
18552
18553	switch (toleranceMode) {
18554		case "fit":
18555			return (l <= x1 && x2 <= r && t <= y1 && y2 <= b);
18556		case "intersect":
18557			return (l < x1 + (draggable.helperProportions.width / 2) && // Right Half
18558				x2 - (draggable.helperProportions.width / 2) < r && // Left Half
18559				t < y1 + (draggable.helperProportions.height / 2) && // Bottom Half
18560				y2 - (draggable.helperProportions.height / 2) < b ); // Top Half
18561		case "pointer":
18562			draggableLeft = ((draggable.positionAbs || draggable.position.absolute).left + (draggable.clickOffset || draggable.offset.click).left);
18563			draggableTop = ((draggable.positionAbs || draggable.position.absolute).top + (draggable.clickOffset || draggable.offset.click).top);
18564			return isOverAxis( draggableTop, t, droppable.proportions.height ) && isOverAxis( draggableLeft, l, droppable.proportions.width );
18565		case "touch":
18566			return (
18567				(y1 >= t && y1 <= b) ||	// Top edge touching
18568				(y2 >= t && y2 <= b) ||	// Bottom edge touching
18569				(y1 < t && y2 > b)		// Surrounded vertically
18570			) && (
18571				(x1 >= l && x1 <= r) ||	// Left edge touching
18572				(x2 >= l && x2 <= r) ||	// Right edge touching
18573				(x1 < l && x2 > r)		// Surrounded horizontally
18574			);
18575		default:
18576			return false;
18577		}
18578
18579};
18580
18581/*
18582	This manager tracks offsets of draggables and droppables
18583*/
18584$.ui.ddmanager = {
18585	current: null,
18586	droppables: { "default": [] },
18587	prepareOffsets: function(t, event) {
18588
18589		var i, j,
18590			m = $.ui.ddmanager.droppables[t.options.scope] || [],
18591			type = event ? event.type : null, // workaround for #2317
18592			list = (t.currentItem || t.element).find(":data(ui-droppable)").addBack();
18593
18594		droppablesLoop: for (i = 0; i < m.length; i++) {
18595
18596			//No disabled and non-accepted
18597			if(m[i].options.disabled || (t && !m[i].accept.call(m[i].element[0],(t.currentItem || t.element)))) {
18598				continue;
18599			}
18600
18601			// Filter out elements in the current dragged item
18602			for (j=0; j < list.length; j++) {
18603				if(list[j] === m[i].element[0]) {
18604					m[i].proportions.height = 0;
18605					continue droppablesLoop;
18606				}
18607			}
18608
18609			m[i].visible = m[i].element.css("display") !== "none";
18610			if(!m[i].visible) {
18611				continue;
18612			}
18613
18614			//Activate the droppable if used directly from draggables
18615			if(type === "mousedown") {
18616				m[i]._activate.call(m[i], event);
18617			}
18618
18619			m[i].offset = m[i].element.offset();
18620			m[i].proportions = { width: m[i].element[0].offsetWidth, height: m[i].element[0].offsetHeight };
18621
18622		}
18623
18624	},
18625	drop: function(draggable, event) {
18626
18627		var dropped = false;
vendor: 61,266 bytes, lines 18628-21132
18628		// Create a copy of the droppables in case the list changes during the drop (#9116)
18629		$.each(($.ui.ddmanager.droppables[draggable.options.scope] || []).slice(), function() {
18630
18631			if(!this.options) {
18632				return;
18633			}
18634			if (!this.options.disabled && this.visible && $.ui.intersect(draggable, this, this.options.tolerance)) {
18635				dropped = this._drop.call(this, event) || dropped;
18636			}
18637
18638			if (!this.options.disabled && this.visible && this.accept.call(this.element[0],(draggable.currentItem || draggable.element))) {
18639				this.isout = true;
18640				this.isover = false;
18641				this._deactivate.call(this, event);
18642			}
18643
18644		});
18645		return dropped;
18646
18647	},
18648	dragStart: function( draggable, event ) {
18649		//Listen for scrolling so that if the dragging causes scrolling the position of the droppables can be recalculated (see #5003)
18650		draggable.element.parentsUntil( "body" ).bind( "scroll.droppable", function() {
18651			if( !draggable.options.refreshPositions ) {
18652				$.ui.ddmanager.prepareOffsets( draggable, event );
18653			}
18654		});
18655	},
18656	drag: function(draggable, event) {
18657
18658		//If you have a highly dynamic page, you might try this option. It renders positions every time you move the mouse.
18659		if(draggable.options.refreshPositions) {
18660			$.ui.ddmanager.prepareOffsets(draggable, event);
18661		}
18662
18663		//Run through all droppables and check their positions based on specific tolerance options
18664		$.each($.ui.ddmanager.droppables[draggable.options.scope] || [], function() {
18665
18666			if(this.options.disabled || this.greedyChild || !this.visible) {
18667				return;
18668			}
18669
18670			var parentInstance, scope, parent,
18671				intersects = $.ui.intersect(draggable, this, this.options.tolerance),
18672				c = !intersects && this.isover ? "isout" : (intersects && !this.isover ? "isover" : null);
18673			if(!c) {
18674				return;
18675			}
18676
18677			if (this.options.greedy) {
18678				// find droppable parents with same scope
18679				scope = this.options.scope;
18680				parent = this.element.parents(":data(ui-droppable)").filter(function () {
18681					return $.data(this, "ui-droppable").options.scope === scope;
18682				});
18683
18684				if (parent.length) {
18685					parentInstance = $.data(parent[0], "ui-droppable");
18686					parentInstance.greedyChild = (c === "isover");
18687				}
18688			}
18689
18690			// we just moved into a greedy child
18691			if (parentInstance && c === "isover") {
18692				parentInstance.isover = false;
18693				parentInstance.isout = true;
18694				parentInstance._out.call(parentInstance, event);
18695			}
18696
18697			this[c] = true;
18698			this[c === "isout" ? "isover" : "isout"] = false;
18699			this[c === "isover" ? "_over" : "_out"].call(this, event);
18700
18701			// we just moved out of a greedy child
18702			if (parentInstance && c === "isout") {
18703				parentInstance.isout = false;
18704				parentInstance.isover = true;
18705				parentInstance._over.call(parentInstance, event);
18706			}
18707		});
18708
18709	},
18710	dragStop: function( draggable, event ) {
18711		draggable.element.parentsUntil( "body" ).unbind( "scroll.droppable" );
18712		//Call prepareOffsets one final time since IE does not fire return scroll events when overflow was caused by drag (see #5003)
18713		if( !draggable.options.refreshPositions ) {
18714			$.ui.ddmanager.prepareOffsets( draggable, event );
18715		}
18716	}
18717};
18718
18719})(jQuery);
18720/*!
18721 * jQuery UI Effects 1.10.3
18722 * http://jqueryui.com
18723 *
18724 * Copyright 2013 jQuery Foundation and other contributors
18725 * Released under the MIT license.
18726 * http://jquery.org/license
18727 *
18728 * http://api.jqueryui.com/category/effects-core/
18729 */
18730
18731(function($, undefined) {
18732
18733var dataSpace = "ui-effects-";
18734
18735$.effects = {
18736	effect: {}
18737};
18738
18739/*!
18740 * jQuery Color Animations v2.1.2
18741 * https://github.com/jquery/jquery-color
18742 *
18743 * Copyright 2013 jQuery Foundation and other contributors
18744 * Released under the MIT license.
18745 * http://jquery.org/license
18746 *
18747 * Date: Wed Jan 16 08:47:09 2013 -0600
18748 */
18749(function( jQuery, undefined ) {
18750
18751	var stepHooks = "backgroundColor borderBottomColor borderLeftColor borderRightColor borderTopColor color columnRuleColor outlineColor textDecorationColor textEmphasisColor",
18752
18753	// plusequals test for += 100 -= 100
18754	rplusequals = /^([\-+])=\s*(\d+\.?\d*)/,
18755	// a set of RE's that can match strings and generate color tuples.
18756	stringParsers = [{
18757			re: /rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
18758			parse: function( execResult ) {
18759				return [
18760					execResult[ 1 ],
18761					execResult[ 2 ],
18762					execResult[ 3 ],
18763					execResult[ 4 ]
18764				];
18765			}
18766		}, {
18767			re: /rgba?\(\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
18768			parse: function( execResult ) {
18769				return [
18770					execResult[ 1 ] * 2.55,
18771					execResult[ 2 ] * 2.55,
18772					execResult[ 3 ] * 2.55,
18773					execResult[ 4 ]
18774				];
18775			}
18776		}, {
18777			// this regex ignores A-F because it's compared against an already lowercased string
18778			re: /#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})/,
18779			parse: function( execResult ) {
18780				return [
18781					parseInt( execResult[ 1 ], 16 ),
18782					parseInt( execResult[ 2 ], 16 ),
18783					parseInt( execResult[ 3 ], 16 )
18784				];
18785			}
18786		}, {
18787			// this regex ignores A-F because it's compared against an already lowercased string
18788			re: /#([a-f0-9])([a-f0-9])([a-f0-9])/,
18789			parse: function( execResult ) {
18790				return [
18791					parseInt( execResult[ 1 ] + execResult[ 1 ], 16 ),
18792					parseInt( execResult[ 2 ] + execResult[ 2 ], 16 ),
18793					parseInt( execResult[ 3 ] + execResult[ 3 ], 16 )
18794				];
18795			}
18796		}, {
18797			re: /hsla?\(\s*(\d+(?:\.\d+)?)\s*,\s*(\d+(?:\.\d+)?)\%\s*,\s*(\d+(?:\.\d+)?)\%\s*(?:,\s*(\d?(?:\.\d+)?)\s*)?\)/,
18798			space: "hsla",
18799			parse: function( execResult ) {
18800				return [
18801					execResult[ 1 ],
18802					execResult[ 2 ] / 100,
18803					execResult[ 3 ] / 100,
18804					execResult[ 4 ]
18805				];
18806			}
18807		}],
18808
18809	// jQuery.Color( )
18810	color = jQuery.Color = function( color, green, blue, alpha ) {
18811		return new jQuery.Color.fn.parse( color, green, blue, alpha );
18812	},
18813	spaces = {
18814		rgba: {
18815			props: {
18816				red: {
18817					idx: 0,
18818					type: "byte"
18819				},
18820				green: {
18821					idx: 1,
18822					type: "byte"
18823				},
18824				blue: {
18825					idx: 2,
18826					type: "byte"
18827				}
18828			}
18829		},
18830
18831		hsla: {
18832			props: {
18833				hue: {
18834					idx: 0,
18835					type: "degrees"
18836				},
18837				saturation: {
18838					idx: 1,
18839					type: "percent"
18840				},
18841				lightness: {
18842					idx: 2,
18843					type: "percent"
18844				}
18845			}
18846		}
18847	},
18848	propTypes = {
18849		"byte": {
18850			floor: true,
18851			max: 255
18852		},
18853		"percent": {
18854			max: 1
18855		},
18856		"degrees": {
18857			mod: 360,
18858			floor: true
18859		}
18860	},
18861	support = color.support = {},
18862
18863	// element for support tests
18864	supportElem = jQuery( "<p>" )[ 0 ],
18865
18866	// colors = jQuery.Color.names
18867	colors,
18868
18869	// local aliases of functions called often
18870	each = jQuery.each;
18871
18872// determine rgba support immediately
18873supportElem.style.cssText = "background-color:rgba(1,1,1,.5)";
18874support.rgba = supportElem.style.backgroundColor.indexOf( "rgba" ) > -1;
18875
18876// define cache name and alpha properties
18877// for rgba and hsla spaces
18878each( spaces, function( spaceName, space ) {
18879	space.cache = "_" + spaceName;
18880	space.props.alpha = {
18881		idx: 3,
18882		type: "percent",
18883		def: 1
18884	};
18885});
18886
18887function clamp( value, prop, allowEmpty ) {
18888	var type = propTypes[ prop.type ] || {};
18889
18890	if ( value == null ) {
18891		return (allowEmpty || !prop.def) ? null : prop.def;
18892	}
18893
18894	// ~~ is an short way of doing floor for positive numbers
18895	value = type.floor ? ~~value : parseFloat( value );
18896
18897	// IE will pass in empty strings as value for alpha,
18898	// which will hit this case
18899	if ( isNaN( value ) ) {
18900		return prop.def;
18901	}
18902
18903	if ( type.mod ) {
18904		// we add mod before modding to make sure that negatives values
18905		// get converted properly: -10 -> 350
18906		return (value + type.mod) % type.mod;
18907	}
18908
18909	// for now all property types without mod have min and max
18910	return 0 > value ? 0 : type.max < value ? type.max : value;
18911}
18912
18913function stringParse( string ) {
18914	var inst = color(),
18915		rgba = inst._rgba = [];
18916
18917	string = string.toLowerCase();
18918
18919	each( stringParsers, function( i, parser ) {
18920		var parsed,
18921			match = parser.re.exec( string ),
18922			values = match && parser.parse( match ),
18923			spaceName = parser.space || "rgba";
18924
18925		if ( values ) {
18926			parsed = inst[ spaceName ]( values );
18927
18928			// if this was an rgba parse the assignment might happen twice
18929			// oh well....
18930			inst[ spaces[ spaceName ].cache ] = parsed[ spaces[ spaceName ].cache ];
18931			rgba = inst._rgba = parsed._rgba;
18932
18933			// exit each( stringParsers ) here because we matched
18934			return false;
18935		}
18936	});
18937
18938	// Found a stringParser that handled it
18939	if ( rgba.length ) {
18940
18941		// if this came from a parsed string, force "transparent" when alpha is 0
18942		// chrome, (and maybe others) return "transparent" as rgba(0,0,0,0)
18943		if ( rgba.join() === "0,0,0,0" ) {
18944			jQuery.extend( rgba, colors.transparent );
18945		}
18946		return inst;
18947	}
18948
18949	// named colors
18950	return colors[ string ];
18951}
18952
18953color.fn = jQuery.extend( color.prototype, {
18954	parse: function( red, green, blue, alpha ) {
18955		if ( red === undefined ) {
18956			this._rgba = [ null, null, null, null ];
18957			return this;
18958		}
18959		if ( red.jquery || red.nodeType ) {
18960			red = jQuery( red ).css( green );
18961			green = undefined;
18962		}
18963
18964		var inst = this,
18965			type = jQuery.type( red ),
18966			rgba = this._rgba = [];
18967
18968		// more than 1 argument specified - assume ( red, green, blue, alpha )
18969		if ( green !== undefined ) {
18970			red = [ red, green, blue, alpha ];
18971			type = "array";
18972		}
18973
18974		if ( type === "string" ) {
18975			return this.parse( stringParse( red ) || colors._default );
18976		}
18977
18978		if ( type === "array" ) {
18979			each( spaces.rgba.props, function( key, prop ) {
18980				rgba[ prop.idx ] = clamp( red[ prop.idx ], prop );
18981			});
18982			return this;
18983		}
18984
18985		if ( type === "object" ) {
18986			if ( red instanceof color ) {
18987				each( spaces, function( spaceName, space ) {
18988					if ( red[ space.cache ] ) {
18989						inst[ space.cache ] = red[ space.cache ].slice();
18990					}
18991				});
18992			} else {
18993				each( spaces, function( spaceName, space ) {
18994					var cache = space.cache;
18995					each( space.props, function( key, prop ) {
18996
18997						// if the cache doesn't exist, and we know how to convert
18998						if ( !inst[ cache ] && space.to ) {
18999
19000							// if the value was null, we don't need to copy it
19001							// if the key was alpha, we don't need to copy it either
19002							if ( key === "alpha" || red[ key ] == null ) {
19003								return;
19004							}
19005							inst[ cache ] = space.to( inst._rgba );
19006						}
19007
19008						// this is the only case where we allow nulls for ALL properties.
19009						// call clamp with alwaysAllowEmpty
19010						inst[ cache ][ prop.idx ] = clamp( red[ key ], prop, true );
19011					});
19012
19013					// everything defined but alpha?
19014					if ( inst[ cache ] && jQuery.inArray( null, inst[ cache ].slice( 0, 3 ) ) < 0 ) {
19015						// use the default of 1
19016						inst[ cache ][ 3 ] = 1;
19017						if ( space.from ) {
19018							inst._rgba = space.from( inst[ cache ] );
19019						}
19020					}
19021				});
19022			}
19023			return this;
19024		}
19025	},
19026	is: function( compare ) {
19027		var is = color( compare ),
19028			same = true,
19029			inst = this;
19030
19031		each( spaces, function( _, space ) {
19032			var localCache,
19033				isCache = is[ space.cache ];
19034			if (isCache) {
19035				localCache = inst[ space.cache ] || space.to && space.to( inst._rgba ) || [];
19036				each( space.props, function( _, prop ) {
19037					if ( isCache[ prop.idx ] != null ) {
19038						same = ( isCache[ prop.idx ] === localCache[ prop.idx ] );
19039						return same;
19040					}
19041				});
19042			}
19043			return same;
19044		});
19045		return same;
19046	},
19047	_space: function() {
19048		var used = [],
19049			inst = this;
19050		each( spaces, function( spaceName, space ) {
19051			if ( inst[ space.cache ] ) {
19052				used.push( spaceName );
19053			}
19054		});
19055		return used.pop();
19056	},
19057	transition: function( other, distance ) {
19058		var end = color( other ),
19059			spaceName = end._space(),
19060			space = spaces[ spaceName ],
19061			startColor = this.alpha() === 0 ? color( "transparent" ) : this,
19062			start = startColor[ space.cache ] || space.to( startColor._rgba ),
19063			result = start.slice();
19064
19065		end = end[ space.cache ];
19066		each( space.props, function( key, prop ) {
19067			var index = prop.idx,
19068				startValue = start[ index ],
19069				endValue = end[ index ],
19070				type = propTypes[ prop.type ] || {};
19071
19072			// if null, don't override start value
19073			if ( endValue === null ) {
19074				return;
19075			}
19076			// if null - use end
19077			if ( startValue === null ) {
19078				result[ index ] = endValue;
19079			} else {
19080				if ( type.mod ) {
19081					if ( endValue - startValue > type.mod / 2 ) {
19082						startValue += type.mod;
19083					} else if ( startValue - endValue > type.mod / 2 ) {
19084						startValue -= type.mod;
19085					}
19086				}
19087				result[ index ] = clamp( ( endValue - startValue ) * distance + startValue, prop );
19088			}
19089		});
19090		return this[ spaceName ]( result );
19091	},
19092	blend: function( opaque ) {
19093		// if we are already opaque - return ourself
19094		if ( this._rgba[ 3 ] === 1 ) {
19095			return this;
19096		}
19097
19098		var rgb = this._rgba.slice(),
19099			a = rgb.pop(),
19100			blend = color( opaque )._rgba;
19101
19102		return color( jQuery.map( rgb, function( v, i ) {
19103			return ( 1 - a ) * blend[ i ] + a * v;
19104		}));
19105	},
19106	toRgbaString: function() {
19107		var prefix = "rgba(",
19108			rgba = jQuery.map( this._rgba, function( v, i ) {
19109				return v == null ? ( i > 2 ? 1 : 0 ) : v;
19110			});
19111
19112		if ( rgba[ 3 ] === 1 ) {
19113			rgba.pop();
19114			prefix = "rgb(";
19115		}
19116
19117		return prefix + rgba.join() + ")";
19118	},
19119	toHslaString: function() {
19120		var prefix = "hsla(",
19121			hsla = jQuery.map( this.hsla(), function( v, i ) {
19122				if ( v == null ) {
19123					v = i > 2 ? 1 : 0;
19124				}
19125
19126				// catch 1 and 2
19127				if ( i && i < 3 ) {
19128					v = Math.round( v * 100 ) + "%";
19129				}
19130				return v;
19131			});
19132
19133		if ( hsla[ 3 ] === 1 ) {
19134			hsla.pop();
19135			prefix = "hsl(";
19136		}
19137		return prefix + hsla.join() + ")";
19138	},
19139	toHexString: function( includeAlpha ) {
19140		var rgba = this._rgba.slice(),
19141			alpha = rgba.pop();
19142
19143		if ( includeAlpha ) {
19144			rgba.push( ~~( alpha * 255 ) );
19145		}
19146
19147		return "#" + jQuery.map( rgba, function( v ) {
19148
19149			// default to 0 when nulls exist
19150			v = ( v || 0 ).toString( 16 );
19151			return v.length === 1 ? "0" + v : v;
19152		}).join("");
19153	},
19154	toString: function() {
19155		return this._rgba[ 3 ] === 0 ? "transparent" : this.toRgbaString();
19156	}
19157});
19158color.fn.parse.prototype = color.fn;
19159
19160// hsla conversions adapted from:
19161// https://code.google.com/p/maashaack/source/browse/packages/graphics/trunk/src/graphics/colors/HUE2RGB.as?r=5021
19162
19163function hue2rgb( p, q, h ) {
19164	h = ( h + 1 ) % 1;
19165	if ( h * 6 < 1 ) {
19166		return p + (q - p) * h * 6;
19167	}
19168	if ( h * 2 < 1) {
19169		return q;
19170	}
19171	if ( h * 3 < 2 ) {
19172		return p + (q - p) * ((2/3) - h) * 6;
19173	}
19174	return p;
19175}
19176
19177spaces.hsla.to = function ( rgba ) {
19178	if ( rgba[ 0 ] == null || rgba[ 1 ] == null || rgba[ 2 ] == null ) {
19179		return [ null, null, null, rgba[ 3 ] ];
19180	}
19181	var r = rgba[ 0 ] / 255,
19182		g = rgba[ 1 ] / 255,
19183		b = rgba[ 2 ] / 255,
19184		a = rgba[ 3 ],
19185		max = Math.max( r, g, b ),
19186		min = Math.min( r, g, b ),
19187		diff = max - min,
19188		add = max + min,
19189		l = add * 0.5,
19190		h, s;
19191
19192	if ( min === max ) {
19193		h = 0;
19194	} else if ( r === max ) {
19195		h = ( 60 * ( g - b ) / diff ) + 360;
19196	} else if ( g === max ) {
19197		h = ( 60 * ( b - r ) / diff ) + 120;
19198	} else {
19199		h = ( 60 * ( r - g ) / diff ) + 240;
19200	}
19201
19202	// chroma (diff) == 0 means greyscale which, by definition, saturation = 0%
19203	// otherwise, saturation is based on the ratio of chroma (diff) to lightness (add)
19204	if ( diff === 0 ) {
19205		s = 0;
19206	} else if ( l <= 0.5 ) {
19207		s = diff / add;
19208	} else {
19209		s = diff / ( 2 - add );
19210	}
19211	return [ Math.round(h) % 360, s, l, a == null ? 1 : a ];
19212};
19213
19214spaces.hsla.from = function ( hsla ) {
19215	if ( hsla[ 0 ] == null || hsla[ 1 ] == null || hsla[ 2 ] == null ) {
19216		return [ null, null, null, hsla[ 3 ] ];
19217	}
19218	var h = hsla[ 0 ] / 360,
19219		s = hsla[ 1 ],
19220		l = hsla[ 2 ],
19221		a = hsla[ 3 ],
19222		q = l <= 0.5 ? l * ( 1 + s ) : l + s - l * s,
19223		p = 2 * l - q;
19224
19225	return [
19226		Math.round( hue2rgb( p, q, h + ( 1 / 3 ) ) * 255 ),
19227		Math.round( hue2rgb( p, q, h ) * 255 ),
19228		Math.round( hue2rgb( p, q, h - ( 1 / 3 ) ) * 255 ),
19229		a
19230	];
19231};
19232
19233
19234each( spaces, function( spaceName, space ) {
19235	var props = space.props,
19236		cache = space.cache,
19237		to = space.to,
19238		from = space.from;
19239
19240	// makes rgba() and hsla()
19241	color.fn[ spaceName ] = function( value ) {
19242
19243		// generate a cache for this space if it doesn't exist
19244		if ( to && !this[ cache ] ) {
19245			this[ cache ] = to( this._rgba );
19246		}
19247		if ( value === undefined ) {
19248			return this[ cache ].slice();
19249		}
19250
19251		var ret,
19252			type = jQuery.type( value ),
19253			arr = ( type === "array" || type === "object" ) ? value : arguments,
19254			local = this[ cache ].slice();
19255
19256		each( props, function( key, prop ) {
19257			var val = arr[ type === "object" ? key : prop.idx ];
19258			if ( val == null ) {
19259				val = local[ prop.idx ];
19260			}
19261			local[ prop.idx ] = clamp( val, prop );
19262		});
19263
19264		if ( from ) {
19265			ret = color( from( local ) );
19266			ret[ cache ] = local;
19267			return ret;
19268		} else {
19269			return color( local );
19270		}
19271	};
19272
19273	// makes red() green() blue() alpha() hue() saturation() lightness()
19274	each( props, function( key, prop ) {
19275		// alpha is included in more than one space
19276		if ( color.fn[ key ] ) {
19277			return;
19278		}
19279		color.fn[ key ] = function( value ) {
19280			var vtype = jQuery.type( value ),
19281				fn = ( key === "alpha" ? ( this._hsla ? "hsla" : "rgba" ) : spaceName ),
19282				local = this[ fn ](),
19283				cur = local[ prop.idx ],
19284				match;
19285
19286			if ( vtype === "undefined" ) {
19287				return cur;
19288			}
19289
19290			if ( vtype === "function" ) {
19291				value = value.call( this, cur );
19292				vtype = jQuery.type( value );
19293			}
19294			if ( value == null && prop.empty ) {
19295				return this;
19296			}
19297			if ( vtype === "string" ) {
19298				match = rplusequals.exec( value );
19299				if ( match ) {
19300					value = cur + parseFloat( match[ 2 ] ) * ( match[ 1 ] === "+" ? 1 : -1 );
19301				}
19302			}
19303			local[ prop.idx ] = value;
19304			return this[ fn ]( local );
19305		};
19306	});
19307});
19308
19309// add cssHook and .fx.step function for each named hook.
19310// accept a space separated string of properties
19311color.hook = function( hook ) {
19312	var hooks = hook.split( " " );
19313	each( hooks, function( i, hook ) {
19314		jQuery.cssHooks[ hook ] = {
19315			set: function( elem, value ) {
19316				var parsed, curElem,
19317					backgroundColor = "";
19318
19319				if ( value !== "transparent" && ( jQuery.type( value ) !== "string" || ( parsed = stringParse( value ) ) ) ) {
19320					value = color( parsed || value );
19321					if ( !support.rgba && value._rgba[ 3 ] !== 1 ) {
19322						curElem = hook === "backgroundColor" ? elem.parentNode : elem;
19323						while (
19324							(backgroundColor === "" || backgroundColor === "transparent") &&
19325							curElem && curElem.style
19326						) {
19327							try {
19328								backgroundColor = jQuery.css( curElem, "backgroundColor" );
19329								curElem = curElem.parentNode;
19330							} catch ( e ) {
19331							}
19332						}
19333
19334						value = value.blend( backgroundColor && backgroundColor !== "transparent" ?
19335							backgroundColor :
19336							"_default" );
19337					}
19338
19339					value = value.toRgbaString();
19340				}
19341				try {
19342					elem.style[ hook ] = value;
19343				} catch( e ) {
19344					// wrapped to prevent IE from throwing errors on "invalid" values like 'auto' or 'inherit'
19345				}
19346			}
19347		};
19348		jQuery.fx.step[ hook ] = function( fx ) {
19349			if ( !fx.colorInit ) {
19350				fx.start = color( fx.elem, hook );
19351				fx.end = color( fx.end );
19352				fx.colorInit = true;
19353			}
19354			jQuery.cssHooks[ hook ].set( fx.elem, fx.start.transition( fx.end, fx.pos ) );
19355		};
19356	});
19357
19358};
19359
19360color.hook( stepHooks );
19361
19362jQuery.cssHooks.borderColor = {
19363	expand: function( value ) {
19364		var expanded = {};
19365
19366		each( [ "Top", "Right", "Bottom", "Left" ], function( i, part ) {
19367			expanded[ "border" + part + "Color" ] = value;
19368		});
19369		return expanded;
19370	}
19371};
19372
19373// Basic color names only.
19374// Usage of any of the other color names requires adding yourself or including
19375// jquery.color.svg-names.js.
19376colors = jQuery.Color.names = {
19377	// 4.1. Basic color keywords
19378	aqua: "#00ffff",
19379	black: "#000000",
19380	blue: "#0000ff",
19381	fuchsia: "#ff00ff",
19382	gray: "#808080",
19383	green: "#008000",
19384	lime: "#00ff00",
19385	maroon: "#800000",
19386	navy: "#000080",
19387	olive: "#808000",
19388	purple: "#800080",
19389	red: "#ff0000",
19390	silver: "#c0c0c0",
19391	teal: "#008080",
19392	white: "#ffffff",
19393	yellow: "#ffff00",
19394
19395	// 4.2.3. "transparent" color keyword
19396	transparent: [ null, null, null, 0 ],
19397
19398	_default: "#ffffff"
19399};
19400
19401})( jQuery );
19402
19403
19404/******************************************************************************/
19405/****************************** CLASS ANIMATIONS ******************************/
19406/******************************************************************************/
19407(function() {
19408
19409var classAnimationActions = [ "add", "remove", "toggle" ],
19410	shorthandStyles = {
19411		border: 1,
19412		borderBottom: 1,
19413		borderColor: 1,
19414		borderLeft: 1,
19415		borderRight: 1,
19416		borderTop: 1,
19417		borderWidth: 1,
19418		margin: 1,
19419		padding: 1
19420	};
19421
19422$.each([ "borderLeftStyle", "borderRightStyle", "borderBottomStyle", "borderTopStyle" ], function( _, prop ) {
19423	$.fx.step[ prop ] = function( fx ) {
19424		if ( fx.end !== "none" && !fx.setAttr || fx.pos === 1 && !fx.setAttr ) {
19425			jQuery.style( fx.elem, prop, fx.end );
19426			fx.setAttr = true;
19427		}
19428	};
19429});
19430
19431function getElementStyles( elem ) {
19432	var key, len,
19433		style = elem.ownerDocument.defaultView ?
19434			elem.ownerDocument.defaultView.getComputedStyle( elem, null ) :
19435			elem.currentStyle,
19436		styles = {};
19437
19438	if ( style && style.length && style[ 0 ] && style[ style[ 0 ] ] ) {
19439		len = style.length;
19440		while ( len-- ) {
19441			key = style[ len ];
19442			if ( typeof style[ key ] === "string" ) {
19443				styles[ $.camelCase( key ) ] = style[ key ];
19444			}
19445		}
19446	// support: Opera, IE <9
19447	} else {
19448		for ( key in style ) {
19449			if ( typeof style[ key ] === "string" ) {
19450				styles[ key ] = style[ key ];
19451			}
19452		}
19453	}
19454
19455	return styles;
19456}
19457
19458
19459function styleDifference( oldStyle, newStyle ) {
19460	var diff = {},
19461		name, value;
19462
19463	for ( name in newStyle ) {
19464		value = newStyle[ name ];
19465		if ( oldStyle[ name ] !== value ) {
19466			if ( !shorthandStyles[ name ] ) {
19467				if ( $.fx.step[ name ] || !isNaN( parseFloat( value ) ) ) {
19468					diff[ name ] = value;
19469				}
19470			}
19471		}
19472	}
19473
19474	return diff;
19475}
19476
19477// support: jQuery <1.8
19478if ( !$.fn.addBack ) {
19479	$.fn.addBack = function( selector ) {
19480		return this.add( selector == null ?
19481			this.prevObject : this.prevObject.filter( selector )
19482		);
19483	};
19484}
19485
19486$.effects.animateClass = function( value, duration, easing, callback ) {
19487	var o = $.speed( duration, easing, callback );
19488
19489	return this.queue( function() {
19490		var animated = $( this ),
19491			baseClass = animated.attr( "class" ) || "",
19492			applyClassChange,
19493			allAnimations = o.children ? animated.find( "*" ).addBack() : animated;
19494
19495		// map the animated objects to store the original styles.
19496		allAnimations = allAnimations.map(function() {
19497			var el = $( this );
19498			return {
19499				el: el,
19500				start: getElementStyles( this )
19501			};
19502		});
19503
19504		// apply class change
19505		applyClassChange = function() {
19506			$.each( classAnimationActions, function(i, action) {
19507				if ( value[ action ] ) {
19508					animated[ action + "Class" ]( value[ action ] );
19509				}
19510			});
19511		};
19512		applyClassChange();
19513
19514		// map all animated objects again - calculate new styles and diff
19515		allAnimations = allAnimations.map(function() {
19516			this.end = getElementStyles( this.el[ 0 ] );
19517			this.diff = styleDifference( this.start, this.end );
19518			return this;
19519		});
19520
19521		// apply original class
19522		animated.attr( "class", baseClass );
19523
19524		// map all animated objects again - this time collecting a promise
19525		allAnimations = allAnimations.map(function() {
19526			var styleInfo = this,
19527				dfd = $.Deferred(),
19528				opts = $.extend({}, o, {
19529					queue: false,
19530					complete: function() {
19531						dfd.resolve( styleInfo );
19532					}
19533				});
19534
19535			this.el.animate( this.diff, opts );
19536			return dfd.promise();
19537		});
19538
19539		// once all animations have completed:
19540		$.when.apply( $, allAnimations.get() ).done(function() {
19541
19542			// set the final class
19543			applyClassChange();
19544
19545			// for each animated element,
19546			// clear all css properties that were animated
19547			$.each( arguments, function() {
19548				var el = this.el;
19549				$.each( this.diff, function(key) {
19550					el.css( key, "" );
19551				});
19552			});
19553
19554			// this is guarnteed to be there if you use jQuery.speed()
19555			// it also handles dequeuing the next anim...
19556			o.complete.call( animated[ 0 ] );
19557		});
19558	});
19559};
19560
19561$.fn.extend({
19562	addClass: (function( orig ) {
19563		return function( classNames, speed, easing, callback ) {
19564			return speed ?
19565				$.effects.animateClass.call( this,
19566					{ add: classNames }, speed, easing, callback ) :
19567				orig.apply( this, arguments );
19568		};
19569	})( $.fn.addClass ),
19570
19571	removeClass: (function( orig ) {
19572		return function( classNames, speed, easing, callback ) {
19573			return arguments.length > 1 ?
19574				$.effects.animateClass.call( this,
19575					{ remove: classNames }, speed, easing, callback ) :
19576				orig.apply( this, arguments );
19577		};
19578	})( $.fn.removeClass ),
19579
19580	toggleClass: (function( orig ) {
19581		return function( classNames, force, speed, easing, callback ) {
19582			if ( typeof force === "boolean" || force === undefined ) {
19583				if ( !speed ) {
19584					// without speed parameter
19585					return orig.apply( this, arguments );
19586				} else {
19587					return $.effects.animateClass.call( this,
19588						(force ? { add: classNames } : { remove: classNames }),
19589						speed, easing, callback );
19590				}
19591			} else {
19592				// without force parameter
19593				return $.effects.animateClass.call( this,
19594					{ toggle: classNames }, force, speed, easing );
19595			}
19596		};
19597	})( $.fn.toggleClass ),
19598
19599	switchClass: function( remove, add, speed, easing, callback) {
19600		return $.effects.animateClass.call( this, {
19601			add: add,
19602			remove: remove
19603		}, speed, easing, callback );
19604	}
19605});
19606
19607})();
19608
19609/******************************************************************************/
19610/*********************************** EFFECTS **********************************/
19611/******************************************************************************/
19612
19613(function() {
19614
19615$.extend( $.effects, {
19616	version: "1.10.3",
19617
19618	// Saves a set of properties in a data storage
19619	save: function( element, set ) {
19620		for( var i=0; i < set.length; i++ ) {
19621			if ( set[ i ] !== null ) {
19622				element.data( dataSpace + set[ i ], element[ 0 ].style[ set[ i ] ] );
19623			}
19624		}
19625	},
19626
19627	// Restores a set of previously saved properties from a data storage
19628	restore: function( element, set ) {
19629		var val, i;
19630		for( i=0; i < set.length; i++ ) {
19631			if ( set[ i ] !== null ) {
19632				val = element.data( dataSpace + set[ i ] );
19633				// support: jQuery 1.6.2
19634				// http://bugs.jquery.com/ticket/9917
19635				// jQuery 1.6.2 incorrectly returns undefined for any falsy value.
19636				// We can't differentiate between "" and 0 here, so we just assume
19637				// empty string since it's likely to be a more common value...
19638				if ( val === undefined ) {
19639					val = "";
19640				}
19641				element.css( set[ i ], val );
19642			}
19643		}
19644	},
19645
19646	setMode: function( el, mode ) {
19647		if (mode === "toggle") {
19648			mode = el.is( ":hidden" ) ? "show" : "hide";
19649		}
19650		return mode;
19651	},
19652
19653	// Translates a [top,left] array into a baseline value
19654	// this should be a little more flexible in the future to handle a string & hash
19655	getBaseline: function( origin, original ) {
19656		var y, x;
19657		switch ( origin[ 0 ] ) {
19658			case "top": y = 0; break;
19659			case "middle": y = 0.5; break;
19660			case "bottom": y = 1; break;
19661			default: y = origin[ 0 ] / original.height;
19662		}
19663		switch ( origin[ 1 ] ) {
19664			case "left": x = 0; break;
19665			case "center": x = 0.5; break;
19666			case "right": x = 1; break;
19667			default: x = origin[ 1 ] / original.width;
19668		}
19669		return {
19670			x: x,
19671			y: y
19672		};
19673	},
19674
19675	// Wraps the element around a wrapper that copies position properties
19676	createWrapper: function( element ) {
19677
19678		// if the element is already wrapped, return it
19679		if ( element.parent().is( ".ui-effects-wrapper" )) {
19680			return element.parent();
19681		}
19682
19683		// wrap the element
19684		var props = {
19685				width: element.outerWidth(true),
19686				height: element.outerHeight(true),
19687				"float": element.css( "float" )
19688			},
19689			wrapper = $( "<div></div>" )
19690				.addClass( "ui-effects-wrapper" )
19691				.css({
19692					fontSize: "100%",
19693					background: "transparent",
19694					border: "none",
19695					margin: 0,
19696					padding: 0
19697				}),
19698			// Store the size in case width/height are defined in % - Fixes #5245
19699			size = {
19700				width: element.width(),
19701				height: element.height()
19702			},
19703			active = document.activeElement;
19704
19705		// support: Firefox
19706		// Firefox incorrectly exposes anonymous content
19707		// https://bugzilla.mozilla.org/show_bug.cgi?id=561664
19708		try {
19709			active.id;
19710		} catch( e ) {
19711			active = document.body;
19712		}
19713
19714		element.wrap( wrapper );
19715
19716		// Fixes #7595 - Elements lose focus when wrapped.
19717		if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
19718			$( active ).focus();
19719		}
19720
19721		wrapper = element.parent(); //Hotfix for jQuery 1.4 since some change in wrap() seems to actually lose the reference to the wrapped element
19722
19723		// transfer positioning properties to the wrapper
19724		if ( element.css( "position" ) === "static" ) {
19725			wrapper.css({ position: "relative" });
19726			element.css({ position: "relative" });
19727		} else {
19728			$.extend( props, {
19729				position: element.css( "position" ),
19730				zIndex: element.css( "z-index" )
19731			});
19732			$.each([ "top", "left", "bottom", "right" ], function(i, pos) {
19733				props[ pos ] = element.css( pos );
19734				if ( isNaN( parseInt( props[ pos ], 10 ) ) ) {
19735					props[ pos ] = "auto";
19736				}
19737			});
19738			element.css({
19739				position: "relative",
19740				top: 0,
19741				left: 0,
19742				right: "auto",
19743				bottom: "auto"
19744			});
19745		}
19746		element.css(size);
19747
19748		return wrapper.css( props ).show();
19749	},
19750
19751	removeWrapper: function( element ) {
19752		var active = document.activeElement;
19753
19754		if ( element.parent().is( ".ui-effects-wrapper" ) ) {
19755			element.parent().replaceWith( element );
19756
19757			// Fixes #7595 - Elements lose focus when wrapped.
19758			if ( element[ 0 ] === active || $.contains( element[ 0 ], active ) ) {
19759				$( active ).focus();
19760			}
19761		}
19762
19763
19764		return element;
19765	},
19766
19767	setTransition: function( element, list, factor, value ) {
19768		value = value || {};
19769		$.each( list, function( i, x ) {
19770			var unit = element.cssUnit( x );
19771			if ( unit[ 0 ] > 0 ) {
19772				value[ x ] = unit[ 0 ] * factor + unit[ 1 ];
19773			}
19774		});
19775		return value;
19776	}
19777});
19778
19779// return an effect options object for the given parameters:
19780function _normalizeArguments( effect, options, speed, callback ) {
19781
19782	// allow passing all options as the first parameter
19783	if ( $.isPlainObject( effect ) ) {
19784		options = effect;
19785		effect = effect.effect;
19786	}
19787
19788	// convert to an object
19789	effect = { effect: effect };
19790
19791	// catch (effect, null, ...)
19792	if ( options == null ) {
19793		options = {};
19794	}
19795
19796	// catch (effect, callback)
19797	if ( $.isFunction( options ) ) {
19798		callback = options;
19799		speed = null;
19800		options = {};
19801	}
19802
19803	// catch (effect, speed, ?)
19804	if ( typeof options === "number" || $.fx.speeds[ options ] ) {
19805		callback = speed;
19806		speed = options;
19807		options = {};
19808	}
19809
19810	// catch (effect, options, callback)
19811	if ( $.isFunction( speed ) ) {
19812		callback = speed;
19813		speed = null;
19814	}
19815
19816	// add options to effect
19817	if ( options ) {
19818		$.extend( effect, options );
19819	}
19820
19821	speed = speed || options.duration;
19822	effect.duration = $.fx.off ? 0 :
19823		typeof speed === "number" ? speed :
19824		speed in $.fx.speeds ? $.fx.speeds[ speed ] :
19825		$.fx.speeds._default;
19826
19827	effect.complete = callback || options.complete;
19828
19829	return effect;
19830}
19831
19832function standardAnimationOption( option ) {
19833	// Valid standard speeds (nothing, number, named speed)
19834	if ( !option || typeof option === "number" || $.fx.speeds[ option ] ) {
19835		return true;
19836	}
19837
19838	// Invalid strings - treat as "normal" speed
19839	if ( typeof option === "string" && !$.effects.effect[ option ] ) {
19840		return true;
19841	}
19842
19843	// Complete callback
19844	if ( $.isFunction( option ) ) {
19845		return true;
19846	}
19847
19848	// Options hash (but not naming an effect)
19849	if ( typeof option === "object" && !option.effect ) {
19850		return true;
19851	}
19852
19853	// Didn't match any standard API
19854	return false;
19855}
19856
19857$.fn.extend({
19858	effect: function( /* effect, options, speed, callback */ ) {
19859		var args = _normalizeArguments.apply( this, arguments ),
19860			mode = args.mode,
19861			queue = args.queue,
19862			effectMethod = $.effects.effect[ args.effect ];
19863
19864		if ( $.fx.off || !effectMethod ) {
19865			// delegate to the original method (e.g., .show()) if possible
19866			if ( mode ) {
19867				return this[ mode ]( args.duration, args.complete );
19868			} else {
19869				return this.each( function() {
19870					if ( args.complete ) {
19871						args.complete.call( this );
19872					}
19873				});
19874			}
19875		}
19876
19877		function run( next ) {
19878			var elem = $( this ),
19879				complete = args.complete,
19880				mode = args.mode;
19881
19882			function done() {
19883				if ( $.isFunction( complete ) ) {
19884					complete.call( elem[0] );
19885				}
19886				if ( $.isFunction( next ) ) {
19887					next();
19888				}
19889			}
19890
19891			// If the element already has the correct final state, delegate to
19892			// the core methods so the internal tracking of "olddisplay" works.
19893			if ( elem.is( ":hidden" ) ? mode === "hide" : mode === "show" ) {
19894				elem[ mode ]();
19895				done();
19896			} else {
19897				effectMethod.call( elem[0], args, done );
19898			}
19899		}
19900
19901		return queue === false ? this.each( run ) : this.queue( queue || "fx", run );
19902	},
19903
19904	show: (function( orig ) {
19905		return function( option ) {
19906			if ( standardAnimationOption( option ) ) {
19907				return orig.apply( this, arguments );
19908			} else {
19909				var args = _normalizeArguments.apply( this, arguments );
19910				args.mode = "show";
19911				return this.effect.call( this, args );
19912			}
19913		};
19914	})( $.fn.show ),
19915
19916	hide: (function( orig ) {
19917		return function( option ) {
19918			if ( standardAnimationOption( option ) ) {
19919				return orig.apply( this, arguments );
19920			} else {
19921				var args = _normalizeArguments.apply( this, arguments );
19922				args.mode = "hide";
19923				return this.effect.call( this, args );
19924			}
19925		};
19926	})( $.fn.hide ),
19927
19928	toggle: (function( orig ) {
19929		return function( option ) {
19930			if ( standardAnimationOption( option ) || typeof option === "boolean" ) {
19931				return orig.apply( this, arguments );
19932			} else {
19933				var args = _normalizeArguments.apply( this, arguments );
19934				args.mode = "toggle";
19935				return this.effect.call( this, args );
19936			}
19937		};
19938	})( $.fn.toggle ),
19939
19940	// helper functions
19941	cssUnit: function(key) {
19942		var style = this.css( key ),
19943			val = [];
19944
19945		$.each( [ "em", "px", "%", "pt" ], function( i, unit ) {
19946			if ( style.indexOf( unit ) > 0 ) {
19947				val = [ parseFloat( style ), unit ];
19948			}
19949		});
19950		return val;
19951	}
19952});
19953
19954})();
19955
19956/******************************************************************************/
19957/*********************************** EASING ***********************************/
19958/******************************************************************************/
19959
19960(function() {
19961
19962// based on easing equations from Robert Penner (http://www.robertpenner.com/easing)
19963
19964var baseEasings = {};
19965
19966$.each( [ "Quad", "Cubic", "Quart", "Quint", "Expo" ], function( i, name ) {
19967	baseEasings[ name ] = function( p ) {
19968		return Math.pow( p, i + 2 );
19969	};
19970});
19971
19972$.extend( baseEasings, {
19973	Sine: function ( p ) {
19974		return 1 - Math.cos( p * Math.PI / 2 );
19975	},
19976	Circ: function ( p ) {
19977		return 1 - Math.sqrt( 1 - p * p );
19978	},
19979	Elastic: function( p ) {
19980		return p === 0 || p === 1 ? p :
19981			-Math.pow( 2, 8 * (p - 1) ) * Math.sin( ( (p - 1) * 80 - 7.5 ) * Math.PI / 15 );
19982	},
19983	Back: function( p ) {
19984		return p * p * ( 3 * p - 2 );
19985	},
19986	Bounce: function ( p ) {
19987		var pow2,
19988			bounce = 4;
19989
19990		while ( p < ( ( pow2 = Math.pow( 2, --bounce ) ) - 1 ) / 11 ) {}
19991		return 1 / Math.pow( 4, 3 - bounce ) - 7.5625 * Math.pow( ( pow2 * 3 - 2 ) / 22 - p, 2 );
19992	}
19993});
19994
19995$.each( baseEasings, function( name, easeIn ) {
19996	$.easing[ "easeIn" + name ] = easeIn;
19997	$.easing[ "easeOut" + name ] = function( p ) {
19998		return 1 - easeIn( 1 - p );
19999	};
20000	$.easing[ "easeInOut" + name ] = function( p ) {
20001		return p < 0.5 ?
20002			easeIn( p * 2 ) / 2 :
20003			1 - easeIn( p * -2 + 2 ) / 2;
20004	};
20005});
20006
20007})();
20008
20009})(jQuery);
20010
20011
20012/*!
20013 * jQuery UI Effects Blind 1.10.3
20014 * http://jqueryui.com
20015 *
20016 * Copyright 2013 jQuery Foundation and other contributors
20017 * Released under the MIT license.
20018 * http://jquery.org/license
20019 *
20020 * http://api.jqueryui.com/blind-effect/
20021 *
20022 * Depends:
20023 *	jquery.ui.effect.js
20024 */
20025
20026(function( $, undefined ) {
20027
20028var rvertical = /up|down|vertical/,
20029	rpositivemotion = /up|left|vertical|horizontal/;
20030
20031$.effects.effect.blind = function( o, done ) {
20032	// Create element
20033	var el = $( this ),
20034		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
20035		mode = $.effects.setMode( el, o.mode || "hide" ),
20036		direction = o.direction || "up",
20037		vertical = rvertical.test( direction ),
20038		ref = vertical ? "height" : "width",
20039		ref2 = vertical ? "top" : "left",
20040		motion = rpositivemotion.test( direction ),
20041		animation = {},
20042		show = mode === "show",
20043		wrapper, distance, margin;
20044
20045	// if already wrapped, the wrapper's properties are my property. #6245
20046	if ( el.parent().is( ".ui-effects-wrapper" ) ) {
20047		$.effects.save( el.parent(), props );
20048	} else {
20049		$.effects.save( el, props );
20050	}
20051	el.show();
20052	wrapper = $.effects.createWrapper( el ).css({
20053		overflow: "hidden"
20054	});
20055
20056	distance = wrapper[ ref ]();
20057	margin = parseFloat( wrapper.css( ref2 ) ) || 0;
20058
20059	animation[ ref ] = show ? distance : 0;
20060	if ( !motion ) {
20061		el
20062			.css( vertical ? "bottom" : "right", 0 )
20063			.css( vertical ? "top" : "left", "auto" )
20064			.css({ position: "absolute" });
20065
20066		animation[ ref2 ] = show ? margin : distance + margin;
20067	}
20068
20069	// start at 0 if we are showing
20070	if ( show ) {
20071		wrapper.css( ref, 0 );
20072		if ( ! motion ) {
20073			wrapper.css( ref2, margin + distance );
20074		}
20075	}
20076
20077	// Animate
20078	wrapper.animate( animation, {
20079		duration: o.duration,
20080		easing: o.easing,
20081		queue: false,
20082		complete: function() {
20083			if ( mode === "hide" ) {
20084				el.hide();
20085			}
20086			$.effects.restore( el, props );
20087			$.effects.removeWrapper( el );
20088			done();
20089		}
20090	});
20091
20092};
20093
20094})(jQuery);
20095
20096
20097/*!
20098 * jQuery UI Effects Bounce 1.10.3
20099 * http://jqueryui.com
20100 *
20101 * Copyright 2013 jQuery Foundation and other contributors
20102 * Released under the MIT license.
20103 * http://jquery.org/license
20104 *
20105 * http://api.jqueryui.com/bounce-effect/
20106 *
20107 * Depends:
20108 *	jquery.ui.effect.js
20109 */
20110
20111(function( $, undefined ) {
20112
20113$.effects.effect.bounce = function( o, done ) {
20114	var el = $( this ),
20115		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
20116
20117		// defaults:
20118		mode = $.effects.setMode( el, o.mode || "effect" ),
20119		hide = mode === "hide",
20120		show = mode === "show",
20121		direction = o.direction || "up",
20122		distance = o.distance,
20123		times = o.times || 5,
20124
20125		// number of internal animations
20126		anims = times * 2 + ( show || hide ? 1 : 0 ),
20127		speed = o.duration / anims,
20128		easing = o.easing,
20129
20130		// utility:
20131		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
20132		motion = ( direction === "up" || direction === "left" ),
20133		i,
20134		upAnim,
20135		downAnim,
20136
20137		// we will need to re-assemble the queue to stack our animations in place
20138		queue = el.queue(),
20139		queuelen = queue.length;
20140
20141	// Avoid touching opacity to prevent clearType and PNG issues in IE
20142	if ( show || hide ) {
20143		props.push( "opacity" );
20144	}
20145
20146	$.effects.save( el, props );
20147	el.show();
20148	$.effects.createWrapper( el ); // Create Wrapper
20149
20150	// default distance for the BIGGEST bounce is the outer Distance / 3
20151	if ( !distance ) {
20152		distance = el[ ref === "top" ? "outerHeight" : "outerWidth" ]() / 3;
20153	}
20154
20155	if ( show ) {
20156		downAnim = { opacity: 1 };
20157		downAnim[ ref ] = 0;
20158
20159		// if we are showing, force opacity 0 and set the initial position
20160		// then do the "first" animation
20161		el.css( "opacity", 0 )
20162			.css( ref, motion ? -distance * 2 : distance * 2 )
20163			.animate( downAnim, speed, easing );
20164	}
20165
20166	// start at the smallest distance if we are hiding
20167	if ( hide ) {
20168		distance = distance / Math.pow( 2, times - 1 );
20169	}
20170
20171	downAnim = {};
20172	downAnim[ ref ] = 0;
20173	// Bounces up/down/left/right then back to 0 -- times * 2 animations happen here
20174	for ( i = 0; i < times; i++ ) {
20175		upAnim = {};
20176		upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
20177
20178		el.animate( upAnim, speed, easing )
20179			.animate( downAnim, speed, easing );
20180
20181		distance = hide ? distance * 2 : distance / 2;
20182	}
20183
20184	// Last Bounce when Hiding
20185	if ( hide ) {
20186		upAnim = { opacity: 0 };
20187		upAnim[ ref ] = ( motion ? "-=" : "+=" ) + distance;
20188
20189		el.animate( upAnim, speed, easing );
20190	}
20191
20192	el.queue(function() {
20193		if ( hide ) {
20194			el.hide();
20195		}
20196		$.effects.restore( el, props );
20197		$.effects.removeWrapper( el );
20198		done();
20199	});
20200
20201	// inject all the animations we just queued to be first in line (after "inprogress")
20202	if ( queuelen > 1) {
20203		queue.splice.apply( queue,
20204			[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
20205	}
20206	el.dequeue();
20207
20208};
20209
20210})(jQuery);
20211
20212
20213/*!
20214 * jQuery UI Effects Clip 1.10.3
20215 * http://jqueryui.com
20216 *
20217 * Copyright 2013 jQuery Foundation and other contributors
20218 * Released under the MIT license.
20219 * http://jquery.org/license
20220 *
20221 * http://api.jqueryui.com/clip-effect/
20222 *
20223 * Depends:
20224 *	jquery.ui.effect.js
20225 */
20226
20227(function( $, undefined ) {
20228
20229$.effects.effect.clip = function( o, done ) {
20230	// Create element
20231	var el = $( this ),
20232		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
20233		mode = $.effects.setMode( el, o.mode || "hide" ),
20234		show = mode === "show",
20235		direction = o.direction || "vertical",
20236		vert = direction === "vertical",
20237		size = vert ? "height" : "width",
20238		position = vert ? "top" : "left",
20239		animation = {},
20240		wrapper, animate, distance;
20241
20242	// Save & Show
20243	$.effects.save( el, props );
20244	el.show();
20245
20246	// Create Wrapper
20247	wrapper = $.effects.createWrapper( el ).css({
20248		overflow: "hidden"
20249	});
20250	animate = ( el[0].tagName === "IMG" ) ? wrapper : el;
20251	distance = animate[ size ]();
20252
20253	// Shift
20254	if ( show ) {
20255		animate.css( size, 0 );
20256		animate.css( position, distance / 2 );
20257	}
20258
20259	// Create Animation Object:
20260	animation[ size ] = show ? distance : 0;
20261	animation[ position ] = show ? 0 : distance / 2;
20262
20263	// Animate
20264	animate.animate( animation, {
20265		queue: false,
20266		duration: o.duration,
20267		easing: o.easing,
20268		complete: function() {
20269			if ( !show ) {
20270				el.hide();
20271			}
20272			$.effects.restore( el, props );
20273			$.effects.removeWrapper( el );
20274			done();
20275		}
20276	});
20277
20278};
20279
20280})(jQuery);
20281
20282
20283/*!
20284 * jQuery UI Effects Drop 1.10.3
20285 * http://jqueryui.com
20286 *
20287 * Copyright 2013 jQuery Foundation and other contributors
20288 * Released under the MIT license.
20289 * http://jquery.org/license
20290 *
20291 * http://api.jqueryui.com/drop-effect/
20292 *
20293 * Depends:
20294 *	jquery.ui.effect.js
20295 */
20296
20297(function( $, undefined ) {
20298
20299$.effects.effect.drop = function( o, done ) {
20300
20301	var el = $( this ),
20302		props = [ "position", "top", "bottom", "left", "right", "opacity", "height", "width" ],
20303		mode = $.effects.setMode( el, o.mode || "hide" ),
20304		show = mode === "show",
20305		direction = o.direction || "left",
20306		ref = ( direction === "up" || direction === "down" ) ? "top" : "left",
20307		motion = ( direction === "up" || direction === "left" ) ? "pos" : "neg",
20308		animation = {
20309			opacity: show ? 1 : 0
20310		},
20311		distance;
20312
20313	// Adjust
20314	$.effects.save( el, props );
20315	el.show();
20316	$.effects.createWrapper( el );
20317
20318	distance = o.distance || el[ ref === "top" ? "outerHeight": "outerWidth" ]( true ) / 2;
20319
20320	if ( show ) {
20321		el
20322			.css( "opacity", 0 )
20323			.css( ref, motion === "pos" ? -distance : distance );
20324	}
20325
20326	// Animation
20327	animation[ ref ] = ( show ?
20328		( motion === "pos" ? "+=" : "-=" ) :
20329		( motion === "pos" ? "-=" : "+=" ) ) +
20330		distance;
20331
20332	// Animate
20333	el.animate( animation, {
20334		queue: false,
20335		duration: o.duration,
20336		easing: o.easing,
20337		complete: function() {
20338			if ( mode === "hide" ) {
20339				el.hide();
20340			}
20341			$.effects.restore( el, props );
20342			$.effects.removeWrapper( el );
20343			done();
20344		}
20345	});
20346};
20347
20348})(jQuery);
20349
20350
20351/*!
20352 * jQuery UI Effects Explode 1.10.3
20353 * http://jqueryui.com
20354 *
20355 * Copyright 2013 jQuery Foundation and other contributors
20356 * Released under the MIT license.
20357 * http://jquery.org/license
20358 *
20359 * http://api.jqueryui.com/explode-effect/
20360 *
20361 * Depends:
20362 *	jquery.ui.effect.js
20363 */
20364
20365(function( $, undefined ) {
20366
20367$.effects.effect.explode = function( o, done ) {
20368
20369	var rows = o.pieces ? Math.round( Math.sqrt( o.pieces ) ) : 3,
20370		cells = rows,
20371		el = $( this ),
20372		mode = $.effects.setMode( el, o.mode || "hide" ),
20373		show = mode === "show",
20374
20375		// show and then visibility:hidden the element before calculating offset
20376		offset = el.show().css( "visibility", "hidden" ).offset(),
20377
20378		// width and height of a piece
20379		width = Math.ceil( el.outerWidth() / cells ),
20380		height = Math.ceil( el.outerHeight() / rows ),
20381		pieces = [],
20382
20383		// loop
20384		i, j, left, top, mx, my;
20385
20386	// children animate complete:
20387	function childComplete() {
20388		pieces.push( this );
20389		if ( pieces.length === rows * cells ) {
20390			animComplete();
20391		}
20392	}
20393
20394	// clone the element for each row and cell.
20395	for( i = 0; i < rows ; i++ ) { // ===>
20396		top = offset.top + i * height;
20397		my = i - ( rows - 1 ) / 2 ;
20398
20399		for( j = 0; j < cells ; j++ ) { // |||
20400			left = offset.left + j * width;
20401			mx = j - ( cells - 1 ) / 2 ;
20402
20403			// Create a clone of the now hidden main element that will be absolute positioned
20404			// within a wrapper div off the -left and -top equal to size of our pieces
20405			el
20406				.clone()
20407				.appendTo( "body" )
20408				.wrap( "<div></div>" )
20409				.css({
20410					position: "absolute",
20411					visibility: "visible",
20412					left: -j * width,
20413					top: -i * height
20414				})
20415
20416			// select the wrapper - make it overflow: hidden and absolute positioned based on
20417			// where the original was located +left and +top equal to the size of pieces
20418				.parent()
20419				.addClass( "ui-effects-explode" )
20420				.css({
20421					position: "absolute",
20422					overflow: "hidden",
20423					width: width,
20424					height: height,
20425					left: left + ( show ? mx * width : 0 ),
20426					top: top + ( show ? my * height : 0 ),
20427					opacity: show ? 0 : 1
20428				}).animate({
20429					left: left + ( show ? 0 : mx * width ),
20430					top: top + ( show ? 0 : my * height ),
20431					opacity: show ? 1 : 0
20432				}, o.duration || 500, o.easing, childComplete );
20433		}
20434	}
20435
20436	function animComplete() {
20437		el.css({
20438			visibility: "visible"
20439		});
20440		$( pieces ).remove();
20441		if ( !show ) {
20442			el.hide();
20443		}
20444		done();
20445	}
20446};
20447
20448})(jQuery);
20449
20450
20451/*!
20452 * jQuery UI Effects Fade 1.10.3
20453 * http://jqueryui.com
20454 *
20455 * Copyright 2013 jQuery Foundation and other contributors
20456 * Released under the MIT license.
20457 * http://jquery.org/license
20458 *
20459 * http://api.jqueryui.com/fade-effect/
20460 *
20461 * Depends:
20462 *	jquery.ui.effect.js
20463 */
20464
20465(function( $, undefined ) {
20466
20467$.effects.effect.fade = function( o, done ) {
20468	var el = $( this ),
20469		mode = $.effects.setMode( el, o.mode || "toggle" );
20470
20471	el.animate({
20472		opacity: mode
20473	}, {
20474		queue: false,
20475		duration: o.duration,
20476		easing: o.easing,
20477		complete: done
20478	});
20479};
20480
20481})( jQuery );
20482
20483
20484/*!
20485 * jQuery UI Effects Fold 1.10.3
20486 * http://jqueryui.com
20487 *
20488 * Copyright 2013 jQuery Foundation and other contributors
20489 * Released under the MIT license.
20490 * http://jquery.org/license
20491 *
20492 * http://api.jqueryui.com/fold-effect/
20493 *
20494 * Depends:
20495 *	jquery.ui.effect.js
20496 */
20497
20498(function( $, undefined ) {
20499
20500$.effects.effect.fold = function( o, done ) {
20501
20502	// Create element
20503	var el = $( this ),
20504		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
20505		mode = $.effects.setMode( el, o.mode || "hide" ),
20506		show = mode === "show",
20507		hide = mode === "hide",
20508		size = o.size || 15,
20509		percent = /([0-9]+)%/.exec( size ),
20510		horizFirst = !!o.horizFirst,
20511		widthFirst = show !== horizFirst,
20512		ref = widthFirst ? [ "width", "height" ] : [ "height", "width" ],
20513		duration = o.duration / 2,
20514		wrapper, distance,
20515		animation1 = {},
20516		animation2 = {};
20517
20518	$.effects.save( el, props );
20519	el.show();
20520
20521	// Create Wrapper
20522	wrapper = $.effects.createWrapper( el ).css({
20523		overflow: "hidden"
20524	});
20525	distance = widthFirst ?
20526		[ wrapper.width(), wrapper.height() ] :
20527		[ wrapper.height(), wrapper.width() ];
20528
20529	if ( percent ) {
20530		size = parseInt( percent[ 1 ], 10 ) / 100 * distance[ hide ? 0 : 1 ];
20531	}
20532	if ( show ) {
20533		wrapper.css( horizFirst ? {
20534			height: 0,
20535			width: size
20536		} : {
20537			height: size,
20538			width: 0
20539		});
20540	}
20541
20542	// Animation
20543	animation1[ ref[ 0 ] ] = show ? distance[ 0 ] : size;
20544	animation2[ ref[ 1 ] ] = show ? distance[ 1 ] : 0;
20545
20546	// Animate
20547	wrapper
20548		.animate( animation1, duration, o.easing )
20549		.animate( animation2, duration, o.easing, function() {
20550			if ( hide ) {
20551				el.hide();
20552			}
20553			$.effects.restore( el, props );
20554			$.effects.removeWrapper( el );
20555			done();
20556		});
20557
20558};
20559
20560})(jQuery);
20561
20562
20563/*!
20564 * jQuery UI Effects Highlight 1.10.3
20565 * http://jqueryui.com
20566 *
20567 * Copyright 2013 jQuery Foundation and other contributors
20568 * Released under the MIT license.
20569 * http://jquery.org/license
20570 *
20571 * http://api.jqueryui.com/highlight-effect/
20572 *
20573 * Depends:
20574 *	jquery.ui.effect.js
20575 */
20576
20577(function( $, undefined ) {
20578
20579$.effects.effect.highlight = function( o, done ) {
20580	var elem = $( this ),
20581		props = [ "backgroundImage", "backgroundColor", "opacity" ],
20582		mode = $.effects.setMode( elem, o.mode || "show" ),
20583		animation = {
20584			backgroundColor: elem.css( "backgroundColor" )
20585		};
20586
20587	if (mode === "hide") {
20588		animation.opacity = 0;
20589	}
20590
20591	$.effects.save( elem, props );
20592
20593	elem
20594		.show()
20595		.css({
20596			backgroundImage: "none",
20597			backgroundColor: o.color || "#ffff99"
20598		})
20599		.animate( animation, {
20600			queue: false,
20601			duration: o.duration,
20602			easing: o.easing,
20603			complete: function() {
20604				if ( mode === "hide" ) {
20605					elem.hide();
20606				}
20607				$.effects.restore( elem, props );
20608				done();
20609			}
20610		});
20611};
20612
20613})(jQuery);
20614
20615
20616/*!
20617 * jQuery UI Effects Pulsate 1.10.3
20618 * http://jqueryui.com
20619 *
20620 * Copyright 2013 jQuery Foundation and other contributors
20621 * Released under the MIT license.
20622 * http://jquery.org/license
20623 *
20624 * http://api.jqueryui.com/pulsate-effect/
20625 *
20626 * Depends:
20627 *	jquery.ui.effect.js
20628 */
20629
20630(function( $, undefined ) {
20631
20632$.effects.effect.pulsate = function( o, done ) {
20633	var elem = $( this ),
20634		mode = $.effects.setMode( elem, o.mode || "show" ),
20635		show = mode === "show",
20636		hide = mode === "hide",
20637		showhide = ( show || mode === "hide" ),
20638
20639		// showing or hiding leaves of the "last" animation
20640		anims = ( ( o.times || 5 ) * 2 ) + ( showhide ? 1 : 0 ),
20641		duration = o.duration / anims,
20642		animateTo = 0,
20643		queue = elem.queue(),
20644		queuelen = queue.length,
20645		i;
20646
20647	if ( show || !elem.is(":visible")) {
20648		elem.css( "opacity", 0 ).show();
20649		animateTo = 1;
20650	}
20651
20652	// anims - 1 opacity "toggles"
20653	for ( i = 1; i < anims; i++ ) {
20654		elem.animate({
20655			opacity: animateTo
20656		}, duration, o.easing );
20657		animateTo = 1 - animateTo;
20658	}
20659
20660	elem.animate({
20661		opacity: animateTo
20662	}, duration, o.easing);
20663
20664	elem.queue(function() {
20665		if ( hide ) {
20666			elem.hide();
20667		}
20668		done();
20669	});
20670
20671	// We just queued up "anims" animations, we need to put them next in the queue
20672	if ( queuelen > 1 ) {
20673		queue.splice.apply( queue,
20674			[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
20675	}
20676	elem.dequeue();
20677};
20678
20679})(jQuery);
20680
20681
20682/*!
20683 * jQuery UI Effects Scale 1.10.3
20684 * http://jqueryui.com
20685 *
20686 * Copyright 2013 jQuery Foundation and other contributors
20687 * Released under the MIT license.
20688 * http://jquery.org/license
20689 *
20690 * http://api.jqueryui.com/scale-effect/
20691 *
20692 * Depends:
20693 *	jquery.ui.effect.js
20694 */
20695
20696(function( $, undefined ) {
20697
20698$.effects.effect.puff = function( o, done ) {
20699	var elem = $( this ),
20700		mode = $.effects.setMode( elem, o.mode || "hide" ),
20701		hide = mode === "hide",
20702		percent = parseInt( o.percent, 10 ) || 150,
20703		factor = percent / 100,
20704		original = {
20705			height: elem.height(),
20706			width: elem.width(),
20707			outerHeight: elem.outerHeight(),
20708			outerWidth: elem.outerWidth()
20709		};
20710
20711	$.extend( o, {
20712		effect: "scale",
20713		queue: false,
20714		fade: true,
20715		mode: mode,
20716		complete: done,
20717		percent: hide ? percent : 100,
20718		from: hide ?
20719			original :
20720			{
20721				height: original.height * factor,
20722				width: original.width * factor,
20723				outerHeight: original.outerHeight * factor,
20724				outerWidth: original.outerWidth * factor
20725			}
20726	});
20727
20728	elem.effect( o );
20729};
20730
20731$.effects.effect.scale = function( o, done ) {
20732
20733	// Create element
20734	var el = $( this ),
20735		options = $.extend( true, {}, o ),
20736		mode = $.effects.setMode( el, o.mode || "effect" ),
20737		percent = parseInt( o.percent, 10 ) ||
20738			( parseInt( o.percent, 10 ) === 0 ? 0 : ( mode === "hide" ? 0 : 100 ) ),
20739		direction = o.direction || "both",
20740		origin = o.origin,
20741		original = {
20742			height: el.height(),
20743			width: el.width(),
20744			outerHeight: el.outerHeight(),
20745			outerWidth: el.outerWidth()
20746		},
20747		factor = {
20748			y: direction !== "horizontal" ? (percent / 100) : 1,
20749			x: direction !== "vertical" ? (percent / 100) : 1
20750		};
20751
20752	// We are going to pass this effect to the size effect:
20753	options.effect = "size";
20754	options.queue = false;
20755	options.complete = done;
20756
20757	// Set default origin and restore for show/hide
20758	if ( mode !== "effect" ) {
20759		options.origin = origin || ["middle","center"];
20760		options.restore = true;
20761	}
20762
20763	options.from = o.from || ( mode === "show" ? {
20764		height: 0,
20765		width: 0,
20766		outerHeight: 0,
20767		outerWidth: 0
20768	} : original );
20769	options.to = {
20770		height: original.height * factor.y,
20771		width: original.width * factor.x,
20772		outerHeight: original.outerHeight * factor.y,
20773		outerWidth: original.outerWidth * factor.x
20774	};
20775
20776	// Fade option to support puff
20777	if ( options.fade ) {
20778		if ( mode === "show" ) {
20779			options.from.opacity = 0;
20780			options.to.opacity = 1;
20781		}
20782		if ( mode === "hide" ) {
20783			options.from.opacity = 1;
20784			options.to.opacity = 0;
20785		}
20786	}
20787
20788	// Animate
20789	el.effect( options );
20790
20791};
20792
20793$.effects.effect.size = function( o, done ) {
20794
20795	// Create element
20796	var original, baseline, factor,
20797		el = $( this ),
20798		props0 = [ "position", "top", "bottom", "left", "right", "width", "height", "overflow", "opacity" ],
20799
20800		// Always restore
20801		props1 = [ "position", "top", "bottom", "left", "right", "overflow", "opacity" ],
20802
20803		// Copy for children
20804		props2 = [ "width", "height", "overflow" ],
20805		cProps = [ "fontSize" ],
20806		vProps = [ "borderTopWidth", "borderBottomWidth", "paddingTop", "paddingBottom" ],
20807		hProps = [ "borderLeftWidth", "borderRightWidth", "paddingLeft", "paddingRight" ],
20808
20809		// Set options
20810		mode = $.effects.setMode( el, o.mode || "effect" ),
20811		restore = o.restore || mode !== "effect",
20812		scale = o.scale || "both",
20813		origin = o.origin || [ "middle", "center" ],
20814		position = el.css( "position" ),
20815		props = restore ? props0 : props1,
20816		zero = {
20817			height: 0,
20818			width: 0,
20819			outerHeight: 0,
20820			outerWidth: 0
20821		};
20822
20823	if ( mode === "show" ) {
20824		el.show();
20825	}
20826	original = {
20827		height: el.height(),
20828		width: el.width(),
20829		outerHeight: el.outerHeight(),
20830		outerWidth: el.outerWidth()
20831	};
20832
20833	if ( o.mode === "toggle" && mode === "show" ) {
20834		el.from = o.to || zero;
20835		el.to = o.from || original;
20836	} else {
20837		el.from = o.from || ( mode === "show" ? zero : original );
20838		el.to = o.to || ( mode === "hide" ? zero : original );
20839	}
20840
20841	// Set scaling factor
20842	factor = {
20843		from: {
20844			y: el.from.height / original.height,
20845			x: el.from.width / original.width
20846		},
20847		to: {
20848			y: el.to.height / original.height,
20849			x: el.to.width / original.width
20850		}
20851	};
20852
20853	// Scale the css box
20854	if ( scale === "box" || scale === "both" ) {
20855
20856		// Vertical props scaling
20857		if ( factor.from.y !== factor.to.y ) {
20858			props = props.concat( vProps );
20859			el.from = $.effects.setTransition( el, vProps, factor.from.y, el.from );
20860			el.to = $.effects.setTransition( el, vProps, factor.to.y, el.to );
20861		}
20862
20863		// Horizontal props scaling
20864		if ( factor.from.x !== factor.to.x ) {
20865			props = props.concat( hProps );
20866			el.from = $.effects.setTransition( el, hProps, factor.from.x, el.from );
20867			el.to = $.effects.setTransition( el, hProps, factor.to.x, el.to );
20868		}
20869	}
20870
20871	// Scale the content
20872	if ( scale === "content" || scale === "both" ) {
20873
20874		// Vertical props scaling
20875		if ( factor.from.y !== factor.to.y ) {
20876			props = props.concat( cProps ).concat( props2 );
20877			el.from = $.effects.setTransition( el, cProps, factor.from.y, el.from );
20878			el.to = $.effects.setTransition( el, cProps, factor.to.y, el.to );
20879		}
20880	}
20881
20882	$.effects.save( el, props );
20883	el.show();
20884	$.effects.createWrapper( el );
20885	el.css( "overflow", "hidden" ).css( el.from );
20886
20887	// Adjust
20888	if (origin) { // Calculate baseline shifts
20889		baseline = $.effects.getBaseline( origin, original );
20890		el.from.top = ( original.outerHeight - el.outerHeight() ) * baseline.y;
20891		el.from.left = ( original.outerWidth - el.outerWidth() ) * baseline.x;
20892		el.to.top = ( original.outerHeight - el.to.outerHeight ) * baseline.y;
20893		el.to.left = ( original.outerWidth - el.to.outerWidth ) * baseline.x;
20894	}
20895	el.css( el.from ); // set top & left
20896
20897	// Animate
20898	if ( scale === "content" || scale === "both" ) { // Scale the children
20899
20900		// Add margins/font-size
20901		vProps = vProps.concat([ "marginTop", "marginBottom" ]).concat(cProps);
20902		hProps = hProps.concat([ "marginLeft", "marginRight" ]);
20903		props2 = props0.concat(vProps).concat(hProps);
20904
20905		el.find( "*[width]" ).each( function(){
20906			var child = $( this ),
20907				c_original = {
20908					height: child.height(),
20909					width: child.width(),
20910					outerHeight: child.outerHeight(),
20911					outerWidth: child.outerWidth()
20912				};
20913			if (restore) {
20914				$.effects.save(child, props2);
20915			}
20916
20917			child.from = {
20918				height: c_original.height * factor.from.y,
20919				width: c_original.width * factor.from.x,
20920				outerHeight: c_original.outerHeight * factor.from.y,
20921				outerWidth: c_original.outerWidth * factor.from.x
20922			};
20923			child.to = {
20924				height: c_original.height * factor.to.y,
20925				width: c_original.width * factor.to.x,
20926				outerHeight: c_original.height * factor.to.y,
20927				outerWidth: c_original.width * factor.to.x
20928			};
20929
20930			// Vertical props scaling
20931			if ( factor.from.y !== factor.to.y ) {
20932				child.from = $.effects.setTransition( child, vProps, factor.from.y, child.from );
20933				child.to = $.effects.setTransition( child, vProps, factor.to.y, child.to );
20934			}
20935
20936			// Horizontal props scaling
20937			if ( factor.from.x !== factor.to.x ) {
20938				child.from = $.effects.setTransition( child, hProps, factor.from.x, child.from );
20939				child.to = $.effects.setTransition( child, hProps, factor.to.x, child.to );
20940			}
20941
20942			// Animate children
20943			child.css( child.from );
20944			child.animate( child.to, o.duration, o.easing, function() {
20945
20946				// Restore children
20947				if ( restore ) {
20948					$.effects.restore( child, props2 );
20949				}
20950			});
20951		});
20952	}
20953
20954	// Animate
20955	el.animate( el.to, {
20956		queue: false,
20957		duration: o.duration,
20958		easing: o.easing,
20959		complete: function() {
20960			if ( el.to.opacity === 0 ) {
20961				el.css( "opacity", el.from.opacity );
20962			}
20963			if( mode === "hide" ) {
20964				el.hide();
20965			}
20966			$.effects.restore( el, props );
20967			if ( !restore ) {
20968
20969				// we need to calculate our new positioning based on the scaling
20970				if ( position === "static" ) {
20971					el.css({
20972						position: "relative",
20973						top: el.to.top,
20974						left: el.to.left
20975					});
20976				} else {
20977					$.each([ "top", "left" ], function( idx, pos ) {
20978						el.css( pos, function( _, str ) {
20979							var val = parseInt( str, 10 ),
20980								toRef = idx ? el.to.left : el.to.top;
20981
20982							// if original was "auto", recalculate the new value from wrapper
20983							if ( str === "auto" ) {
20984								return toRef + "px";
20985							}
20986
20987							return val + toRef + "px";
20988						});
20989					});
20990				}
20991			}
20992
20993			$.effects.removeWrapper( el );
20994			done();
20995		}
20996	});
20997
20998};
20999
21000})(jQuery);
21001
21002
21003/*!
21004 * jQuery UI Effects Shake 1.10.3
21005 * http://jqueryui.com
21006 *
21007 * Copyright 2013 jQuery Foundation and other contributors
21008 * Released under the MIT license.
21009 * http://jquery.org/license
21010 *
21011 * http://api.jqueryui.com/shake-effect/
21012 *
21013 * Depends:
21014 *	jquery.ui.effect.js
21015 */
21016
21017(function( $, undefined ) {
21018
21019$.effects.effect.shake = function( o, done ) {
21020
21021	var el = $( this ),
21022		props = [ "position", "top", "bottom", "left", "right", "height", "width" ],
21023		mode = $.effects.setMode( el, o.mode || "effect" ),
21024		direction = o.direction || "left",
21025		distance = o.distance || 20,
21026		times = o.times || 3,
21027		anims = times * 2 + 1,
21028		speed = Math.round(o.duration/anims),
21029		ref = (direction === "up" || direction === "down") ? "top" : "left",
21030		positiveMotion = (direction === "up" || direction === "left"),
21031		animation = {},
21032		animation1 = {},
21033		animation2 = {},
21034		i,
21035
21036		// we will need to re-assemble the queue to stack our animations in place
21037		queue = el.queue(),
21038		queuelen = queue.length;
21039
21040	$.effects.save( el, props );
21041	el.show();
21042	$.effects.createWrapper( el );
21043
21044	// Animation
21045	animation[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance;
21046	animation1[ ref ] = ( positiveMotion ? "+=" : "-=" ) + distance * 2;
21047	animation2[ ref ] = ( positiveMotion ? "-=" : "+=" ) + distance * 2;
21048
21049	// Animate
21050	el.animate( animation, speed, o.easing );
21051
21052	// Shakes
21053	for ( i = 1; i < times; i++ ) {
21054		el.animate( animation1, speed, o.easing ).animate( animation2, speed, o.easing );
21055	}
21056	el
21057		.animate( animation1, speed, o.easing )
21058		.animate( animation, speed / 2, o.easing )
21059		.queue(function() {
21060			if ( mode === "hide" ) {
21061				el.hide();
21062			}
21063			$.effects.restore( el, props );
21064			$.effects.removeWrapper( el );
21065			done();
21066		});
21067
21068	// inject all the animations we just queued to be first in line (after "inprogress")
21069	if ( queuelen > 1) {
21070		queue.splice.apply( queue,
21071			[ 1, 0 ].concat( queue.splice( queuelen, anims + 1 ) ) );
21072	}
21073	el.dequeue();
21074
21075};
21076
21077})(jQuery);
21078
21079
21080/*!
21081 * jQuery UI Effects Slide 1.10.3
21082 * http://jqueryui.com
21083 *
21084 * Copyright 2013 jQuery Foundation and other contributors
21085 * Released under the MIT license.
21086 * http://jquery.org/license
21087 *
21088 * http://api.jqueryui.com/slide-effect/
21089 *
21090 * Depends:
21091 *	jquery.ui.effect.js
21092 */
21093
21094(function( $, undefined ) {
21095
21096$.effects.effect.slide = function( o, done ) {
21097
21098	// Create element
21099	var el = $( this ),
21100		props = [ "position", "top", "bottom", "left", "right", "width", "height" ],
21101		mode = $.effects.setMode( el, o.mode || "show" ),
21102		show = mode === "show",
21103		direction = o.direction || "left",
21104		ref = (direction === "up" || direction === "down") ? "top" : "left",
21105		positiveMotion = (direction === "up" || direction === "left"),
21106		distance,
21107		animation = {};
21108
21109	// Adjust
21110	$.effects.save( el, props );
21111	el.show();
21112	distance = o.distance || el[ ref === "top" ? "outerHeight" : "outerWidth" ]( true );
21113
21114	$.effects.createWrapper( el ).css({
21115		overflow: "hidden"
21116	});
21117
21118	if ( show ) {
21119		el.css( ref, positiveMotion ? (isNaN(distance) ? "-" + distance : -distance) : distance );
21120	}
21121
21122	// Animation
21123	animation[ ref ] = ( show ?
21124		( positiveMotion ? "+=" : "-=") :
21125		( positiveMotion ? "-=" : "+=")) +
21126		distance;
21127
21128	// Animate
21129	el.animate( animation, {
21130		queue: false,
21131		duration: o.duration,
21132		easing: o.easing,
21133		complete: function() {
21134			if ( mode === "hide" ) {
21135				el.hide();
21136			}
21137			$.effects.restore( el, props );
21138			$.effects.removeWrapper( el );
21139			done();
21140		}
21141	});
21142};
21143
21144})(jQuery);
21145
21146
21147/*!
21148 * jQuery UI Effects Transfer 1.10.3
21149 * http://jqueryui.com
21150 *
21151 * Copyright 2013 jQuery Foundation and other contributors
21152 * Released under the MIT license.
21153 * http://jquery.org/license
21154 *
21155 * http://api.jqueryui.com/transfer-effect/
21156 *
21157 * Depends:
21158 *	jquery.ui.effect.js
21159 */
21160
21161(function( $, undefined ) {
21162
21163$.effects.effect.transfer = function( o, done ) {
21164	var elem = $( this ),
21165		target = $( o.to ),
21166		targetFixed = target.css( "position" ) === "fixed",
21167		body = $("body"),
21168		fixTop = targetFixed ? body.scrollTop() : 0,
21169		fixLeft = targetFixed ? body.scrollLeft() : 0,
21170		endPosition = target.offset(),
21171		animation = {
21172			top: endPosition.top - fixTop ,
21173			left: endPosition.left - fixLeft ,
21174			height: target.innerHeight(),
21175			width: target.innerWidth()
21176		},
21177		startPosition = elem.offset(),
21178		transfer = $( "<div class='ui-effects-transfer'></div>" )
21179			.appendTo( document.body )
21180			.addClass( o.className )
21181			.css({
21182				top: startPosition.top - fixTop ,
21183				left: startPosition.left - fixLeft ,
vendor: 52,232 bytes, lines 21184-23101
21184				height: elem.innerHeight(),
21185				width: elem.innerWidth(),
21186				position: targetFixed ? "fixed" : "absolute"
21187			})
21188			.animate( animation, o.duration, o.easing, function() {
21189				transfer.remove();
21190				done();
21191			});
21192};
21193
21194})(jQuery);
21195
21196
21197
21198/*!
21199 * jQuery UI Progressbar 1.10.3
21200 * http://jqueryui.com
21201 *
21202 * Copyright 2013 jQuery Foundation and other contributors
21203 * Released under the MIT license.
21204 * http://jquery.org/license
21205 *
21206 * http://api.jqueryui.com/progressbar/
21207 *
21208 * Depends:
21209 *   jquery.ui.core.js
21210 *   jquery.ui.widget.js
21211 */
21212
21213(function( $, undefined ) {
21214
21215$.widget( "ui.progressbar", {
21216	version: "1.10.3",
21217	options: {
21218		max: 100,
21219		value: 0,
21220
21221		change: null,
21222		complete: null
21223	},
21224
21225	min: 0,
21226
21227	_create: function() {
21228		// Constrain initial value
21229		this.oldValue = this.options.value = this._constrainedValue();
21230
21231		this.element
21232			.addClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
21233			.attr({
21234				// Only set static values, aria-valuenow and aria-valuemax are
21235				// set inside _refreshValue()
21236				role: "progressbar",
21237				"aria-valuemin": this.min
21238			});
21239
21240		this.valueDiv = $( "<div class='ui-progressbar-value ui-widget-header ui-corner-left'></div>" )
21241			.appendTo( this.element );
21242
21243		this._refreshValue();
21244	},
21245
21246	_destroy: function() {
21247		this.element
21248			.removeClass( "ui-progressbar ui-widget ui-widget-content ui-corner-all" )
21249			.removeAttr( "role" )
21250			.removeAttr( "aria-valuemin" )
21251			.removeAttr( "aria-valuemax" )
21252			.removeAttr( "aria-valuenow" );
21253
21254		this.valueDiv.remove();
21255	},
21256
21257	value: function( newValue ) {
21258		if ( newValue === undefined ) {
21259			return this.options.value;
21260		}
21261
21262		this.options.value = this._constrainedValue( newValue );
21263		this._refreshValue();
21264	},
21265
21266	_constrainedValue: function( newValue ) {
21267		if ( newValue === undefined ) {
21268			newValue = this.options.value;
21269		}
21270
21271		this.indeterminate = newValue === false;
21272
21273		// sanitize value
21274		if ( typeof newValue !== "number" ) {
21275			newValue = 0;
21276		}
21277
21278		return this.indeterminate ? false :
21279			Math.min( this.options.max, Math.max( this.min, newValue ) );
21280	},
21281
21282	_setOptions: function( options ) {
21283		// Ensure "value" option is set after other values (like max)
21284		var value = options.value;
21285		delete options.value;
21286
21287		this._super( options );
21288
21289		this.options.value = this._constrainedValue( value );
21290		this._refreshValue();
21291	},
21292
21293	_setOption: function( key, value ) {
21294		if ( key === "max" ) {
21295			// Don't allow a max less than min
21296			value = Math.max( this.min, value );
21297		}
21298
21299		this._super( key, value );
21300	},
21301
21302	_percentage: function() {
21303		return this.indeterminate ? 100 : 100 * ( this.options.value - this.min ) / ( this.options.max - this.min );
21304	},
21305
21306	_refreshValue: function() {
21307		var value = this.options.value,
21308			percentage = this._percentage();
21309
21310		this.valueDiv
21311			.toggle( this.indeterminate || value > this.min )
21312			.toggleClass( "ui-corner-right", value === this.options.max )
21313			.width( percentage.toFixed(0) + "%" );
21314
21315		this.element.toggleClass( "ui-progressbar-indeterminate", this.indeterminate );
21316
21317		if ( this.indeterminate ) {
21318			this.element.removeAttr( "aria-valuenow" );
21319			if ( !this.overlayDiv ) {
21320				this.overlayDiv = $( "<div class='ui-progressbar-overlay'></div>" ).appendTo( this.valueDiv );
21321			}
21322		} else {
21323			this.element.attr({
21324				"aria-valuemax": this.options.max,
21325				"aria-valuenow": value
21326			});
21327			if ( this.overlayDiv ) {
21328				this.overlayDiv.remove();
21329				this.overlayDiv = null;
21330			}
21331		}
21332
21333		if ( this.oldValue !== value ) {
21334			this.oldValue = value;
21335			this._trigger( "change" );
21336		}
21337		if ( value === this.options.max ) {
21338			this._trigger( "complete" );
21339		}
21340	}
21341});
21342
21343})( jQuery );
21344
21345
21346
21347
21348/*!
21349 * jQuery UI Selectable 1.10.3
21350 * http://jqueryui.com
21351 *
21352 * Copyright 2013 jQuery Foundation and other contributors
21353 * Released under the MIT license.
21354 * http://jquery.org/license
21355 *
21356 * http://api.jqueryui.com/selectable/
21357 *
21358 * Depends:
21359 *	jquery.ui.core.js
21360 *	jquery.ui.mouse.js
21361 *	jquery.ui.widget.js
21362 */
21363
21364(function( $, undefined ) {
21365
21366$.widget("ui.selectable", $.ui.mouse, {
21367	version: "1.10.3",
21368	options: {
21369		appendTo: "body",
21370		autoRefresh: true,
21371		distance: 0,
21372		filter: "*",
21373		tolerance: "touch",
21374
21375		// callbacks
21376		selected: null,
21377		selecting: null,
21378		start: null,
21379		stop: null,
21380		unselected: null,
21381		unselecting: null
21382	},
21383	_create: function() {
21384		var selectees,
21385			that = this;
21386
21387		this.element.addClass("ui-selectable");
21388
21389		this.dragged = false;
21390
21391		// cache selectee children based on filter
21392		this.refresh = function() {
21393			selectees = $(that.options.filter, that.element[0]);
21394			selectees.addClass("ui-selectee");
21395			selectees.each(function() {
21396				var $this = $(this),
21397					pos = $this.offset();
21398				$.data(this, "selectable-item", {
21399					element: this,
21400					$element: $this,
21401					left: pos.left,
21402					top: pos.top,
21403					right: pos.left + $this.outerWidth(),
21404					bottom: pos.top + $this.outerHeight(),
21405					startselected: false,
21406					selected: $this.hasClass("ui-selected"),
21407					selecting: $this.hasClass("ui-selecting"),
21408					unselecting: $this.hasClass("ui-unselecting")
21409				});
21410			});
21411		};
21412		this.refresh();
21413
21414		this.selectees = selectees.addClass("ui-selectee");
21415
21416		this._mouseInit();
21417
21418		this.helper = $("<div class='ui-selectable-helper'></div>");
21419	},
21420
21421	_destroy: function() {
21422		this.selectees
21423			.removeClass("ui-selectee")
21424			.removeData("selectable-item");
21425		this.element
21426			.removeClass("ui-selectable ui-selectable-disabled");
21427		this._mouseDestroy();
21428	},
21429
21430	_mouseStart: function(event) {
21431		var that = this,
21432			options = this.options;
21433
21434		this.opos = [event.pageX, event.pageY];
21435
21436		if (this.options.disabled) {
21437			return;
21438		}
21439
21440		this.selectees = $(options.filter, this.element[0]);
21441
21442		this._trigger("start", event);
21443
21444		$(options.appendTo).append(this.helper);
21445		// position helper (lasso)
21446		this.helper.css({
21447			"left": event.pageX,
21448			"top": event.pageY,
21449			"width": 0,
21450			"height": 0
21451		});
21452
21453		if (options.autoRefresh) {
21454			this.refresh();
21455		}
21456
21457		this.selectees.filter(".ui-selected").each(function() {
21458			var selectee = $.data(this, "selectable-item");
21459			selectee.startselected = true;
21460			if (!event.metaKey && !event.ctrlKey) {
21461				selectee.$element.removeClass("ui-selected");
21462				selectee.selected = false;
21463				selectee.$element.addClass("ui-unselecting");
21464				selectee.unselecting = true;
21465				// selectable UNSELECTING callback
21466				that._trigger("unselecting", event, {
21467					unselecting: selectee.element
21468				});
21469			}
21470		});
21471
21472		$(event.target).parents().addBack().each(function() {
21473			var doSelect,
21474				selectee = $.data(this, "selectable-item");
21475			if (selectee) {
21476				doSelect = (!event.metaKey && !event.ctrlKey) || !selectee.$element.hasClass("ui-selected");
21477				selectee.$element
21478					.removeClass(doSelect ? "ui-unselecting" : "ui-selected")
21479					.addClass(doSelect ? "ui-selecting" : "ui-unselecting");
21480				selectee.unselecting = !doSelect;
21481				selectee.selecting = doSelect;
21482				selectee.selected = doSelect;
21483				// selectable (UN)SELECTING callback
21484				if (doSelect) {
21485					that._trigger("selecting", event, {
21486						selecting: selectee.element
21487					});
21488				} else {
21489					that._trigger("unselecting", event, {
21490						unselecting: selectee.element
21491					});
21492				}
21493				return false;
21494			}
21495		});
21496
21497	},
21498
21499	_mouseDrag: function(event) {
21500
21501		this.dragged = true;
21502
21503		if (this.options.disabled) {
21504			return;
21505		}
21506
21507		var tmp,
21508			that = this,
21509			options = this.options,
21510			x1 = this.opos[0],
21511			y1 = this.opos[1],
21512			x2 = event.pageX,
21513			y2 = event.pageY;
21514
21515		if (x1 > x2) { tmp = x2; x2 = x1; x1 = tmp; }
21516		if (y1 > y2) { tmp = y2; y2 = y1; y1 = tmp; }
21517		this.helper.css({left: x1, top: y1, width: x2-x1, height: y2-y1});
21518
21519		this.selectees.each(function() {
21520			var selectee = $.data(this, "selectable-item"),
21521				hit = false;
21522
21523			//prevent helper from being selected if appendTo: selectable
21524			if (!selectee || selectee.element === that.element[0]) {
21525				return;
21526			}
21527
21528			if (options.tolerance === "touch") {
21529				hit = ( !(selectee.left > x2 || selectee.right < x1 || selectee.top > y2 || selectee.bottom < y1) );
21530			} else if (options.tolerance === "fit") {
21531				hit = (selectee.left > x1 && selectee.right < x2 && selectee.top > y1 && selectee.bottom < y2);
21532			}
21533
21534			if (hit) {
21535				// SELECT
21536				if (selectee.selected) {
21537					selectee.$element.removeClass("ui-selected");
21538					selectee.selected = false;
21539				}
21540				if (selectee.unselecting) {
21541					selectee.$element.removeClass("ui-unselecting");
21542					selectee.unselecting = false;
21543				}
21544				if (!selectee.selecting) {
21545					selectee.$element.addClass("ui-selecting");
21546					selectee.selecting = true;
21547					// selectable SELECTING callback
21548					that._trigger("selecting", event, {
21549						selecting: selectee.element
21550					});
21551				}
21552			} else {
21553				// UNSELECT
21554				if (selectee.selecting) {
21555					if ((event.metaKey || event.ctrlKey) && selectee.startselected) {
21556						selectee.$element.removeClass("ui-selecting");
21557						selectee.selecting = false;
21558						selectee.$element.addClass("ui-selected");
21559						selectee.selected = true;
21560					} else {
21561						selectee.$element.removeClass("ui-selecting");
21562						selectee.selecting = false;
21563						if (selectee.startselected) {
21564							selectee.$element.addClass("ui-unselecting");
21565							selectee.unselecting = true;
21566						}
21567						// selectable UNSELECTING callback
21568						that._trigger("unselecting", event, {
21569							unselecting: selectee.element
21570						});
21571					}
21572				}
21573				if (selectee.selected) {
21574					if (!event.metaKey && !event.ctrlKey && !selectee.startselected) {
21575						selectee.$element.removeClass("ui-selected");
21576						selectee.selected = false;
21577
21578						selectee.$element.addClass("ui-unselecting");
21579						selectee.unselecting = true;
21580						// selectable UNSELECTING callback
21581						that._trigger("unselecting", event, {
21582							unselecting: selectee.element
21583						});
21584					}
21585				}
21586			}
21587		});
21588
21589		return false;
21590	},
21591
21592	_mouseStop: function(event) {
21593		var that = this;
21594
21595		this.dragged = false;
21596
21597		$(".ui-unselecting", this.element[0]).each(function() {
21598			var selectee = $.data(this, "selectable-item");
21599			selectee.$element.removeClass("ui-unselecting");
21600			selectee.unselecting = false;
21601			selectee.startselected = false;
21602			that._trigger("unselected", event, {
21603				unselected: selectee.element
21604			});
21605		});
21606		$(".ui-selecting", this.element[0]).each(function() {
21607			var selectee = $.data(this, "selectable-item");
21608			selectee.$element.removeClass("ui-selecting").addClass("ui-selected");
21609			selectee.selecting = false;
21610			selectee.selected = true;
21611			selectee.startselected = true;
21612			that._trigger("selected", event, {
21613				selected: selectee.element
21614			});
21615		});
21616		this._trigger("stop", event);
21617
21618		this.helper.remove();
21619
21620		return false;
21621	}
21622
21623});
21624
21625})(jQuery);
21626
21627
21628
21629
21630/*!
21631 * jQuery UI Slider 1.10.3
21632 * http://jqueryui.com
21633 *
21634 * Copyright 2013 jQuery Foundation and other contributors
21635 * Released under the MIT license.
21636 * http://jquery.org/license
21637 *
21638 * http://api.jqueryui.com/slider/
21639 *
21640 * Depends:
21641 *	jquery.ui.core.js
21642 *	jquery.ui.mouse.js
21643 *	jquery.ui.widget.js
21644 */
21645
21646(function( $, undefined ) {
21647
21648// number of pages in a slider
21649// (how many times can you page up/down to go through the whole range)
21650var numPages = 5;
21651
21652$.widget( "ui.slider", $.ui.mouse, {
21653	version: "1.10.3",
21654	widgetEventPrefix: "slide",
21655
21656	options: {
21657		animate: false,
21658		distance: 0,
21659		max: 100,
21660		min: 0,
21661		orientation: "horizontal",
21662		range: false,
21663		step: 1,
21664		value: 0,
21665		values: null,
21666
21667		// callbacks
21668		change: null,
21669		slide: null,
21670		start: null,
21671		stop: null
21672	},
21673
21674	_create: function() {
21675		this._keySliding = false;
21676		this._mouseSliding = false;
21677		this._animateOff = true;
21678		this._handleIndex = null;
21679		this._detectOrientation();
21680		this._mouseInit();
21681
21682		this.element
21683			.addClass( "ui-slider" +
21684				" ui-slider-" + this.orientation +
21685				" ui-widget" +
21686				" ui-widget-content" +
21687				" ui-corner-all");
21688
21689		this._refresh();
21690		this._setOption( "disabled", this.options.disabled );
21691
21692		this._animateOff = false;
21693	},
21694
21695	_refresh: function() {
21696		this._createRange();
21697		this._createHandles();
21698		this._setupEvents();
21699		this._refreshValue();
21700	},
21701
21702	_createHandles: function() {
21703		var i, handleCount,
21704			options = this.options,
21705			existingHandles = this.element.find( ".ui-slider-handle" ).addClass( "ui-state-default ui-corner-all" ),
21706			handle = "<a class='ui-slider-handle ui-state-default ui-corner-all' href='#'></a>",
21707			handles = [];
21708
21709		handleCount = ( options.values && options.values.length ) || 1;
21710
21711		if ( existingHandles.length > handleCount ) {
21712			existingHandles.slice( handleCount ).remove();
21713			existingHandles = existingHandles.slice( 0, handleCount );
21714		}
21715
21716		for ( i = existingHandles.length; i < handleCount; i++ ) {
21717			handles.push( handle );
21718		}
21719
21720		this.handles = existingHandles.add( $( handles.join( "" ) ).appendTo( this.element ) );
21721
21722		this.handle = this.handles.eq( 0 );
21723
21724		this.handles.each(function( i ) {
21725			$( this ).data( "ui-slider-handle-index", i );
21726		});
21727	},
21728
21729	_createRange: function() {
21730		var options = this.options,
21731			classes = "";
21732
21733		if ( options.range ) {
21734			if ( options.range === true ) {
21735				if ( !options.values ) {
21736					options.values = [ this._valueMin(), this._valueMin() ];
21737				} else if ( options.values.length && options.values.length !== 2 ) {
21738					options.values = [ options.values[0], options.values[0] ];
21739				} else if ( $.isArray( options.values ) ) {
21740					options.values = options.values.slice(0);
21741				}
21742			}
21743
21744			if ( !this.range || !this.range.length ) {
21745				this.range = $( "<div></div>" )
21746					.appendTo( this.element );
21747
21748				classes = "ui-slider-range" +
21749				// note: this isn't the most fittingly semantic framework class for this element,
21750				// but worked best visually with a variety of themes
21751				" ui-widget-header ui-corner-all";
21752			} else {
21753				this.range.removeClass( "ui-slider-range-min ui-slider-range-max" )
21754					// Handle range switching from true to min/max
21755					.css({
21756						"left": "",
21757						"bottom": ""
21758					});
21759			}
21760
21761			this.range.addClass( classes +
21762				( ( options.range === "min" || options.range === "max" ) ? " ui-slider-range-" + options.range : "" ) );
21763		} else {
21764			this.range = $([]);
21765		}
21766	},
21767
21768	_setupEvents: function() {
21769		var elements = this.handles.add( this.range ).filter( "a" );
21770		this._off( elements );
21771		this._on( elements, this._handleEvents );
21772		this._hoverable( elements );
21773		this._focusable( elements );
21774	},
21775
21776	_destroy: function() {
21777		this.handles.remove();
21778		this.range.remove();
21779
21780		this.element
21781			.removeClass( "ui-slider" +
21782				" ui-slider-horizontal" +
21783				" ui-slider-vertical" +
21784				" ui-widget" +
21785				" ui-widget-content" +
21786				" ui-corner-all" );
21787
21788		this._mouseDestroy();
21789	},
21790
21791	_mouseCapture: function( event ) {
21792		var position, normValue, distance, closestHandle, index, allowed, offset, mouseOverHandle,
21793			that = this,
21794			o = this.options;
21795
21796		if ( o.disabled ) {
21797			return false;
21798		}
21799
21800		this.elementSize = {
21801			width: this.element.outerWidth(),
21802			height: this.element.outerHeight()
21803		};
21804		this.elementOffset = this.element.offset();
21805
21806		position = { x: event.pageX, y: event.pageY };
21807		normValue = this._normValueFromMouse( position );
21808		distance = this._valueMax() - this._valueMin() + 1;
21809		this.handles.each(function( i ) {
21810			var thisDistance = Math.abs( normValue - that.values(i) );
21811			if (( distance > thisDistance ) ||
21812				( distance === thisDistance &&
21813					(i === that._lastChangedValue || that.values(i) === o.min ))) {
21814				distance = thisDistance;
21815				closestHandle = $( this );
21816				index = i;
21817			}
21818		});
21819
21820		allowed = this._start( event, index );
21821		if ( allowed === false ) {
21822			return false;
21823		}
21824		this._mouseSliding = true;
21825
21826		this._handleIndex = index;
21827
21828		closestHandle
21829			.addClass( "ui-state-active" )
21830			.focus();
21831
21832		offset = closestHandle.offset();
21833		mouseOverHandle = !$( event.target ).parents().addBack().is( ".ui-slider-handle" );
21834		this._clickOffset = mouseOverHandle ? { left: 0, top: 0 } : {
21835			left: event.pageX - offset.left - ( closestHandle.width() / 2 ),
21836			top: event.pageY - offset.top -
21837				( closestHandle.height() / 2 ) -
21838				( parseInt( closestHandle.css("borderTopWidth"), 10 ) || 0 ) -
21839				( parseInt( closestHandle.css("borderBottomWidth"), 10 ) || 0) +
21840				( parseInt( closestHandle.css("marginTop"), 10 ) || 0)
21841		};
21842
21843		if ( !this.handles.hasClass( "ui-state-hover" ) ) {
21844			this._slide( event, index, normValue );
21845		}
21846		this._animateOff = true;
21847		return true;
21848	},
21849
21850	_mouseStart: function() {
21851		return true;
21852	},
21853
21854	_mouseDrag: function( event ) {
21855		var position = { x: event.pageX, y: event.pageY },
21856			normValue = this._normValueFromMouse( position );
21857
21858		this._slide( event, this._handleIndex, normValue );
21859
21860		return false;
21861	},
21862
21863	_mouseStop: function( event ) {
21864		this.handles.removeClass( "ui-state-active" );
21865		this._mouseSliding = false;
21866
21867		this._stop( event, this._handleIndex );
21868		this._change( event, this._handleIndex );
21869
21870		this._handleIndex = null;
21871		this._clickOffset = null;
21872		this._animateOff = false;
21873
21874		return false;
21875	},
21876
21877	_detectOrientation: function() {
21878		this.orientation = ( this.options.orientation === "vertical" ) ? "vertical" : "horizontal";
21879	},
21880
21881	_normValueFromMouse: function( position ) {
21882		var pixelTotal,
21883			pixelMouse,
21884			percentMouse,
21885			valueTotal,
21886			valueMouse;
21887
21888		if ( this.orientation === "horizontal" ) {
21889			pixelTotal = this.elementSize.width;
21890			pixelMouse = position.x - this.elementOffset.left - ( this._clickOffset ? this._clickOffset.left : 0 );
21891		} else {
21892			pixelTotal = this.elementSize.height;
21893			pixelMouse = position.y - this.elementOffset.top - ( this._clickOffset ? this._clickOffset.top : 0 );
21894		}
21895
21896		percentMouse = ( pixelMouse / pixelTotal );
21897		if ( percentMouse > 1 ) {
21898			percentMouse = 1;
21899		}
21900		if ( percentMouse < 0 ) {
21901			percentMouse = 0;
21902		}
21903		if ( this.orientation === "vertical" ) {
21904			percentMouse = 1 - percentMouse;
21905		}
21906
21907		valueTotal = this._valueMax() - this._valueMin();
21908		valueMouse = this._valueMin() + percentMouse * valueTotal;
21909
21910		return this._trimAlignValue( valueMouse );
21911	},
21912
21913	_start: function( event, index ) {
21914		var uiHash = {
21915			handle: this.handles[ index ],
21916			value: this.value()
21917		};
21918		if ( this.options.values && this.options.values.length ) {
21919			uiHash.value = this.values( index );
21920			uiHash.values = this.values();
21921		}
21922		return this._trigger( "start", event, uiHash );
21923	},
21924
21925	_slide: function( event, index, newVal ) {
21926		var otherVal,
21927			newValues,
21928			allowed;
21929
21930		if ( this.options.values && this.options.values.length ) {
21931			otherVal = this.values( index ? 0 : 1 );
21932
21933			if ( ( this.options.values.length === 2 && this.options.range === true ) &&
21934					( ( index === 0 && newVal > otherVal) || ( index === 1 && newVal < otherVal ) )
21935				) {
21936				newVal = otherVal;
21937			}
21938
21939			if ( newVal !== this.values( index ) ) {
21940				newValues = this.values();
21941				newValues[ index ] = newVal;
21942				// A slide can be canceled by returning false from the slide callback
21943				allowed = this._trigger( "slide", event, {
21944					handle: this.handles[ index ],
21945					value: newVal,
21946					values: newValues
21947				} );
21948				otherVal = this.values( index ? 0 : 1 );
21949				if ( allowed !== false ) {
21950					this.values( index, newVal, true );
21951				}
21952			}
21953		} else {
21954			if ( newVal !== this.value() ) {
21955				// A slide can be canceled by returning false from the slide callback
21956				allowed = this._trigger( "slide", event, {
21957					handle: this.handles[ index ],
21958					value: newVal
21959				} );
21960				if ( allowed !== false ) {
21961					this.value( newVal );
21962				}
21963			}
21964		}
21965	},
21966
21967	_stop: function( event, index ) {
21968		var uiHash = {
21969			handle: this.handles[ index ],
21970			value: this.value()
21971		};
21972		if ( this.options.values && this.options.values.length ) {
21973			uiHash.value = this.values( index );
21974			uiHash.values = this.values();
21975		}
21976
21977		this._trigger( "stop", event, uiHash );
21978	},
21979
21980	_change: function( event, index ) {
21981		if ( !this._keySliding && !this._mouseSliding ) {
21982			var uiHash = {
21983				handle: this.handles[ index ],
21984				value: this.value()
21985			};
21986			if ( this.options.values && this.options.values.length ) {
21987				uiHash.value = this.values( index );
21988				uiHash.values = this.values();
21989			}
21990
21991			//store the last changed value index for reference when handles overlap
21992			this._lastChangedValue = index;
21993
21994			this._trigger( "change", event, uiHash );
21995		}
21996	},
21997
21998	value: function( newValue ) {
21999		if ( arguments.length ) {
22000			this.options.value = this._trimAlignValue( newValue );
22001			this._refreshValue();
22002			this._change( null, 0 );
22003			return;
22004		}
22005
22006		return this._value();
22007	},
22008
22009	values: function( index, newValue ) {
22010		var vals,
22011			newValues,
22012			i;
22013
22014		if ( arguments.length > 1 ) {
22015			this.options.values[ index ] = this._trimAlignValue( newValue );
22016			this._refreshValue();
22017			this._change( null, index );
22018			return;
22019		}
22020
22021		if ( arguments.length ) {
22022			if ( $.isArray( arguments[ 0 ] ) ) {
22023				vals = this.options.values;
22024				newValues = arguments[ 0 ];
22025				for ( i = 0; i < vals.length; i += 1 ) {
22026					vals[ i ] = this._trimAlignValue( newValues[ i ] );
22027					this._change( null, i );
22028				}
22029				this._refreshValue();
22030			} else {
22031				if ( this.options.values && this.options.values.length ) {
22032					return this._values( index );
22033				} else {
22034					return this.value();
22035				}
22036			}
22037		} else {
22038			return this._values();
22039		}
22040	},
22041
22042	_setOption: function( key, value ) {
22043		var i,
22044			valsLength = 0;
22045
22046		if ( key === "range" && this.options.range === true ) {
22047			if ( value === "min" ) {
22048				this.options.value = this._values( 0 );
22049				this.options.values = null;
22050			} else if ( value === "max" ) {
22051				this.options.value = this._values( this.options.values.length-1 );
22052				this.options.values = null;
22053			}
22054		}
22055
22056		if ( $.isArray( this.options.values ) ) {
22057			valsLength = this.options.values.length;
22058		}
22059
22060		$.Widget.prototype._setOption.apply( this, arguments );
22061
22062		switch ( key ) {
22063			case "orientation":
22064				this._detectOrientation();
22065				this.element
22066					.removeClass( "ui-slider-horizontal ui-slider-vertical" )
22067					.addClass( "ui-slider-" + this.orientation );
22068				this._refreshValue();
22069				break;
22070			case "value":
22071				this._animateOff = true;
22072				this._refreshValue();
22073				this._change( null, 0 );
22074				this._animateOff = false;
22075				break;
22076			case "values":
22077				this._animateOff = true;
22078				this._refreshValue();
22079				for ( i = 0; i < valsLength; i += 1 ) {
22080					this._change( null, i );
22081				}
22082				this._animateOff = false;
22083				break;
22084			case "min":
22085			case "max":
22086				this._animateOff = true;
22087				this._refreshValue();
22088				this._animateOff = false;
22089				break;
22090			case "range":
22091				this._animateOff = true;
22092				this._refresh();
22093				this._animateOff = false;
22094				break;
22095		}
22096	},
22097
22098	//internal value getter
22099	// _value() returns value trimmed by min and max, aligned by step
22100	_value: function() {
22101		var val = this.options.value;
22102		val = this._trimAlignValue( val );
22103
22104		return val;
22105	},
22106
22107	//internal values getter
22108	// _values() returns array of values trimmed by min and max, aligned by step
22109	// _values( index ) returns single value trimmed by min and max, aligned by step
22110	_values: function( index ) {
22111		var val,
22112			vals,
22113			i;
22114
22115		if ( arguments.length ) {
22116			val = this.options.values[ index ];
22117			val = this._trimAlignValue( val );
22118
22119			return val;
22120		} else if ( this.options.values && this.options.values.length ) {
22121			// .slice() creates a copy of the array
22122			// this copy gets trimmed by min and max and then returned
22123			vals = this.options.values.slice();
22124			for ( i = 0; i < vals.length; i+= 1) {
22125				vals[ i ] = this._trimAlignValue( vals[ i ] );
22126			}
22127
22128			return vals;
22129		} else {
22130			return [];
22131		}
22132	},
22133
22134	// returns the step-aligned value that val is closest to, between (inclusive) min and max
22135	_trimAlignValue: function( val ) {
22136		if ( val <= this._valueMin() ) {
22137			return this._valueMin();
22138		}
22139		if ( val >= this._valueMax() ) {
22140			return this._valueMax();
22141		}
22142		var step = ( this.options.step > 0 ) ? this.options.step : 1,
22143			valModStep = (val - this._valueMin()) % step,
22144			alignValue = val - valModStep;
22145
22146		if ( Math.abs(valModStep) * 2 >= step ) {
22147			alignValue += ( valModStep > 0 ) ? step : ( -step );
22148		}
22149
22150		// Since JavaScript has problems with large floats, round
22151		// the final value to 5 digits after the decimal point (see #4124)
22152		return parseFloat( alignValue.toFixed(5) );
22153	},
22154
22155	_valueMin: function() {
22156		return this.options.min;
22157	},
22158
22159	_valueMax: function() {
22160		return this.options.max;
22161	},
22162
22163	_refreshValue: function() {
22164		var lastValPercent, valPercent, value, valueMin, valueMax,
22165			oRange = this.options.range,
22166			o = this.options,
22167			that = this,
22168			animate = ( !this._animateOff ) ? o.animate : false,
22169			_set = {};
22170
22171		if ( this.options.values && this.options.values.length ) {
22172			this.handles.each(function( i ) {
22173				valPercent = ( that.values(i) - that._valueMin() ) / ( that._valueMax() - that._valueMin() ) * 100;
22174				_set[ that.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
22175				$( this ).stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
22176				if ( that.options.range === true ) {
22177					if ( that.orientation === "horizontal" ) {
22178						if ( i === 0 ) {
22179							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { left: valPercent + "%" }, o.animate );
22180						}
22181						if ( i === 1 ) {
22182							that.range[ animate ? "animate" : "css" ]( { width: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
22183						}
22184					} else {
22185						if ( i === 0 ) {
22186							that.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { bottom: ( valPercent ) + "%" }, o.animate );
22187						}
22188						if ( i === 1 ) {
22189							that.range[ animate ? "animate" : "css" ]( { height: ( valPercent - lastValPercent ) + "%" }, { queue: false, duration: o.animate } );
22190						}
22191					}
22192				}
22193				lastValPercent = valPercent;
22194			});
22195		} else {
22196			value = this.value();
22197			valueMin = this._valueMin();
22198			valueMax = this._valueMax();
22199			valPercent = ( valueMax !== valueMin ) ?
22200					( value - valueMin ) / ( valueMax - valueMin ) * 100 :
22201					0;
22202			_set[ this.orientation === "horizontal" ? "left" : "bottom" ] = valPercent + "%";
22203			this.handle.stop( 1, 1 )[ animate ? "animate" : "css" ]( _set, o.animate );
22204
22205			if ( oRange === "min" && this.orientation === "horizontal" ) {
22206				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { width: valPercent + "%" }, o.animate );
22207			}
22208			if ( oRange === "max" && this.orientation === "horizontal" ) {
22209				this.range[ animate ? "animate" : "css" ]( { width: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
22210			}
22211			if ( oRange === "min" && this.orientation === "vertical" ) {
22212				this.range.stop( 1, 1 )[ animate ? "animate" : "css" ]( { height: valPercent + "%" }, o.animate );
22213			}
22214			if ( oRange === "max" && this.orientation === "vertical" ) {
22215				this.range[ animate ? "animate" : "css" ]( { height: ( 100 - valPercent ) + "%" }, { queue: false, duration: o.animate } );
22216			}
22217		}
22218	},
22219
22220	_handleEvents: {
22221		keydown: function( event ) {
22222			/*jshint maxcomplexity:25*/
22223			var allowed, curVal, newVal, step,
22224				index = $( event.target ).data( "ui-slider-handle-index" );
22225
22226			switch ( event.keyCode ) {
22227				case $.ui.keyCode.HOME:
22228				case $.ui.keyCode.END:
22229				case $.ui.keyCode.PAGE_UP:
22230				case $.ui.keyCode.PAGE_DOWN:
22231				case $.ui.keyCode.UP:
22232				case $.ui.keyCode.RIGHT:
22233				case $.ui.keyCode.DOWN:
22234				case $.ui.keyCode.LEFT:
22235					event.preventDefault();
22236					if ( !this._keySliding ) {
22237						this._keySliding = true;
22238						$( event.target ).addClass( "ui-state-active" );
22239						allowed = this._start( event, index );
22240						if ( allowed === false ) {
22241							return;
22242						}
22243					}
22244					break;
22245			}
22246
22247			step = this.options.step;
22248			if ( this.options.values && this.options.values.length ) {
22249				curVal = newVal = this.values( index );
22250			} else {
22251				curVal = newVal = this.value();
22252			}
22253
22254			switch ( event.keyCode ) {
22255				case $.ui.keyCode.HOME:
22256					newVal = this._valueMin();
22257					break;
22258				case $.ui.keyCode.END:
22259					newVal = this._valueMax();
22260					break;
22261				case $.ui.keyCode.PAGE_UP:
22262					newVal = this._trimAlignValue( curVal + ( (this._valueMax() - this._valueMin()) / numPages ) );
22263					break;
22264				case $.ui.keyCode.PAGE_DOWN:
22265					newVal = this._trimAlignValue( curVal - ( (this._valueMax() - this._valueMin()) / numPages ) );
22266					break;
22267				case $.ui.keyCode.UP:
22268				case $.ui.keyCode.RIGHT:
22269					if ( curVal === this._valueMax() ) {
22270						return;
22271					}
22272					newVal = this._trimAlignValue( curVal + step );
22273					break;
22274				case $.ui.keyCode.DOWN:
22275				case $.ui.keyCode.LEFT:
22276					if ( curVal === this._valueMin() ) {
22277						return;
22278					}
22279					newVal = this._trimAlignValue( curVal - step );
22280					break;
22281			}
22282
22283			this._slide( event, index, newVal );
22284		},
22285		click: function( event ) {
22286			event.preventDefault();
22287		},
22288		keyup: function( event ) {
22289			var index = $( event.target ).data( "ui-slider-handle-index" );
22290
22291			if ( this._keySliding ) {
22292				this._keySliding = false;
22293				this._stop( event, index );
22294				this._change( event, index );
22295				$( event.target ).removeClass( "ui-state-active" );
22296			}
22297		}
22298	}
22299
22300});
22301
22302}(jQuery));
22303
22304
22305
22306
22307/*!
22308 * jQuery UI Sortable 1.10.3
22309 * http://jqueryui.com
22310 *
22311 * Copyright 2013 jQuery Foundation and other contributors
22312 * Released under the MIT license.
22313 * http://jquery.org/license
22314 *
22315 * http://api.jqueryui.com/sortable/
22316 *
22317 * Depends:
22318 *	jquery.ui.core.js
22319 *	jquery.ui.mouse.js
22320 *	jquery.ui.widget.js
22321 */
22322
22323(function( $, undefined ) {
22324
22325/*jshint loopfunc: true */
22326
22327function isOverAxis( x, reference, size ) {
22328	return ( x > reference ) && ( x < ( reference + size ) );
22329}
22330
22331function isFloating(item) {
22332	return (/left|right/).test(item.css("float")) || (/inline|table-cell/).test(item.css("display"));
22333}
22334
22335$.widget("ui.sortable", $.ui.mouse, {
22336	version: "1.10.3",
22337	widgetEventPrefix: "sort",
22338	ready: false,
22339	options: {
22340		appendTo: "parent",
22341		axis: false,
22342		connectWith: false,
22343		containment: false,
22344		cursor: "auto",
22345		cursorAt: false,
22346		dropOnEmpty: true,
22347		forcePlaceholderSize: false,
22348		forceHelperSize: false,
22349		grid: false,
22350		handle: false,
22351		helper: "original",
22352		items: "> *",
22353		opacity: false,
22354		placeholder: false,
22355		revert: false,
22356		scroll: true,
22357		scrollSensitivity: 20,
22358		scrollSpeed: 20,
22359		scope: "default",
22360		tolerance: "intersect",
22361		zIndex: 1000,
22362
22363		// callbacks
22364		activate: null,
22365		beforeStop: null,
22366		change: null,
22367		deactivate: null,
22368		out: null,
22369		over: null,
22370		receive: null,
22371		remove: null,
22372		sort: null,
22373		start: null,
22374		stop: null,
22375		update: null
22376	},
22377	_create: function() {
22378
22379		var o = this.options;
22380		this.containerCache = {};
22381		this.element.addClass("ui-sortable");
22382
22383		//Get the items
22384		this.refresh();
22385
22386		//Let's determine if the items are being displayed horizontally
22387		this.floating = this.items.length ? o.axis === "x" || isFloating(this.items[0].item) : false;
22388
22389		//Let's determine the parent's offset
22390		this.offset = this.element.offset();
22391
22392		//Initialize mouse events for interaction
22393		this._mouseInit();
22394
22395		//We're ready to go
22396		this.ready = true;
22397
22398	},
22399
22400	_destroy: function() {
22401		this.element
22402			.removeClass("ui-sortable ui-sortable-disabled");
22403		this._mouseDestroy();
22404
22405		for ( var i = this.items.length - 1; i >= 0; i-- ) {
22406			this.items[i].item.removeData(this.widgetName + "-item");
22407		}
22408
22409		return this;
22410	},
22411
22412	_setOption: function(key, value){
22413		if ( key === "disabled" ) {
22414			this.options[ key ] = value;
22415
22416			this.widget().toggleClass( "ui-sortable-disabled", !!value );
22417		} else {
22418			// Don't call widget base _setOption for disable as it adds ui-state-disabled class
22419			$.Widget.prototype._setOption.apply(this, arguments);
22420		}
22421	},
22422
22423	_mouseCapture: function(event, overrideHandle) {
22424		var currentItem = null,
22425			validHandle = false,
22426			that = this;
22427
22428		if (this.reverting) {
22429			return false;
22430		}
22431
22432		if(this.options.disabled || this.options.type === "static") {
22433			return false;
22434		}
22435
22436		//We have to refresh the items data once first
22437		this._refreshItems(event);
22438
22439		//Find out if the clicked node (or one of its parents) is a actual item in this.items
22440		$(event.target).parents().each(function() {
22441			if($.data(this, that.widgetName + "-item") === that) {
22442				currentItem = $(this);
22443				return false;
22444			}
22445		});
22446		if($.data(event.target, that.widgetName + "-item") === that) {
22447			currentItem = $(event.target);
22448		}
22449
22450		if(!currentItem) {
22451			return false;
22452		}
22453		if(this.options.handle && !overrideHandle) {
22454			$(this.options.handle, currentItem).find("*").addBack().each(function() {
22455				if(this === event.target) {
22456					validHandle = true;
22457				}
22458			});
22459			if(!validHandle) {
22460				return false;
22461			}
22462		}
22463
22464		this.currentItem = currentItem;
22465		this._removeCurrentsFromItems();
22466		return true;
22467
22468	},
22469
22470	_mouseStart: function(event, overrideHandle, noActivation) {
22471
22472		var i, body,
22473			o = this.options;
22474
22475		this.currentContainer = this;
22476
22477		//We only need to call refreshPositions, because the refreshItems call has been moved to mouseCapture
22478		this.refreshPositions();
22479
22480		//Create and append the visible helper
22481		this.helper = this._createHelper(event);
22482
22483		//Cache the helper size
22484		this._cacheHelperProportions();
22485
22486		/*
22487		 * - Position generation -
22488		 * This block generates everything position related - it's the core of draggables.
22489		 */
22490
22491		//Cache the margins of the original element
22492		this._cacheMargins();
22493
22494		//Get the next scrolling parent
22495		this.scrollParent = this.helper.scrollParent();
22496
22497		//The element's absolute position on the page minus margins
22498		this.offset = this.currentItem.offset();
22499		this.offset = {
22500			top: this.offset.top - this.margins.top,
22501			left: this.offset.left - this.margins.left
22502		};
22503
22504		$.extend(this.offset, {
22505			click: { //Where the click happened, relative to the element
22506				left: event.pageX - this.offset.left,
22507				top: event.pageY - this.offset.top
22508			},
22509			parent: this._getParentOffset(),
22510			relative: this._getRelativeOffset() //This is a relative to absolute position minus the actual position calculation - only used for relative positioned helper
22511		});
22512
22513		// Only after we got the offset, we can change the helper's position to absolute
22514		// TODO: Still need to figure out a way to make relative sorting possible
22515		this.helper.css("position", "absolute");
22516		this.cssPosition = this.helper.css("position");
22517
22518		//Generate the original position
22519		this.originalPosition = this._generatePosition(event);
22520		this.originalPageX = event.pageX;
22521		this.originalPageY = event.pageY;
22522
22523		//Adjust the mouse offset relative to the helper if "cursorAt" is supplied
22524		(o.cursorAt && this._adjustOffsetFromHelper(o.cursorAt));
22525
22526		//Cache the former DOM position
22527		this.domPosition = { prev: this.currentItem.prev()[0], parent: this.currentItem.parent()[0] };
22528
22529		//If the helper is not the original, hide the original so it's not playing any role during the drag, won't cause anything bad this way
22530		if(this.helper[0] !== this.currentItem[0]) {
22531			this.currentItem.hide();
22532		}
22533
22534		//Create the placeholder
22535		this._createPlaceholder();
22536
22537		//Set a containment if given in the options
22538		if(o.containment) {
22539			this._setContainment();
22540		}
22541
22542		if( o.cursor && o.cursor !== "auto" ) { // cursor option
22543			body = this.document.find( "body" );
22544
22545			// support: IE
22546			this.storedCursor = body.css( "cursor" );
22547			body.css( "cursor", o.cursor );
22548
22549			this.storedStylesheet = $( "<style>*{ cursor: "+o.cursor+" !important; }</style>" ).appendTo( body );
22550		}
22551
22552		if(o.opacity) { // opacity option
22553			if (this.helper.css("opacity")) {
22554				this._storedOpacity = this.helper.css("opacity");
22555			}
22556			this.helper.css("opacity", o.opacity);
22557		}
22558
22559		if(o.zIndex) { // zIndex option
22560			if (this.helper.css("zIndex")) {
22561				this._storedZIndex = this.helper.css("zIndex");
22562			}
22563			this.helper.css("zIndex", o.zIndex);
22564		}
22565
22566		//Prepare scrolling
22567		if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
22568			this.overflowOffset = this.scrollParent.offset();
22569		}
22570
22571		//Call callbacks
22572		this._trigger("start", event, this._uiHash());
22573
22574		//Recache the helper size
22575		if(!this._preserveHelperProportions) {
22576			this._cacheHelperProportions();
22577		}
22578
22579
22580		//Post "activate" events to possible containers
22581		if( !noActivation ) {
22582			for ( i = this.containers.length - 1; i >= 0; i-- ) {
22583				this.containers[ i ]._trigger( "activate", event, this._uiHash( this ) );
22584			}
22585		}
22586
22587		//Prepare possible droppables
22588		if($.ui.ddmanager) {
22589			$.ui.ddmanager.current = this;
22590		}
22591
22592		if ($.ui.ddmanager && !o.dropBehaviour) {
22593			$.ui.ddmanager.prepareOffsets(this, event);
22594		}
22595
22596		this.dragging = true;
22597
22598		this.helper.addClass("ui-sortable-helper");
22599		this._mouseDrag(event); //Execute the drag once - this causes the helper not to be visible before getting its correct position
22600		return true;
22601
22602	},
22603
22604	_mouseDrag: function(event) {
22605		var i, item, itemElement, intersection,
22606			o = this.options,
22607			scrolled = false;
22608
22609		//Compute the helpers position
22610		this.position = this._generatePosition(event);
22611		this.positionAbs = this._convertPositionTo("absolute");
22612
22613		if (!this.lastPositionAbs) {
22614			this.lastPositionAbs = this.positionAbs;
22615		}
22616
22617		//Do scrolling
22618		if(this.options.scroll) {
22619			if(this.scrollParent[0] !== document && this.scrollParent[0].tagName !== "HTML") {
22620
22621				if((this.overflowOffset.top + this.scrollParent[0].offsetHeight) - event.pageY < o.scrollSensitivity) {
22622					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop + o.scrollSpeed;
22623				} else if(event.pageY - this.overflowOffset.top < o.scrollSensitivity) {
22624					this.scrollParent[0].scrollTop = scrolled = this.scrollParent[0].scrollTop - o.scrollSpeed;
22625				}
22626
22627				if((this.overflowOffset.left + this.scrollParent[0].offsetWidth) - event.pageX < o.scrollSensitivity) {
22628					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft + o.scrollSpeed;
22629				} else if(event.pageX - this.overflowOffset.left < o.scrollSensitivity) {
22630					this.scrollParent[0].scrollLeft = scrolled = this.scrollParent[0].scrollLeft - o.scrollSpeed;
22631				}
22632
22633			} else {
22634
22635				if(event.pageY - $(document).scrollTop() < o.scrollSensitivity) {
22636					scrolled = $(document).scrollTop($(document).scrollTop() - o.scrollSpeed);
22637				} else if($(window).height() - (event.pageY - $(document).scrollTop()) < o.scrollSensitivity) {
22638					scrolled = $(document).scrollTop($(document).scrollTop() + o.scrollSpeed);
22639				}
22640
22641				if(event.pageX - $(document).scrollLeft() < o.scrollSensitivity) {
22642					scrolled = $(document).scrollLeft($(document).scrollLeft() - o.scrollSpeed);
22643				} else if($(window).width() - (event.pageX - $(document).scrollLeft()) < o.scrollSensitivity) {
22644					scrolled = $(document).scrollLeft($(document).scrollLeft() + o.scrollSpeed);
22645				}
22646
22647			}
22648
22649			if(scrolled !== false && $.ui.ddmanager && !o.dropBehaviour) {
22650				$.ui.ddmanager.prepareOffsets(this, event);
22651			}
22652		}
22653
22654		//Regenerate the absolute position used for position checks
22655		this.positionAbs = this._convertPositionTo("absolute");
22656
22657		//Set the helper position
22658		if(!this.options.axis || this.options.axis !== "y") {
22659			this.helper[0].style.left = this.position.left+"px";
22660		}
22661		if(!this.options.axis || this.options.axis !== "x") {
22662			this.helper[0].style.top = this.position.top+"px";
22663		}
22664
22665		//Rearrange
22666		for (i = this.items.length - 1; i >= 0; i--) {
22667
22668			//Cache variables and intersection, continue if no intersection
22669			item = this.items[i];
22670			itemElement = item.item[0];
22671			intersection = this._intersectsWithPointer(item);
22672			if (!intersection) {
22673				continue;
22674			}
22675
22676			// Only put the placeholder inside the current Container, skip all
22677			// items form other containers. This works because when moving
22678			// an item from one container to another the
22679			// currentContainer is switched before the placeholder is moved.
22680			//
22681			// Without this moving items in "sub-sortables" can cause the placeholder to jitter
22682			// beetween the outer and inner container.
22683			if (item.instance !== this.currentContainer) {
22684				continue;
22685			}
22686
22687			// cannot intersect with itself
22688			// no useless actions that have been done before
22689			// no action if the item moved is the parent of the item checked
22690			if (itemElement !== this.currentItem[0] &&
22691				this.placeholder[intersection === 1 ? "next" : "prev"]()[0] !== itemElement &&
22692				!$.contains(this.placeholder[0], itemElement) &&
22693				(this.options.type === "semi-dynamic" ? !$.contains(this.element[0], itemElement) : true)
22694			) {
22695
22696				this.direction = intersection === 1 ? "down" : "up";
22697
22698				if (this.options.tolerance === "pointer" || this._intersectsWithSides(item)) {
22699					this._rearrange(event, item);
22700				} else {
22701					break;
22702				}
22703
22704				this._trigger("change", event, this._uiHash());
22705				break;
22706			}
22707		}
22708
22709		//Post events to containers
22710		this._contactContainers(event);
22711
22712		//Interconnect with droppables
22713		if($.ui.ddmanager) {
22714			$.ui.ddmanager.drag(this, event);
22715		}
22716
22717		//Call callbacks
22718		this._trigger("sort", event, this._uiHash());
22719
22720		this.lastPositionAbs = this.positionAbs;
22721		return false;
22722
22723	},
22724
22725	_mouseStop: function(event, noPropagation) {
22726
22727		if(!event) {
22728			return;
22729		}
22730
22731		//If we are using droppables, inform the manager about the drop
22732		if ($.ui.ddmanager && !this.options.dropBehaviour) {
22733			$.ui.ddmanager.drop(this, event);
22734		}
22735
22736		if(this.options.revert) {
22737			var that = this,
22738				cur = this.placeholder.offset(),
22739				axis = this.options.axis,
22740				animation = {};
22741
22742			if ( !axis || axis === "x" ) {
22743				animation.left = cur.left - this.offset.parent.left - this.margins.left + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollLeft);
22744			}
22745			if ( !axis || axis === "y" ) {
22746				animation.top = cur.top - this.offset.parent.top - this.margins.top + (this.offsetParent[0] === document.body ? 0 : this.offsetParent[0].scrollTop);
22747			}
22748			this.reverting = true;
22749			$(this.helper).animate( animation, parseInt(this.options.revert, 10) || 500, function() {
22750				that._clear(event);
22751			});
22752		} else {
22753			this._clear(event, noPropagation);
22754		}
22755
22756		return false;
22757
22758	},
22759
22760	cancel: function() {
22761
22762		if(this.dragging) {
22763
22764			this._mouseUp({ target: null });
22765
22766			if(this.options.helper === "original") {
22767				this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
22768			} else {
22769				this.currentItem.show();
22770			}
22771
22772			//Post deactivating events to containers
22773			for (var i = this.containers.length - 1; i >= 0; i--){
22774				this.containers[i]._trigger("deactivate", null, this._uiHash(this));
22775				if(this.containers[i].containerCache.over) {
22776					this.containers[i]._trigger("out", null, this._uiHash(this));
22777					this.containers[i].containerCache.over = 0;
22778				}
22779			}
22780
22781		}
22782
22783		if (this.placeholder) {
22784			//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
22785			if(this.placeholder[0].parentNode) {
22786				this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
22787			}
22788			if(this.options.helper !== "original" && this.helper && this.helper[0].parentNode) {
22789				this.helper.remove();
22790			}
22791
22792			$.extend(this, {
22793				helper: null,
22794				dragging: false,
22795				reverting: false,
22796				_noFinalSort: null
22797			});
22798
22799			if(this.domPosition.prev) {
22800				$(this.domPosition.prev).after(this.currentItem);
22801			} else {
22802				$(this.domPosition.parent).prepend(this.currentItem);
22803			}
22804		}
22805
22806		return this;
22807
22808	},
22809
22810	serialize: function(o) {
22811
22812		var items = this._getItemsAsjQuery(o && o.connected),
22813			str = [];
22814		o = o || {};
22815
22816		$(items).each(function() {
22817			var res = ($(o.item || this).attr(o.attribute || "id") || "").match(o.expression || (/(.+)[\-=_](.+)/));
22818			if (res) {
22819				str.push((o.key || res[1]+"[]")+"="+(o.key && o.expression ? res[1] : res[2]));
22820			}
22821		});
22822
22823		if(!str.length && o.key) {
22824			str.push(o.key + "=");
22825		}
22826
22827		return str.join("&");
22828
22829	},
22830
22831	toArray: function(o) {
22832
22833		var items = this._getItemsAsjQuery(o && o.connected),
22834			ret = [];
22835
22836		o = o || {};
22837
22838		items.each(function() { ret.push($(o.item || this).attr(o.attribute || "id") || ""); });
22839		return ret;
22840
22841	},
22842
22843	/* Be careful with the following core functions */
22844	_intersectsWith: function(item) {
22845
22846		var x1 = this.positionAbs.left,
22847			x2 = x1 + this.helperProportions.width,
22848			y1 = this.positionAbs.top,
22849			y2 = y1 + this.helperProportions.height,
22850			l = item.left,
22851			r = l + item.width,
22852			t = item.top,
22853			b = t + item.height,
22854			dyClick = this.offset.click.top,
22855			dxClick = this.offset.click.left,
22856			isOverElementHeight = ( this.options.axis === "x" ) || ( ( y1 + dyClick ) > t && ( y1 + dyClick ) < b ),
22857			isOverElementWidth = ( this.options.axis === "y" ) || ( ( x1 + dxClick ) > l && ( x1 + dxClick ) < r ),
22858			isOverElement = isOverElementHeight && isOverElementWidth;
22859
22860		if ( this.options.tolerance === "pointer" ||
22861			this.options.forcePointerForContainers ||
22862			(this.options.tolerance !== "pointer" && this.helperProportions[this.floating ? "width" : "height"] > item[this.floating ? "width" : "height"])
22863		) {
22864			return isOverElement;
22865		} else {
22866
22867			return (l < x1 + (this.helperProportions.width / 2) && // Right Half
22868				x2 - (this.helperProportions.width / 2) < r && // Left Half
22869				t < y1 + (this.helperProportions.height / 2) && // Bottom Half
22870				y2 - (this.helperProportions.height / 2) < b ); // Top Half
22871
22872		}
22873	},
22874
22875	_intersectsWithPointer: function(item) {
22876
22877		var isOverElementHeight = (this.options.axis === "x") || isOverAxis(this.positionAbs.top + this.offset.click.top, item.top, item.height),
22878			isOverElementWidth = (this.options.axis === "y") || isOverAxis(this.positionAbs.left + this.offset.click.left, item.left, item.width),
22879			isOverElement = isOverElementHeight && isOverElementWidth,
22880			verticalDirection = this._getDragVerticalDirection(),
22881			horizontalDirection = this._getDragHorizontalDirection();
22882
22883		if (!isOverElement) {
22884			return false;
22885		}
22886
22887		return this.floating ?
22888			( ((horizontalDirection && horizontalDirection === "right") || verticalDirection === "down") ? 2 : 1 )
22889			: ( verticalDirection && (verticalDirection === "down" ? 2 : 1) );
22890
22891	},
22892
22893	_intersectsWithSides: function(item) {
22894
22895		var isOverBottomHalf = isOverAxis(this.positionAbs.top + this.offset.click.top, item.top + (item.height/2), item.height),
22896			isOverRightHalf = isOverAxis(this.positionAbs.left + this.offset.click.left, item.left + (item.width/2), item.width),
22897			verticalDirection = this._getDragVerticalDirection(),
22898			horizontalDirection = this._getDragHorizontalDirection();
22899
22900		if (this.floating && horizontalDirection) {
22901			return ((horizontalDirection === "right" && isOverRightHalf) || (horizontalDirection === "left" && !isOverRightHalf));
22902		} else {
22903			return verticalDirection && ((verticalDirection === "down" && isOverBottomHalf) || (verticalDirection === "up" && !isOverBottomHalf));
22904		}
22905
22906	},
22907
22908	_getDragVerticalDirection: function() {
22909		var delta = this.positionAbs.top - this.lastPositionAbs.top;
22910		return delta !== 0 && (delta > 0 ? "down" : "up");
22911	},
22912
22913	_getDragHorizontalDirection: function() {
22914		var delta = this.positionAbs.left - this.lastPositionAbs.left;
22915		return delta !== 0 && (delta > 0 ? "right" : "left");
22916	},
22917
22918	refresh: function(event) {
22919		this._refreshItems(event);
22920		this.refreshPositions();
22921		return this;
22922	},
22923
22924	_connectWith: function() {
22925		var options = this.options;
22926		return options.connectWith.constructor === String ? [options.connectWith] : options.connectWith;
22927	},
22928
22929	_getItemsAsjQuery: function(connected) {
22930
22931		var i, j, cur, inst,
22932			items = [],
22933			queries = [],
22934			connectWith = this._connectWith();
22935
22936		if(connectWith && connected) {
22937			for (i = connectWith.length - 1; i >= 0; i--){
22938				cur = $(connectWith[i]);
22939				for ( j = cur.length - 1; j >= 0; j--){
22940					inst = $.data(cur[j], this.widgetFullName);
22941					if(inst && inst !== this && !inst.options.disabled) {
22942						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element) : $(inst.options.items, inst.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), inst]);
22943					}
22944				}
22945			}
22946		}
22947
22948		queries.push([$.isFunction(this.options.items) ? this.options.items.call(this.element, null, { options: this.options, item: this.currentItem }) : $(this.options.items, this.element).not(".ui-sortable-helper").not(".ui-sortable-placeholder"), this]);
22949
22950		for (i = queries.length - 1; i >= 0; i--){
22951			queries[i][0].each(function() {
22952				items.push(this);
22953			});
22954		}
22955
22956		return $(items);
22957
22958	},
22959
22960	_removeCurrentsFromItems: function() {
22961
22962		var list = this.currentItem.find(":data(" + this.widgetName + "-item)");
22963
22964		this.items = $.grep(this.items, function (item) {
22965			for (var j=0; j < list.length; j++) {
22966				if(list[j] === item.item[0]) {
22967					return false;
22968				}
22969			}
22970			return true;
22971		});
22972
22973	},
22974
22975	_refreshItems: function(event) {
22976
22977		this.items = [];
22978		this.containers = [this];
22979
22980		var i, j, cur, inst, targetData, _queries, item, queriesLength,
22981			items = this.items,
22982			queries = [[$.isFunction(this.options.items) ? this.options.items.call(this.element[0], event, { item: this.currentItem }) : $(this.options.items, this.element), this]],
22983			connectWith = this._connectWith();
22984
22985		if(connectWith && this.ready) { //Shouldn't be run the first time through due to massive slow-down
22986			for (i = connectWith.length - 1; i >= 0; i--){
22987				cur = $(connectWith[i]);
22988				for (j = cur.length - 1; j >= 0; j--){
22989					inst = $.data(cur[j], this.widgetFullName);
22990					if(inst && inst !== this && !inst.options.disabled) {
22991						queries.push([$.isFunction(inst.options.items) ? inst.options.items.call(inst.element[0], event, { item: this.currentItem }) : $(inst.options.items, inst.element), inst]);
22992						this.containers.push(inst);
22993					}
22994				}
22995			}
22996		}
22997
22998		for (i = queries.length - 1; i >= 0; i--) {
22999			targetData = queries[i][1];
23000			_queries = queries[i][0];
23001
23002			for (j=0, queriesLength = _queries.length; j < queriesLength; j++) {
23003				item = $(_queries[j]);
23004
23005				item.data(this.widgetName + "-item", targetData); // Data for target checking (mouse manager)
23006
23007				items.push({
23008					item: item,
23009					instance: targetData,
23010					width: 0, height: 0,
23011					left: 0, top: 0
23012				});
23013			}
23014		}
23015
23016	},
23017
23018	refreshPositions: function(fast) {
23019
23020		//This has to be redone because due to the item being moved out/into the offsetParent, the offsetParent's position will change
23021		if(this.offsetParent && this.helper) {
23022			this.offset.parent = this._getParentOffset();
23023		}
23024
23025		var i, item, t, p;
23026
23027		for (i = this.items.length - 1; i >= 0; i--){
23028			item = this.items[i];
23029
23030			//We ignore calculating positions of all connected containers when we're not over them
23031			if(item.instance !== this.currentContainer && this.currentContainer && item.item[0] !== this.currentItem[0]) {
23032				continue;
23033			}
23034
23035			t = this.options.toleranceElement ? $(this.options.toleranceElement, item.item) : item.item;
23036
23037			if (!fast) {
23038				item.width = t.outerWidth();
23039				item.height = t.outerHeight();
23040			}
23041
23042			p = t.offset();
23043			item.left = p.left;
23044			item.top = p.top;
23045		}
23046
23047		if(this.options.custom && this.options.custom.refreshContainers) {
23048			this.options.custom.refreshContainers.call(this);
23049		} else {
23050			for (i = this.containers.length - 1; i >= 0; i--){
23051				p = this.containers[i].element.offset();
23052				this.containers[i].containerCache.left = p.left;
23053				this.containers[i].containerCache.top = p.top;
23054				this.containers[i].containerCache.width	= this.containers[i].element.outerWidth();
23055				this.containers[i].containerCache.height = this.containers[i].element.outerHeight();
23056			}
23057		}
23058
23059		return this;
23060	},
23061
23062	_createPlaceholder: function(that) {
23063		that = that || this;
23064		var className,
23065			o = that.options;
23066
23067		if(!o.placeholder || o.placeholder.constructor === String) {
23068			className = o.placeholder;
23069			o.placeholder = {
23070				element: function() {
23071
23072					var nodeName = that.currentItem[0].nodeName.toLowerCase(),
23073						element = $( "<" + nodeName + ">", that.document[0] )
23074							.addClass(className || that.currentItem[0].className+" ui-sortable-placeholder")
23075							.removeClass("ui-sortable-helper");
23076
23077					if ( nodeName === "tr" ) {
23078						that.currentItem.children().each(function() {
23079							$( "<td>&#160;</td>", that.document[0] )
23080								.attr( "colspan", $( this ).attr( "colspan" ) || 1 )
23081								.appendTo( element );
23082						});
23083					} else if ( nodeName === "img" ) {
23084						element.attr( "src", that.currentItem.attr( "src" ) );
23085					}
23086
23087					if ( !className ) {
23088						element.css( "visibility", "hidden" );
23089					}
23090
23091					return element;
23092				},
23093				update: function(container, p) {
23094
23095					// 1. If a className is set as 'placeholder option, we don't force sizes - the class is responsible for that
23096					// 2. The option 'forcePlaceholderSize can be enabled to force it even if a class name is specified
23097					if(className && !o.forcePlaceholderSize) {
23098						return;
23099					}
23100
23101					//If the element doesn't have a actual height by itself (without styles coming from a stylesheet), it receives the inline height from the dragged item
23102					if(!p.height()) { p.height(that.currentItem.innerHeight() - parseInt(that.currentItem.css("paddingTop")||0, 10) - parseInt(that.currentItem.css("paddingBottom")||0, 10)); }
23103					if(!p.width()) { p.width(that.currentItem.innerWidth() - parseInt(that.currentItem.css("paddingLeft")||0, 10) - parseInt(that.currentItem.css("paddingRight")||0, 10)); }
23104				}
23105			};
23106		}
23107
23108		//Create the placeholder
23109		that.placeholder = $(o.placeholder.element.call(that.element, that.currentItem));
23110
23111		//Append it after the actual current item
23112		that.currentItem.after(that.placeholder);
23113
23114		//Update the size of the placeholder (TODO: Logic to fuzzy, see line 316/317)
23115		o.placeholder.update(that, that.placeholder);
23116
23117	},
23118
23119	_contactContainers: function(event) {
23120		var i, j, dist, itemWithLeastDistance, posProperty, sizeProperty, base, cur, nearBottom, floating,
23121			innermostContainer = null,
23122			innermostIndex = null;
23123
23124		// get innermost container that intersects with item
23125		for (i = this.containers.length - 1; i >= 0; i--) {
23126
23127			// never consider a container that's located within the item itself
23128			if($.contains(this.currentItem[0], this.containers[i].element[0])) {
23129				continue;
23130			}
23131
23132			if(this._intersectsWith(this.containers[i].containerCache)) {
23133
23134				// if we've already found a container and it's more "inner" than this, then continue
23135				if(innermostContainer && $.contains(this.containers[i].element[0], innermostContainer.element[0])) {
23136					continue;
23137				}
23138
23139				innermostContainer = this.containers[i];
23140				innermostIndex = i;
23141
23142			} else {
23143				// container doesn't intersect. trigger "out" event if necessary
23144				if(this.containers[i].containerCache.over) {
23145					this.containers[i]._trigger("out", event, this._uiHash(this));
23146					this.containers[i].containerCache.over = 0;
23147				}
23148			}
23149
23150		}
23151
23152		// if no intersecting containers found, return
23153		if(!innermostContainer) {
23154			return;
23155		}
23156
23157		// move the item into the container if it's not there already
23158		if(this.containers.length === 1) {
23159			if (!this.containers[innermostIndex].containerCache.over) {
23160				this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
23161				this.containers[innermostIndex].containerCache.over = 1;
23162			}
23163		} else {
23164
23165			//When entering a new container, we will find the item with the least distance and append our item near it
23166			dist = 10000;
23167			itemWithLeastDistance = null;
23168			floating = innermostContainer.floating || isFloating(this.currentItem);
23169			posProperty = floating ? "left" : "top";
23170			sizeProperty = floating ? "width" : "height";
23171			base = this.positionAbs[posProperty] + this.offset.click[posProperty];
23172			for (j = this.items.length - 1; j >= 0; j--) {
23173				if(!$.contains(this.containers[innermostIndex].element[0], this.items[j].item[0])) {
23174					continue;
23175				}
23176				if(this.items[j].item[0] === this.currentItem[0]) {
23177					continue;
23178				}
23179				if (floating && !isOverAxis(this.positionAbs.top + this.offset.click.top, this.items[j].top, this.items[j].height)) {
23180					continue;
23181				}
23182				cur = this.items[j].item.offset()[posProperty];
23183				nearBottom = false;
23184				if(Math.abs(cur - base) > Math.abs(cur + this.items[j][sizeProperty] - base)){
23185					nearBottom = true;
23186					cur += this.items[j][sizeProperty];
23187				}
23188
23189				if(Math.abs(cur - base) < dist) {
23190					dist = Math.abs(cur - base); itemWithLeastDistance = this.items[j
vendor: 13,544 bytes, lines 23190-23511
23190];
23191					this.direction = nearBottom ? "up": "down";
23192				}
23193			}
23194
23195			//Check if dropOnEmpty is enabled
23196			if(!itemWithLeastDistance && !this.options.dropOnEmpty) {
23197				return;
23198			}
23199
23200			if(this.currentContainer === this.containers[innermostIndex]) {
23201				return;
23202			}
23203
23204			itemWithLeastDistance ? this._rearrange(event, itemWithLeastDistance, null, true) : this._rearrange(event, null, this.containers[innermostIndex].element, true);
23205			this._trigger("change", event, this._uiHash());
23206			this.containers[innermostIndex]._trigger("change", event, this._uiHash(this));
23207			this.currentContainer = this.containers[innermostIndex];
23208
23209			//Update the placeholder
23210			this.options.placeholder.update(this.currentContainer, this.placeholder);
23211
23212			this.containers[innermostIndex]._trigger("over", event, this._uiHash(this));
23213			this.containers[innermostIndex].containerCache.over = 1;
23214		}
23215
23216
23217	},
23218
23219	_createHelper: function(event) {
23220
23221		var o = this.options,
23222			helper = $.isFunction(o.helper) ? $(o.helper.apply(this.element[0], [event, this.currentItem])) : (o.helper === "clone" ? this.currentItem.clone() : this.currentItem);
23223
23224		//Add the helper to the DOM if that didn't happen already
23225		if(!helper.parents("body").length) {
23226			$(o.appendTo !== "parent" ? o.appendTo : this.currentItem[0].parentNode)[0].appendChild(helper[0]);
23227		}
23228
23229		if(helper[0] === this.currentItem[0]) {
23230			this._storedCSS = { width: this.currentItem[0].style.width, height: this.currentItem[0].style.height, position: this.currentItem.css("position"), top: this.currentItem.css("top"), left: this.currentItem.css("left") };
23231		}
23232
23233		if(!helper[0].style.width || o.forceHelperSize) {
23234			helper.width(this.currentItem.width());
23235		}
23236		if(!helper[0].style.height || o.forceHelperSize) {
23237			helper.height(this.currentItem.height());
23238		}
23239
23240		return helper;
23241
23242	},
23243
23244	_adjustOffsetFromHelper: function(obj) {
23245		if (typeof obj === "string") {
23246			obj = obj.split(" ");
23247		}
23248		if ($.isArray(obj)) {
23249			obj = {left: +obj[0], top: +obj[1] || 0};
23250		}
23251		if ("left" in obj) {
23252			this.offset.click.left = obj.left + this.margins.left;
23253		}
23254		if ("right" in obj) {
23255			this.offset.click.left = this.helperProportions.width - obj.right + this.margins.left;
23256		}
23257		if ("top" in obj) {
23258			this.offset.click.top = obj.top + this.margins.top;
23259		}
23260		if ("bottom" in obj) {
23261			this.offset.click.top = this.helperProportions.height - obj.bottom + this.margins.top;
23262		}
23263	},
23264
23265	_getParentOffset: function() {
23266
23267
23268		//Get the offsetParent and cache its position
23269		this.offsetParent = this.helper.offsetParent();
23270		var po = this.offsetParent.offset();
23271
23272		// This is a special case where we need to modify a offset calculated on start, since the following happened:
23273		// 1. The position of the helper is absolute, so it's position is calculated based on the next positioned parent
23274		// 2. The actual offset parent is a child of the scroll parent, and the scroll parent isn't the document, which means that
23275		//    the scroll is included in the initial calculation of the offset of the parent, and never recalculated upon drag
23276		if(this.cssPosition === "absolute" && this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) {
23277			po.left += this.scrollParent.scrollLeft();
23278			po.top += this.scrollParent.scrollTop();
23279		}
23280
23281		// This needs to be actually done for all browsers, since pageX/pageY includes this information
23282		// with an ugly IE fix
23283		if( this.offsetParent[0] === document.body || (this.offsetParent[0].tagName && this.offsetParent[0].tagName.toLowerCase() === "html" && $.ui.ie)) {
23284			po = { top: 0, left: 0 };
23285		}
23286
23287		return {
23288			top: po.top + (parseInt(this.offsetParent.css("borderTopWidth"),10) || 0),
23289			left: po.left + (parseInt(this.offsetParent.css("borderLeftWidth"),10) || 0)
23290		};
23291
23292	},
23293
23294	_getRelativeOffset: function() {
23295
23296		if(this.cssPosition === "relative") {
23297			var p = this.currentItem.position();
23298			return {
23299				top: p.top - (parseInt(this.helper.css("top"),10) || 0) + this.scrollParent.scrollTop(),
23300				left: p.left - (parseInt(this.helper.css("left"),10) || 0) + this.scrollParent.scrollLeft()
23301			};
23302		} else {
23303			return { top: 0, left: 0 };
23304		}
23305
23306	},
23307
23308	_cacheMargins: function() {
23309		this.margins = {
23310			left: (parseInt(this.currentItem.css("marginLeft"),10) || 0),
23311			top: (parseInt(this.currentItem.css("marginTop"),10) || 0)
23312		};
23313	},
23314
23315	_cacheHelperProportions: function() {
23316		this.helperProportions = {
23317			width: this.helper.outerWidth(),
23318			height: this.helper.outerHeight()
23319		};
23320	},
23321
23322	_setContainment: function() {
23323
23324		var ce, co, over,
23325			o = this.options;
23326		if(o.containment === "parent") {
23327			o.containment = this.helper[0].parentNode;
23328		}
23329		if(o.containment === "document" || o.containment === "window") {
23330			this.containment = [
23331				0 - this.offset.relative.left - this.offset.parent.left,
23332				0 - this.offset.relative.top - this.offset.parent.top,
23333				$(o.containment === "document" ? document : window).width() - this.helperProportions.width - this.margins.left,
23334				($(o.containment === "document" ? document : window).height() || document.body.parentNode.scrollHeight) - this.helperProportions.height - this.margins.top
23335			];
23336		}
23337
23338		if(!(/^(document|window|parent)$/).test(o.containment)) {
23339			ce = $(o.containment)[0];
23340			co = $(o.containment).offset();
23341			over = ($(ce).css("overflow") !== "hidden");
23342
23343			this.containment = [
23344				co.left + (parseInt($(ce).css("borderLeftWidth"),10) || 0) + (parseInt($(ce).css("paddingLeft"),10) || 0) - this.margins.left,
23345				co.top + (parseInt($(ce).css("borderTopWidth"),10) || 0) + (parseInt($(ce).css("paddingTop"),10) || 0) - this.margins.top,
23346				co.left+(over ? Math.max(ce.scrollWidth,ce.offsetWidth) : ce.offsetWidth) - (parseInt($(ce).css("borderLeftWidth"),10) || 0) - (parseInt($(ce).css("paddingRight"),10) || 0) - this.helperProportions.width - this.margins.left,
23347				co.top+(over ? Math.max(ce.scrollHeight,ce.offsetHeight) : ce.offsetHeight) - (parseInt($(ce).css("borderTopWidth"),10) || 0) - (parseInt($(ce).css("paddingBottom"),10) || 0) - this.helperProportions.height - this.margins.top
23348			];
23349		}
23350
23351	},
23352
23353	_convertPositionTo: function(d, pos) {
23354
23355		if(!pos) {
23356			pos = this.position;
23357		}
23358		var mod = d === "absolute" ? 1 : -1,
23359			scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent,
23360			scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
23361
23362		return {
23363			top: (
23364				pos.top	+																// The absolute mouse position
23365				this.offset.relative.top * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
23366				this.offset.parent.top * mod -											// The offsetParent's offset without borders (offset + border)
23367				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ) * mod)
23368			),
23369			left: (
23370				pos.left +																// The absolute mouse position
23371				this.offset.relative.left * mod +										// Only for relative positioned nodes: Relative offset from element to offset parent
23372				this.offset.parent.left * mod	-										// The offsetParent's offset without borders (offset + border)
23373				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ) * mod)
23374			)
23375		};
23376
23377	},
23378
23379	_generatePosition: function(event) {
23380
23381		var top, left,
23382			o = this.options,
23383			pageX = event.pageX,
23384			pageY = event.pageY,
23385			scroll = this.cssPosition === "absolute" && !(this.scrollParent[0] !== document && $.contains(this.scrollParent[0], this.offsetParent[0])) ? this.offsetParent : this.scrollParent, scrollIsRootNode = (/(html|body)/i).test(scroll[0].tagName);
23386
23387		// This is another very weird special case that only happens for relative elements:
23388		// 1. If the css position is relative
23389		// 2. and the scroll parent is the document or similar to the offset parent
23390		// we have to refresh the relative offset during the scroll so there are no jumps
23391		if(this.cssPosition === "relative" && !(this.scrollParent[0] !== document && this.scrollParent[0] !== this.offsetParent[0])) {
23392			this.offset.relative = this._getRelativeOffset();
23393		}
23394
23395		/*
23396		 * - Position constraining -
23397		 * Constrain the position to a mix of grid, containment.
23398		 */
23399
23400		if(this.originalPosition) { //If we are not dragging yet, we won't check for options
23401
23402			if(this.containment) {
23403				if(event.pageX - this.offset.click.left < this.containment[0]) {
23404					pageX = this.containment[0] + this.offset.click.left;
23405				}
23406				if(event.pageY - this.offset.click.top < this.containment[1]) {
23407					pageY = this.containment[1] + this.offset.click.top;
23408				}
23409				if(event.pageX - this.offset.click.left > this.containment[2]) {
23410					pageX = this.containment[2] + this.offset.click.left;
23411				}
23412				if(event.pageY - this.offset.click.top > this.containment[3]) {
23413					pageY = this.containment[3] + this.offset.click.top;
23414				}
23415			}
23416
23417			if(o.grid) {
23418				top = this.originalPageY + Math.round((pageY - this.originalPageY) / o.grid[1]) * o.grid[1];
23419				pageY = this.containment ? ( (top - this.offset.click.top >= this.containment[1] && top - this.offset.click.top <= this.containment[3]) ? top : ((top - this.offset.click.top >= this.containment[1]) ? top - o.grid[1] : top + o.grid[1])) : top;
23420
23421				left = this.originalPageX + Math.round((pageX - this.originalPageX) / o.grid[0]) * o.grid[0];
23422				pageX = this.containment ? ( (left - this.offset.click.left >= this.containment[0] && left - this.offset.click.left <= this.containment[2]) ? left : ((left - this.offset.click.left >= this.containment[0]) ? left - o.grid[0] : left + o.grid[0])) : left;
23423			}
23424
23425		}
23426
23427		return {
23428			top: (
23429				pageY -																// The absolute mouse position
23430				this.offset.click.top -													// Click offset (relative to the element)
23431				this.offset.relative.top	-											// Only for relative positioned nodes: Relative offset from element to offset parent
23432				this.offset.parent.top +												// The offsetParent's offset without borders (offset + border)
23433				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollTop() : ( scrollIsRootNode ? 0 : scroll.scrollTop() ) ))
23434			),
23435			left: (
23436				pageX -																// The absolute mouse position
23437				this.offset.click.left -												// Click offset (relative to the element)
23438				this.offset.relative.left	-											// Only for relative positioned nodes: Relative offset from element to offset parent
23439				this.offset.parent.left +												// The offsetParent's offset without borders (offset + border)
23440				( ( this.cssPosition === "fixed" ? -this.scrollParent.scrollLeft() : scrollIsRootNode ? 0 : scroll.scrollLeft() ))
23441			)
23442		};
23443
23444	},
23445
23446	_rearrange: function(event, i, a, hardRefresh) {
23447
23448		a ? a[0].appendChild(this.placeholder[0]) : i.item[0].parentNode.insertBefore(this.placeholder[0], (this.direction === "down" ? i.item[0] : i.item[0].nextSibling));
23449
23450		//Various things done here to improve the performance:
23451		// 1. we create a setTimeout, that calls refreshPositions
23452		// 2. on the instance, we have a counter variable, that get's higher after every append
23453		// 3. on the local scope, we copy the counter variable, and check in the timeout, if it's still the same
23454		// 4. this lets only the last addition to the timeout stack through
23455		this.counter = this.counter ? ++this.counter : 1;
23456		var counter = this.counter;
23457
23458		this._delay(function() {
23459			if(counter === this.counter) {
23460				this.refreshPositions(!hardRefresh); //Precompute after each DOM insertion, NOT on mousemove
23461			}
23462		});
23463
23464	},
23465
23466	_clear: function(event, noPropagation) {
23467
23468		this.reverting = false;
23469		// We delay all events that have to be triggered to after the point where the placeholder has been removed and
23470		// everything else normalized again
23471		var i,
23472			delayedTriggers = [];
23473
23474		// We first have to update the dom position of the actual currentItem
23475		// Note: don't do it if the current item is already removed (by a user), or it gets reappended (see #4088)
23476		if(!this._noFinalSort && this.currentItem.parent().length) {
23477			this.placeholder.before(this.currentItem);
23478		}
23479		this._noFinalSort = null;
23480
23481		if(this.helper[0] === this.currentItem[0]) {
23482			for(i in this._storedCSS) {
23483				if(this._storedCSS[i] === "auto" || this._storedCSS[i] === "static") {
23484					this._storedCSS[i] = "";
23485				}
23486			}
23487			this.currentItem.css(this._storedCSS).removeClass("ui-sortable-helper");
23488		} else {
23489			this.currentItem.show();
23490		}
23491
23492		if(this.fromOutside && !noPropagation) {
23493			delayedTriggers.push(function(event) { this._trigger("receive", event, this._uiHash(this.fromOutside)); });
23494		}
23495		if((this.fromOutside || this.domPosition.prev !== this.currentItem.prev().not(".ui-sortable-helper")[0] || this.domPosition.parent !== this.currentItem.parent()[0]) && !noPropagation) {
23496			delayedTriggers.push(function(event) { this._trigger("update", event, this._uiHash()); }); //Trigger update callback if the DOM position has changed
23497		}
23498
23499		// Check if the items Container has Changed and trigger appropriate
23500		// events.
23501		if (this !== this.currentContainer) {
23502			if(!noPropagation) {
23503				delayedTriggers.push(function(event) { this._trigger("remove", event, this._uiHash()); });
23504				delayedTriggers.push((function(c) { return function(event) { c._trigger("receive", event, this._uiHash(this)); };  }).call(this, this.currentContainer));
23505				delayedTriggers.push((function(c) { return function(event) { c._trigger("update", event, this._uiHash(this));  }; }).call(this, this.currentContainer));
23506			}
23507		}
23508
23509
23510		//Post events to containers
23511		f
vendor: 11,400 bytes, lines 23511-23952
23511or (i = this.containers.length - 1; i >= 0; i--){
23512			if(!noPropagation) {
23513				delayedTriggers.push((function(c) { return function(event) { c._trigger("deactivate", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
23514			}
23515			if(this.containers[i].containerCache.over) {
23516				delayedTriggers.push((function(c) { return function(event) { c._trigger("out", event, this._uiHash(this)); };  }).call(this, this.containers[i]));
23517				this.containers[i].containerCache.over = 0;
23518			}
23519		}
23520
23521		//Do what was originally in plugins
23522		if ( this.storedCursor ) {
23523			this.document.find( "body" ).css( "cursor", this.storedCursor );
23524			this.storedStylesheet.remove();
23525		}
23526		if(this._storedOpacity) {
23527			this.helper.css("opacity", this._storedOpacity);
23528		}
23529		if(this._storedZIndex) {
23530			this.helper.css("zIndex", this._storedZIndex === "auto" ? "" : this._storedZIndex);
23531		}
23532
23533		this.dragging = false;
23534		if(this.cancelHelperRemoval) {
23535			if(!noPropagation) {
23536				this._trigger("beforeStop", event, this._uiHash());
23537				for (i=0; i < delayedTriggers.length; i++) {
23538					delayedTriggers[i].call(this, event);
23539				} //Trigger all delayed events
23540				this._trigger("stop", event, this._uiHash());
23541			}
23542
23543			this.fromOutside = false;
23544			return false;
23545		}
23546
23547		if(!noPropagation) {
23548			this._trigger("beforeStop", event, this._uiHash());
23549		}
23550
23551		//$(this.placeholder[0]).remove(); would have been the jQuery way - unfortunately, it unbinds ALL events from the original node!
23552		this.placeholder[0].parentNode.removeChild(this.placeholder[0]);
23553
23554		if(this.helper[0] !== this.currentItem[0]) {
23555			this.helper.remove();
23556		}
23557		this.helper = null;
23558
23559		if(!noPropagation) {
23560			for (i=0; i < delayedTriggers.length; i++) {
23561				delayedTriggers[i].call(this, event);
23562			} //Trigger all delayed events
23563			this._trigger("stop", event, this._uiHash());
23564		}
23565
23566		this.fromOutside = false;
23567		return true;
23568
23569	},
23570
23571	_trigger: function() {
23572		if ($.Widget.prototype._trigger.apply(this, arguments) === false) {
23573			this.cancel();
23574		}
23575	},
23576
23577	_uiHash: function(_inst) {
23578		var inst = _inst || this;
23579		return {
23580			helper: inst.helper,
23581			placeholder: inst.placeholder || $([]),
23582			position: inst.position,
23583			originalPosition: inst.originalPosition,
23584			offset: inst.positionAbs,
23585			item: inst.currentItem,
23586			sender: _inst ? _inst.element : null
23587		};
23588	}
23589
23590});
23591
23592})(jQuery);
23593
23594
23595
23596
23597/*!
23598 * jQuery UI Spinner 1.10.3
23599 * http://jqueryui.com
23600 *
23601 * Copyright 2013 jQuery Foundation and other contributors
23602 * Released under the MIT license.
23603 * http://jquery.org/license
23604 *
23605 * http://api.jqueryui.com/spinner/
23606 *
23607 * Depends:
23608 *  jquery.ui.core.js
23609 *  jquery.ui.widget.js
23610 *  jquery.ui.button.js
23611 */
23612
23613(function( $ ) {
23614
23615function modifier( fn ) {
23616	return function() {
23617		var previous = this.element.val();
23618		fn.apply( this, arguments );
23619		this._refresh();
23620		if ( previous !== this.element.val() ) {
23621			this._trigger( "change" );
23622		}
23623	};
23624}
23625
23626$.widget( "ui.spinner", {
23627	version: "1.10.3",
23628	defaultElement: "<input>",
23629	widgetEventPrefix: "spin",
23630	options: {
23631		culture: null,
23632		icons: {
23633			down: "ui-icon-triangle-1-s",
23634			up: "ui-icon-triangle-1-n"
23635		},
23636		incremental: true,
23637		max: null,
23638		min: null,
23639		numberFormat: null,
23640		page: 10,
23641		step: 1,
23642
23643		change: null,
23644		spin: null,
23645		start: null,
23646		stop: null
23647	},
23648
23649	_create: function() {
23650		// handle string values that need to be parsed
23651		this._setOption( "max", this.options.max );
23652		this._setOption( "min", this.options.min );
23653		this._setOption( "step", this.options.step );
23654
23655		// format the value, but don't constrain
23656		this._value( this.element.val(), true );
23657
23658		this._draw();
23659		this._on( this._events );
23660		this._refresh();
23661
23662		// turning off autocomplete prevents the browser from remembering the
23663		// value when navigating through history, so we re-enable autocomplete
23664		// if the page is unloaded before the widget is destroyed. #7790
23665		this._on( this.window, {
23666			beforeunload: function() {
23667				this.element.removeAttr( "autocomplete" );
23668			}
23669		});
23670	},
23671
23672	_getCreateOptions: function() {
23673		var options = {},
23674			element = this.element;
23675
23676		$.each( [ "min", "max", "step" ], function( i, option ) {
23677			var value = element.attr( option );
23678			if ( value !== undefined && value.length ) {
23679				options[ option ] = value;
23680			}
23681		});
23682
23683		return options;
23684	},
23685
23686	_events: {
23687		keydown: function( event ) {
23688			if ( this._start( event ) && this._keydown( event ) ) {
23689				event.preventDefault();
23690			}
23691		},
23692		keyup: "_stop",
23693		focus: function() {
23694			this.previous = this.element.val();
23695		},
23696		blur: function( event ) {
23697			if ( this.cancelBlur ) {
23698				delete this.cancelBlur;
23699				return;
23700			}
23701
23702			this._stop();
23703			this._refresh();
23704			if ( this.previous !== this.element.val() ) {
23705				this._trigger( "change", event );
23706			}
23707		},
23708		mousewheel: function( event, delta ) {
23709			if ( !delta ) {
23710				return;
23711			}
23712			if ( !this.spinning && !this._start( event ) ) {
23713				return false;
23714			}
23715
23716			this._spin( (delta > 0 ? 1 : -1) * this.options.step, event );
23717			clearTimeout( this.mousewheelTimer );
23718			this.mousewheelTimer = this._delay(function() {
23719				if ( this.spinning ) {
23720					this._stop( event );
23721				}
23722			}, 100 );
23723			event.preventDefault();
23724		},
23725		"mousedown .ui-spinner-button": function( event ) {
23726			var previous;
23727
23728			// We never want the buttons to have focus; whenever the user is
23729			// interacting with the spinner, the focus should be on the input.
23730			// If the input is focused then this.previous is properly set from
23731			// when the input first received focus. If the input is not focused
23732			// then we need to set this.previous based on the value before spinning.
23733			previous = this.element[0] === this.document[0].activeElement ?
23734				this.previous : this.element.val();
23735			function checkFocus() {
23736				var isActive = this.element[0] === this.document[0].activeElement;
23737				if ( !isActive ) {
23738					this.element.focus();
23739					this.previous = previous;
23740					// support: IE
23741					// IE sets focus asynchronously, so we need to check if focus
23742					// moved off of the input because the user clicked on the button.
23743					this._delay(function() {
23744						this.previous = previous;
23745					});
23746				}
23747			}
23748
23749			// ensure focus is on (or stays on) the text field
23750			event.preventDefault();
23751			checkFocus.call( this );
23752
23753			// support: IE
23754			// IE doesn't prevent moving focus even with event.preventDefault()
23755			// so we set a flag to know when we should ignore the blur event
23756			// and check (again) if focus moved off of the input.
23757			this.cancelBlur = true;
23758			this._delay(function() {
23759				delete this.cancelBlur;
23760				checkFocus.call( this );
23761			});
23762
23763			if ( this._start( event ) === false ) {
23764				return;
23765			}
23766
23767			this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
23768		},
23769		"mouseup .ui-spinner-button": "_stop",
23770		"mouseenter .ui-spinner-button": function( event ) {
23771			// button will add ui-state-active if mouse was down while mouseleave and kept down
23772			if ( !$( event.currentTarget ).hasClass( "ui-state-active" ) ) {
23773				return;
23774			}
23775
23776			if ( this._start( event ) === false ) {
23777				return false;
23778			}
23779			this._repeat( null, $( event.currentTarget ).hasClass( "ui-spinner-up" ) ? 1 : -1, event );
23780		},
23781		// TODO: do we really want to consider this a stop?
23782		// shouldn't we just stop the repeater and wait until mouseup before
23783		// we trigger the stop event?
23784		"mouseleave .ui-spinner-button": "_stop"
23785	},
23786
23787	_draw: function() {
23788		var uiSpinner = this.uiSpinner = this.element
23789			.addClass( "ui-spinner-input" )
23790			.attr( "autocomplete", "off" )
23791			.wrap( this._uiSpinnerHtml() )
23792			.parent()
23793				// add buttons
23794				.append( this._buttonHtml() );
23795
23796		this.element.attr( "role", "spinbutton" );
23797
23798		// button bindings
23799		this.buttons = uiSpinner.find( ".ui-spinner-button" )
23800			.attr( "tabIndex", -1 )
23801			.button()
23802			.removeClass( "ui-corner-all" );
23803
23804		// IE 6 doesn't understand height: 50% for the buttons
23805		// unless the wrapper has an explicit height
23806		if ( this.buttons.height() > Math.ceil( uiSpinner.height() * 0.5 ) &&
23807				uiSpinner.height() > 0 ) {
23808			uiSpinner.height( uiSpinner.height() );
23809		}
23810
23811		// disable spinner if element was already disabled
23812		if ( this.options.disabled ) {
23813			this.disable();
23814		}
23815	},
23816
23817	_keydown: function( event ) {
23818		var options = this.options,
23819			keyCode = $.ui.keyCode;
23820
23821		switch ( event.keyCode ) {
23822		case keyCode.UP:
23823			this._repeat( null, 1, event );
23824			return true;
23825		case keyCode.DOWN:
23826			this._repeat( null, -1, event );
23827			return true;
23828		case keyCode.PAGE_UP:
23829			this._repeat( null, options.page, event );
23830			return true;
23831		case keyCode.PAGE_DOWN:
23832			this._repeat( null, -options.page, event );
23833			return true;
23834		}
23835
23836		return false;
23837	},
23838
23839	_uiSpinnerHtml: function() {
23840		return "<span class='ui-spinner ui-widget ui-widget-content ui-corner-all'></span>";
23841	},
23842
23843	_buttonHtml: function() {
23844		return "" +
23845			"<a class='ui-spinner-button ui-spinner-up ui-corner-tr'>" +
23846				"<span class='ui-icon " + this.options.icons.up + "'>&#9650;</span>" +
23847			"</a>" +
23848			"<a class='ui-spinner-button ui-spinner-down ui-corner-br'>" +
23849				"<span class='ui-icon " + this.options.icons.down + "'>&#9660;</span>" +
23850			"</a>";
23851	},
23852
23853	_start: function( event ) {
23854		if ( !this.spinning && this._trigger( "start", event ) === false ) {
23855			return false;
23856		}
23857
23858		if ( !this.counter ) {
23859			this.counter = 1;
23860		}
23861		this.spinning = true;
23862		return true;
23863	},
23864
23865	_repeat: function( i, steps, event ) {
23866		i = i || 500;
23867
23868		clearTimeout( this.timer );
23869		this.timer = this._delay(function() {
23870			this._repeat( 40, steps, event );
23871		}, i );
23872
23873		this._spin( steps * this.options.step, event );
23874	},
23875
23876	_spin: function( step, event ) {
23877		var value = this.value() || 0;
23878
23879		if ( !this.counter ) {
23880			this.counter = 1;
23881		}
23882
23883		value = this._adjustValue( value + step * this._increment( this.counter ) );
23884
23885		if ( !this.spinning || this._trigger( "spin", event, { value: value } ) !== false) {
23886			this._value( value );
23887			this.counter++;
23888		}
23889	},
23890
23891	_increment: function( i ) {
23892		var incremental = this.options.incremental;
23893
23894		if ( incremental ) {
23895			return $.isFunction( incremental ) ?
23896				incremental( i ) :
23897				Math.floor( i*i*i/50000 - i*i/500 + 17*i/200 + 1 );
23898		}
23899
23900		return 1;
23901	},
23902
23903	_precision: function() {
23904		var precision = this._precisionOf( this.options.step );
23905		if ( this.options.min !== null ) {
23906			precision = Math.max( precision, this._precisionOf( this.options.min ) );
23907		}
23908		return precision;
23909	},
23910
23911	_precisionOf: function( num ) {
23912		var str = num.toString(),
23913			decimal = str.indexOf( "." );
23914		return decimal === -1 ? 0 : str.length - decimal - 1;
23915	},
23916
23917	_adjustValue: function( value ) {
23918		var base, aboveMin,
23919			options = this.options;
23920
23921		// make sure we're at a valid step
23922		// - find out where we are relative to the base (min or 0)
23923		base = options.min !== null ? options.min : 0;
23924		aboveMin = value - base;
23925		// - round to the nearest step
23926		aboveMin = Math.round(aboveMin / options.step) * options.step;
23927		// - rounding is based on 0, so adjust back to our base
23928		value = base + aboveMin;
23929
23930		// fix precision from bad JS floating point math
23931		value = parseFloat( value.toFixed( this._precision() ) );
23932
23933		// clamp the value
23934		if ( options.max !== null && value > options.max) {
23935			return options.max;
23936		}
23937		if ( options.min !== null && value < options.min ) {
23938			return options.min;
23939		}
23940
23941		return value;
23942	},
23943
23944	_stop: function( event ) {
23945		if ( !this.spinning ) {
23946			return;
23947		}
23948
23949		clearTimeout( this.timer );
23950		clearTimeout( this.mousewheelTimer );
23951		this.counter = 0;
23952		this.spinning = false;
23953		this._trigger( "stop", event );
23954	},
23955
23956	_setOption: function( key, value ) {
23957		if ( key === "culture" || key === "numberFormat" ) {
23958			var prevValue = this._parse( this.element.val() );
23959			this.options[ key ] = value;
23960			this.element.val( this._format( prevValue ) );
23961			return;
23962		}
23963
23964		if ( key === "max" || key === "min" || key === "step" ) {
23965			if ( typeof value === "string" ) {
23966				value = this._parse( value );
23967			}
23968		}
23969		if ( key === "icons" ) {
23970			this.buttons.first().find( ".ui-icon" )
23971				.removeClass( this.options.icons.up )
23972				.addClass( value.up );
23973			this.buttons.last().find( ".ui-icon" )
23974				.removeClass( this.options.icons.down )
23975				.addClass( value.down );
23976		}
23977
23978		this._super( key, value );
23979
23980		if ( key === "disabled" ) {
23981			if ( value ) {
23982				this.element.prop( "disabled", true );
23983				this.buttons.button( "disable" );
23984			} else {
23985				this.element.prop( "disabled", false );
23986				this.buttons.button( "enable" );
23987			}
23988		}
23989	},
23990
23991	_setOptions: modifier(function( options ) {
23992		this._super( options );
23993		this._value( this.element.val() );
23994	}),
23995
23996	_parse: function( val ) {
23997		if ( typeof val === "string" && val !== "" ) {
23998			val = window.Globalize && this.options.numberFormat ?
23999				Globalize.parseFloat( val, 10, this.options.culture ) : +val;
24000		}
24001		return val === "" || isNaN( val ) ? null : val;
24002	},
24003
24004	_format: function( value ) {
24005		if ( value === "" ) {
24006			return "";
24007		}
24008		return window.Globalize && this.options.numberFormat ?
24009			Globalize.format( value, this.options.numberFormat, this.options.culture ) :
24010			value;
24011	},
24012
24013	_refresh: function() {
24014		this.element.attr({
24015			"aria-valuemin": this.options.min,
24016			"aria-valuemax": this.options.max,
24017			// TODO: what should we do with values that can't be parsed?
24018			"aria-valuenow": this._parse( this.element.val() )
24019		});
24020	},
24021
24022	// update the value without triggering change
24023	_value: function( value, allowAny ) {
24024		var parsed;
24025		if ( value !== "" ) {
24026			parsed = this._parse( value );
24027			if ( parsed !== null ) {
24028				if ( !allowAny ) {
24029					parsed = this._adjustValue( parsed );
24030				}
24031				value = this._format( parsed );
24032			}
24033		}
24034		this.element.val( value );
24035		this._refresh();
24036	},
24037
24038	_destroy: function() {
24039		this.element
24040			.removeClass( "ui-spinner-input" )
24041			.prop( "disabled", false )
24042			.removeAttr( "autocomplete" )
24043			.removeAttr( "role" )
24044			.removeAttr( "aria-valuemin" )
24045			.removeAttr( "aria-valuemax" )
24046			.removeAttr( "aria-valuenow" );
24047		this.uiSpinner.replaceWith( this.element );
24048	},
24049
24050	stepUp: modifier(function( steps ) {
24051		this._stepUp( steps );
24052	}),
24053	_stepUp: function( steps ) {
24054		if ( this._start() ) {
24055			this._spin( (steps || 1) * this.options.step );
24056			this._stop();
24057		}
24058	},
24059
24060	stepDown: modifier(function( steps ) {
24061		this._stepDown( steps );
24062	}),
24063	_stepDown: function( steps ) {
24064		if ( this._start() ) {
24065			this._spin( (steps || 1) * -this.options.step );
24066			this._stop();
24067		}
24068	},
24069
24070	pageUp: modifier(function( pages ) {
24071		this._stepUp( (pages || 1) * this.options.page );
24072	}),
24073
24074	pageDown: modifier(function( pages ) {
24075		this._stepDown( (pages || 1) * this.options.page );
24076	}),
24077
24078	value: function( newVal ) {
24079		if ( !arguments.length ) {
24080			return this._parse( this.element.val() );
24081		}
24082		modifier( this._value ).call( this, newVal );
24083	},
24084
24085	widget: function() {
24086		return this.uiSpinner;
24087	}
24088});
24089
24090}( jQuery ) );
24091
24092
24093
24094/*!
24095 * jQuery UI Tabs 1.10.3
24096 * http://jqueryui.com
24097 *
24098 * Copyright 2013 jQuery Foundation and other contributors
24099 * Released under the MIT license.
24100 * http://jquery.org/license
24101 *
24102 * http://api.jqueryui.com/tabs/
24103 *
24104 * Depends:
24105 *	jquery.ui.core.js
24106 *	jquery.ui.widget.js
24107 */
24108
24109(function( $, undefined ) {
24110
24111var tabId = 0,
24112	rhash = /#.*$/;
24113
24114function getNextTabId() {
24115	return ++tabId;
24116}
24117
24118function isLocal( anchor ) {
24119	return anchor.hash.length > 1 &&
24120		decodeURIComponent( anchor.href.replace( rhash, "" ) ) ===
24121			decodeURIComponent( location.href.replace( rhash, "" ) );
24122}
24123
24124$.widget( "ui.tabs", {
24125	version: "1.10.3",
24126	delay: 300,
24127	options: {
24128		active: null,
24129		collapsible: false,
24130		event: "click",
24131		heightStyle: "content",
24132		hide: null,
24133		show: null,
24134
24135		// callbacks
24136		activate: null,
24137		beforeActivate: null,
24138		beforeLoad: null,
24139		load: null
24140	},
24141
24142	_create: function() {
24143		var that = this,
24144			options = this.options;
24145
24146		this.running = false;
vendor: 27,525 bytes, lines 24147-25185
24147
24148		this.element
24149			.addClass( "ui-tabs ui-widget ui-widget-content ui-corner-all" )
24150			.toggleClass( "ui-tabs-collapsible", options.collapsible )
24151			// Prevent users from focusing disabled tabs via click
24152			.delegate( ".ui-tabs-nav > li", "mousedown" + this.eventNamespace, function( event ) {
24153				if ( $( this ).is( ".ui-state-disabled" ) ) {
24154					event.preventDefault();
24155				}
24156			})
24157			// support: IE <9
24158			// Preventing the default action in mousedown doesn't prevent IE
24159			// from focusing the element, so if the anchor gets focused, blur.
24160			// We don't have to worry about focusing the previously focused
24161			// element since clicking on a non-focusable element should focus
24162			// the body anyway.
24163			.delegate( ".ui-tabs-anchor", "focus" + this.eventNamespace, function() {
24164				if ( $( this ).closest( "li" ).is( ".ui-state-disabled" ) ) {
24165					this.blur();
24166				}
24167			});
24168
24169		this._processTabs();
24170		options.active = this._initialActive();
24171
24172		// Take disabling tabs via class attribute from HTML
24173		// into account and update option properly.
24174		if ( $.isArray( options.disabled ) ) {
24175			options.disabled = $.unique( options.disabled.concat(
24176				$.map( this.tabs.filter( ".ui-state-disabled" ), function( li ) {
24177					return that.tabs.index( li );
24178				})
24179			) ).sort();
24180		}
24181
24182		// check for length avoids error when initializing empty list
24183		if ( this.options.active !== false && this.anchors.length ) {
24184			this.active = this._findActive( options.active );
24185		} else {
24186			this.active = $();
24187		}
24188
24189		this._refresh();
24190
24191		if ( this.active.length ) {
24192			this.load( options.active );
24193		}
24194	},
24195
24196	_initialActive: function() {
24197		var active = this.options.active,
24198			collapsible = this.options.collapsible,
24199			locationHash = location.hash.substring( 1 );
24200
24201		if ( active === null ) {
24202			// check the fragment identifier in the URL
24203			if ( locationHash ) {
24204				this.tabs.each(function( i, tab ) {
24205					if ( $( tab ).attr( "aria-controls" ) === locationHash ) {
24206						active = i;
24207						return false;
24208					}
24209				});
24210			}
24211
24212			// check for a tab marked active via a class
24213			if ( active === null ) {
24214				active = this.tabs.index( this.tabs.filter( ".ui-tabs-active" ) );
24215			}
24216
24217			// no active tab, set to false
24218			if ( active === null || active === -1 ) {
24219				active = this.tabs.length ? 0 : false;
24220			}
24221		}
24222
24223		// handle numbers: negative, out of range
24224		if ( active !== false ) {
24225			active = this.tabs.index( this.tabs.eq( active ) );
24226			if ( active === -1 ) {
24227				active = collapsible ? false : 0;
24228			}
24229		}
24230
24231		// don't allow collapsible: false and active: false
24232		if ( !collapsible && active === false && this.anchors.length ) {
24233			active = 0;
24234		}
24235
24236		return active;
24237	},
24238
24239	_getCreateEventData: function() {
24240		return {
24241			tab: this.active,
24242			panel: !this.active.length ? $() : this._getPanelForTab( this.active )
24243		};
24244	},
24245
24246	_tabKeydown: function( event ) {
24247		/*jshint maxcomplexity:15*/
24248		var focusedTab = $( this.document[0].activeElement ).closest( "li" ),
24249			selectedIndex = this.tabs.index( focusedTab ),
24250			goingForward = true;
24251
24252		if ( this._handlePageNav( event ) ) {
24253			return;
24254		}
24255
24256		switch ( event.keyCode ) {
24257			case $.ui.keyCode.RIGHT:
24258			case $.ui.keyCode.DOWN:
24259				selectedIndex++;
24260				break;
24261			case $.ui.keyCode.UP:
24262			case $.ui.keyCode.LEFT:
24263				goingForward = false;
24264				selectedIndex--;
24265				break;
24266			case $.ui.keyCode.END:
24267				selectedIndex = this.anchors.length - 1;
24268				break;
24269			case $.ui.keyCode.HOME:
24270				selectedIndex = 0;
24271				break;
24272			case $.ui.keyCode.SPACE:
24273				// Activate only, no collapsing
24274				event.preventDefault();
24275				clearTimeout( this.activating );
24276				this._activate( selectedIndex );
24277				return;
24278			case $.ui.keyCode.ENTER:
24279				// Toggle (cancel delayed activation, allow collapsing)
24280				event.preventDefault();
24281				clearTimeout( this.activating );
24282				// Determine if we should collapse or activate
24283				this._activate( selectedIndex === this.options.active ? false : selectedIndex );
24284				return;
24285			default:
24286				return;
24287		}
24288
24289		// Focus the appropriate tab, based on which key was pressed
24290		event.preventDefault();
24291		clearTimeout( this.activating );
24292		selectedIndex = this._focusNextTab( selectedIndex, goingForward );
24293
24294		// Navigating with control key will prevent automatic activation
24295		if ( !event.ctrlKey ) {
24296			// Update aria-selected immediately so that AT think the tab is already selected.
24297			// Otherwise AT may confuse the user by stating that they need to activate the tab,
24298			// but the tab will already be activated by the time the announcement finishes.
24299			focusedTab.attr( "aria-selected", "false" );
24300			this.tabs.eq( selectedIndex ).attr( "aria-selected", "true" );
24301
24302			this.activating = this._delay(function() {
24303				this.option( "active", selectedIndex );
24304			}, this.delay );
24305		}
24306	},
24307
24308	_panelKeydown: function( event ) {
24309		if ( this._handlePageNav( event ) ) {
24310			return;
24311		}
24312
24313		// Ctrl+up moves focus to the current tab
24314		if ( event.ctrlKey && event.keyCode === $.ui.keyCode.UP ) {
24315			event.preventDefault();
24316			this.active.focus();
24317		}
24318	},
24319
24320	// Alt+page up/down moves focus to the previous/next tab (and activates)
24321	_handlePageNav: function( event ) {
24322		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_UP ) {
24323			this._activate( this._focusNextTab( this.options.active - 1, false ) );
24324			return true;
24325		}
24326		if ( event.altKey && event.keyCode === $.ui.keyCode.PAGE_DOWN ) {
24327			this._activate( this._focusNextTab( this.options.active + 1, true ) );
24328			return true;
24329		}
24330	},
24331
24332	_findNextTab: function( index, goingForward ) {
24333		var lastTabIndex = this.tabs.length - 1;
24334
24335		function constrain() {
24336			if ( index > lastTabIndex ) {
24337				index = 0;
24338			}
24339			if ( index < 0 ) {
24340				index = lastTabIndex;
24341			}
24342			return index;
24343		}
24344
24345		while ( $.inArray( constrain(), this.options.disabled ) !== -1 ) {
24346			index = goingForward ? index + 1 : index - 1;
24347		}
24348
24349		return index;
24350	},
24351
24352	_focusNextTab: function( index, goingForward ) {
24353		index = this._findNextTab( index, goingForward );
24354		this.tabs.eq( index ).focus();
24355		return index;
24356	},
24357
24358	_setOption: function( key, value ) {
24359		if ( key === "active" ) {
24360			// _activate() will handle invalid values and update this.options
24361			this._activate( value );
24362			return;
24363		}
24364
24365		if ( key === "disabled" ) {
24366			// don't use the widget factory's disabled handling
24367			this._setupDisabled( value );
24368			return;
24369		}
24370
24371		this._super( key, value);
24372
24373		if ( key === "collapsible" ) {
24374			this.element.toggleClass( "ui-tabs-collapsible", value );
24375			// Setting collapsible: false while collapsed; open first panel
24376			if ( !value && this.options.active === false ) {
24377				this._activate( 0 );
24378			}
24379		}
24380
24381		if ( key === "event" ) {
24382			this._setupEvents( value );
24383		}
24384
24385		if ( key === "heightStyle" ) {
24386			this._setupHeightStyle( value );
24387		}
24388	},
24389
24390	_tabId: function( tab ) {
24391		return tab.attr( "aria-controls" ) || "ui-tabs-" + getNextTabId();
24392	},
24393
24394	_sanitizeSelector: function( hash ) {
24395		return hash ? hash.replace( /[!"$%&'()*+,.\/:;<=>?@\[\]\^`{|}~]/g, "\\$&" ) : "";
24396	},
24397
24398	refresh: function() {
24399		var options = this.options,
24400			lis = this.tablist.children( ":has(a[href])" );
24401
24402		// get disabled tabs from class attribute from HTML
24403		// this will get converted to a boolean if needed in _refresh()
24404		options.disabled = $.map( lis.filter( ".ui-state-disabled" ), function( tab ) {
24405			return lis.index( tab );
24406		});
24407
24408		this._processTabs();
24409
24410		// was collapsed or no tabs
24411		if ( options.active === false || !this.anchors.length ) {
24412			options.active = false;
24413			this.active = $();
24414		// was active, but active tab is gone
24415		} else if ( this.active.length && !$.contains( this.tablist[ 0 ], this.active[ 0 ] ) ) {
24416			// all remaining tabs are disabled
24417			if ( this.tabs.length === options.disabled.length ) {
24418				options.active = false;
24419				this.active = $();
24420			// activate previous tab
24421			} else {
24422				this._activate( this._findNextTab( Math.max( 0, options.active - 1 ), false ) );
24423			}
24424		// was active, active tab still exists
24425		} else {
24426			// make sure active index is correct
24427			options.active = this.tabs.index( this.active );
24428		}
24429
24430		this._refresh();
24431	},
24432
24433	_refresh: function() {
24434		this._setupDisabled( this.options.disabled );
24435		this._setupEvents( this.options.event );
24436		this._setupHeightStyle( this.options.heightStyle );
24437
24438		this.tabs.not( this.active ).attr({
24439			"aria-selected": "false",
24440			tabIndex: -1
24441		});
24442		this.panels.not( this._getPanelForTab( this.active ) )
24443			.hide()
24444			.attr({
24445				"aria-expanded": "false",
24446				"aria-hidden": "true"
24447			});
24448
24449		// Make sure one tab is in the tab order
24450		if ( !this.active.length ) {
24451			this.tabs.eq( 0 ).attr( "tabIndex", 0 );
24452		} else {
24453			this.active
24454				.addClass( "ui-tabs-active ui-state-active" )
24455				.attr({
24456					"aria-selected": "true",
24457					tabIndex: 0
24458				});
24459			this._getPanelForTab( this.active )
24460				.show()
24461				.attr({
24462					"aria-expanded": "true",
24463					"aria-hidden": "false"
24464				});
24465		}
24466	},
24467
24468	_processTabs: function() {
24469		var that = this;
24470
24471		this.tablist = this._getList()
24472			.addClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
24473			.attr( "role", "tablist" );
24474
24475		this.tabs = this.tablist.find( "> li:has(a[href])" )
24476			.addClass( "ui-state-default ui-corner-top" )
24477			.attr({
24478				role: "tab",
24479				tabIndex: -1
24480			});
24481
24482		this.anchors = this.tabs.map(function() {
24483				return $( "a", this )[ 0 ];
24484			})
24485			.addClass( "ui-tabs-anchor" )
24486			.attr({
24487				role: "presentation",
24488				tabIndex: -1
24489			});
24490
24491		this.panels = $();
24492
24493		this.anchors.each(function( i, anchor ) {
24494			var selector, panel, panelId,
24495				anchorId = $( anchor ).uniqueId().attr( "id" ),
24496				tab = $( anchor ).closest( "li" ),
24497				originalAriaControls = tab.attr( "aria-controls" );
24498
24499			// inline tab
24500			if ( isLocal( anchor ) ) {
24501				selector = anchor.hash;
24502				panel = that.element.find( that._sanitizeSelector( selector ) );
24503			// remote tab
24504			} else {
24505				panelId = that._tabId( tab );
24506				selector = "#" + panelId;
24507				panel = that.element.find( selector );
24508				if ( !panel.length ) {
24509					panel = that._createPanel( panelId );
24510					panel.insertAfter( that.panels[ i - 1 ] || that.tablist );
24511				}
24512				panel.attr( "aria-live", "polite" );
24513			}
24514
24515			if ( panel.length) {
24516				that.panels = that.panels.add( panel );
24517			}
24518			if ( originalAriaControls ) {
24519				tab.data( "ui-tabs-aria-controls", originalAriaControls );
24520			}
24521			tab.attr({
24522				"aria-controls": selector.substring( 1 ),
24523				"aria-labelledby": anchorId
24524			});
24525			panel.attr( "aria-labelledby", anchorId );
24526		});
24527
24528		this.panels
24529			.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
24530			.attr( "role", "tabpanel" );
24531	},
24532
24533	// allow overriding how to find the list for rare usage scenarios (#7715)
24534	_getList: function() {
24535		return this.element.find( "ol,ul" ).eq( 0 );
24536	},
24537
24538	_createPanel: function( id ) {
24539		return $( "<div>" )
24540			.attr( "id", id )
24541			.addClass( "ui-tabs-panel ui-widget-content ui-corner-bottom" )
24542			.data( "ui-tabs-destroy", true );
24543	},
24544
24545	_setupDisabled: function( disabled ) {
24546		if ( $.isArray( disabled ) ) {
24547			if ( !disabled.length ) {
24548				disabled = false;
24549			} else if ( disabled.length === this.anchors.length ) {
24550				disabled = true;
24551			}
24552		}
24553
24554		// disable tabs
24555		for ( var i = 0, li; ( li = this.tabs[ i ] ); i++ ) {
24556			if ( disabled === true || $.inArray( i, disabled ) !== -1 ) {
24557				$( li )
24558					.addClass( "ui-state-disabled" )
24559					.attr( "aria-disabled", "true" );
24560			} else {
24561				$( li )
24562					.removeClass( "ui-state-disabled" )
24563					.removeAttr( "aria-disabled" );
24564			}
24565		}
24566
24567		this.options.disabled = disabled;
24568	},
24569
24570	_setupEvents: function( event ) {
24571		var events = {
24572			click: function( event ) {
24573				event.preventDefault();
24574			}
24575		};
24576		if ( event ) {
24577			$.each( event.split(" "), function( index, eventName ) {
24578				events[ eventName ] = "_eventHandler";
24579			});
24580		}
24581
24582		this._off( this.anchors.add( this.tabs ).add( this.panels ) );
24583		this._on( this.anchors, events );
24584		this._on( this.tabs, { keydown: "_tabKeydown" } );
24585		this._on( this.panels, { keydown: "_panelKeydown" } );
24586
24587		this._focusable( this.tabs );
24588		this._hoverable( this.tabs );
24589	},
24590
24591	_setupHeightStyle: function( heightStyle ) {
24592		var maxHeight,
24593			parent = this.element.parent();
24594
24595		if ( heightStyle === "fill" ) {
24596			maxHeight = parent.height();
24597			maxHeight -= this.element.outerHeight() - this.element.height();
24598
24599			this.element.siblings( ":visible" ).each(function() {
24600				var elem = $( this ),
24601					position = elem.css( "position" );
24602
24603				if ( position === "absolute" || position === "fixed" ) {
24604					return;
24605				}
24606				maxHeight -= elem.outerHeight( true );
24607			});
24608
24609			this.element.children().not( this.panels ).each(function() {
24610				maxHeight -= $( this ).outerHeight( true );
24611			});
24612
24613			this.panels.each(function() {
24614				$( this ).height( Math.max( 0, maxHeight -
24615					$( this ).innerHeight() + $( this ).height() ) );
24616			})
24617			.css( "overflow", "auto" );
24618		} else if ( heightStyle === "auto" ) {
24619			maxHeight = 0;
24620			this.panels.each(function() {
24621				maxHeight = Math.max( maxHeight, $( this ).height( "" ).height() );
24622			}).height( maxHeight );
24623		}
24624	},
24625
24626	_eventHandler: function( event ) {
24627		var options = this.options,
24628			active = this.active,
24629			anchor = $( event.currentTarget ),
24630			tab = anchor.closest( "li" ),
24631			clickedIsActive = tab[ 0 ] === active[ 0 ],
24632			collapsing = clickedIsActive && options.collapsible,
24633			toShow = collapsing ? $() : this._getPanelForTab( tab ),
24634			toHide = !active.length ? $() : this._getPanelForTab( active ),
24635			eventData = {
24636				oldTab: active,
24637				oldPanel: toHide,
24638				newTab: collapsing ? $() : tab,
24639				newPanel: toShow
24640			};
24641
24642		event.preventDefault();
24643
24644		if ( tab.hasClass( "ui-state-disabled" ) ||
24645				// tab is already loading
24646				tab.hasClass( "ui-tabs-loading" ) ||
24647				// can't switch durning an animation
24648				this.running ||
24649				// click on active header, but not collapsible
24650				( clickedIsActive && !options.collapsible ) ||
24651				// allow canceling activation
24652				( this._trigger( "beforeActivate", event, eventData ) === false ) ) {
24653			return;
24654		}
24655
24656		options.active = collapsing ? false : this.tabs.index( tab );
24657
24658		this.active = clickedIsActive ? $() : tab;
24659		if ( this.xhr ) {
24660			this.xhr.abort();
24661		}
24662
24663		if ( !toHide.length && !toShow.length ) {
24664			$.error( "jQuery UI Tabs: Mismatching fragment identifier." );
24665		}
24666
24667		if ( toShow.length ) {
24668			this.load( this.tabs.index( tab ), event );
24669		}
24670		this._toggle( event, eventData );
24671	},
24672
24673	// handles show/hide for selecting tabs
24674	_toggle: function( event, eventData ) {
24675		var that = this,
24676			toShow = eventData.newPanel,
24677			toHide = eventData.oldPanel;
24678
24679		this.running = true;
24680
24681		function complete() {
24682			that.running = false;
24683			that._trigger( "activate", event, eventData );
24684		}
24685
24686		function show() {
24687			eventData.newTab.closest( "li" ).addClass( "ui-tabs-active ui-state-active" );
24688
24689			if ( toShow.length && that.options.show ) {
24690				that._show( toShow, that.options.show, complete );
24691			} else {
24692				toShow.show();
24693				complete();
24694			}
24695		}
24696
24697		// start out by hiding, then showing, then completing
24698		if ( toHide.length && this.options.hide ) {
24699			this._hide( toHide, this.options.hide, function() {
24700				eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
24701				show();
24702			});
24703		} else {
24704			eventData.oldTab.closest( "li" ).removeClass( "ui-tabs-active ui-state-active" );
24705			toHide.hide();
24706			show();
24707		}
24708
24709		toHide.attr({
24710			"aria-expanded": "false",
24711			"aria-hidden": "true"
24712		});
24713		eventData.oldTab.attr( "aria-selected", "false" );
24714		// If we're switching tabs, remove the old tab from the tab order.
24715		// If we're opening from collapsed state, remove the previous tab from the tab order.
24716		// If we're collapsing, then keep the collapsing tab in the tab order.
24717		if ( toShow.length && toHide.length ) {
24718			eventData.oldTab.attr( "tabIndex", -1 );
24719		} else if ( toShow.length ) {
24720			this.tabs.filter(function() {
24721				return $( this ).attr( "tabIndex" ) === 0;
24722			})
24723			.attr( "tabIndex", -1 );
24724		}
24725
24726		toShow.attr({
24727			"aria-expanded": "true",
24728			"aria-hidden": "false"
24729		});
24730		eventData.newTab.attr({
24731			"aria-selected": "true",
24732			tabIndex: 0
24733		});
24734	},
24735
24736	_activate: function( index ) {
24737		var anchor,
24738			active = this._findActive( index );
24739
24740		// trying to activate the already active panel
24741		if ( active[ 0 ] === this.active[ 0 ] ) {
24742			return;
24743		}
24744
24745		// trying to collapse, simulate a click on the current active header
24746		if ( !active.length ) {
24747			active = this.active;
24748		}
24749
24750		anchor = active.find( ".ui-tabs-anchor" )[ 0 ];
24751		this._eventHandler({
24752			target: anchor,
24753			currentTarget: anchor,
24754			preventDefault: $.noop
24755		});
24756	},
24757
24758	_findActive: function( index ) {
24759		return index === false ? $() : this.tabs.eq( index );
24760	},
24761
24762	_getIndex: function( index ) {
24763		// meta-function to give users option to provide a href string instead of a numerical index.
24764		if ( typeof index === "string" ) {
24765			index = this.anchors.index( this.anchors.filter( "[href$='" + index + "']" ) );
24766		}
24767
24768		return index;
24769	},
24770
24771	_destroy: function() {
24772		if ( this.xhr ) {
24773			this.xhr.abort();
24774		}
24775
24776		this.element.removeClass( "ui-tabs ui-widget ui-widget-content ui-corner-all ui-tabs-collapsible" );
24777
24778		this.tablist
24779			.removeClass( "ui-tabs-nav ui-helper-reset ui-helper-clearfix ui-widget-header ui-corner-all" )
24780			.removeAttr( "role" );
24781
24782		this.anchors
24783			.removeClass( "ui-tabs-anchor" )
24784			.removeAttr( "role" )
24785			.removeAttr( "tabIndex" )
24786			.removeUniqueId();
24787
24788		this.tabs.add( this.panels ).each(function() {
24789			if ( $.data( this, "ui-tabs-destroy" ) ) {
24790				$( this ).remove();
24791			} else {
24792				$( this )
24793					.removeClass( "ui-state-default ui-state-active ui-state-disabled " +
24794						"ui-corner-top ui-corner-bottom ui-widget-content ui-tabs-active ui-tabs-panel" )
24795					.removeAttr( "tabIndex" )
24796					.removeAttr( "aria-live" )
24797					.removeAttr( "aria-busy" )
24798					.removeAttr( "aria-selected" )
24799					.removeAttr( "aria-labelledby" )
24800					.removeAttr( "aria-hidden" )
24801					.removeAttr( "aria-expanded" )
24802					.removeAttr( "role" );
24803			}
24804		});
24805
24806		this.tabs.each(function() {
24807			var li = $( this ),
24808				prev = li.data( "ui-tabs-aria-controls" );
24809			if ( prev ) {
24810				li
24811					.attr( "aria-controls", prev )
24812					.removeData( "ui-tabs-aria-controls" );
24813			} else {
24814				li.removeAttr( "aria-controls" );
24815			}
24816		});
24817
24818		this.panels.show();
24819
24820		if ( this.options.heightStyle !== "content" ) {
24821			this.panels.css( "height", "" );
24822		}
24823	},
24824
24825	enable: function( index ) {
24826		var disabled = this.options.disabled;
24827		if ( disabled === false ) {
24828			return;
24829		}
24830
24831		if ( index === undefined ) {
24832			disabled = false;
24833		} else {
24834			index = this._getIndex( index );
24835			if ( $.isArray( disabled ) ) {
24836				disabled = $.map( disabled, function( num ) {
24837					return num !== index ? num : null;
24838				});
24839			} else {
24840				disabled = $.map( this.tabs, function( li, num ) {
24841					return num !== index ? num : null;
24842				});
24843			}
24844		}
24845		this._setupDisabled( disabled );
24846	},
24847
24848	disable: function( index ) {
24849		var disabled = this.options.disabled;
24850		if ( disabled === true ) {
24851			return;
24852		}
24853
24854		if ( index === undefined ) {
24855			disabled = true;
24856		} else {
24857			index = this._getIndex( index );
24858			if ( $.inArray( index, disabled ) !== -1 ) {
24859				return;
24860			}
24861			if ( $.isArray( disabled ) ) {
24862				disabled = $.merge( [ index ], disabled ).sort();
24863			} else {
24864				disabled = [ index ];
24865			}
24866		}
24867		this._setupDisabled( disabled );
24868	},
24869
24870	load: function( index, event ) {
24871		index = this._getIndex( index );
24872		var that = this,
24873			tab = this.tabs.eq( index ),
24874			anchor = tab.find( ".ui-tabs-anchor" ),
24875			panel = this._getPanelForTab( tab ),
24876			eventData = {
24877				tab: tab,
24878				panel: panel
24879			};
24880
24881		// not remote
24882		if ( isLocal( anchor[ 0 ] ) ) {
24883			return;
24884		}
24885
24886		this.xhr = $.ajax( this._ajaxSettings( anchor, event, eventData ) );
24887
24888		// support: jQuery <1.8
24889		// jQuery <1.8 returns false if the request is canceled in beforeSend,
24890		// but as of 1.8, $.ajax() always returns a jqXHR object.
24891		if ( this.xhr && this.xhr.statusText !== "canceled" ) {
24892			tab.addClass( "ui-tabs-loading" );
24893			panel.attr( "aria-busy", "true" );
24894
24895			this.xhr
24896				.success(function( response ) {
24897					// support: jQuery <1.8
24898					// http://bugs.jquery.com/ticket/11778
24899					setTimeout(function() {
24900						panel.html( response );
24901						that._trigger( "load", event, eventData );
24902					}, 1 );
24903				})
24904				.complete(function( jqXHR, status ) {
24905					// support: jQuery <1.8
24906					// http://bugs.jquery.com/ticket/11778
24907					setTimeout(function() {
24908						if ( status === "abort" ) {
24909							that.panels.stop( false, true );
24910						}
24911
24912						tab.removeClass( "ui-tabs-loading" );
24913						panel.removeAttr( "aria-busy" );
24914
24915						if ( jqXHR === that.xhr ) {
24916							delete that.xhr;
24917						}
24918					}, 1 );
24919				});
24920		}
24921	},
24922
24923	_ajaxSettings: function( anchor, event, eventData ) {
24924		var that = this;
24925		return {
24926			url: anchor.attr( "href" ),
24927			beforeSend: function( jqXHR, settings ) {
24928				return that._trigger( "beforeLoad", event,
24929					$.extend( { jqXHR : jqXHR, ajaxSettings: settings }, eventData ) );
24930			}
24931		};
24932	},
24933
24934	_getPanelForTab: function( tab ) {
24935		var id = $( tab ).attr( "aria-controls" );
24936		return this.element.find( this._sanitizeSelector( "#" + id ) );
24937	}
24938});
24939
24940})( jQuery );
24941
24942
24943
24944
24945/*!
24946 * jQuery UI Tooltip 1.10.3
24947 * http://jqueryui.com
24948 *
24949 * Copyright 2013 jQuery Foundation and other contributors
24950 * Released under the MIT license.
24951 * http://jquery.org/license
24952 *
24953 * http://api.jqueryui.com/tooltip/
24954 *
24955 * Depends:
24956 *	jquery.ui.core.js
24957 *	jquery.ui.widget.js
24958 *	jquery.ui.position.js
24959 */
24960
24961(function( $ ) {
24962
24963var increments = 0;
24964
24965function addDescribedBy( elem, id ) {
24966	var describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ );
24967	describedby.push( id );
24968	elem
24969		.data( "ui-tooltip-id", id )
24970		.attr( "aria-describedby", $.trim( describedby.join( " " ) ) );
24971}
24972
24973function removeDescribedBy( elem ) {
24974	var id = elem.data( "ui-tooltip-id" ),
24975		describedby = (elem.attr( "aria-describedby" ) || "").split( /\s+/ ),
24976		index = $.inArray( id, describedby );
24977	if ( index !== -1 ) {
24978		describedby.splice( index, 1 );
24979	}
24980
24981	elem.removeData( "ui-tooltip-id" );
24982	describedby = $.trim( describedby.join( " " ) );
24983	if ( describedby ) {
24984		elem.attr( "aria-describedby", describedby );
24985	} else {
24986		elem.removeAttr( "aria-describedby" );
24987	}
24988}
24989
24990$.widget( "ui.tooltip", {
24991	version: "1.10.3",
24992	options: {
24993		content: function() {
24994			// support: IE<9, Opera in jQuery <1.7
24995			// .text() can't accept undefined, so coerce to a string
24996			var title = $( this ).attr( "title" ) || "";
24997			// Escape title, since we're going from an attribute to raw HTML
24998			return $( "<a>" ).text( title ).html();
24999		},
25000		hide: true,
25001		// Disabled elements have inconsistent behavior across browsers (#8661)
25002		items: "[title]:not([disabled])",
25003		position: {
25004			my: "left top+15",
25005			at: "left bottom",
25006			collision: "flipfit flip"
25007		},
25008		show: true,
25009		tooltipClass: null,
25010		track: false,
25011
25012		// callbacks
25013		close: null,
25014		open: null
25015	},
25016
25017	_create: function() {
25018		this._on({
25019			mouseover: "open",
25020			focusin: "open"
25021		});
25022
25023		// IDs of generated tooltips, needed for destroy
25024		this.tooltips = {};
25025		// IDs of parent tooltips where we removed the title attribute
25026		this.parents = {};
25027
25028		if ( this.options.disabled ) {
25029			this._disable();
25030		}
25031	},
25032
25033	_setOption: function( key, value ) {
25034		var that = this;
25035
25036		if ( key === "disabled" ) {
25037			this[ value ? "_disable" : "_enable" ]();
25038			this.options[ key ] = value;
25039			// disable element style changes
25040			return;
25041		}
25042
25043		this._super( key, value );
25044
25045		if ( key === "content" ) {
25046			$.each( this.tooltips, function( id, element ) {
25047				that._updateContent( element );
25048			});
25049		}
25050	},
25051
25052	_disable: function() {
25053		var that = this;
25054
25055		// close open tooltips
25056		$.each( this.tooltips, function( id, element ) {
25057			var event = $.Event( "blur" );
25058			event.target = event.currentTarget = element[0];
25059			that.close( event, true );
25060		});
25061
25062		// remove title attributes to prevent native tooltips
25063		this.element.find( this.options.items ).addBack().each(function() {
25064			var element = $( this );
25065			if ( element.is( "[title]" ) ) {
25066				element
25067					.data( "ui-tooltip-title", element.attr( "title" ) )
25068					.attr( "title", "" );
25069			}
25070		});
25071	},
25072
25073	_enable: function() {
25074		// restore title attributes
25075		this.element.find( this.options.items ).addBack().each(function() {
25076			var element = $( this );
25077			if ( element.data( "ui-tooltip-title" ) ) {
25078				element.attr( "title", element.data( "ui-tooltip-title" ) );
25079			}
25080		});
25081	},
25082
25083	open: function( event ) {
25084		var that = this,
25085			target = $( event ? event.target : this.element )
25086				// we need closest here due to mouseover bubbling,
25087				// but always pointing at the same event target
25088				.closest( this.options.items );
25089
25090		// No element to show a tooltip for or the tooltip is already open
25091		if ( !target.length || target.data( "ui-tooltip-id" ) ) {
25092			return;
25093		}
25094
25095		if ( target.attr( "title" ) ) {
25096			target.data( "ui-tooltip-title", target.attr( "title" ) );
25097		}
25098
25099		target.data( "ui-tooltip-open", true );
25100
25101		// kill parent tooltips, custom or native, for hover
25102		if ( event && event.type === "mouseover" ) {
25103			target.parents().each(function() {
25104				var parent = $( this ),
25105					blurEvent;
25106				if ( parent.data( "ui-tooltip-open" ) ) {
25107					blurEvent = $.Event( "blur" );
25108					blurEvent.target = blurEvent.currentTarget = this;
25109					that.close( blurEvent, true );
25110				}
25111				if ( parent.attr( "title" ) ) {
25112					parent.uniqueId();
25113					that.parents[ this.id ] = {
25114						element: this,
25115						title: parent.attr( "title" )
25116					};
25117					parent.attr( "title", "" );
25118				}
25119			});
25120		}
25121
25122		this._updateContent( target, event );
25123	},
25124
25125	_updateContent: function( target, event ) {
25126		var content,
25127			contentOption = this.options.content,
25128			that = this,
25129			eventType = event ? event.type : null;
25130
25131		if ( typeof contentOption === "string" ) {
25132			return this._open( event, target, contentOption );
25133		}
25134
25135		content = contentOption.call( target[0], function( response ) {
25136			// ignore async response if tooltip was closed already
25137			if ( !target.data( "ui-tooltip-open" ) ) {
25138				return;
25139			}
25140			// IE may instantly serve a cached response for ajax requests
25141			// delay this call to _open so the other call to _open runs first
25142			that._delay(function() {
25143				// jQuery creates a special event for focusin when it doesn't
25144				// exist natively. To improve performance, the native event
25145				// object is reused and the type is changed. Therefore, we can't
25146				// rely on the type being correct after the event finished
25147				// bubbling, so we set it back to the previous value. (#8740)
25148				if ( event ) {
25149					event.type = eventType;
25150				}
25151				this._open( event, target, response );
25152			});
25153		});
25154		if ( content ) {
25155			this._open( event, target, content );
25156		}
25157	},
25158
25159	_open: function( event, target, content ) {
25160		var tooltip, events, delayedShow,
25161			positionOption = $.extend( {}, this.options.position );
25162
25163		if ( !content ) {
25164			return;
25165		}
25166
25167		// Content can be updated multiple times. If the tooltip already
25168		// exists, then just update the content and bail.
25169		tooltip = this._find( target );
25170		if ( tooltip.length ) {
25171			tooltip.find( ".ui-tooltip-content" ).html( content );
25172			return;
25173		}
25174
25175		// if we have a title, clear it to prevent the native tooltip
25176		// we have to check first to avoid defining a title if none exists
25177		// (we don't want to cause an element to start matching [title])
25178		//
25179		// We use removeAttr only for key events, to allow IE to export the correct
25180		// accessible attributes. For mouse events, set to empty string to avoid
25181		// native tooltip showing up (happens only when removing inside mouseover).
25182		if ( target.is( "[title]" ) ) {
25183			if ( event && event.type === "mouseover" ) {
25184				target.attr( "title", "" );
25185			} else {
25186				target.removeAttr( "title" );
25187			}
25188		}
25189
25190		tooltip = this._tooltip( target );
25191		addDescribedBy( target, tooltip.attr( "id" ) );
25192		tooltip.find( ".ui-tooltip-content" ).html( content );
25193
25194		function position( event ) {
25195			positionOption.of = event;
25196			if ( tooltip.is( ":hidden" ) ) {
25197				return;
25198			}
25199			tooltip.position( positionOption );
25200		}
25201		if ( this.options.track && event && /^mouse/.test( event.type ) ) {
25202			this._on( this.document, {
25203				mousemove: position
25204			});
25205			// trigger once to override element-relative positioning
25206			position( event );
25207		} else {
25208			tooltip.position( $.extend({
25209				of: target
25210			}, this.options.position ) );
25211		}
25212
25213		tooltip.hide();
25214
25215		this._show( tooltip, this.options.show );
25216		// Handle tracking tooltips that are shown with a delay (#8644). As soon
25217		// as the tooltip is visible, position the tooltip using the most recent
25218		// event.
25219		if ( this.options.show && this.options.show.delay ) {
25220			delayedShow = this.delayedShow = setInterval(function() {
25221				if ( tooltip.is( ":visible" ) ) {
25222					position( positionOption.of );
25223					clearInterval( delayedShow );
25224				}
25225			}, $.fx.interval );
25226		}
25227
25228		this._trigger( "open", event, { tooltip: tooltip } );
25229
25230		events = {
25231			keyup: function( event ) {
25232				if ( event.keyCode === $.ui.keyCode.ESCAPE ) {
25233					var fakeEvent = $.Event(event);
25234					fakeEvent.currentTarget = target[0];
25235					this.close( fakeEvent, true );
25236				}
25237			},
25238			remove: function() {
25239				this._removeTooltip( tooltip );
25240			}
25241		};
25242		if ( !event || event.type === "mouseover" ) {
25243			events.mouseleave = "close";
25244		}
25245		if ( !event || event.type === "focusin" ) {
25246			events.focusout = "close";
25247		}
25248		this._on( true, target, events );
25249	},
25250
25251	close: function( event ) {
25252		var that = this,
25253			target = $( event ? event.currentTarget : this.element ),
25254			tooltip = this._find( target );
25255
25256		// disabling closes the tooltip, so we need to track when we're closing
25257		// to avoid an infinite loop in case the tooltip becomes disabled on close
25258		if ( this.closing ) {
25259			return;
25260		}
25261
25262		// Clear the interval for delayed tracking tooltips
25263		clearInterval( this.delayedShow );
25264
25265		// only set title if we had one before (see comment in _open())
25266		if ( target.data( "ui-tooltip-title" ) ) {
25267			target.attr( "title", target.data( "ui-tooltip-title" ) );
25268		}
25269
25270		removeDescribedBy( target );
25271
25272		tooltip.stop( true );
25273		this._hide( tooltip, this.options.hide, function() {
25274			that._removeTooltip( $( this ) );
25275		});
25276
25277		target.removeData( "ui-tooltip-open" );
25278		this._off( target, "mouseleave focusout keyup" );
25279		// Remove 'remove' binding only on delegated targets
25280		if ( target[0] !== this.element[0] ) {
25281			this._off( target, "remove" );
25282		}
25283		this._off( this.document, "mousemove" );
25284
25285		if ( event && event.type === "mouseleave" ) {
25286			$.each( this.parents, function( id, parent ) {
25287				$( parent.element ).attr( "title", parent.title );
25288				delete that.parents[ id ];
25289			});
25290		}
25291
25292		this.closing = true;
25293		this._trigger( "close", event, { tooltip: tooltip } );
25294		this.closing = false;
25295	},
25296
25297	_tooltip: function( element ) {
25298		var id = "ui-tooltip-" + increments++,
25299			tooltip = $( "<div>" )
25300				.attr({
25301					id: id,
25302					role: "tooltip"
25303				})
25304				.addClass( "ui-tooltip ui-widget ui-corner-all ui-widget-content " +
25305					( this.options.tooltipClass || "" ) );
25306		$( "<div>" )
25307			.addClass( "ui-tooltip-content" )
25308			.appendTo( tooltip );
25309		tooltip.appendTo( this.document[0].body );
25310		this.tooltips[ id ] = element;
25311		return tooltip;
25312	},
25313
25314	_find: function( target ) {
25315		var id = target.data( "ui-tooltip-id" );
25316		return id ? $( "#" + id ) : $();
25317	},
25318
25319	_removeTooltip: function( tooltip ) {
25320		tooltip.remove();
25321		delete this.tooltips[ tooltip.attr( "id" ) ];
25322	},
25323
25324	_destroy: function() {
25325		var that = this;
25326
25327		// close open tooltips
25328		$.each( this.tooltips, function( id, element ) {
25329			// Delegate to close method to handle common cleanup
25330			var event = $.Event( "blur" );
25331			event.target = event.currentTarget = element[0];
25332			that.close( event, true );
25333
25334			// Remove immediately; destroying an open tooltip doesn't use the
25335			// hide animation
25336			$( "#" + id ).remove();
25337
25338			// Restore the title
25339			if ( element.data( "ui-tooltip-title" ) ) {
25340				element.attr( "title", element.data( "ui-tooltip-title" ) );
25341				element.removeData( "ui-tooltip-title" );
25342			}
25343		});
25344	}
25345});
25346
25347}( jQuery ) );
25348
25349
25350
25351
25352
25353
25354
25355
25356
25357
25358
25359
25360
25361
25362
25363
25364
25365
25366
25367
25368
25369
25370
25371
25372
25373
25374
25375
25376
25377
25378
25379
25380
25381
25382var $j = jQuery.noConflict();
25383
25384/**
25385 * Copyright(c) The Allen Institute for Brain Science 2011
25386 *
25387 *
25388 * Tim Dolbeare
25389 */
25390
25391/**
25392 * The root of the javascript libraries for working with the Allen Institute
25393 * warehouse API.
25394 * <p></p>
25395 * The namespace is divided into two main sections--
25396 * <p></p>
25397 * DATA, which not surprisingly handles
25398 * low-level communication with the various services as well as some higher-level fetching and
25399 * persistence.
25400 * <p></p>
25401 * UI, which also not surprisingly deals with displaying data and capturing user input.  It's
25402 * intended that the UI bits be as de-coupled from the data and its schema as practical.
25403 * <p></p>
25404 *
25405 * @namespace
25406 */
25407
25408var AIBS = {};
25409
25410
25411/**
25412 * The root of the Data javascript libraries for working with the Allen Institute
25413 * analysis services.
25414 * <p></p>
25415 *
25416 * @namespace
25417 */
25418AIBS.Analysis = {};
25419
25420/**
25421 * The root of the Data javascript libraries for working with the Allen Institute
25422 * warehouse data.
25423 * <p></p>
25424 *
25425 * @namespace
25426 */
25427AIBS.DATA = {};
25428
25429
25430/**
25431 * The root of the UI javascript libraries for working with the Allen Institute
25432 * warehouse data.
25433 * <p></p>
25434 * It's intended that the UI bits be as de-coupled from the data and its schema as practical.
25435 * This provokes some repeating patterns in the widgets; often a data map is supplied to a widget
25436 * constructor, defining what data goes where.
25437 * <p></p>
25438 *
25439 * @namespace
25440 */
25441AIBS.UI = {};
25442
25443
25444/**
25445 * A container for Components, which are defined as a set of classes required to manage both the
25446 * data and UI for a particular task.
25447 * <p></p>
25448 *
25449 * @namespace
25450 */
25451AIBS.Components = {};
25452
25453
25454/**
25455 * Any code that is coupled up with what we call the framework, and scoped to be usable
25456 * throughout an application.
25457 *
25458 * @namespace
25459 */
25460AIBS.Application = {};
25461
25462
25463function application(options) {
25464
25465    // default settings, can be overridden by options
25466    this.settings = {
25467        theme:'projection',
25468        urls:{
25469            data_service:'/api/v2/data/',
25470            analysis_service:'/mouse/data/',
25471            mapping_service:'/api/v2/'
25472        },
25473        experiment_list_key:'experiment_lists',
25474        default_experiment_list:'default',
25475        config_name: 'default',
25476        config_location:'/application.json',
25477        on_error:do_json_file_error
25478    };
25479
25480    $j.extend(true, this.settings, options);
25481
25482    function inspect(prefix, obj) {
25483        var s = "";
25484        for(key in obj) {
25485            if((typeof obj[key]) == "function")
25486                s = s;
25487            else if((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
25488                s += "\n" + prefix + "key: " + key + inspect(prefix + "    ", obj[key]);
25489            else
25490                s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
25491        }
25492        return(s);
25493    }
25494
25495    this.version = function() {
25496        return(_version);
25497    }
25498
25499    this.current_config_name = function() {
25500        return(_config.current_config);
25501    }
25502
25503    this.app_config = function() {
25504        return(_app_config.application);
25505    }
25506
25507    /**
25508     *
25509     * @param theme_name optional argument; it allows you to access info from any of the defined themes
25510     * @return {*}
25511     */
25512    this.theme_config = function(theme_name) {
25513        var tname = theme_name == undefined ? this.settings.theme : theme_name;
25514        return(_app_config.themes[tname]);
25515    }
25516
25517    /**
25518     *
25519     * @param page_name name of page within the theme
25520     * @param theme_name optional argument; it allows you to access page info from any of the defined themes
25521     * @return {*}
25522     */
25523    this.page_config = function(page_name, theme_name) {
25524        var tname = theme_name == undefined ? this.settings.theme : theme_name;
25525        return(_app_config.themes[tname].pages[page_name]);
25526    }
25527
25528    this.DAOs = function() {
25529        return(_the_daos);
25530    }
25531
25532    this.change_config = function(config_name) {
25533        do_change_config(config_name);
25534    }
25535
25536    this.set_user_data = function(key, value) {
25537        _user_data_store.set(key, value);
25538    }
25539
25540    this.get_user_data = function(key) {
25541        return(_user_data_store.get(key));
25542    }
25543
25544    this.experiment_id_store = function() {
25545        return(_experiment_id_store);
25546    }
25547
25548    this.app_data = function() {
25549        return(_app_data);
25550    }
25551
25552    this.on_ready = options ? options.on_ready ? options.on_ready : null : null;
25553
25554
25555
25556    var _self = this;
25557    var _current_config_name = "";
25558    var _current_experiment_list = "";
25559    var _config = {};
25560    var _app_config = {};
25561
25562    var _title = "Application Title";
25563    var _version = "Unknown";
25564    var _app_data = {};
25565    var _the_daos = {};
25566
25567    var _user_data_store = null;
25568    var _experiment_id_store = null;
25569
25570    var _parse_filenames = true; // do this when parsing main application.json, but not for included .json files.
25571    var _include_files = [];
25572
25573
25574    // loads up the app configuration data from the main application.json file and any extra config files
25575    function load_config() {
25576        // note: jQuery.getJSON does not call error handlers, use jQuery.ajax instead. See http://stackoverflow.com/questions/5388934/jquery-handle-errors-in-getjson
25577        $j.ajax({
25578             url: _self.settings.config_location,
25579             dataType: 'json',
25580            dataFilter: do_parse_comments,
25581            success: function(data) {
25582                if (_include_files.length > 0) {
25583                    // _include_files is populated in do_parse_comments
25584                    data = do_include_configs(data);
25585                }
25586                _config = data;
25587                _version = data.version;
25588                _app_data = data.data;
25589                _current_experiment_list = data.current_experiment_list; // not used by app code, it's for testing
25590                do_change_config(_config.current_config);
25591                if(_self.on_ready)
25592                    _self.on_ready(_self);
25593             },
25594             error: function(data) {
25595                 _self.settings.on_error("JSON file access error: " +  data.statusText);
25596             }
25597        });
25598    }
25599
25600    /**
25601     * Reentrant function that reads in and merges local JSON config files into the main JSON configuration.
25602     * These additional config files are specified within the main config file (application.json) by the use
25603     * of special comment lines: "// include=/the.name.of.the.file.json"
25604     * Will continue until it iterates over the entire _include_files array.
25605     *
25606     * @param {hash} json_data
25607     * @return {hash} JSON data, augmented with elements from the included files
25608     */
25609    function do_include_configs(json_data) {
25610        if (_include_files.length > 0) {
25611            var file_path = _include_files[0];
25612            _include_files.splice(0, 1); // remove the file path from the array
25613            load_additional_config(json_data, file_path);
25614        }
25615
25616        return json_data;
25617    }
25618
25619    /**
25620     * Synchronous merge of extra config files (JSON format) into current JSON config.
25621     * Recursively calls do_include_configs() until there are no more config files to load and merge.
25622     *
25623     * @param {hash} json_data
25624     * @param {string} file_path
25625     * @returns {hash} JSON data merged with extra config
25626     */
25627    function load_additional_config(json_data, file_path) {
25628        _parse_filenames = false; // avoid loading config files within config files
25629
25630        $j.ajax({
25631            async: false,
25632            url: file_path,
25633            cache: true,
25634            dataType: 'json',
25635            dataFilter: do_parse_comments,
25636            success: function(data) {
25637                $j.extend(true, json_data, data);
25638                do_include_configs(json_data);
25639            },
25640            error: function(data) {
25641                _self.settings.on_error("JSON file access error: " +  data.statusText);
25642            }
25643        });
25644
25645        return json_data;
25646    }
25647
25648    /**
25649     * Go thru the raw results of the Ajax call, parsing all single line javascipt comments.
25650     * Lines with '// include=' are parsed for JSON filenames, which are saved for later loading in _include_files.
25651     * After the initial pass to collect filenames, all comment content is removed to ensure the file meet JSON requirements.
25652     *
25653     * @param {string} data raw data from ajax call
25654     * @param {string} type data type, probably 'json'
25655     * @return {string} data stripped of single-line comments. This makes the data JSON-compatible
25656     */
25657    function do_parse_comments(data, type) {
25658        var re,
25659            match,
25660            index;
25661
25662        if (_parse_filenames) {
25663            re = /\/\/\s*include=(.*\.json)/gm;
25664            match = data.match(re);
25665            if (match) {
25666                for (var i = 0, len = match.length; i < len; ++i) {
25667                    // example match data: "// include=/foo.bar.json", only want "/foo.bar.json"
25668                    index = match[i].indexOf('=') + 1;
25669                    _include_files.push(match[i].slice(index));
25670                }
25671            }
25672        }
25673
25674        // remove all line content from '//' to the end of line
25675        return data.replace(/^\/\/.*|[ ]\/\/.*/gm, '');
25676    }
25677
25678    function do_change_config(config_name) {
25679        _app_config = _config.configs[config_name];
25680        _title = _app_config.application.title;
25681        _current_config_name = config_name;
25682
25683        build_daos();
25684        setup_user_data();
25685    }
25686
25687    function build_daos() {
25688
25689        var opts = {
25690            product:_app_config.themes[_self.settings.theme].application.data_sets.join(','),
25691            timeout:_app_config.themes[_self.settings.theme].application.timeout
25692        };
25693
25694        // for convenience, an attempt is made to load these 3 daos.
25695        // For additional/alternative dao objects, configure the dao-name/class-name
25696        // in application.json app.themes.application.daos{}
25697        var daos = {
25698            data:     "AIBS.DATA.ISH_DAO",
25699            analysis: "AIBS.Analysis.DAO",
25700            mapping:  "AIBS.DATA.Mapping_DAO"
25701        };
25702        var dao_options = _app_config.themes[_self.settings.theme].application.daos ? _app_config.themes[_self.settings.theme].application.daos : {}
25703        $j.extend(true, daos, dao_options);
25704
25705        // If the configured dao class exists, it will be loaded.
25706        // NOTE that it is assumed that for each named dao (such as data, analysis, mapping, etc.)
25707        // there is a corresponding service URL.  For example, if a dao n
25707amed 'blargh' is configured
25708        // there should be a blargh_service entry in the options.urls hash used to instantiate this app object.
25709        for(var dao_name in daos) {
25710            if(eval(daos[dao_name]))
25711                _the_daos[dao_name] = eval("new " + daos[dao_name] + "(_self.settings.urls." + dao_name + "_service, opts);");
25712            else
25713                if(window.console)
25714                    console.log("The configured DAO '" + dao_name + "' is not available and has not been loaded.")
25715        }
25716    }
25717
25718    function setup_user_data() {
25719        _user_data_store = new user_data();
25720
25721        // make sure the key 'experiment_lists' is populated with at least 'default'
25722        var exp_list = _user_data_store.get(_self.settings.experiment_list_key);
25723        if(!exp_list || !exp_list['default'])
25724            _user_data_store.set(_self.settings.experiment_list_key, {'default':{}});
25725
25726        _experiment_id_store = new experiment_list(_user_data_store, _self.settings.experiment_list_key, _current_experiment_list);
25727
25728    }
25729
25730    // this is primary for the use of the developer, to flag application.json formatting errors
25731    function do_json_file_error(error) {
25732        alert(error);
25733    }
25734
25735    function init() {
25736
25737        load_config();
25738
25739
25740    }
25741
25742    init();
25743
25744}
25745;
25746/**
25747 * @class
25748 *
25749 * The AIBS.Application.controller is intended to be almost an abstract class; that is it doesn't do
25750 * very much on its own, but can be useful when used with overrides or delegates. This class might be
25751 * used as a page controller, or as a controller for a widget or component.
25752 * <p/>
25753 * The controller's job is to glue together the various components under its control.  When
25754 * one component raises an event that might change the page or component state, and a different
25755 * component should respond, the controller will mediate.
25756 * <p/>
25757 * One of the optional settings provided to the controller at construction is a hash of widget_config hashes.
25758 * It is expected that a derived or composing class will provide a 'widget_properties' delegate
25759 * function that will supplement each widget_config with additional runtime properties, such as
25760 * resource locations or callback/delegate functions.
25761 * <p/>
25762 * A controller is usually used in conjunction with a view class.  Another optional setting for this
25763 * controller class is a 'view_settings' hash. If provided, the view settings should include
25764 * a view_class field that is the name of a view class that has a prototype that
25765 * is at least a subset of that defined by <a href="AIBS.Application.view.html">AIBS.Application.view</a>.
25766 * If provided, the view will be instantiated and the widget_configs passed in to its constructor. The
25767 * view_settings hash can of course contain anything else necessary for the view, and will be passed
25768 * to the view's constructor.
25769 * <p/>
25770 * When used in a page controller the composing class will also probably make use of the <a href="AIBS.Application.page.html">
25771 * AIBS.Application.page</a> class, which encapsulates page state. A View class will probably be defined and
25772 * configured at startup, and a widget_properties_delegate will probably be provided to set event
25773 * handler functions to perform the mediation between the Page object and other controlled components.
25774 * <p/>
25775 *
25776 * @param container         The jQuery object representing the HTML container within which all
25777 *                          of the controlled  UI will live.
25778 * <p/>
25779 * @param settings          <pre>Settings include:
25780 *
25781 *     view_settings:   Optional. A hash containing settings for a view to be associated with this controller.
25782 *                      If the view_settings hash includes a "view_class" member, the given class will
25783 *                      be instantiated with two arguments; the first argument will be the container,
25784 *                      the second will be a hash containing widget_configs (see below) and the contents
25785 *                      of view_settings.
25786 *
25787 *     widget_configs:  A hash of named widget_config objects.  The widget_config object should look like this:
25788 *
25789 *                      "widget_name":{
25790 *                          "class_name":"some_class_name",  // Required. The name of the widget class
25791 *                          "group":"1",                     // Required. Could be used by the View to place
25792 *                                                           // widgets in different areas of the layout.
25793 *                          "order":10,                      // Required. Relative to the group.
25794 *                          "container_class":"some_css_class_name" // Required (can be blank).  A css class that will
25795 *                                                           // be added to the container constructed for this widget.
25796 *                          "settings":{},                   // Required. Any other settings the widget needs.
25797 *                          "build_delegate":some_function(container, settings, view)
25798 *                                                           // Optional.  If provided this delegate will be called to
25799 *                                                           // instantiate the widget.  It must return
25800 *                                                           // a reference to the widget instance.
25801 *                      }
25802 *
25803 *      delegates:      A hash containing delegate references.  Currently the only delegate
25804 *                      defined is widget_properties_delegate. If provided, this delegate will be
25805 *                      called with a single argument, which is a hash of named widgets_config
25806 *                      objects.  The delegate will be called prior to the View being constructed
25807 *                      to allow the delegate to add any additional runtime configuration to each
25808 *                      widget.
25809 * </pre>
25810 *
25811 */
25812
25813AIBS.Application.controller = (function () {
25814
25815    return(
25816        function (container, settings) {
25817
25818            this.container = $j(container);
25819            this.settings = {
25820                view_settings:{
25821                    view_class:null
25822                },
25823                widget_config:{},
25824                delegates:{
25825                    widget_properties_delegate:null
25826                }
25827            };
25828            $j.extend(true, this.settings, settings);
25829
25830
25831            /**
25832             *
25833             * @return {Object} A reference to the instance of a AIBS.Application.view-derived object
25834             * managed by this controller.
25835             */
25836            this.get_view = function () {
25837
25838                return(_the_view);
25839            };
25840
25841
25842            /**
25843             * @private
25844             * @scope AIBS.Application.controller
25845             */
25846            var _self = this;
25847            var _the_view = null;
25848
25849
25850            // this routine first calls 'set_widget_properties' to, you know,
25851            // set the properties of the UI widgets.  The idea is that the
25852            // View is strictly layout and user interface. Any connection
25853            // between the data, the configuration, or between UI elements
25854            // happens here in the controller.
25855            function build_view() {
25856
25857                set_widget_properties(_self.settings.widget_config);
25858
25859                if (!_self.settings.view_settings.view_class)
25860                    return;
25861
25862                var options = {
25863                    widget_config:{}
25864                };
25865                $j.extend(true, options, _self.settings.view_settings);
25866                $j.extend(true, options.widget_config, _self.settings.widget_config);
25867
25868                _the_view = new (eval(_self.settings.view_settings.view_class))(_self.container, options);
25869            }
25870
25871
25872            // Some of the widgets need to have run-time configuration data set, like
25873            // resource locations, event handlers, etc.  Or there may be config decisions
25874            // that will be based on this particular data set. This routine sets these
25875            // config options for each of the widgets prior to handing them off to the
25876            // view, where they are constructed.
25877            function set_widget_properties(widgets) {
25878
25879                if (_self.settings.delegates.widget_properties_delegate)
25880                    _self.settings.delegates.widget_properties_delegate(widgets);
25881            }
25882
25883            function init() {
25884
25885                build_view();
25886            }
25887
25888            init();
25889        });
25890})();
25891
25892/*
25893 *  A wrapper around the user_data object to simplify getting/setting experiment lists.
25894 *  This assumes a data store with methods to get & set key/value pairs.  It's also assumed
25895 *  that the data store contains an object that looks like this:
25896      storage = {
25897         <root_name>:{
25898             experiment_lists:{
25899                 Default:{}
25900             }
25901         }
25902      };
25903 */
25904
25905function experiment_list(store, root_name, initial_list_name) {
25906
25907// public properties
25908    this.data_store = store;
25909    this.root_name = root_name;
25910    this.list_name = initial_list_name;
25911
25912// events
25913    this.on_add = null;
25914    this.on_remove = null;
25915    this.on_clear = null;
25916
25917// public methods
25918    this.exists = function(id) {
25919       return(check_exists(this.list_name, id));
25920    }
25921
25922    // note that if the current list_name does not exist, it will be created
25923    // when something is added to it with this method.
25924    this.add = function(id) {
25925        var ret = add_to_list(this.list_name, id);
25926        if(this.on_add)
25927            this.on_add(this, id);
25928
25929        return(ret);
25930    }
25931
25932    this.remove = function(id) {
25933        var ret = remove_from_list(this.list_name, id);
25934        if(this.on_remove)
25935            this.on_remove(this, id);
25936
25937        return(ret);
25938    }
25939
25940    this.clear = function() {
25941        var ret = clear_list(this.list_name);
25942        if(this.on_clear)
25943            this.on_clear(this, this.list_name);
25944
25945        return(ret);
25946    }
25947
25948    this.get_list = function() {
25949       var root = this.data_store.get(this.root_name);
25950       if(!root || root.length == 0 || !root || root.length == 0)
25951         return(null);
25952
25953       return(root[this.list_name]);
25954    }
25955
25956    this.set_list = function(list_name) {
25957       get_or_create_list(list_name);
25958       this.list_name = list_name;
25959    }
25960
25961    this.get_list_names = function() {
25962        var root = this.data_store.get(this.root_name);
25963        var lists = root;
25964
25965        var names = [];
25966        for(key in lists)
25967            names.push(key);
25968
25969        return(names);
25970    }
25971
25972    this.count = function() {
25973        var root = this.data_store.get(this.root_name);
25974        var list = root[this.list_name];
25975        var i = 0;
25976        for(key in list)
25977            i++;
25978
25979        return(i);
25980    }
25981
25982
25983// private properties
25984    var _self = this;
25985
25986
25987// private methods
25988    function check_exists(list_name, id) {
25989       var root = _self.data_store.get(_self.root_name);
25990       if(!root || root.length == 0 || !root || root.length == 0 || !root[list_name])
25991         return(false);
25992
25993       return(root[list_name][id] != undefined);
25994    }
25995
25996    function add_to_list(list_name, id) {
25997       var root = _self.data_store.get(_self.root_name);
25998       if(!root || root.length == 0 || !root || root.length == 0)
25999         return(false);
26000
26001       if(!root[list_name])
26002         root[list_name] = {};
26003
26004       root[list_name][id] = true;
26005       _self.data_store.set(_self.root_name, root);
26006       return(true);
26007    }
26008
26009    function get_or_create_list(list_name) {
26010        var list = root[list_name];
26011        if(!list)
26012           root[list_name] = {};
26013
26014        return(list);
26015    }
26016
26017    function remove_from_list(list_name, id) {
26018       var root = _self.data_store.get(_self.root_name);
26019       if(!root || root.length == 0 || !root || root.length == 0 || !root[list_name])
26020         return(false);
26021
26022       delete root[list_name][id];
26023       _self.data_store.set(_self.root_name, root);
26024       return(true);
26025    }
26026
26027    function clear_list(list_name) {
26028       var root = _self.data_store.get(_self.root_name);
26029       if(!root || root.length == 0 || !root || root.length == 0 || !root[list_name])
26030         return(false);
26031
26032       delete root[list_name];
26033       root[list_name] = {};
26034       _self.data_store.set(_self.root_name, root);
26035
26036       return(true);
26037    }
26038
26039}
26040;
26041
26042
26043
26044function app_page(container, name, app, options) {
26045
26046
26047    this.container = container;
26048    this.page_name = name;
26049    this.application = app;
26050
26051    this.settings = {
26052        on_page_change:null
26053    };
26054
26055    $j.extend(true, this.settings, options);
26056
26057
26058    this.set_busy_container = function(new_container) {
26059        position_busy_indicator(new_container);
26060    }
26061
26062    this.show_busy = function(busy) {
26063        do_show_busy(busy);
26064    }
26065
26066    this.set_state = function(state) {
26067        do_push_state(state);
26068    }
26069
26070    this.get_state = function() {
26071        return(_state);
26072    }
26073
26074    this.get_config = function() {
26075        return(_config);
26076    }
26077
26078
26079    var _self = this;
26080    var _events_setup = false;
26081    var _config = {};
26082    var _state = {};
26083    var _busy_indicator = {};
26084    var _history = null;
26085    var _changing_state = false;
26086
26087
26088    function position_busy_indicator(container) {
26089
26090         if(!_busy_indicator.panel)
26091            setup_busy_indicator(container);
26092
26093         var pos = container.offset();
26094         var container_width = container.outerWidth();
26095         var container_height = container.outerHeight();
26096
26097         _busy_indicator.panel.css({
26098             position:'absolute',
26099	     background: "url('/assets/aiclient/ajax-loader.gif') no-repeat center center",
26100             top:pos.top,
26101             left:pos.left,
26102             'background-color':'#FFF',
26103             opacity:0.7,
26104             width:container_width,
26105             height:container_height,
26106             display:'none'
26107         });
26108/*
26109         _busy_indicator.indicator.css({
26110             position:'absolute',
26111             top:pos.top + (container_height/2),
26112             left:pos.left + (container_width/2),
26113             display:'none'
26114         });
26115*/
26116    }
26117
26118    function setup_busy_indicator(container) {
26119
26120         _self.container = container;
26121
26122         var html = "<div id='busy_panel'></div>";
26123         _busy_indicator.panel = $j(html);
26124         _busy_indicator.panel.appendTo(container);
26125/*
26126         var indicator_html = "<img id='busy_indicator' src='/assets/ajax-loader.gif'/>";
26127         _busy_indicator.indicator = $j(indicator_html);
26128         _busy_indicator.indicator.appendTo(container);
26129*/
26130        //position_busy_indicator(container);
26131        setup_events();
26132    }
26133
26134    function do_show_busy(busy) {
26135
26136        if(!_busy_indicator.panel) {
26137            setup_busy_indicator(_self.container);
26138        }
26139
26140        if(busy) {
26141
26142            position_busy_indicator(_self.container);
26143            _busy_indicator.panel.show();
26144//            _busy_indicator.indicator.show();
26145        }
26146        else {
26147
26148            _busy_indicator.panel.hide();
26149//            _busy_indicator.indicator.hide();
26150        }
26151    }
26152
26153    function load_state() {
26154
26155      var state = {};
26156      var url_state = split_url(window.location.href);
26157
26158      if(!$j.isEmptyObject(url_state)) {
26159          for(var key in url_state) {
26160              if(!key || key === "") continue;
26161              state[key] = decode_term(url_state[key]);
26162          }
26163
26164          // convert the true or false strings into true or false values
26165          if(state.exact_match)
26166            state.exact_match = state.exact_match == 'true';
26167          if(state.no_paging)
26168            state.no_paging = state.no_paging == 'true';
26169      }
26170
26171      return(state);
26172    }
26173
26174    function setup_history() {
26175      _history = window.history;
26176      if (!_history.enabled)
26177            return;
26178
26179      // setup the baseline url so we can compare changes and raise an event is necessary
26180      _history.Adapter.bind(window, 'statechange', check_state_change);
26181    }
26182
26183    function check_state_change() {
26184        if(_changing_state) {
26185            _changing_state = false;
26186            return;
26187        }
26188
26189        _state = load_state();
26190        if(_self.on_page_change)
26191            _self.on_page_change(_state);
26192    }
26193
26194    function inspect(prefix, obj) {
26195        var s = "";
26196        for(key in obj) {
26197            if((typeof obj[key]) == "function")
26198                s = s;
26199            else if((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
26200                s += "\n" + prefix + "key: " + key + inspect(prefix + "    ", obj[key]);
26201            else
26202                s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
26203        }
26204        return(s);
26205    }
26206
26207    function do_push_state(state) {
26208        _state = state;
26209        var state_string = "?";
26210        var count = 0;
26211        var val = "";
26212
26213        for(var key in state) {
26214            if(count++)
26215              state_string += "&";
26216
26217            if( (typeof state[key]) == "object" && ( state[key] instanceof Array ) && state[key] !== null ){
26218                state_string += encode_array(state[key], key);
26219            }
26220            else if((typeof state[key]) == "object" && state[key] !== null) {
26221                state_string += encode_object(state[key]);
26222            } else {
26223            val =  encode_term(new String(state[key]));
26224            state_string += key + "=" + val;
26225            }
26226
26227        }
26228        _changing_state = true;
26229        History.pushState({state:1}, _config.title, state_string);
26230    }
26231
26232    function encode_object(obj) {
26233        var val = ''
26234        for(var key in obj) {
26235            if(val.length)
26236                val += '&';
26237            val += key + '=' + new String(obj[key]);
26238        }
26239        return(encodeURIComponent(val));
26240    }
26241
26242    function encode_array(arr,attr_key) {
26243        var target_str = '';
26244        for(var i=0; i < arr.length; i++) {
26245            if(i>0)
26246                target_str += ',';
26247            else
26248                target_str += attr_key + '=';
26249            target_str += arr[i];
26250        }
26251        return target_str;
26252    }
26253
26254    function split_url(url) {
26255         var param_hash = {};
26256
26257         url = url.replace('?&', '?');
26258         if(!url || !url.split("?") || url.split("?").length < 2)
26259           return({});
26260
26261         // Edge case: if this is an IE url that is carrying history info on the url, it will look like http://foobar/show?arg1=moo#show?arg1=moo2 etc.
26262         // For this kind of url, you want to collect the original args, and overwrite with newer history arg values.
26263         var numQ = url.split("?").length - 1;
26264         if (numQ > 1) {
26265             // take into account edge case, where original url had no args, but new url has args: url like http://foobar/show?#show?arg1=moo2
26266             var oldAndNew = url.split("#");
26267             var paramsOld = oldAndNew[0].split("?");
26268             if (paramsOld.length == 2)
26269                 paramsOld = paramsOld[1].split("&");
26270             else
26271                 paramsOld = [];
26272
26273             var paramsNew = oldAndNew[1].split("?");
26274             if (paramsNew.length == 2)
26275                 paramsNew = paramsNew[1].split("&");
26276             else
26277                 paramsNew = [];
26278
26279             var terms;
26280             for (var i = 0; i < paramsOld.length; i++) {
26281                 terms = paramsOld[i].split("=");
26282                 param_hash[terms[0]] = terms[1];
26283             }
26284             // overwrite existing term values, add new terms and values
26285             for (i = 0; i < paramsNew.length; i++) {
26286                 terms = paramsNew[i].split("=");
26287                 param_hash[terms[0]] = terms[1];
26288             }
26289         }
26290         else {
26291             // normal url processing
26292             var params = url.split("?")[1].split("&");
26293
26294             for(var i = 0; i < params.length; i++) {
26295                 var terms = params[i].split("=");
26296                 param_hash[terms[0]] = terms[1];
26297             }
26298         }
26299
26300         return(param_hash);
26301     }
26302
26303
26304    function decode_term(term) {
26305
26306        // decode our private encoding for newlines-- \n == $
26307        var new_term = term.replace(/\$/g,'\n');
26308        // url decoding
26309        new_term = decodeURIComponent(new_term);
26310
26311        return(new_term);
26312    }
26313
26314    function encode_each_term(term, delimiter) {
26315        var terms = term.split(delimiter);
26316
26317        if(terms.length == 1)
26318            return(encodeURIComponent(trim(term)));
26319
26320        var term_string = '';
26321        for(var i = 0; i < terms.length; i++)
26322            term_string += trim(terms[i]) + '$';
26323
26324        var new_term = encodeURIComponent(term_string);
26325
26326        return(new_term);
26327    }
26328
26329    function encode_term(term) {
26330        return(encode_each_term(encode_each_term(term, '\n'), '\t'));
26331    }
26332
26333    function trim(s)
26334    {
26335        var l = 0;
26336        var r = s.length - 1;
26337
26338        while(l < s.length && s[l] == ' ') l++;
26339        while(r > l && s[r] == ' ') r -= 1;
26340
26341        return s.substring(l, r+1);
26342    }
26343
26344    function setup_events() {
26345        if(_events_setup)
26346            return;
26347
26348        $j(window).bind('resize', function(){position_busy_indicator(_self.container)});
26349        _events_setup = true;
26350    }
26351
26352    function init() {
26353        _config = _self.application.page_config(_self.page_name);
26354        setup_history();
26355        //setup_busy_indicator(_self.container);
26356        _state = load_state();
26357        //setup_events();
26358    }
26359
26360    init();
26361}
26362;
26363
26364/*
26365 * Peristent key/value storage. If initialized with default values the defaults
26366 * will be returned when the data store does not have the given key.
26367 */
26368
26369
26370function user_data(defaults) {
26371
26372    this.defaults = defaults ? defaults : {};
26373
26374    this.set = function(name, value) {
26375        // Backward-compatibility priority given to toJSON use, if defined. Otherwise, use more generally-acceptable stringify
26376        if (Object.toJSON) // Prototype javascript lib, see http://jira.corp.alleninstitute.org/browse/IT-7265
26377            set_data(name, Object.toJSON(value));
26378        else
26379            set_data(name, JSON.stringify(value));
26380    }
26381
26382    this.get = function(name) {
26383
26384        var data = get_data(name);
26385        if(data && data.length > 0 && data != '""') {
26386            var value = eval("("+data+")");
26387            if(value == 'false' || value == 'true')
26388                value = (value == 'true');
26389
26390            return(value);
26391        }
26392        else
26393            data = this.defaults[name];
26394
26395        return(data);
26396    }
26397
26398    function set_data(name, value) {
26399        localStorage.setItem(name, encodeURI(value));
26400    }
26401
26402    function get_data(name) {
26403
26404      var data = localStorage.getItem(name);
26405      if(data)
26406        return(decodeURI(data));
26407
26408      return(null);
26409    }
26410}
26411
26412;
26413
26414
26415AIBS.Application.__view_count__ = AIBS.Application.__view_count__ || 0;
26416
26417/**
26418 * @class
26419 *
26420 * The AIBS.Application.view is intended primarily to be used as a base or composition class for component
26421 * or page views.  The primary work performed by this view is to create a container for and instantiate each
26422 * of the list of components given in the settings.widget_configs param.  It is expected that the derived or
26423 * composing class will provide a layout delegate that will construct the HTML scaffold in which the widgets
26424 * will live.
26425 * <p/>
26426 * See also <a href="AIBS.Application.controller.html">AIBS.Application.controller</a> for a Controller
26427 * class designed to work with this View class.
26428 *
26429 *
26430 * @param container         The jQuery object representing the HTML container within which all
26431 *                          of the UI will live.
26432 * <p/>
26433 * @param settings          <pre>Settings include:
26434 *
26435 *     widget_configs:  A hash of named widget_config objects.  The widget_config object should look like this:
26436 *
26437 *                      "widget_name":{
26438 *                          "class_name":"some_class_name",  // Required. The name of the widget class
26439 *                          "group":"1",                     // Required. Could be used by the View to place
26440 *                                                           // widgets in different areas of the layout.
26441 *                          "order":10,                      // Required. Relative to the group.
26442 *                          "settings":{
26443 *                              on_ready:null
26444 *                          },                               // Required. The settings hash should include any settings a build
26445 *                                                           // delegate would need to construct the widget.
26446 *                                                           // One requied key in the settings hash is an on_ready callback.
26447 *                                                           // The on_ready callback must be invoked by the widget builder
26448 *                                                           // when the widget is ready for programatic and/or user interaction.
26449 *                                                           // The callback should be invoked with a reference to the widget itself,
26450 *                                                           // as in _self.settings.on_ready(_self);
26451 *
26452 *                          "build_delegate":some_builder(container_id, widget_settings, view_reference)
26453 *                                                           // Optional.  If provided, the widget's build_delegate will be called
26454 *                                                           // to instantiate the widget.  The delegate should return a
26455 *                                                           // reference to the widget instance.  All widgets should
26456 *                      }
26457 *
26458 *      delegates:      A hash containing delegate references.  Currently the only delegate
26459 *                      defined is the layout_delegate.
26460 *
26461 *                      layout_delegate:  It's expected that a delegate will be supplied for this.  The delegate will be invoked with
26462 *                                   three arguments:
26463 *                                   container:       A jQuery element that is the container for everything that is part of this view.
26464 *                                   widget_config:   A hash of widget configuration objects, keyed by widget name.
26465 *                                   id:              An ID value that is unique within this page scope which can be used as a prefix
26466 *                                                    to uniquely identify widgets belonging to this view.
26467 *
26468 *                                   The primary requirement of a layout_delegate is to make sure that each widget_config.container_id
26469 *                                   has a corresponding element with html id = the appropriate widget.container_id.  This view will use the
26470 *                                   jQuery selector ('#' + widget_config.container_id) when constructing (or invoking a build_delegate)
26471 *                                   for each widget.
26472 *
26473 *                                   the layout delegate is responsible for attaching any new content to the given container.
26474 *
26475 *                                   If no delegate is provided, a default layout is created consisting of a container div
26476 *                                   and one contained div per widget.
26477 * </pre>
26478 *
26479 */
26480AIBS.Application.view = (function() {
26481
26482    return(
26483        function(container, settings) {
26484
26485// public properties
26486        this.container = $j(container);
26487        this.options = {
26488            on_ready:null,
26489            delegates:{
26490                layout_delegate:null
26491            },
26492            widget_config:{}
26493        };
26494        $j.extend(true, this.options, settings);
26495
26496// public methods
26497
26498        /**
26499         * Returns the instance ID for this view.
26500         * @return {*}
26501         */
26502        this.id = function () {
26503            return(_id);
26504        };
26505
26506        /**
26507         * Set an error message to be displayed in the view.  The error
26508         * message will be displayed in place of the named widget.
26509         *
26510         * @param widget_name
26511         * @param msg
26512         */
26513        this.set_error = function (widget_name, msg) {
26514
26515            var widget_container = $j('#' + _widget_list[widget_name].container_id);
26516            container_error($j(widget_container[0].parentNode), msg);
26517        };
26518
26519        /**
26520         * Finds and returns the named widget.  The name value is a key given
26521         * in settings.widget_configs.
26522         *
26523         * @param name
26524         * @return {Object} A widget_config object
26525         */
26526        this.get_widget_by_name = function (name) {
26527
26528            if (_widget_list[name])
26529                return(_widget_list[name].instance);
26530
26531            return(null);
26532        };
26533
26534        /**
26535         * The same widget_config hash as settings.widget_configs.
26536         *
26537         * @return {Object} The same widget_config hash as settings.widget_configs.
26538         */
26539        this.get_widgets = function() {
26540
26541            return(_widget_list);
26542        };
26543
26544        this.destroy = function () {
26545
26546            for(var name in _widget_list)
26547                if(_widget_list[name].destroy)
26548                    _widget_list[name].destroy();
26549
26550            if(_own_layout)
26551                _own_layout.remove();
26552        };
26553
26554
26555// private properties
26556
26557        /**
26558         * @private
26559         * @scope AIBS.Application.view
26560         */
26561        var _self = this;
26562        var _id = null;
26563        var _widget_list = null;
26564        var _own_layout = false;
26565
26566
26567        function container_error(container, msg) {
26568
26569            var err_html = "<div class='err_notice ui-state-error'>" + msg + "</div>";
26570            container.html(err_html);
26571        }
26572
26573        // It's expected that a delegate will be supplied for this.  The delegate will be invoked with
26574        // three arguments:
26575        // container:       A jQuery element that is the container for everything that is part of this view.
26576        // widget_config:   A hash of widget configuration objects, keyed by widget name.
26577        // id:              An ID value that is unique within this page scope which can be used as a prefix
26578        //                  to uniquely identify widgets belonging to this view.
26579        //
26580        // The primary requirement of a layout_delegate is to make sure that each widget_config.container_id
26581        // has a corresponding element with html id = the appropriate widget.container_id.  This view will use the
26582        // jQuery selector ('#' + widget_config.container_id) when constructing (or invoking a build_delegate)
26583        // for each widget.
26584        //
26585        // the layout delegate is responsible for attaching any new content to the given container.
26586        //
26587        // If no delegate is provided, a default layout is created consisting of a container div
26588        // and one contained div per widget.
26589        function build_layout() {
26590
26591            if (_self.options.delegates.layout_delegate) {
26592                _self.options.delegates.layout_delegate(_self.container, _self.options.widget_config, _id);
26593                return;
26594            }
26595
26596            // the default layout is just a container full of divs.
26597            var html = "<div id='" + _id + "_page_view_wrapper' class='_page_view_wrapper'>";
26598            for(var name in _self.options.widget_config) {
26599
26600                var widget = _self.options.widget_config[name];
26601                widget.container_id = _id + "_" + name;
26602                html += "<div id='" + widget.container_id + "' class='" + widget.container_class + "'></div>";
26603            }
26604
26605            html += "</div>";
26606            _own_layout = $j(html).appendTo(_self.container);
26607        }
26608
26609        function build_widgets() {
26610
26611            _widget_list = {};
26612            for (var name in _self.options.widget_config) {
26613
26614                var widget = _self.options.widget_config[name];
26615                _widget_list[name] = widget;
26616
26617                // many oddball widgets will need build delegates
26618                if (widget.build_delegate) {
26619
26620                    // Some widgets do some asynchronous construction; if so they should invoke an on_ready
26621                    // call back when they are ready enough to have their 'set_state' method called.
26622                    widget.settings.on_ready = function(the_instance) { _widget_list[name].instance = the_instance; widget.ready = true; check_ready(); }
26623                    _widget_list[name].instance = widget.build_delegate('#' + widget.container_id, widget.settings, _self);
26624                }
26625                // the info_widgets are numerous and similar enough to be built directly
26626                else {
26627
26628                    // this is a syncronous process, so mark them ready immediately
26629                    _widget_list[name].instance = new (eval(widget.class_name))('#'+widget.container_id, widget.settings, _self);
26630                    widget.ready = true;
26631                }
26632            }
26633
26634            // maybe everything's already ready?
26635             check_ready();
26636        }
26637
26638        function check_ready() {
26639
26640            for(var name in _widget_list)
26641                if(!_widget_list[name].ready)
26642                    return;
26643
26644            trace(_id + " is ready.");
26645            if(_self.options.on_ready)
26646                _self.options.on_ready(_self);
26647        }
26648
26649        function init() {
26650
26651            _id = 'view' + AIBS.Application.__view_count__++;
26652trace("initializing " + _id);
26653            build_layout();
26654            build_widgets();
26655        }
26656
26657        init();
26658    });
26659})();
26660
26661
26662
26663
26664
26665
26666/*! 
26667 * jquery.event.drag - v 2.0.0 
26668 * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
26669 * Open Source MIT License - http://threedubmedia.com/code/license
26670 */
26671// Created: 2008-06-04 
26672// Updated: 2010-06-07
26673// REQUIRES: jquery 1.4.2+
26674
26675;(function( $ ){
26676
26677// add the jquery instance method
26678$.fn.drag = function( str, arg, opts ){
26679	// figure out the event type
26680	var type = typeof str == "string" ? str : "",
26681	// figure out the event handler...
26682	fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null;
26683	// fix the event type
26684	if ( type.indexOf("drag") !== 0 ) 
26685		type = "drag"+ type;
26686	// were options passed
26687	opts = ( str == fn ? arg : opts ) || {};
26688	// trigger or bind event handler
26689	return fn ? this.bind( type, opts, fn ) : this.trigger( type );
26690};
26691
26692// local refs (increase compression)
26693var $event = $.event, 
26694$special = $event.special,
26695// configure the drag special event 
26696drag = $special.drag = {
26697	
26698	// these are the default settings
26699	defaults: {
26700		which: 1, // mouse button pressed to start drag sequence
26701		distance: 0, // distance dragged before dragstart
26702		not: ':input', // selector to suppress dragging on target elements
26703		handle: null, // selector to match handle target elements
26704		relative: false, // true to use "position", false to use "offset"
26705		drop: true, // false to suppress drop events, true or selector to allow
26706		click: false // false to suppress click events after dragend (no proxy)
26707	},
26708	
26709	// the key name for stored drag data
26710	datakey: "dragdata",
26711	
26712	// the namespace for internal live events
26713	livekey: "livedrag",
26714	
26715	// count bound related events
26716	add: function( obj ){ 
26717		// read the interaction data
26718		var data = $.data( this, drag.datakey ),
26719		// read any passed options 
26720		opts = obj.data || {};
26721		// count another realted event
26722		data.related += 1;
26723		// bind the live "draginit" delegator
26724		if ( !data.live && obj.selector ){
26725			data.live = true;
26726			$event.add( this, "draginit."+ drag.livekey, drag.delegate );
26727		}
26728		// extend data options bound with this event
26729		// don't iterate "opts" in case it is a node 
26730		$.each( drag.defaults, function( key, def ){
26731			if ( opts[ key ] !== undefined )
26732				data[ key ] = opts[ key ];
26733		});
26734	},
26735	
26736	// forget unbound related events
26737	remove: function(){
26738		$.data( this, drag.datakey ).related -= 1;
26739	},
26740	
26741	// configure interaction, capture settings
26742	setup: function(){
26743		// check for related events
26744		if ( $.data( this, drag.datakey ) ) 
26745			return;
26746		// initialize the drag data with copied defaults
26747		var data = $.extend({ related:0 }, drag.defaults );
26748		// store the interaction data
26749		$.data( this, drag.datakey, data );
26750		// bind the mousedown event, which starts drag interactions
26751		$event.add( this, "mousedown", drag.init, data );
26752		// prevent image dragging in IE...
26753		if ( this.attachEvent ) 
26754			this.attachEvent("ondragstart", drag.dontstart ); 
26755	},
26756	
26757	// destroy configured interaction
26758	teardown: function(){
26759		// check for related events
26760		if ( $.data( this, drag.datakey ).related ) 
26761			return;
26762		// remove the stored data
26763		$.removeData( this, drag.datakey );
26764		// remove the mousedown event
26765		$event.remove( this, "mousedown", drag.init );
26766		// remove the "live" delegation
26767		$event.remove( this, "draginit", drag.delegate );
26768		// enable text selection
26769		drag.textselect( true ); 
26770		// un-prevent image dragging in IE...
26771		if ( this.detachEvent ) 
26772			this.detachEvent("ondragstart", drag.dontstart ); 
26773	},
26774		
26775	// initialize the interaction
26776	init: function( event ){
26777		// the drag/drop interaction data
26778		var dd = event.data, results;
26779		// check the which directive
26780		if ( dd.which > 0 && event.which != dd.which ) 
26781			return; 
26782		// check for suppressed selector
26783		if ( $( event.target ).is( dd.not ) ) 
26784			return;
26785		// check for handle selector
26786		if ( dd.handle && !$( event.target ).closest( dd.handle, event.currentTarget ).length ) 
26787			return;
26788		// store/reset some initial attributes
26789		dd.propagates = 1;
26790		dd.interactions = [ drag.interaction( this, dd ) ];
26791		dd.target = event.target;
26792		dd.pageX = event.pageX;
26793		dd.pageY = event.pageY;
26794		dd.dragging = null;
26795		// handle draginit event... 
26796		results = drag.hijack( event, "draginit", dd );
26797		// early cancel
26798		if ( !dd.propagates )
26799			return;
26800		// flatten the result set
26801		results = drag.flatten( results );
26802		// insert new interaction elements
26803		if ( results && results.length ){
26804			dd.interactions = [];
26805			$.each( results, function(){
26806				dd.interactions.push( drag.interaction( this, dd ) );
26807			});
26808		}
26809		// remember how many interactions are propagating
26810		dd.propagates = dd.interactions.length;
26811		// locate and init the drop targets
26812		if ( dd.drop !== false && $special.drop ) 
26813			$special.drop.handler( event, dd );
26814		// disable text selection
26815		drag.textselect( false ); 
26816		// bind additional events...
26817		$event.add( document, "mousemove mouseup", drag.handler, dd );
26818		// helps prevent text selection
26819		return false;
26820	},	
26821	// returns an interaction object
26822	interaction: function( elem, dd ){
26823		return {
26824			drag: elem, 
26825			callback: new drag.callback(), 
26826			droppable: [],
26827			offset: $( elem )[ dd.relative ? "position" : "offset" ]() || { top:0, left
26827:0 }
26828		};
26829	},
26830	// handle drag-releatd DOM events
26831	handler: function( event ){ 
26832		// read the data before hijacking anything
26833		var dd = event.data;
26834		// handle various events
26835		switch ( event.type ){
26836			// mousemove, check distance, start dragging
26837			case !dd.dragging && 'mousemove': 
26838				//  drag tolerance, x² + y² = distance²
26839				if ( Math.pow(  event.pageX-dd.pageX, 2 ) + Math.pow(  event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) ) 
26840					break; // distance tolerance not reached
26841				event.target = dd.target; // force target from "mousedown" event (fix distance issue)
26842				drag.hijack( event, "dragstart", dd ); // trigger "dragstart"
26843				if ( dd.propagates ) // "dragstart" not rejected
26844					dd.dragging = true; // activate interaction
26845			// mousemove, dragging
26846			case 'mousemove': 
26847				if ( dd.dragging ){
26848					// trigger "drag"		
26849					drag.hijack( event, "drag", dd );
26850					if ( dd.propagates ){
26851						// manage drop events
26852						if ( dd.drop !== false && $special.drop )
26853							$special.drop.handler( event, dd ); // "dropstart", "dropend"
26854						break; // "drag" not rejected, stop		
26855					}
26856					event.type = "mouseup"; // helps "drop" handler behave
26857				}
26858			// mouseup, stop dragging
26859			case 'mouseup': 
26860				$event.remove( document, "mousemove mouseup", drag.handler ); // remove page events
26861				if ( dd.dragging ){
26862					if ( dd.drop !== false && $special.drop ) 
26863						$special.drop.handler( event, dd ); // "drop"
26864					drag.hijack( event, "dragend", dd ); // trigger "dragend"	
26865					}
26866				drag.textselect( true ); // enable text selection
26867				
26868				// if suppressing click events...
26869				if ( dd.click === false && dd.dragging ){
26870					jQuery.event.triggered = true;
26871					setTimeout(function(){
26872						jQuery.event.triggered = false;
26873					}, 20 );
26874				dd.dragging = false; // deactivate element	
26875				}
26876				break;
26877		}
26878	},
26879	
26880	// identify potential delegate elements
26881	delegate: function( event ){
26882		// local refs
26883		var elems = [], target, 
26884		// element event structure
26885		events = $.data( this, "events" ) || {};
26886		// query live events
26887		$.each( events.live || [], function( i, obj ){
26888			// no event type matches
26889			if ( obj.preType.indexOf("drag") !== 0 )
26890				return;
26891			// locate the element to delegate
26892			target = $( event.target ).closest( obj.selector, event.currentTarget )[0];
26893			// no element found
26894			if ( !target ) 
26895				return;
26896			// add an event handler
26897			$event.add( target, obj.origType+'.'+drag.livekey, obj.origHandler, obj.data );
26898			// remember new elements
26899			if ( $.inArray( target, elems ) < 0 )
26900				elems.push( target );		
26901		});
26902		// if there are no elements, break
26903		if ( !elems.length ) 
26904			return false;
26905		// return the matched results, and clenup when complete		
26906		return $( elems ).bind("dragend."+ drag.livekey, function(){
26907			$event.remove( this, "."+ drag.livekey ); // cleanup delegation
26908		});
26909	},
26910	
26911	// re-use event object for custom events
26912	hijack: function( event, type, dd, x, elem ){
26913		// not configured
26914		if ( !dd ) 
26915			return;
26916		// remember the original event and type
26917		var orig = { event:event.originalEvent, type: event.type },
26918		// is the event drag related or drog related?
26919		mode = type.indexOf("drop") ? "drag" : "drop",
26920		// iteration vars
26921		result, i = x || 0, ia, $elems, callback,
26922		len = !isNaN( x ) ? x : dd.interactions.length;
26923		// modify the event type
26924		event.type = type;
26925		// remove the original event
26926		event.originalEvent = null;
26927		// initialize the results
26928		dd.results = [];
26929		// handle each interacted element
26930		do if ( ia = dd.interactions[ i ] ){
26931			// validate the interaction
26932			if ( type !== "dragend" && ia.cancelled )
26933				continue;
26934			// set the dragdrop properties on the event object
26935			callback = drag.properties( event, dd, ia );
26936			// prepare for more results
26937			ia.results = [];
26938			// handle each element
26939			$( elem || ia[ mode ] || dd.droppable ).each(function( p, subject ){
26940				// identify drag or drop targets individually
26941				callback.target = subject;
26942				// handle the event	
26943				result = subject ? $event.handle.call( subject, event, callback ) : null;
26944				// stop the drag interaction for this element
26945				if ( result === false ){
26946					if ( mode == "drag" ){
26947						ia.cancelled = true;
26948						dd.propagates -= 1;
26949					}
26950					if ( type == "drop" ){
26951						ia[ mode ][p] = null;
26952					}
26953				}
26954				// assign any dropinit elements
26955				else if ( type == "dropinit" )
26956					ia.droppable.push( drag.element( result ) || subject );
26957				// accept a returned proxy element 
26958				if ( type == "dragstart" )
26959					ia.proxy = $( drag.element( result ) || ia.drag )[0];
26960				// remember this result	
26961				ia.results.push( result );
26962				// forget the event result, for recycling
26963				delete event.result;
26964				// break on cancelled handler
26965				if ( type !== "dropinit" )
26966					return result;
26967			});	
26968			// flatten the results	
26969			dd.results[ i ] = drag.flatten( ia.results );	
26970			// accept a set of valid drop targets
26971			if ( type == "dropinit" )
26972				ia.droppable = drag.flatten( ia.droppable );
26973			// locate drop targets
26974			if ( type == "dragstart" && !ia.cancelled )
26975				callback.update(); 
26976		}
26977		while ( ++i < len )
26978		// restore the original event & type
26979		event.type = orig.type;
26980		event.originalEvent = orig.event;
26981		// return all handler results
26982		return drag.flatten( dd.results );
26983	},
26984		
26985	// extend the callback object with drag/drop properties...
26986	properties: function( event, dd, ia ){		
26987		var obj = ia.callback;
26988		// elements
26989		obj.drag = ia.drag;
26990		obj.proxy = ia.proxy || ia.drag;
26991		// starting mouse position
26992		obj.startX = dd.pageX;
26993		obj.startY = dd.pageY;
26994		// current distance dragged
26995		obj.deltaX = event.pageX - dd.pageX;
26996		obj.deltaY = event.pageY - dd.pageY;
26997		// original element position
26998		obj.originalX = ia.offset.left;
26999		obj.originalY = ia.offset.top;
27000		// adjusted element position
27001		obj.offsetX = event.pageX - ( dd.pageX - obj.originalX );
27002		obj.offsetY = event.pageY - ( dd.pageY - obj.originalY );
27003		// assign the drop targets information
27004		obj.drop = drag.flatten( ( ia.drop || [] ).slice() );
27005		obj.available = drag.flatten( ( ia.droppable || [] ).slice() );
27006		return obj;	
27007	},
27008	
27009	// determine is the argument is an element or jquery instance
27010	element: function( arg ){
27011		if ( arg && ( arg.jquery || arg.nodeType == 1 ) )
27012			return arg;
27013	},
27014	
27015	// flatten nested jquery objects and arrays into a single dimension array
27016	flatten: function( arr ){
27017		return $.map( arr, function( member ){
27018			return member && member.jquery ? $.makeArray( member ) : 
27019				member && member.length ? drag.flatten( member ) : member;
27020		});
27021	},
27022	
27023	// toggles text selection attributes ON (true) or OFF (false)
27024	textselect: function( bool ){ 
27025		$( document )[ bool ? "unbind" : "bind" ]("selectstart", drag.dontstart )
27026			.attr("unselectable", bool ? "off" : "on" )
27027			.css("MozUserSelect", bool ? "" : "none" );
27028	},
27029	
27030	// suppress "selectstart" and "ondragstart" events
27031	dontstart: function(){ 
27032		return false; 
27033	},
27034	
27035	// a callback instance contructor
27036	callback: function(){}
27037	
27038};
27039
27040// callback methods
27041drag.callback.prototype = {
27042	update: function(){
27043		if ( $special.drop && this.available.length )
27044			$.each( this.available, function( i ){
27045				$special.drop.locate( this, i );
27046			});
27047	}
27048};
27049
27050// share the same special event configuration with related events...
27051$special.draginit = $special.dragstart = $special.dragend = drag;
27052
27053})( jQuery );
27054/*! 
27055 * jquery.event.drop - v 2.0.0 
27056 * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
27057 * Open Source MIT License - http://threedubmedia.com/code/license
27058 */
27059// Created: 2008-06-04 
27060// Updated: 2010-06-08
27061// REQUIRES: jquery 1.4.2+, jquery.event.drag 2+
27062
27063;(function($){ // secure $ jQuery alias
27064
27065// Events: drop, dropinit, dropstart, dropend
27066
27067// add the jquery instance method
27068$.fn.drop = function( str, arg, opts ){
27069	// figure out the event type
27070	var type = typeof str == "string" ? str : "",
27071	// figure out the event handler...
27072	fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null;
27073	// fix the event type
27074	if ( type.indexOf("drop") !== 0 ) 
27075		type = "drop"+ type;
27076	// were options passed
27077	opts = ( str == fn ? arg : opts ) || {};
27078	// trigger or bind event handler
27079	return fn ? this.bind( type, opts, fn ) : this.trigger( type );
27080};
27081
27082// DROP MANAGEMENT UTILITY
27083// returns filtered drop target elements, caches their positions
27084$.drop = function( opts ){ 
27085	opts = opts || {};
27086	// safely set new options...
27087	drop.multi = opts.multi === true ? Infinity : 
27088		opts.multi === false ? 1 : !isNaN( opts.multi ) ? opts.multi : drop.multi;
27089	drop.delay = opts.delay || drop.delay;
27090	drop.tolerance = $.isFunction( opts.tolerance ) ? opts.tolerance : 
27091		opts.tolerance === null ? null : drop.tolerance;
27092	drop.mode = opts.mode || drop.mode || 'intersect';
27093};
27094
27095// local refs (increase compression)
27096var $event = $.event, 
27097$special = $event.special,
27098// configure the drop special event
27099drop = $.event.special.drop = {
27100
27101	// these are the default settings
27102	multi: 1, // allow multiple drop winners per dragged element
27103	delay: 20, // async timeout delay
27104	mode: 'overlap', // drop tolerance mode
27105		
27106	// internal cache
27107	targets: [], 
27108	
27109	// the key name for stored drop data
27110	datakey: "dropdata",
27111	
27112	// the namespace for internal live events
27113	livekey: "livedrop",
27114	
27115	// count bound related events
27116	add: function( obj ){ 
27117		// read the interaction data
27118		var data = $.data( this, drop.datakey );
27119		// count another realted event
27120		data.related += 1;
27121		// bind the live "dropinit" delegator
27122		if ( !data.live && obj.selector ){
27123			data.live = true;
27124			$event.add( this, "dropinit."+ drop.livekey, drop.delegate );
27125		}
27126	},
27127	
27128	// forget unbound related events
27129	remove: function(){
27130		$.data( this, drop.datakey ).related -= 1;
27131	},
27132	
27133	// configure the interactions
27134	setup: function(){
27135		// check for related events
27136		if ( $.data( this, drop.datakey ) ) 
27137			return;
27138		// initialize the drop element data
27139		var data = { 
27140			related: 0,
27141			active: [],
27142			anyactive: 0,
27143			winner: 0,
27144			location: {}
27145		};
27146		// store the drop data on the element
27147		$.data( this, drop.datakey, data );
27148		// store the drop target in internal cache
27149		drop.targets.push( this );
27150	},
27151	
27152	// destroy the configure interaction	
27153	teardown: function(){ 
27154		// check for related events
27155		if ( $.data( this, drop.datakey ).related ) 
27156			return;
27157		// remove the stored data
27158		$.removeData( this, drop.datakey );
27159		// remove the "live" delegation
27160		$event.remove( this, "dropinit", drop.delegate );
27161		// reference the targeted element
27162		var element = this;
27163		// remove from the internal cache
27164		drop.targets = $.grep( drop.targets, function( target ){ 
27165			return ( target !== element ); 
27166		});
27167	},
27168	
27169	// shared event handler
27170	handler: function( event, dd ){ 
27171		// local vars
27172		var results, $targets;
27173		// make sure the right data is available
27174		if ( !dd ) 
27175			return;
27176		// handle various events
27177		switch ( event.type ){
27178			// draginit, from $.event.special.drag
27179			case 'mousedown': // DROPINIT >>
27180				// collect and assign the drop targets
27181				$targets =  $( drop.targets );
27182				if ( typeof dd.drop == "string" )
27183					$targets = $targets.filter( dd.drop );
27184				// reset drop data winner properties
27185				$targets.each(function(){
27186					var data = $.data( this, drop.datakey );
27187					data.active = [];
27188					data.anyactive = 0;
27189					data.winner = 0;
27190				});
27191				// set available target elements
27192				dd.droppable = $targets;
27193				// hold any live elements 
27194				drop.delegates = [];
27195				// activate drop targets for the initial element being dragged
27196				$special.drag.hijack( event, "dropinit", dd ); 
27197				// flatten all the live elements
27198				drop.delegates = $.unique( $special.drag.flatten( drop.delegates ) );
27199				break;
27200			// drag, from $.event.special.drag
27201			case 'mousemove': // TOLERATE >>
27202				drop.event = event; // store the mousemove event
27203				if ( !drop.timer )
27204					// monitor drop targets
27205					drop.tolerate( dd ); 
27206				break;
27207			// dragend, from $.event.special.drag
27208			case 'mouseup': // DROP >> DROPEND >>
27209				drop.timer = clearTimeout( drop.timer ); // delete timer	
27210				if ( dd.propagates ){
27211					$special.drag.hijack( event, "drop", dd ); 
27212					$special.drag.hijack( event, "dropend", dd ); 
27213					// cleanout live events...
27214					$.each( drop.delegates || [], function(){
27215						$event.remove( this, '.'+ drop.livekey );							
27216					});
27217				}
27218				break;
27219		}
27220	},
27221	
27222	// identify potential delegate elements
27223	delegate: function( event ){
27224		// local refs
27225		var elems = [], $targets, 
27226		// element event structure
27227		events = $.data( this, "events" ) || {};
27228		// query live events
27229		$.each( events.live || [], function( i, obj ){
27230			// no event type matches
27231			if ( obj.preType.indexOf("drop") !== 0 )
27232				return;
27233			// locate the elements to delegate
27234			$targets = $( event.currentTarget ).find( obj.selector );
27235			// no element found
27236			if ( !$targets.length ) 
27237				return;
27238			// take each target...
27239			$targets.each(function(){
27240				// add an event handler
27241				$event.add( this, obj.origType +'.'+ drop.livekey, obj.origHandler, obj.data );
27242				// remember new elements
27243				if ( $.inArray( this, elems ) < 0 )
27244					elems.push( this );	
27245			});	
27246		});
27247		drop.delegates.push( elems );
27248		return elems.length ? $( elems ) : false;
27249	},
27250	
27251	// returns the location positions of an element
27252	locate: function( elem, index ){ 
27253		var data = $.data( elem, drop.datakey ),
27254		$elem = $( elem ), 
27255		posi = $elem.offset() || {}, 
27256		height = $elem.outerHeight(), 
27257		width = $elem.outerWidth(),
27258		location = { 
27259			elem: elem, 
27260			width: width, 
27261			height: height,
27262			top: posi.top, 
27263			left: posi.left, 
27264			right: posi.left + width, 
27265			bottom: posi.top + height
27266		};
27267		// drag elements might not have dropdata
27268		if ( data ){
27269			data.location = location;
27270			data.index = index;
27271			data.elem = elem;
27272		}
27273		return location;
27274	},
27275	
27276	// test the location positions of an element against another OR an X,Y coord
27277	contains: function( target, test ){ // target { location } contains test [x,y] or { location }
27278		return ( ( test[0] || test.left ) >= target.left && ( test[0] || test.right ) <= target.right
27279			&& ( test[1] || test.top ) >= target.top && ( test[1] || test.bottom ) <= target.bottom ); 
27280	},
27281	
27282	// stored tolerance modes
27283	modes: { // fn scope: "$.event.special.drop" object 
27284		// target with mouse wins, else target with most overlap wins
27285		'intersect': function( event, proxy, target ){
27286			return this.contains( target, [ event.pageX, event.pageY ] ) ? // check cursor
27287				1e9 : this.modes.overlap.apply( this, arguments ); // check overlap
27288		},
27289		// target with most overlap wins	
27290		'overlap': function( event, proxy, target ){
27291			// calculate the area of overlap...
27292			return Math.max( 0, Math.min( target.bottom, proxy.bottom ) - Math.max( target.top, proxy.top ) )
27293				* Math.max( 0, Math.min( target.right, proxy.right ) - Math.max( target.left, proxy.left ) );
27294		},
27295		// proxy is completely contained within target bounds	
27296		'fit': function( event, proxy, target ){
27297			return this.contains( target, proxy ) ? 1 : 0;
27298		},
27299		// center of the proxy is contained within target bounds	
27300		'middle': function( event, proxy, target ){
27301			return this.contains( target, [ proxy.left + proxy.width * .5, proxy.top + proxy.height * .5 ] ) ? 1 : 0;
27302		}
27303	},	
27304	
27305	// sort drop target cache by by winner (dsc), then index (asc)
27306	sort: function( a, b ){
27307		return ( b.winner - a.winner ) || ( a.index - b.index );
27308	},
27309		
27310	// async, recursive tolerance execution
27311	tolerate: function( dd ){		
27312		// declare local refs
27313		var i, drp, drg, data, arr, len, elem,
27314		// interaction iteration variables
27315		x = 0, ia, end = dd.interactions.length,
27316		// determine the mouse coords
27317		xy = [ drop.event.pageX, drop.event.pageY ],
27318		// custom or stored tolerance fn
27319		tolerance = drop.tolerance || drop.modes[ drop.mode ];
27320		// go through each passed interaction...
27321		do if ( ia = dd.interactions[x] ){
27322			// check valid interaction
27323			if ( !ia )
27324				return; 
27325			// initialize or clear the drop data
27326			ia.drop = [];
27327			// holds the drop elements
27328			arr = []; 
27329			len = ia.droppable.length;
27330			// determine the proxy location, if needed
27331			if ( tolerance )
27332				drg = drop.locate( ia.proxy ); 
27333			// reset the loop
27334			i = 0;
27335			// loop each stored drop target
27336			do if ( elem = ia.droppable[i] ){ 
27337				data = $.data( elem, drop.datakey );
27338				drp = data.location;
27339				if ( !drp ) continue;
27340				// find a winner: tolerance function is defined, call it
27341				data.winner = tolerance ? tolerance.call( drop, drop.event, drg, drp ) 
27342					// mouse position is always the fallback
27343					: drop.contains( drp, xy ) ? 1 : 0; 
27344				arr.push( data );	
27345			} while ( ++i < len ); // loop 
27346			// sort the drop targets
27347			arr.sort( drop.sort );			
27348			// reset the loop
27349			i = 0;
27350			// loop through all of the targets again
27351			do if ( data = arr[ i ] ){
27352				// winners...
27353				if ( data.winner && ia.drop.length < drop.multi ){
27354					// new winner... dropstart
27355					if ( !data.active[x] && !data.anyactive ){
27356						// check to make sure that this is not prevented
27357						if ( $special.drag.hijack( drop.event, "dropstart", dd, x, data.elem )[0] !== false ){ 	
27358							data.active[x] = 1;
27359							data.anyactive += 1;
27360						}
27361						// if false, it is not a winner
27362						else
27363							data.winner = 0;
27364					}
27365					// if it is still a winner
27366					if ( data.winner )
27367						ia.drop.push( data.elem );
27368				}
27369				// losers... 
27370				else if ( data.active[x] && data.anyactive == 1 ){
27371					// former winner... dropend
27372					$special.drag.hijack( drop.event, "dropend", dd, x, data.elem ); 
27373					data.active[x] = 0;
27374					data.anyactive -= 1;
27375				}
27376			} while ( ++i < len ); // loop 		
27377		} while ( ++x < end ) // loop
27378		// check if the mouse is still moving or is idle
27379		if ( drop.last && xy[0] == drop.last.pageX && xy[1] == drop.last.pageY ) 
27380			delete drop.timer; // idle, don't recurse
27381		else  // recurse
27382			drop.timer = setTimeout(function(){ 
27383				drop.tolerate( dd ); 
27384			}, drop.delay );
27385		// remember event, to compare idleness
27386		drop.last = drop.event; 
27387	}
27388	
27389};
27390
27391// share the same special event configuration with related events...
27392$special.dropinit = $special.dropstart = $special.dropend = drop;
27393
27394})(jQuery); // confine scope	
27395;
27396/**
27397 * @license
27398 * (c) 2009-2010 Michael Leibman ([email protected])
27399 * http://github.com/mleibman/slickgrid
27400 * Distributed under MIT license.
27401 * All rights reserved.
27402 *
27403 * SlickGrid v1.4.3
27404 *
27405 * TODO:
27406 * - frozen columns
27407 * - consistent events (EventHelper?  jQuery events?)
27408 *
27409 *
27410 * OPTIONS:
27411 *     rowHeight                - (default 25px) Row height in pixels.
27412 *     enableAddRow             - (default false) If true, a blank row will be displayed at the bottom - typing values in that row will add a new one.
27413 *     leaveSpaceForNewRows     - (default false)
27414 *     editable                 - (default false) If false, no cells will be switched into edit mode.
27415 *     autoEdit                 - (default true) Cell will not automatically go into edit mode when selected.
27416 *     enableCellNavigation     - (default true) If false, no cells will be selectable.
27417 *     enableCellRangeSelection - (default false) If true, user will be able to select a cell range.  onCellRangeSelected event will be fired.
27418 *     defaultColumnWidth       - (default 80px) Default column width in pixels (if columns[cell].width is not specified).
27419 *     enableColumnReorder      - (default true) Allows the user to reorder columns.
27420 *     asyncEditorLoading       - (default false) Makes cell editors load asynchronously after a small delay.
27421 *                                This greatly increases keyboard navigation speed.
27422 *     asyncEditorLoadDelay     - (default 100msec) Delay after which cell editor is loaded. Ignored unless asyncEditorLoading is true.
27423 *     forceFitColumns          - (default false) Force column sizes to fit into the viewport (avoid horizontal scrolling).
27424 *     enableAsyncPostRender    - (default false) If true, async post rendering will occur and asyncPostRender delegates on columns will be called.
27425 *     asyncPostRenderDelay     - (default 60msec) Delay after which async post renderer delegate is called.
27426 *     autoHeight               - (default false) If true, vertically resizes to fit all rows.
27427 *     editorLock               - (default Slick.GlobalEditorLock) A Slick.EditorLock instance to use for controlling concurrent data edits.
27428 *     showSecondaryHeaderRow   - (default false) If true, an extra blank (to be populated externally) row will be displayed just below the header columns.
27429 *     secondaryHeaderRowHeight - (default 25px) The height of the secondary header row.
27430 *     syncColumnCellResize     - (default false) Synchronously resize column cells when column headers are resized
27431 *     rowCssClasses            - (default null) A function which (given a row's data item as an argument) returns a space-delimited string of CSS classes that will be applied to the slick-row element. Note that this should be fast, as it is called every time a row is displayed.
27432 *     cellHighlightCssClass    - (default "highlighted") A CSS class to apply to cells highlighted via setHighlightedCells().
27433 *     cellFlashingCssClass     - (default "flashing") A CSS class to apply to flashing cells (flashCell()).
27434 *     formatterFactory         - (default null) A factory object responsible to creating a formatter for a given cell.
27435 *                                Must implement getFormatter(column).
27436 *     editorFactory            - (default null) A factory object responsible to creating an editor for a given cell.
27437 *                                Must implement getEditor(column).
27438 *     multiSelect              - (default true) Enable multiple row selection.
27439 *
27440 * COLUMN DEFINITION (columns) OPTIONS:
27441 *     id                  - Column ID.
27442 *     name                - Column name to put in the header.
27443 *     toolTip             - Tooltip (if different from name).
27444 *     field               - Property of the data context to bind to.
27445 *     formatter           - (default 'return value || ""') Function responsible for rendering the contents of a cell. Signature: function formatter(row, cell, value, columnDef, dataContext) { ... return "..."; }
27446 *     editor              - An Editor class.
27447 *     validator           - An extra validation function to be passed to the editor.
27448 *     unselectable        - If true, the cell cannot be selected (and therefore edited).
27449 *     cannotTriggerInsert - If true, a new row cannot be created from just the value of this cell.
27450 *     width               - Width of the column in pixels.
27451 *     resizable           - (default true) If false, the column cannot be resized.
27452 *     sortable            - (default false) If true, the column can be sorted (onSort will be called).
27453 *     minWidth            - Minimum allowed column width for resizing.
27454 *     maxWidth            - Maximum allowed column width for resizing.
27455 *     cssClass            - A CSS class to add to the cell.
27456 *     rerenderOnResize    - Rerender the column when it is resized (useful for columns relying on cell width or adaptive formatters).
27457 *     asyncPostRender     - Function responsible for manipulating the cell DOM node after it has been rendered (called in the background).
27458 *     behavior            - Configures the column with one of several available predefined behaviors:  "select", "move", "selectAndMove".
27459 *
27460 *
27461 * EVENTS:
27462 *     onSort                -
27463 *     onHeaderContextMenu   -
27464 *     onHeaderClick         -
27465 *     onClick               -
27466 *     onDblClick            -
27467 *     onContextMenu         -
27468 *     onKeyDown             -
27469 *     onAddNewRow           -
27470 *     onValidationError     -
27471 *     onViewportChanged     -
27472 *     onSelectedRowsChanged -
27473 *     onColumnsReordered    -
27474 *     onColumnsResized      -
27475 *     onBeforeMoveRows      -
27476 *     onMoveRows            -
27477 *     onCellChange          -  Raised when cell has been edited.   Args: row,cell,dataContext.
27478 *     onBeforeEditCell      -  Raised before a cell goes into edit mode.  Return false to cancel.  Arg
27478s: row,cell,dataContext.
27479 *     onBeforeCellEditorDestroy    - Raised before a cell editor is destroyed.  Args: current cell editor.
27480 *     onBeforeDestroy       -  Raised just before the grid control is destroyed (part of the destroy() method).
27481 *     onCurrentCellChanged  -  Raised when the selected (active) cell changed.  Args: {row:currentRow, cell:currentCell}.
27482 *     onCellRangeSelected   -  Raised when a user selects a range of cells.  Args: {from:{row,cell}, to:{row,cell}}.
27483 *
27484 * NOTES:
27485 *     Cell/row DOM manipulations are done directly bypassing jQuery's DOM manipulation methods.
27486 *     This increases the speed dramatically, but can only be done safely because there are no event handlers
27487 *     or data associated with any cell/row DOM nodes.  Cell editors must make sure they implement .destroy()
27488 *     and do proper cleanup.
27489 *
27490 *
27491 * @param {Node}              container   Container node to create the grid in.
27492 * @param {Array} or {Object} data        An array of objects for databinding.
27493 * @param {Array}             columns     An array of column definitions.
27494 * @param {Object}            options     Grid options.
27495 */
27496
27497// make sure required JavaScript modules are loaded
27498if (typeof jQuery === "undefined") {
27499    throw new Error("SlickGrid requires jquery module to be loaded");
27500}
27501if (!jQuery.fn.drag) {
27502    throw new Error("SlickGrid requires jquery.event.drag module to be loaded");
27503}
27504
27505(function($) {
27506    var scrollbarDimensions; // shared across all grids on this page
27507
27508
27509    //////////////////////////////////////////////////////////////////////////////////////////////
27510    // EditorLock class implementation (available as Slick.EditorLock)
27511
27512    /** @constructor */
27513    function EditorLock() {
27514        /// <summary>
27515        /// Track currently active edit controller and ensure
27516        /// that onle a single controller can be active at a time.
27517        /// Edit controller is an object that is responsible for
27518        /// gory details of looking after editor in the browser,
27519        /// and allowing EditorLock clients to either accept
27520        /// or cancel editor changes without knowing any of the
27521        /// implementation details. SlickGrid instance is used
27522        /// as edit controller for cell editors.
27523        /// </summary>
27524
27525        var currentEditController = null;
27526
27527        this.isActive = function isActive(editController) {
27528            /// <summary>
27529            /// Return true if the specified editController
27530            /// is currently active in this lock instance
27531            /// (i.e. if that controller acquired edit lock).
27532            /// If invoked without parameters ("editorLock.isActive()"),
27533            /// return true if any editController is currently
27534            /// active in this lock instance.
27535            /// </summary>
27536            return (editController ? currentEditController === editController : currentEditController !== null);
27537        };
27538
27539        this.activate = function activate(editController) {
27540            /// <summary>
27541            /// Set the specified editController as the active
27542            /// controller in this lock instance (acquire edit lock).
27543            /// If another editController is already active,
27544            /// an error will be thrown (i.e. before calling
27545            /// this method isActive() must be false,
27546            /// afterwards isActive() will be true).
27547            /// </summary>
27548            if (editController === currentEditController) { // already activated?
27549                return;
27550            }
27551            if (currentEditController !== null) {
27552                throw "SlickGrid.EditorLock.activate: an editController is still active, can't activate another editController";
27553            }
27554            if (!editController.commitCurrentEdit) {
27555                throw "SlickGrid.EditorLock.activate: editController must implement .commitCurrentEdit()";
27556            }
27557            if (!editController.cancelCurrentEdit) {
27558                throw "SlickGrid.EditorLock.activate: editController must implement .cancelCurrentEdit()";
27559            }
27560            currentEditController = editController;
27561        };
27562
27563        this.deactivate = function deactivate(editController) {
27564            /// <summary>
27565            /// Unset the specified editController as the active
27566            /// controller in this lock instance (release edit lock).
27567            /// If the specified editController is not the editController
27568            /// that is currently active in this lock instance,
27569            /// an error will be thrown.
27570            /// </summary>
27571            if (currentEditController !== editController) {
27572                throw "SlickGrid.EditorLock.deactivate: specified editController is not the currently active one";
27573            }
27574            currentEditController = null;
27575        };
27576
27577        this.commitCurrentEdit = function commitCurrentEdit() {
27578            /// <summary>
27579            /// Invoke the "commitCurrentEdit" method on the
27580            /// editController that is active in this lock
27581            /// instance and return the return value of that method
27582            /// (if no controller is active, return true).
27583            /// "commitCurrentEdit" is expected to return true
27584            /// to indicate successful commit, false otherwise.
27585            /// </summary>
27586            return (currentEditController ? currentEditController.commitCurrentEdit() : true);
27587        };
27588
27589        this.cancelCurrentEdit = function cancelCurrentEdit() {
27590            /// <summary>
27591            /// Invoke the "cancelCurrentEdit" method on the
27592            /// editController that is active in this lock
27593            /// instance (if no controller is active, do nothing).
27594            /// Returns true if the edit was succesfully cancelled.
27595            /// </summary>
27596            return (currentEditController ? currentEditController.cancelCurrentEdit() : true);
27597        };
27598    } // end of EditorLock function (class)
27599
27600
27601
27602    //////////////////////////////////////////////////////////////////////////////////////////////
27603    // SlickGrid class implementation (available as Slick.Grid)
27604
27605    /** @constructor */
27606    function SlickGrid(container,data,columns,options) {
27607        /// <summary>
27608        /// Create and manage virtual grid in the specified $container,
27609        /// connecting it to the specified data source. Data is presented
27610        /// as a grid with the specified columns and data.length rows.
27611        /// Options alter behaviour of the grid.
27612        /// </summary>
27613
27614        // settings
27615        var defaults = {
27616            rowHeight: 25,
27617            defaultColumnWidth: 80,
27618            enableAddRow: false,
27619            leaveSpaceForNewRows: false,
27620            editable: false,
27621            autoEdit: true,
27622            enableCellNavigation: true,
27623            enableCellRangeSelection: false,
27624            enableColumnReorder: true,
27625            asyncEditorLoading: false,
27626            asyncEditorLoadDelay: 100,
27627            forceFitColumns: false,
27628            enableAsyncPostRender: false,
27629            asyncPostRenderDelay: 60,
27630            autoHeight: false,
27631            editorLock: Slick.GlobalEditorLock,
27632            showSecondaryHeaderRow: false,
27633            secondaryHeaderRowHeight: 25,
27634            syncColumnCellResize: false,
27635            enableAutoTooltips: true,
27636            toolTipMaxLength: null,
27637            formatterFactory: null,
27638            editorFactory: null,
27639            cellHighlightCssClass: "highlighted",
27640            cellFlashingCssClass: "flashing",
27641            multiSelect: true
27642        },
27643        gridData, gridDataGetLength, gridDataGetItem;
27644
27645        var columnDefaults = {
27646            name: "",
27647            resizable: true,
27648            sortable: false,
27649            minWidth: 30
27650        };
27651
27652        // scroller
27653        var maxSupportedCssHeight;      // browser's breaking point
27654        var th;                         // virtual height
27655        var h;                          // real scrollable height
27656        var ph;                         // page height
27657        var n;                          // number of pages
27658        var cj;                         // "jumpiness" coefficient
27659
27660        var page = 0;                   // current page
27661        var offset = 0;                 // current page offset
27662        var scrollDir = 1;
27663
27664        // private
27665        var $container;
27666        var uid = "slickgrid_" + Math.round(1000000 * Math.random());
27667        var self = this;
27668        var $headerScroller;
27669        var $headers;
27670        var $secondaryHeaderScroller;
27671        var $secondaryHeaders;
27672        var $viewport;
27673        var $canvas;
27674        var $style;
27675        var stylesheet;
27676        var viewportH, viewportW;
27677        var viewportHasHScroll;
27678        var headerColumnWidthDiff, headerColumnHeightDiff, cellWidthDiff, cellHeightDiff;  // padding+border
27679        var absoluteColumnMinWidth;
27680
27681        var currentRow, currentCell;
27682        var currentCellNode = null;
27683        var currentEditor = null;
27684        var serializedEditorValue;
27685        var editController;
27686
27687        var rowsCache = {};
27688        var renderedRows = 0;
27689        var numVisibleRows;
27690        var prevScrollTop = 0;
27691        var scrollTop = 0;
27692        var lastRenderedScrollTop = 0;
27693        var prevScrollLeft = 0;
27694        var avgRowRenderTime = 10;
27695
27696        var selectedRows = [];
27697        var selectedRowsLookup = {};
27698        var columnsById = {};
27699        var highlightedCells;
27700        var sortColumnId;
27701        var sortAsc = true;
27702
27703        // async call handles
27704        var h_editorLoader = null;
27705        var h_render = null;
27706        var h_postrender = null;
27707        var postProcessedRows = {};
27708        var postProcessToRow = null;
27709        var postProcessFromRow = null;
27710
27711        // perf counters
27712        var counter_rows_rendered = 0;
27713        var counter_rows_removed = 0;
27714
27715
27716        //////////////////////////////////////////////////////////////////////////////////////////////
27717        // Initialization
27718
27719        function init() {
27720            /// <summary>
27721            /// Initialize 'this' (self) instance of a SlickGrid.
27722            /// This function is called by the constructor.
27723            /// </summary>
27724
27725            $container = $(container);
27726
27727            gridData = data;
27728            gridDataGetLength = gridData.getLength || defaultGetLength;
27729            gridDataGetItem = gridData.getItem || defaultGetItem;
27730
27731            maxSupportedCssHeight = getMaxSupportedCssHeight();
27732
27733            scrollbarDimensions = scrollbarDimensions || measureScrollbar(); // skip measurement if already have dimensions
27734            options = $.extend({},defaults,options);
27735            columnDefaults.width = options.defaultColumnWidth;
27736
27737            // validate loaded JavaScript modules against requested options
27738            if (options.enableColumnReorder && !$.fn.sortable) {
27739                throw new Error("SlickGrid's \"enableColumnReorder = true\" option requires jquery-ui.sortable module to be loaded");
27740            }
27741
27742            editController = {
27743                "commitCurrentEdit": commitCurrentEdit,
27744                "cancelCurrentEdit": cancelCurrentEdit
27745            };
27746
27747            $container
27748                .empty()
27749                .attr("tabIndex",0)
27750                .attr("hideFocus",true)
27751                .css("overflow","hidden")
27752                .css("outline",0)
27753                .addClass(uid)
27754                .addClass("ui-widget");
27755
27756            // set up a positioning container if needed
27757            if (!/relative|absolute|fixed/.test($container.css("position")))
27758                $container.css("position","relative");
27759
27760            $headerScroller = $("<div class='slick-header ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
27761            $headers = $("<div class='slick-header-columns' style='width:100000px; left:-10000px' />").appendTo($headerScroller);
27762
27763            $secondaryHeaderScroller = $("<div class='slick-header-secondary ui-state-default' style='overflow:hidden;position:relative;' />").appendTo($container);
27764            $secondaryHeaders = $("<div class='slick-header-columns-secondary' style='width:100000px' />").appendTo($secondaryHeaderScroller);
27765
27766            if (!options.showSecondaryHeaderRow) {
27767                $secondaryHeaderScroller.hide();
27768            }
27769
27770            $viewport = $("<div class='slick-viewport' tabIndex='0' hideFocus style='width:100%;overflow-x:auto;outline:0;position:relative;overflow-y:auto;'>").appendTo($container);
27771            $canvas = $("<div class='grid-canvas' tabIndex='0' hideFocus />").appendTo($viewport);
27772
27773            // header columns and cells may have different padding/border skewing width calculations (box-sizing, hello?)
27774            // calculate the diff so we can set consistent sizes
27775            measureCellPaddingAndBorder();
27776
27777            $viewport.height(
27778                $container.innerHeight() -
27779                $headerScroller.outerHeight() -
27780                (options.showSecondaryHeaderRow ? $secondaryHeaderScroller.outerHeight() : 0));
27781
27782            // for usability reasons, all text selection in SlickGrid is disabled
27783            // with the exception of input and textarea elements (selection must
27784            // be enabled there so that editors work as expected); note that
27785            // selection in grid cells (grid body) is already unavailable in
27786            // all browsers except IE
27787            disableSelection($headers); // disable all text selection in header (including input and textarea)
27788            $viewport.bind("selectstart.ui", function (event) { return $(event.target).is("input,textarea"); }); // disable text selection in grid cells except in input and textarea elements (this is IE-specific, because selectstart event will only fire in IE)
27789
27790            createColumnHeaders();
27791            setupColumnSort();
27792            setupDragEvents();
27793            createCssRules();
27794
27795            resizeAndRender();
27796
27797            bindAncestorScrollEvents();
27798            $viewport.bind("scroll.slickgrid", handleScroll);
27799            $container.bind("resize.slickgrid", resizeAndRender);
27800            $canvas.bind("keydown.slickgrid", handleKeyDown);
27801            $canvas.bind("click.slickgrid", handleClick);
27802            $canvas.bind("dblclick.slickgrid", handleDblClick);
27803            $canvas.bind("contextmenu.slickgrid", handleContextMenu);
27804            $canvas.bind("mouseover.slickgrid", handleHover);
27805            $headerScroller.bind("contextmenu.slickgrid", handleHeaderContextMenu);
27806            $headerScroller.bind("click.slickgrid", handleHeaderClick);
27807        }
27808
27809        function measureScrollbar() {
27810            /// <summary>
27811            /// Measure width of a vertical scrollbar
27812            /// and height of a horizontal scrollbar.
27813            /// </summary
27814            /// <returns>
27815            /// { width: pixelWidth, height: pixelHeight }
27816            /// </returns>
27817            var $c = $("<div style='position:absolute; top:-10000px; left:-10000px; width:100px; height:100px; overflow:scroll;'></div>").appendTo("body");
27818            var dim = { width: $c.width() - $c[0].clientWidth, height: $c.height() - $c[0].clientHeight };
27819            $c.remove();
27820            return dim;
27821        }
27822
27823        function setCanvasWidth(width) {
27824            $canvas.width(width);
27825            viewportHasHScroll = (width > viewportW - scrollbarDimensions.width);
27826        }
27827
27828        function disableSelection($target) {
27829            /// <summary>
27830            /// Disable text selection (using mouse) in
27831            /// the specified target.
27832            /// </summary
27833            if ($target && $target.jquery) {
27834                $target.attr('unselectable', 'on').css('MozUserSelect', 'none').bind('selectstart.ui', function() { return false; }); // from jquery:ui.core.js 1.7.2
27835            }
27836        }
27837
27838        function defaultGetLength() {
27839            /// <summary>
27840            /// Default implementation of getLength method
27841            /// returns the length of the array.
27842            /// </summary
27843            return gridData.length;
27844        }
27845
27846        function defaultGetItem(i) {
27847            /// <summary>
27848            /// Default implementation of getItem method
27849            /// returns the item at specified position in
27850            /// the array.
27851            /// </summary
27852            return gridData[i];
27853        }
27854
27855        function getMaxSupportedCssHeight() {
27856            var increment = 1000000;
27857            var supportedHeight = 0;
27858            // FF reports the height back but still renders blank after ~6M px
27859            var testUpTo = ($.browser.mozilla) ? 5000000 : 1000000000;
27860            var div = $("<div style='display:none' />").appendTo(document.body);
27861
27862            while (supportedHeight <= testUpTo) {
27863                div.css("height", supportedHeight + increment);
27864                if (div.height() !== supportedHeight + increment)
27865                    break;
27866                else
27867                    supportedHeight += increment;
27868            }
27869
27870            div.remove();
27871            return supportedHeight;
27872        }
27873
27874        // TODO:  this is static.  need to handle page mutation.
27875        function bindAncestorScrollEvents() {
27876            var elem = $canvas[0];
27877            while ((elem = elem.parentNode) != document.body) {
27878                // bind to scroll containers only
27879                if (elem == $viewport[0] || elem.scrollWidth != elem.clientWidth || elem.scrollHeight != elem.clientHeight)
27880                    $(elem).bind("scroll.slickgrid", handleCurrentCellPositionChange);
27881            }
27882        }
27883
27884        function unbindAncestorScrollEvents() {
27885            $canvas.parents().unbind("scroll.slickgrid");
27886        }
27887
27888        function createColumnHeaders() {
27889            var i;
27890
27891            function hoverBegin() {
27892                $(this).addClass("ui-state-hover");
27893            }
27894            function hoverEnd() {
27895                $(this).removeClass("ui-state-hover");
27896            }
27897
27898            $headers.empty();
27899            columnsById = {};
27900
27901            for (i = 0; i < columns.length; i++) {
27902                var m = columns[i] = $.extend({},columnDefaults,columns[i]);
27903                columnsById[m.id] = i;
27904
27905                var header = $("<div class='ui-state-default slick-header-column' id='" + uid + m.id + "' />")
27906                    .html("<span class='slick-column-name'>" + m.name + "</span>")
27907                    .width((m.currentWidth || m.width) - headerColumnWidthDiff)
27908                    .attr("title", m.toolTip || m.name || "")
27909                    .data("fieldId", m.id)
27910                    .appendTo($headers);
27911
27912                if (options.enableColumnReorder || m.sortable) {
27913                    header.hover(hoverBegin, hoverEnd);
27914                }
27915
27916                if (m.sortable) {
27917                    header.append("<span class='slick-sort-indicator' />");
27918                }
27919            }
27920
27921            setSortColumn(sortColumnId,sortAsc);
27922            setupColumnResize();
27923            if (options.enableColumnReorder) {
27924                setupColumnReorder();
27925            }
27926        }
27927
27928        function setupColumnSort() {
27929            $headers.click(function(e) {
27930                if ($(e.target).hasClass("slick-resizable-handle")) {
27931                    return;
27932                }
27933
27934                if (self.onSort) {
27935                    var $col = $(e.target).closest(".slick-header-column");
27936                    if (!$col.length)
27937                        return;
27938
27939                    var column = columns[getSiblingIndex($col[0])];
27940                    if (column.sortable) {
27941                        if (!options.editorLock.commitCurrentEdit())
27942                            return;
27943
27944                        if (column.id === sortColumnId) {
27945                            sortAsc = !sortAsc;
27946                        }
27947                        else {
27948                            sortColumnId = column.id;
27949                            sortAsc = true;
27950                        }
27951
27952                        setSortColumn(sortColumnId,sortAsc);
27953                        self.onSort(column,sortAsc);
27954                    }
27955                }
27956            });
27957        }
27958
27959        function setupColumnReorder() {
27960            $headers.sortable({
27961                containment: "parent",
27962                axis: "x",
27963                cursor: "default",
27964                tolerance: "intersection",
27965                helper: "clone",
27966                placeholder: "slick-sortable-placeholder ui-state-default slick-header-column",
27967                forcePlaceholderSize: true,
27968                start: function(e, ui) { $(ui.helper).addClass("slick-header-column-active"); },
27969                beforeStop: function(e, ui) { $(ui.helper).removeClass("slick-header-column-active"); },
27970                stop: function(e) {
27971                    if (!options.editorLock.commitCurrentEdit()) {
27972                        $(this).sortable("cancel");
27973                        return;
27974                    }
27975
27976                    var reorderedIds = $headers.sortable("toArray");
27977                    var reorderedColumns = [];
27978                    for (var i=0; i<reorderedIds.length; i++) {
27979                        reorderedColumns.push(columns[getColumnIndex(reorderedIds[i].replace(uid,""))]);
27980                    }
27981                    setColumns(reorderedColumns);
27982
27983                    if (self.onColumnsReordered) {
27984                        self.onColumnsReordered();
27985                    }
27986                    e.stopPropagation();
27987                    setupColumnResize();
27988                }
27989            });
27990        }
27991
27992        function setupColumnResize() {
27993            var $col, j, c, pageX, columnElements, minPageX, maxPageX, firstResizable, lastResizable, originalCanvasWidth;
27994            columnElements = $headers.children();
27995            columnElements.find(".slick-resizable-handle").remove();
27996            columnElements.each(function(i,e) {
27997                if (columns[i].resizable) {
27998                    if (firstResizable === undefined) { firstResizable = i; }
27999                    lastResizable = i;
28000                }
28001            });
28002            columnElements.each(function(i,e) {
28003                if ((firstResizable !== undefined && i < firstResizable) || (options.forceFitColumns && i >= lastResizable)) { return; }
28004                $col = $(e);
28005                $("<div class='slick-resizable-handle' />")
28006                    .appendTo(e)
28007                    .bind("dragstart", function(e,dd) {
28008                        if (!options.editorLock.commitCurrentEdit()) { return false; }
28009                        pageX = e.pageX;
28010                        $(this).parent().addClass("slick-header-column-active");
28011                        var shrinkLeewayOnRight = null, stretchLeewayOnRight = null;
28012                        // lock each column's width option to current width
28013                        columnElements.each(function(i,e) { columns[i].previousWidth = $(e).outerWidth(); });
28014                        if (options.forceFitColumns) {
28015                            shrinkLeewayOnRight = 0;
28016                            stretchLeewayOnRight = 0;
28017                            // colums on right affect maxPageX/minPageX
28018                            for (j = i + 1; j < columnElements.length; j++) {
28019                                c = columns[j];
28020                                if (c.resizable) {
28021                                    if (stretchLeewayOnRight !== null) {
28022                                        if (c.maxWidth) {
28023                                            stretchLeewayOnRight += c.maxWidth - c.previousWidth;
28024                                        }
28025                                        else {
28026                                            stretchLeewayOnRight = null;
28027                                        }
28028                                    }
28029                                    shrinkLeewayOnRight += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
28030                                }
28031                            }
28032                        }
28033                        var shrinkLeewayOnLeft = 0, stretchLeewayOnLeft = 0;
28034                        for (j = 0; j <= i; j++) {
28035                            // columns on left only affect minPageX
28036                            c = columns[j];
28037                            if (c.resizable) {
28038                                if (stretchLeewayOnLeft !== null) {
28039                                    if (c.maxWidth) {
28040                                        stretchLeewayOnLeft += c.maxWidth - c.previousWidth;
28041                                    }
28042                                    else {
28043                                        stretchLeewayOnLeft = null;
28044                                    }
28045                                }
28046                                shrinkLeewayOnLeft += c.previousWidth - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
28047                            }
28048                        }
28049                        if (shrinkLeewayOnRight === null) { shrinkLeewayOnRight = 100000; }
28050                        if (shrinkLeewayOnLeft === null) { shrinkLeewayOnLeft = 100000; }
28051                        if (stretchLeewayOnRight === null) { stretchLeewayOnRight = 100000; }
28052                        if (stretchLeewayOnLeft === null) { stretchLeewayOnLeft = 100000; }
28053                        maxPageX = pageX + Math.min(shrinkLeewayOnRight, stretchLeewayOnLeft);
28054                        minPageX = pageX - Math.min(shrinkLeewayOnLeft, stretchLeewayOnRight);
28055                        originalCanvasWidth = $canvas.width();
28056                    })
28057                    .bind("drag", function(e,dd) {
28058                        var actualMinWidth, d = Math.min(maxPageX, Math.max(minPageX, e.pageX)) - pageX, x, ci;
28059                        if (d < 0) { // shrink column
28060                            x = d;
28061                            for (j = i; j >= 0; j--) {
28062                                c = columns[j];
28063                                if (c.resizable) {
28064                                    actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
28065                                    if (x && c.previousWidth + x < actualMinWidth) {
28066                                        x += c.previousWidth - actualMinWidth;
28067                                        styleColumnWidth(j, actualMinWidth, options.syncColumnCellResize);
28068                                    } else {
28069                                        styleColumnWidth(j, c.previousWidth + x, options.syncColumnCellResize);
28070                                        x = 0;
28071                                    }
28072                                }
28073                            }
28074
28075                            if (options.forceFitColumns) {
28076                                x = -d;
28077                                for (j = i + 1; j < columnElements.length; j++) {
28078                                    c = columns[j];
28079                                    if (c.resizable) {
28080                                        if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
28081                                            x -= c.maxWidth - c.previousWidth;
28082                                            styleColumnWidth(j, c.maxWidth, options.syncColumnCellResize);
28083                                        } else {
28084                                            styleColumnWidth(j, c.previousWidth + x, options.syncColumnCellResize);
28085                                            x = 0;
28086                                        }
28087                                    }
28088                                }
28089                            } else if (options.syncColumnCellResize) {
28090                                setCanvasWidth(originalCanvasWidth + d);
28091                            }
28092                        } else { // stretch column
28093                            x = d;
28094                            for (j = i; j >= 0; j--) {
28095                                c = columns[j];
28096                                if (c.resizable) {
28097                                    if (x && c.maxWidth && (c.maxWidth - c.previousWidth < x)) {
28098                                        x -= c.maxWidth - c.previousWidth;
28099                                        styleColumnWidth(j, c.maxWidth, options.syncColumnCellResize);
28100                                    } else {
28101                                        styleColumnWidth(j, c.previousWidth + x, options.syncColumnCellResize);
28102                                        x = 0;
28103                                    }
28104                                }
28105                            }
28106
28107                            if (options.forceFitColumns) {
28108                                x = -d;
28109                                for (j = i + 1; j < columnElements.length; j++) {
28110                                    c = columns[j];
28111                                    if (c.resizable) {
28112                                        actualMinWidth = Math.max(c.minWidth || 0, absoluteColumnMinWidth);
28113                                        if (x && c.previousWidth + x < actualMinWidth) {
28114                                            x += c.previousWidth - actualMinWidth;
28115                                            styleColumnWidth(j, actualMinWidth, options.syncColumnCellResize);
28116                                        } else {
28117                                            styleColumnWidth(j, c.previousWidth + x, options.syncColumnCellResize);
28118                                            x = 0;
28119                                        }
28120                                    }
28121                                }
28122                            } else if (options.syncColumnCellResize) {
28123                                setCanvasWidth(originalCanvasWidth + d);
28124                            }
28125                        }
28126                    })
28127                    .bind("dragend", function(e,dd) {
28128                        var newWidth;
28129                        $(this).parent().removeClass("slick-header-column-active");
28130                        for (j = 0; j < columnElements.length; j++) {
28131                            c = columns[j];
28132                            newWidth = $(columnElements[j]).outerWidth();
28133
28134                            if (c.previousWidth !== newWidth && c.rerenderOnResize) {
28135                                removeAllRows();
28136                            }
28137                            if (options.forceFitColumns) {
28138                                c.width = Math.floor(c.width * (newWidth - c.previousWidth) / c.previousWidth) + c.width;
28139                            } else {
28140                                c.width = newWidth;
28141                            }
28142                            if (!options.syncColumnCellResize && c.previousWidth !== newWidth) {
28143                                styleColumnWidth(j, newWidth, true);
28144                            }
28145                        }
28146                        resizeCanvas();
28147                        if (self.onColumnsResized) {
28148                            self.onColumnsResized();
28149                        }
28150                    });
28151                });
28152        }
28153
28154        function setupDragEvents() {
28155            var MOVE_ROWS = 1;
28156            var SELECT_CELLS = 2;
28157
28158            function fixUpRange(range) {
28159                var r1 = Math.min(range.start.row,range.end.row);
28160                var c1 = Math.min(range.start.cell,range.end.cell);
28161                var r2 = Math.max(range.start.row,range.end.row);
28162                var c2 = Math.max(range.start.cell,range.end.cell);
28163                return {
28164                    start: {row:r1, cell:c1},
28165                    end: {row:r2, cell:c2}
28166                };
28167            }
28168
28169            $canvas
28170                .bind("draginit", function(e,dd) {
28171                    var $cell = $(e.target).closest(".slick-cell");
28172                    if ($cell.length === 0) { return false; }
28173                    if (parseInt($cell.parent().attr("row"), 10) >= gridDataGetLength())
28174                        return false;
28175
28176                    var colDef = columns[getSiblingIndex($cell[0])];
28177                    if (colDef.behavior == "move" || colDef.behavior == "selectAndMove") {
28178                        dd.mode = MOVE_ROWS;
28179                    }
28180                    else if (options.enableCellRangeSelection) {
28181                        dd.mode = SELECT_CELLS;
28182                    }
28183                    else
28184                        return false;
28185                })
28186                .bind("dragstart", function(e,dd) {
28187                    if (!options.editorLock.commitCurrentEdit()) { return false; }
28188                    var row = parseInt($(e.target).closest(".slick-row").attr("row"), 10);
28189
28190                    if (dd.mode == MOVE_ROWS) {
28191                        if (!selectedRowsLookup[row]) {
28192                            setSelectedRows([row]);
28193                        }
28194
28195                        dd.selectionProxy = $("<div class='slick-reorder-proxy'/>")
28196                            .css("position", "absolute")
28197                            .css("zIndex", "99999")
28198                            .css("width", $(this).innerWidth())
28199                            .css("height", options.rowHeight*selectedRows.length)
28200                            .appendTo($viewport);
28201
28202                        dd.guide = $("<div class='slick-reorder-guide'/>")
28203                            .css("position", "absolute")
28204                            .css("zIndex", "99998")
28205                            .css("width", $(this).innerWidth())
28206                            .css("top", -1000)
28207                            .appendTo($viewport);
28208
28209                        dd.insertBefore = -1;
28210                    }
28211
28212                    if (dd.mode == SELECT_CELLS) {
28213                        var start = getCellFromPoint(dd.startX - $canvas.offset().left, dd.startY - $canvas.offset().top);
28214                        if (!cellExists(start.row,start.cell))
28215                            return false;
28216
28217                        dd.range = {start:start,end:{}};
28218                        return $("<div class='slick-selection'></div>").appendTo($canvas);
28219                    }
28220                })
28221                .bind("drag", function(e,dd) {
28222                    if (dd.mode == MOVE_ROWS) {
28223                        var top = e.pageY - $(this).offset().top;
28224                        dd.selectionProxy.css("top",top-5);
28225
28226                        var insertBefore = Math.max(0,Math.min(Math.round(top/options.rowHeight),gridDataGetLength()));
28227                        if (insertBefore !== dd.insertBefore) {
28228                            if (self.onBeforeMoveRows && self.onBeforeMoveRows(getSelectedRows(),insertBefore) === false) {
28229                                dd.guide.css("top", -1000);
28230                                dd.canMove = false;
28231                            }
28232                            else {
28233                                dd.guide.css("top",insertBefore*options.rowHeight);
28234                                dd.canMove = true;
28235                            }
28236                            dd.insertBefore = insertBefore;
28237                        }
28238                    }
28239
28240                    if (dd.mode == SELECT_CELLS) {
28241                        var end = getCellFromPoint(e.clientX - $canvas.offset().left, e.clientY - $canvas.offset().top);
28242                        if (!cellExists(end.row,end.cell))
28243                            return;
28244
28245                        dd.range.end = end;
28246                        var r = fixUpRange(dd.range);
28247                        var from = getCellNodeBox(r.start.row,r.start.cell);
28248                        var to = getCellNodeBox(r.end.row,r.end.cell);
28249                        $(dd.proxy).css({
28250                            top: from.top,
28251                            left: from.left,
28252                            height: to.bottom - from.top - 2,
28253                            width: to.right - from.left - 2
28254                        });
28255                    }
28256                })
28257                .bind("dragend", function(e,dd) {
28258                    if (dd.mode == MOVE_ROWS) {
28259                        dd.guide.remove();
28260                        dd.selectionProxy.remove();
28261                        if (self.onMoveRows && dd.canMove) {
28262                            self.onMoveRows(getSelectedRows(),dd.insertBefore);
28263                        }
28264                    }
28265
28266                    if (dd.mode == SELECT_CELLS) {
28267                        $(dd.proxy).remove();
28268
28269                        if (self.onCellRangeSelected)
28270                            self.onCellRangeSelected(fixUpRange(dd.range));
28271                    }
28272                });
28273        }
28274
28275        function measureCellPaddingAndBorder() {
28276            var tmp = $("<div class='ui-state-default slick-header-column' style='visibility:hidden'>-</div>").appendTo($headers);
28277            headerColumnWidthDiff = tmp.outerWidth() - tmp.width();
28278            headerColumnHeightDiff = tmp.outerHeight() - tmp.height();
28279            tmp.remove();
28280
28281            var r = $("<div class='slick-row' />").appendTo($canvas);
28282            tmp = $("<div class='slick-cell' id='' style='visibility:hidden'>-</div>").appendTo(r);
28283            cellWidthDiff = tmp.outerWidth() - tmp.width();
28284            cellHeightDiff = tmp.outerHeight() - tmp.height();
28285            r.remove();
28286
28287            absoluteColumnMinWidth = Math.max(headerColumnWidthDiff,cellWidthDiff);
28288        }
28289
28290        function createCssRules() {
28291            $style = $("<style type='text/css' rel='stylesheet' />").appendTo($("head"));
28292            var rowHeight = (options.rowHeight - cellHeightDiff);
28293
28294            var rules = [
28295                "." + uid + " .slick-header-column { left: 10000px; }",
28296                "." + uid + " .slick-header-columns-secondary {  height:" + options.secondaryHeaderRowHeight + "px; }",
28297                "." + uid + " .slick-cell { height:" + rowHeight + "px; }"
28298            ];
28299
28300            for (var i=0; i<columns.length; i++) {
28301                rules.push(
28302                    "." + uid + " .c" + i + " { " +
28303                    "width:" + ((columns[i].currentWidth || columns[i].width) - cellWidthDiff) + "px; " +
28304                    " } ");
28305            }
28306
28307            if ($style[0].styleSheet) { // IE
28308                $style[0].styleSheet.cssText = rules.join("");
28309            }
28310            else {
28311                $style[0].appendChild(document.createTextNode(rules.join(" ")));
28312            }
28313
28314            var sheets = document.styleSheets;
28315            for (var i=0; i<sheets.length; i++) {
28316                if ((sheets[i].ownerNode || sheets[i].owningElement) == $style[0]) {
28317                    stylesheet = sheets[i];
28318                    break;
28319                }
28320            }
28321        }
28322
28323        function findCssRule(selector) {
28324            var rules = (stylesheet.cssRules || stylesheet.rules);
28325
28326            for (var i=0; i<rules.length; i++) {
28327                if (rules[i].selectorText == selector)
28328                    return rules[i];
28329            }
28330
28331            return null;
28332        }
28333
28334        function findCssRuleForCell(index) {
28335            return findCssRule("." + uid + " .c" + index);
28336        }
28337
28338        function removeCssRules() {
28339            $style.remove();
28340        }
28341
28342        function destroy() {
28343            options.editorLock.cancelCurrentEdit();
28344
28345            if (self.onBeforeDestroy)
28346                self.onBeforeDestroy();
28347
28348            if (options.enableColumnReorder && $headers.sortable) 
28349                $headers.sortable("destroy");
28350
28351            unbindAncestorScrollEvents();
28352            $container.unbind(".slickgrid");
28353            removeCssRules();
28354
28355            $canvas.unbind("draginit dragstart dragend drag");
28356            $container.empty().removeClass(uid);
28357        }
28358
28359
28360        //////////////////////////////////////////////////////////////////////////////////////////////
28361        // General
28362
28363        function getEditController() {
28364            return editController;
28365        }
28366
28367        function getColumnIndex(id) {
28368            return columnsById[id];
28369        }
28370
28371        function autosizeColumns() {
28372            var i, c,
28373                widths = [],
28374                shrinkLeeway = 0,
28375                viewportW = $viewport.innerWidth(), // may not be initialized yet
28376                availWidth = (options.autoHeight ? viewportW : viewportW - scrollbarDimensions.width), // with AutoHeight, we do not need to accomodate the vertical scroll bar
28377                total = 0,
28378                existingTotal = 0;
28379
28380            for (i = 0; i < columns.length; i++) {
28381                c = columns[i];
28382                widths.push(c.width);
28383                existingTotal += c.width;
28384                shrinkLeeway += c.width - Math.max(c.minWidth || 0, absoluteColumnMinWidth);
28385            }
28386
28387            total = existingTotal;
28388
28389            removeAllRows();
28390
28391            // shrink
28392            while (total > availWidth) {
28393                if (!shrinkLeeway) { return; }
28394                var shrinkProportion = (total - availWidth) / shrinkLeeway;
28395                for (i = 0; i < columns.length && total > availWidth; i++) {
28396                    c = columns[i];
28397                    if (!c.resizable || c.minWidth === c.width || c.width === absoluteColumnMinWidth) { continue; }
28398                    var shrinkSize = Math.floor(shrinkProportion * (c.width - Math.max(c.minWidth || 0, absoluteColumnMinWidth))) || 1;
28399                    total -= shrinkSize;
28400                    widths[i] -= shrinkSize;
28401                }
28402            }
28403
28404            // grow
28405            var previousTotal = total;
28406            while (total < availWidth) {
28407                var growProportion = availWidth / total;
28408                for (i = 0; i < columns.length && total < availWidth; i++) {
28409                    c = columns[i];
28410                    if (!c.resizable || c.maxWidth <= c.width) { continue; }
28411                    var growSize = Math.min(Math.floor(growProportion * c.width) - c.width, (c.maxWidth - c.width) || 1000000) || 1;
28412                    total += growSize;
28413                    widths[i] += growSize;
28414                }
28415                if (previousTotal == total) break; // if total is not changing, will result in infinite loop
28416                previousTotal = total;
28417            }
28418
28419            for (i=0; i<columns.length; i++) {
28420                styleColumnWidth(i, columns[i].currentWidth = widths[i], true);
28421            }
28422
28423            resizeCanvas();
28424        }
28425
28426        function styleColumnWidth(index,width,styleCells) {
28427            columns[index].currentWidth = width;
28428            $headers.children().eq(index).css("width", width - headerColumnWidthDiff);
28429            if (styleCells) {
28430                findCssRuleForCell(index).style.width = (width - cellWidthDiff) + "px";
28431            }
28432        }
28433
28434        function setSortColumn(columnId, ascending) {
28435            sortColumnId = columnId;
28436            sortAsc = ascending;
28437            var columnIndex = getColumnIndex(sortColumnId);
28438
28439            $headers.children().removeClass("slick-header-column-sorted");
28440            $headers.find(".slick-sort-indicator").removeClass("slick-sort-indicator-asc slick-sort-indicator-desc");
28441
28442            if (columnIndex != null) {
28443                $headers.children().eq(columnIndex)
28444                    .addClass("slick-header-column-sorted")
28445                    .find(".slick-sort-indicator")
28446                        .addClass(sortAsc ? "slick-sort-indicator-asc" : "slick-sort-indicator-desc");
28447            }
28448        }
28449
28450        function getSelectedRows() {
28451            return selectedRows.concat();
28452        }
28453
28454        function setSelectedRows(rows) {
28455            var i, row;
28456            var lookup = {};
28457            for (i=0; i<rows.length; i++) {
28458                lookup[rows[i]] = true;
28459            }
28460
28461            // unselect old rows
28462            for (i=0; i<selectedRows.length; i++) {
28463                row = selectedRows[i];
28464                if (rowsCache[row] && !lookup[row]) {
28465                    $(rowsCache[row]).removeClass("ui-state-active selected");
28466                }
28467            }
28468
28469            // select new ones
28470            for (i=0; i<rows.length; i++) {
28471                row = rows[i];
28472                if (rowsCache[row] && !selectedRowsLookup[row]) {
28473                    $(rowsCache[row]).addClass("ui-state-active selected");
28474                }
28475            }
28476
28477            selectedRows = rows.concat();
28478            selectedRowsLookup = lookup;
28479        }
28480
28481        function getColumns() {
28482            return columns;
28483        }
28484
28485        function setColumns(columnDefinitions) {
28486            columns = columnDefinitions;
28487            removeAllRows();
28488            createColumnHeaders();
28489            removeCssRules();
28490            createCssRules();
28491            resizeAndRender();
28492            handleScroll();
28493        }
28494
28495        function getOptions() {
28496            return options;
28497        }
28498
28499        function setOptions(args) {
28500            if (!options.editorLock.commitCurrentEdit()) {
28501                return;
28502            }
28503
28504            makeSelectedCellNormal();
28505
28506            if (options.enableAddRow !== args.enableAddRow) {
28507                removeRow(gridDataGetLength());
28508            }
28509
28510            options = $.extend(options,args);
28511
28512            render();
28513        }
28514
28515        function setData(newData,scrollToTop) {
28516            removeAllRows();
28517            data = newData;
28518            gridData = data;
28519            gridDataGetLength = gridData.getLength || defaultGetLength;
28520            gridDataGetItem = gridData.getItem || defaultGetItem;
28521            if (scrollToTop)
28522                scrollTo(0);
28523        }
28524
28525        function getData() {
28526            return gridData;
28527        }
28528
28529        function getSecondaryHeaderRow() {
28530            return $secondaryHeaders[0];
28531        }
28532
28533        function showSecondaryHeaderRow() {
28534            options.showSecondaryHeaderRow = true;
28535            $secondaryHeaderScroller.slideDown("fast", resizeCanvas);
28536        }
28537
28538        function hideSecondaryHeaderRow() {
28539            options.showSecondaryHeaderRow = false;
28540            $secondaryHeaderScroller.slideUp("fast", resizeCanvas);
28541        }
28542
28543        //////////////////////////////////////////////////////////////////////////////////////////////
28544        // Rendering / Scrolling
28545
28546        function scrollTo(y) {
28547            var oldOffset = offset;
28548
28549            page = Math.min(n-1, Math.floor(y / ph));
28550            offset = Math.round(page * cj);
28551            var newScrollTop = y - offset;
28552
28553            if (offset != oldOffset) {
28554                var range = getVisibleRange(newScrollTop);
28555                cleanupRows(range.top,range.bottom);
28556                updateRowPositions();
28557            }
28558
28559            if (prevScrollTop != newScrollTop) {
28560                scrollDir = (prevScrollTop + oldOffset < newScrollTop + offset) ? 1 : -1;
28561                $viewport[0].scrollTop = (lastRenderedScrollTop = scrollTop = prevScrollTop = newScrollTop);
28562
28563                if (self.onViewportChanged) {
28564                    self.onViewportChanged();
28565                }
28566            }
28567        }
28568
28569        function defaultFormatter(row, cell, value, columnDef, dataContext) {
28570            return (value === null || value === undefined) ? "" : value;
28571        }
28572
28573        function getFormatter(column) {
28574            return column.formatter ||
28575                    (options.formatterFactory && options.formatterFactory.getFormatter(column)) ||
28576                    defaultFormatter;
28577        }
28578
28579        function getEditor(column) {
28580            return column.editor || (options.editorFactory && options.editorFactory.getEditor(column));
28581        }
28582
28583        function appendRowHtml(stringArray,row) {
28584            var d = gridDataGetItem(row);
28585            var dataLoading = row < gridDataGetLength() && !d;
28586            var cellCss;
28587            var css = "slick-row " +
28588                (dataLoading ? " loading" : "") +
28589                (selectedRowsLookup[row] ? " selected ui-state-active" : "") +
28590                (row % 2 == 1 ? ' odd' : ' even');
28591
28592            // if the user has specified a function to provide additional per-row css classes, call it here
28593            if (options.rowCssClasses) {
28594                css += ' ' + options.rowCssClasses(d);
28595            }
28596
28597            stringArray.push("<div class='ui-widget-content " + css + "' row='" + row + "' style='top:" + (options.rowHeight*row-offset) + "px'>");
28598
28599            for (var i=0, cols=columns.length; i<cols; i++) {
28600                var m = columns[i];
28601
28602                cellCss = "slick-cell c" + i + (m.cssClass ? " " + m.cssClass : "");
28603                if (highlightedCells && highlightedCells[row] && highlightedCells[row][m.id])
28604                    cellCss += (" " + options.cellHighlightCssClass);
28605
28606                stringArray.push("<div class='" + cellCss + "'>");
28607
28608                // if there is a corresponding row (if not, this is the Add New row or this data hasn't been loaded yet)
28609                if (d) {
28610                    stringArray.push(getFormatter(m)(row, i, d[m.field], m, d));
28611                }
28612
28613                stringArray.push("</div>");
28614            }
28615
28616            stringArray.push("</div>");
28617        }
28618
28619        function cleanupRows(rangeToKeep) {
28620            for (var i in rowsCache) {
28621                if (((i = parseInt(i, 10)) !== currentRow) && (i < rangeToKeep.top || i > rangeToKeep.bottom)) {
28622                    removeRowFromCache(i);
28623                }
28624            }
28625        }
28626
28627        function invalidate() {
28628           updateRowCount();
28629           removeAllRows();
28630           render();
28631        }
28632
28633        function removeAllRows() {
28634            if (currentEditor) {
28635                makeSelectedCellNormal();
28636            }
28637// modified 9/26/2011 timd
28638            if($canvas && $canvas.length)
28639                $canvas[0].innerHTML = "";
28640// end modification
28641            rowsCache= {};
28642            postProcessedRows = {};
28643            counter_rows_removed += renderedRows;
28644            renderedRows = 0;
28645        }
28646
28647        function removeRowFromCache(row) {
28648            var node = rowsCache[row];
28649            if (!node) { return; }
28650            $canvas[0].removeChild(node);
28651
28652            delete rowsCache[row];
28653            delete postProcessedRows[row];
28654            renderedRows--;
28655            counter_rows_removed++;
28656        }
28657
28658        function removeRows(rows) {
28659            var i, rl, nl;
28660            if (!rows || !rows.length) { return; }
28661            scrollDir = 0;
28662            var nodes = [];
28663            for (i=0, rl=rows.length; i<rl; i++) {
28664                if (currentEditor && currentRow === i) {
28665                    makeSelectedCellNormal();
28666                }
28667
28668                if (rowsCache[rows[i]]) {
28669                    nodes.push(rows[i]);
28670                }
28671            }
28672
28673            if (renderedRows > 10 && nodes.length === renderedRows) {
28674                removeAllRows();
28675            }
28676            else {
28677                for (i=0, nl=nodes.length; i<nl; i++) {
28678                    removeRowFromCache(nodes[i]);
28679                }
28680            }
28681        }
28682
28683        function removeRow(row) {
28684            removeRows([row]);
28685        }
28686
28687        function updateCell(row,cell) {
28688            if (!rowsCache[row]) { return; }
28689            var $cell = $(rowsCache[row]).children().eq(cell);
28690            if ($cell.length === 0) { return; }
28691
28692            var m = columns[cell], d = gridDataGetItem(row);
28693            if (currentEditor && currentRow === row && currentCell === cell) {
28694                currentEditor.loadValue(d);
28695            }
28696            else {
28697                $cell[0].innerHTML = d ? getFormatter(m)(row, cell, d[m.field], m, d) : "";
28698                invalidatePostProcessingResults(row);
28699            }
28700        }
28701
28702        function updateRow(row) {
28703            if (!rowsCache[row]) { return; }
28704
28705            $(rowsCache[row]).children().each(function(i) {
28706                var m = columns[i];
28707                if (row === currentRow && i === currentCell && currentEditor) {
28708                    currentEditor.loadValue(gridDataGetItem(currentRow));
28709                }
28710                else if (gridDataGetItem(row)) {
28711                    this.innerHTML = getFormatter(m)(row, i, gridDataGetItem(row)[m.field], m, gridDataGetItem(row));
28712                }
28713                else {
28714                    this.innerHTML = "";
28715                }
28716            });
28717
28718            invalidatePostProcessingResults(row);
28719        }
28720
28721        function resizeCanvas() {
28722            var newViewportH = options.rowHeight * (gridDataGetLength() + (options.enableAddRow ? 1 : 0) + (options.leaveSpaceForNewRows? numVisibleRows - 1 : 0));
28723            if (options.autoHeight) { // use computed height to set both canvas _and_ divMainScroller, effectively hiding scroll bars.
28724                $viewport.height(newViewportH);
28725            }
28726            else {
28727                $viewport.height(
28728                        $container.innerHeight() -
28729                        $headerScroller.outerHeight() -
28730                        (options.showSecondaryHeaderRow ? $secondaryHeaderScroller.outerHeight() : 0));
28731            }
28732
28733            viewportW = $viewport.innerWidth();
28734            viewportH = $viewport.innerHeight();
28735            numVisibleRows = Math.ceil(viewportH / options.rowHeight);
28736
28737            var totalWidth = 0;
28738            $headers.find(".slick-header-column").each(function() {
28739                totalWidth += $(this).outerWidth();
28740            });
28741            setCanvasWidth(totalWidth);
28742
28743            updateRowCount();
28744            render();
28745        }
28746
28747        function resizeAndRender() {
28748            if (options.forceFitColumns) {
28749                autosizeColumns();
28750            } else {
28751                resizeCanvas();
28752            }
28753        }
28754
28755        function updateRowCount() {
28756            var newRowCount = gridDataGetLength() + (options.enableAddRow?1:0) + (options.leaveSpaceForNewRows?numVisibleRows-1:0);
28757            var oldH = h;
28758
28759            // remove the rows that are now outside of the data range
28760            // this helps avoid redundant calls to .removeRow() when the size of the data decreased by thousands of rows
28761            var l = options.enableAddRow ? gridDataGetLength() : gridDataGetLength() - 1;
28762            for (var i in rowsCache) {
28763                if (i >= l) {
28764                    removeRowFromCache(i);
28765                }
28766            }
28767            th = Math.max(options.rowHeight * newRowCount, viewportH - scrollbarDimensions.height);
28768            if (th < maxSupportedCssHeight) {
28769                // just one page
28770                h = ph = th;
28771                n = 1;
28772                cj = 0;
28773            }
28774            else {
28775                // break into pages
28776                h = maxSupportedCssHeight;
28777                ph = h / 100;
28778                n = Math.floor(th / ph);
28779                cj = (th - h) / (n - 1);
28780            }
28781
28782            if (h !== oldH) {
28783                $canvas.css("height",h);
28784                scrollTop = $viewport[0].scrollTop;
28785            }
28786
28787            var oldScrollTopInRange = (scrollTop + offset <= th - viewportH);
28788
28789            if (th == 0 || scrollTop == 0) {
28790                page = offset = 0;
28791            }
28792            else if (oldScrollTopInRange) {
28793                // maintain virtual position
28794                scrollTo(scrollTop+offset);
28795            }
28796            else {
28797                // scroll to bottom
28798                scrollTo(th-viewportH);
28799            }
28800
28801            if (h != oldH && options.autoHeight) {
28802                resizeCanvas();
28803            }
28804        }
28805
28806        function getVisibleRange(viewportTop) {
28807            if (viewportTop == null)
28808                viewportTop = scrollTop;
28809
28810            return {
28811                top: Math.floor((scrollTop+offset)/options.rowHeight),
28812                bottom: Math.ceil((scrollTop+offset+viewportH)/options.rowHeight)
28813            };
28814        }
28815
28816        function getRenderedRange(viewportTop) {
28817            var range = getVisibleRange(viewportTop);
28818            var buffer = Math.round(viewportH/options.rowHeight);
28819            var minBuffer = 3;
28820
28821            if (scrollDir == -1) {
28822                range.top -= buffer;
28823                range.bottom += minBuffer;
28824            }
28825            else if (scrollDir == 1) {
28826                range.top -= minBuffer;
28827                range.bottom += buffer;
28828            }
28829            else {
28830                range.top -= minBuffer;
28831                range.bottom += minBuffer;
28832            }
28833
28834            range.top = Math.max(0,range.top);
28835            range.bottom = Math.min(options.enableAddRow ? gridDataGetLength() : gridDataGetLength() - 1,range.bottom);
28836
28837            return range;
28838        }
28839
28840        function renderRows(range) {
28841            var i, l,
28842                parentNode = $canvas[0],
28843                rowsBefore = renderedRows,
28844                stringArray = [],
28845                rows = [],
28846                startTimestamp = new Date(),
28847                needToReselectCell = false;
28848
28849            for (i = range.top; i <= range.bottom; i++) {
28850                if (rowsCache[i]) { continue; }
28851                renderedRows++;
28852                rows.push(i);
28853                appendRowHtml(stringArray,i);
28854                if (currentCellNode && currentRow === i)
28855                    needToReselectCell = true;
28856                counter_rows_rendered++;
28857            }
28858
28859            var x = document.createElement("div");
28860            x.innerHTML = stringArray.join("");
28861
28862            for (i = 0, l = x.childNodes.length; i < l; i++) {
28863                rowsCache[rows[i]] = parentNode.appendChild(x.firstChild);
28864            }
28865
28866            if (needToReselectCell) {
28867                currentCellNode = $(rowsCache[currentRow]).children().eq(currentCell)[0];
28868                setSelectedCell(currentCellNode,false);
28869            }
28870
28871            if (renderedRows - rowsBefore > 5) {
28872                avgRowRenderTime = (new Date() - startTimestamp) / (renderedRows - rowsBefore);
28873            }
28874        }
28875
28876        function startPostProcessing() {
28877            if (!options.enableAsyncPostRender) { return; }
28878            clearTimeout(h_postrender);
28879            h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
28880        }
28881
28882        function invalidatePostProcessingResults(row) {
28883            delete postProcessedRows[row];
28884            postProcessFromRow = Math.min(postProcessFromRow,row);
28885            postProcessToRow = Math.max(postProcessToRow,row);
28886            startPostProcessing();
28887        }
28888
28889        function updateRowPositions() {
28890            for (var row in rowsCache) {
28891                rowsCache[row].style.top = (row*options.rowHeight-offset) + "px";
28892            }
28893        }
28894
28895        function render() {
28896            var visible = getVisibleRange();
28897            var rendered = getRenderedRange();
28898
28899            // remove rows no longer in the viewport
28900            cleanupRows(rendered);
28901
28902            // add new rows
28903            renderRows(rendered);
28904
28905            postProcessFromRow = visible.top;
28906            postProcessToRow = Math.min(options.enableAddRow ? gridDataGetLength() : gridDataGetLength() - 1, visible.bottom);
28907            startPostProcessing();
28908
28909            lastRenderedScrollTop = scrollTop;
28910            h_render = null;
28911        }
28912
28913        function handleScroll() {
28914            scrollTop = $viewport[0].scrollTop;
28915            var scrollLeft = $viewport[0].scrollLeft;
28916            var scrollDist = Math.abs(scrollTop - prevScrollTop);
28917
28918            if (scrollLeft !== prevScrollLeft) {
28919                prevScrollLeft = scrollLeft;
28920                $headerScroller[0].scrollLeft = scrollLeft;
28921                $secondaryHeaderScroller[0].scrollLeft = scrollLeft;
28922            }
28923
28924            if (!scrollDist) return;
28925
28926            scrollDir = prevScrollTop < scrollTop ? 1 : -1;
28927            prevScrollTop = scrollTop;
28928
28929            // switch virtual pages if needed
28930            if (scrollDist < viewportH) {
28931                scrollTo(scrollTop + offset);
28932            }
28933            else {
28934                var oldOffset = offset;
28935                page = Math.min(n - 1, Math.floor(scrollTop * ((th - viewportH) / (h - viewportH)) * (1 / ph)));
28936                offset = Math.round(page * cj);
28937                if (oldOffset != offset)
28938                    removeAllRows();
28939            }
28940
28941            if (h_render)
28942                clearTimeout(h_render);
28943
28944            if (Math.abs(lastRenderedScrollTop - scrollTop) < viewportH)
28945                render();
28946            else
28947                h_render = setTimeout(render, 50);
28948
28949            if (self.onViewportChanged) {
28950                self.onViewportChanged();
28951            }
28952        }
28953
28954        function asyncPostProcessRows() {
28955            while (postProcessFromRow <= postProcessToRow) {
28956                var row = (scrollDir >= 0) ? postProcessFromRow++ : postProcessToRow--;
28957                var rowNode = rowsCache[row];
28958                if (!rowNode || postProcessedRows[row] || row>=gridDataGetLength()) { continue; }
28959
28960                var d = gridDataGetItem(row), cellNodes = rowNode.childNodes;
28961                for (var i=0, j=0, l=columns.length; i<l; ++i) {
28962                    var m = columns[i];
28963                    if (m.asyncPostRender) { m.asyncPostRender(cellNodes[j], postProcessFromRow, d, m); }
28964                    ++j;
28965                }
28966
28967                postProcessedRows[row] = true;
28968                h_postrender = setTimeout(asyncPostProcessRows, options.asyncPostRenderDelay);
28969                return;
28970            }
28971        }
28972
28973        function setHighlightedCells(cellsToHighlight) {
28974            var i, $cell, hasHighlight, hadHighlight;
28975
28976            for (var row in rowsCache) {
28977                for (i=0; i<columns.length; i++) {
28978                    hadHighlight = highlightedCells && highlightedCells[row] && highlightedCells[row][columns[i].id];
28979                    hasHighlight = cellsToHighlight && cellsToHighlight[row] && cellsToHighlight[row][columns[i].id];
28980
28981                    if (hadHighlight != hasHighlight) {
28982                        $cell = $(rowsCache[row]).children().eq(i);
28983                        if ($cell.length) {
28984                            $cell.toggleClass(options.cellHighlightCssClass);
28985                        }
28986                    }
28987                }
28988            }
28989
28990            highlightedCells = cellsToHighlight;
28991        }
28992
28993        function flashCell(row, cell, speed) {
28994            speed = speed || 100;
28995            if (rowsCache[row]) {
28996                var $cell = $(rowsCache[row]).children().eq(cell);
28997
28998                function toggleCellClass(times) {
28999                    if (!times) return;
29000                    setTimeout(function() {
29001                        $cell.queue(function() {
29002                            $cell.toggleClass(options.cellFlashingCssClass).dequeue();
29003                            toggleCellClass(times-1);
29004                        });
29005                    },
29006                    speed);
29007                }
29008
29009                toggleCellClass(4);
29010            }
29011        }
29012
29013        //////////////////////////////////////////////////////////////////////////////////////////////
29014        // Interactivity
29015
29016        function getSiblingIndex(node) {
29017            var idx = 0;
29018            while (node && node.previousSibling) {
29019                idx++;
29020                node = node.previousSibling;
29021            }
29022            return idx;
29023        }
29024
29025        function handleKeyDown(e) {
29026            // give registered handler chance to process the keyboard event
29027            var handled = (self.onKeyDown && // a handler must be registered
29028                !options.editorLock.isActive() && // grid must not be in edit mode;
29029                self.onKeyDown(e, currentRow, currentCell)); // handler must return truthy-value to indicate it handled the event
29030
29031            if (!handled) {
29032                if (!e.shiftKey && !e.altKey && !e.ctrlKey) {
29033                    if (e.which == 27) {
29034                        if (!options.editorLock.isActive()) {
29035                            return; // no editing mode to cancel, allow bubbling and default processing (exit without cancelling the event)
29036                        }
29037                        cancelEditAndSetFocus();
29038                    }
29039                    else if (e.which == 37) {
29040                        navigateLeft();
29041                    }
29042                    else if (e.which == 39) {
29043                        navigateRight();
29044                    }
29045                    else if (e.which == 38) {
29046                        navigateUp();
29047                    }
29048                    else if (e.which == 40) {
29049                        navigateDown();
29050                    }
29051                    else if (e.which == 9) {
29052                        navigateNext();
29053                    }
29054                    else if (e.which == 13) {
29055                        if (options.editable) {
29056                            if (currentEditor) {
29057                                // adding new row
29058                                if (currentRow === defaultGetLength()) {
29059                                    navigateDown();
29060                                }
29061                                else {
29062                                    commitEditAndSetFocus();
29063                                }
29064                            } else {
29065                                if (options.editorLock.commitCurrentEdit()) {
29066                                    makeSelectedCellEditable();
29067                                }
29068                            }
29069                        }
29070                    }
29071                    else
29072                        return;
29073                }
29074                else if (e.which == 9 && e.shiftKey && !e.ctrlKey && !e.altKey) {
29075                        navigatePrev();
29076                }
29077                else
29078                    return;
29079            }
29080
29081            // the event has been handled so don't let parent element (bubbling/propagation) or browser (default) handle it
29082            e.stopPropagation();
29083            e.preventDefault();
29084            try {
29085                e.originalEvent.keyCode = 0; // prevent default behaviour for special keys in IE browsers (F3, F5, etc.)
29086            }
29087            catch (error) {} // ignore exceptions - setting the original event's keycode throws access denied exception for "Ctrl" (hitting control key only, nothing else), "Shift" (maybe others)
29088        }
29089
29090        function handleClick(e) {
29091
29092//MODIFIED 8/19/2011 timd to allow ctrl-click to open link in a new tab
29093            if(e.ctrlKey && e.target.tagName.toLowerCase() == 'a')
29094                return(true);
29095// end modified
29096
29097            var $cell = $(e.target).closest(".slick-cell", $canvas);
29098            if ($cell.length === 0) { return; }
29099
29100            // are we editing this cell?
29101            if (currentCellNode === $cell[0] && currentEditor !== null) { return; }
29102
29103            var row = parseInt($cell.parent().attr("row"), 10);
29104            var cell = getSiblingIndex($cell[0]);
29105            var validated = null;
29106            var c = columns[cell];
29107            var item = gridDataGetItem(row);
29108
29109            // is this a 'select' column or a Ctrl/Shift-click?
29110            if (item && (c.behavior === "selectAndMove" || c.behavior === "select" || (e.ctrlKey || e.shiftKey))) {
29111                // grid must not be in edit mode
29112                validated = options.editorLock.commitCurrentEdit();
29113                if (validated) {
29114                    var selection = getSelectedRows();
29115                    var idx = $.inArray(row, selection);
29116
29117                    if (!e.ctrlKey && !e.shiftKey && !e.metaKey) {
29118                        selection = [row];
29119                    }
29120                    else if (options.multiSelect) {
29121                        if (idx === -1 && (e.ctrlKey || e.metaKey)) {
29122                            selection.push(row);
29123                        }
29124                        else if (idx !== -1 && (e.ctrlKey || e.metaKey)) {
29125                            selection = $.grep(selection, function(o, i) { return (o !== row); });
29126                        }
29127                        else if (selection.length && e.shiftKey) {
29128                            var last = selection.pop();
29129                            var from = Math.min(row, last);
29130                            var to = Math.max(row, last);
29131                            selection = [];
29132                            for (var i = from; i <= to; i++) {
29133                                if (i !== last) {
29134                                    selection.push(i);
29135                                }
29136                            }
29137                            selection.push(last);
29138                        }
29139                    }
29140                    resetCurrentCell();
29141                    setSelectedRows(selection);
29142                    if (self.onSelectedRowsChanged) {
29143                        self.onSelectedRowsChanged();
29144                    }
29145
29146                    if (!$.browser.msie) {
29147                        $canvas[0].focus();
29148                    }
29149
29150                    return false;
29151                }
29152            }
29153
29154            // do we have any registered handlers?
29155            if (item && self.onClick) {
29156                // grid must not be in edit mode
29157                validated = options.editorLock.commitCurrentEdit();
29158                if (validated) {
29159                    // handler will return true if the event was handled
29160                    if (self.onClick(e, row, cell)) {
29161                        e.stopPropagation();
29162                        e.preventDefault();
29163                        return false;
29164                    }
29165                }
29166            }
29167
29168            if (options.enableCellNavigation && !columns[cell].unselectable) {
29169                // commit current edit before proceeding
29170                if (validated === true || (validated === null && options.editorLock.commitCurrentEdit())) {
29171                    scrollRowIntoView(row,false);
29172                    setSelectedCellAndRow($cell[0], (row === defaultGetLength()) || options.autoEdit);
29173                }
29174            }
29175        }
29176
29177        function handleContextMenu(e) {
29178            var $cell = $(e.target).closest(".slick-cell", $canvas);
29179            if ($cell.length === 0) { return; }
29180
29181            // are we editing this cell?
29182            if (currentCellNode === $cell[0] && currentEditor !== null) { return; }
29183
29184            var row = parseInt($cell.parent().attr("row"), 10);
29185            var cell = getSiblingIndex($cell[0]);
29186            var validated = null;
29187
29188            // do we have any registered handlers?
29189            if (gridDataGetItem(row) && self.onContextMenu) {
29190                // grid must not be in edit mode
29191                validated = options.editorLock.commitCurrentEdit();
29192                if (validated) {
29193                    // handler will return true if the event was handled
29194                    if (self.onContextMenu(e, row, cell)) {
29195                        e.stopPropagation();
29196                        e.preventDefault();
29197                        return false;
29198                    }
29199                }
29200            }
29201        }
29202
29203        function handleDblClick(e) {
29204            var $cell = $(e.target).closest(".slick-cell", $canvas);
29205            if ($cell.length === 0) { return; }
29206
29207            // are we editing this cell?
29208            if (currentCellNode === $cell[0] && currentEditor !== null) { return; }
29209
29210            var row = parseInt($cell.parent().attr("row"), 10);
29211            var cell = getSiblingIndex($cell[0]);
29212            var validated = null;
29213
29214            // do we have any registered handlers?
29215            if (gridDataGetItem(row) && self.onDblClick) {
29216                // grid must not be in edit mode
29217                validated = options.editorLock.commitCurrentEdit();
29218                if (validated) {
29219                    // handler will return true if the event was handled
29220                    if (self.onDblClick(e, row, cell)) {
29221                        e.stopPropagation();
29222                        e.preventDefault();
29223                        return false;
29224                    }
29225                }
29226            }
29227
29228            if (options.editable) {
29229                gotoCell(row, cell, true);
29230            }
29231        }
29232
29233        function handleHeaderContextMenu(e) {
29234            if (self.onHeaderContextMenu && options.editorLock.commitCurrentEdit()) {
29235                e.preventDefault();
29236                var selectedElement = $(e.target).closest(".slick-header-column", ".slick-header-columns");
29237                self.onHeaderContextMenu(e, columns[self.getColumnIndex(selectedElement.data("fieldId"))]);
29238            }
29239        }
29240
29241        function handleHeaderClick(e) {
29242            var $col = $(e.target).closest(".slick-header-column");
29243            if ($col.length ==0) { return; }
29244            var column = columns[getSiblingIndex($col[0])];
29245
29246            if (self.onHeaderClick && options.editorLock.commitCurrentEdit()) {
29247                e.preventDefault();
29248                self.onHeaderClick(e, column);
29249            }
29250        }
29251
29252        function handleHover(e) {
29253            if (!options.enableAutoTooltips) return;
29254            var $cell = $(e.target).closest(".slick-cell",$canvas);
29255            if ($cell.length) {
29256                if ($cell.innerWidth() < $cell[0].scrollWidth) {
29257                    var text = $.trim($cell.text());
29258                    $cell.attr("title", (options.toolTipMaxLength && text.length > options.toolTipMaxLength) ?  text.substr(0, options.toolTipMaxLength - 3) + "..." : text);
29259                }
29260                else {
29261                    $cell.attr("title","");
29262                }
29263            }
29264        }
29265
29266        function cellExists(row,cell) {
29267            return !(row < 0 || row >= gridDataGetLength() || cell < 0 || cell >= columns.length);
29268        }
29269
29270        function getCellFromPoint(x,y) {
29271            var row = Math.floor((y+offset)/options.rowHeight);
29272            var cell = 0;
29273
29274            var w = 0;
29275            for (var i=0; i<columns.length && w<x; i++) {
29276                w += columns[i].width;
29277                cell++;
29278            }
29279
29280            return {row:row,cell:cell-1};
29281        }
29282
29283        function getCellFromEvent(e) {
29284            var $cell = $(e.target).closest(".slick-cell", $canvas);
29285            if (!$cell.length)
29286                return null;
29287
29288            return {
29289                row: $cell.parent().attr("row") | 0,
29290                cell: getSiblingIndex($cell[0])
29291            };
29292        }
29293
29294        function getCellNodeBox(row,cell) {
29295             if (!cellExists(row,cell))
29296                 return null;
29297
29298             var y1 = row * options.rowHeight - offset;
29299             var y2 = y1 + options.rowHeight - 1;
29300             var x1 = 0;
29301             for (var i=0; i<cell; i++) {
29302                 x1 += columns[i].width;
29303             }
29304             var x2 = x1 + columns[cell].width;
29305
29306             return {
29307                 top: y1,
29308                 left: x1,
29309                 bottom: y2,
29310                 right: x2
29311             };
29312         }
29313
29314        //////////////////////////////////////////////////////////////////////////////////////////////
29315        // Cell switching
29316
29317        function resetCurrentCell() {
29318            setSelectedCell(null,false);
29319        }
29320
29321        function focusOnCurrentCell() {
29322            // lazily enable the cell to receive keyboard focus
29323            $(currentCellNode)
29324                .attr("tabIndex",0)
29325                .attr("hideFocus",true);
29326
29327            // IE7 tries to scroll the viewport so that the item being focused is aligned to the left border
29328            // IE-specific .setActive() sets the focus, but doesn't scroll
29329            if ($.browser.msie && parseInt($.browser.version) < 8)
29330                currentCellNode.setActive();
29331            else
29332                currentCellNode.focus();
29333
29334            var left = $(currentCellNode).position().left,
29335                right = left + $(currentCellNode).outerWidth(),
29336                scrollLeft = $viewport.scrollLeft(),
29337                scrollRight = scrollLeft + $viewport.width();
29338
29339            if (left < scrollLeft)
29340                $viewport.scrollLeft(left);
29341            else if (right > scrollRight)
29342                $viewport.scrollLeft(Math.min(left, right - $viewport[0].clientWidth));
29343        }
29344
29345        function setSelectedCell(newCell,editMode) {
29346            if (currentCellNode !== null) {
29347                makeSelectedCellNormal();
29348                $(currentCellNode).removeClass("selected");
29349            }
29350
29351            currentCellNode = newCell;
29352
29353            if (currentCellNode != null) {
29354                currentRow = parseInt($(currentCellNode).parent().attr("row"), 10);
29355                currentCell = getSiblingIndex(currentCellNode);
29356
29357                $(currentCellNode).addClass("selected");
29358
29359                if (options.editable && editMode && isCellPotentiallyEditable(currentRow,currentCell)) {
29360                    clearTimeout(h_editorLoader);
29361
29362                    if (options.asyncEditorLoading) {
29363                        h_editorLoader = setTimeout(makeSelectedCellEditable, options.asyncEditorLoadDelay);
29364                    }
29365                    else {
29366                        makeSelectedCellEditable();
29367                    }
29368                }
29369                else {
29370                    focusOnCurrentCell()
29371                }
29372                if (self.onCurrentCellChanged)
29373                    self.onCurrentCellChanged(getCurrentCell());
29374            }
29375            else {
29376                currentRow = null;
29377                currentCell = null;
29378            }
29379        }
29380
29381        function setSelectedCellAndRow(newCell,editMode) {
29382            setSelectedCell(newCell,editMode);
29383
29384            if (newCell) {
29385                setSelectedRows([currentRow]);
29386            }
29387            else {
29388                setSelectedRows([]);
29389            }
29390
29391            if (self.onSelectedRowsChanged) {
29392                self.onSelectedRowsChanged();
29393            }
29394        }
29395
29396        function clearTextSelection() {
29397            if (document.selection && document.selection.empty) {
29398                document.selection.empty();
29399            }
29400            else if (window.getSelection) {
29401                var sel = window.getSelection();
29402                if (sel && sel.removeAllRanges) {
29403                    sel.removeAllRanges();
29404                }
29405            }
29406        }
29407
29408        function isCellPotentiallyEditable(row,cell) {
29409            // is the data for this row loaded?
29410            if (row < gridDataGetLength() && !gridDataGetItem(row)) {
29411                return false;
29412            }
29413
29414            // are we in the Add New row?  can we create new from this cell?
29415            if (columns[cell].cannotTriggerInsert && row >= gridDataGetLength()) {
29416                return false;
29417            }
29418
29419            // does this cell have an editor?
29420            if (!getEditor(columns[cell])) {
29421                return false;
29422            }
29423
29424            return true;
29425        }
29426
29427        function makeSelectedCellNormal() {
29428            if (!currentEditor) { return; }
29429
29430            if (self.onBeforeCellEditorDestroy) {
29431                self.onBeforeCellEditorDestroy(currentEditor);
29432            }
29433            currentEditor.destroy();
29434            currentEditor = null;
29435
29436            if (currentCellNode) {
29437                $(currentCellNode).removeClass("editable invalid");
29438
29439                if (gridDataGetItem(currentRow)) {
29440                    var column = columns[currentCell];
29441                    currentCellNode.innerHTML = getFormatter(column)(currentRow, currentCell, gridDataGetItem(currentRow)[column.field], column, gridDataGetItem(currentRow));
29442                    invalidatePostProcessingResults(currentRow);
29443                }
29444            }
29445
29446            // if there previously was text selected on a page (such as selected text in the edit cell just removed),
29447            // IE can't set focus to anything else correctly
29448            if ($.browser.msie) { clearTextSelection(); }
29449
29450            options.editorLock.deactivate(editController);
29451        }
29452
29453        function makeSelectedCellEditable() {
29454            if (!currentCellNode) { return; }
29455            if (!options.editable) {
29456                throw "Grid : makeSelectedCellEditable : should never get called when options.editable is false";
29457            }
29458
29459            // cancel pending async call if there is one
29460            clearTimeout(h_editorLoader);
29461
29462            if (!isCellPotentiallyEditable(currentRow,currentCell)) {
29463                return;
29464            }
29465
29466            if (self.onBeforeEditCell && self.onBeforeEditCell(currentRow,currentCell,gridDataGetItem(currentRow)) === false) {
29467                focusOnCurrentCell();
29468                return;
29469            }
29470
29471            options.editorLock.activate(editController);
29472            $(currentCellNode).addClass("editable");
29473
29474            currentCellNode.innerHTML = "";
29475
29476            var columnDef = columns[currentCell];
29477            var item = gridDataGetItem(currentRow);
29478
29479            currentEditor = new (getEditor(columnDef))({
29480                grid: self,
29481                gridPosition: absBox($container[0]),
29482                position: absBox(currentCellNode),
29483                container: currentCellNode,
29484                column: columnDef,
29485                item: item || {},
29486                commitChanges: commitEditAndSetFocus,
29487                cancelChanges: cancelEditAndSetFocus
29488            });
29489
29490            if (item)
29491                currentEditor.loadValue(item);
29492
29493            serializedEditorValue = currentEditor.serializeValue();
29494
29495            if (currentEditor.position)
29496                handleCurrentCellPositionChange();
29497        }
29498
29499        function commitEditAndSetFocus() {
29500            // if the commit fails, it would do so due to a validation error
29501            // if so, do not steal the focus from the editor
29502            if (options.editorLock.commitCurrentEdit()) {
29503                focusOnCurrentCell();
29504
29505                if (options.autoEdit) {
29506                    navigateDown();
29507                }
29508            }
29509        }
29510
29511        function cancelEditAndSetFocus() {
29512            if (options.editorLock.cancelCurrentEdit()) {
29513                focusOnCurrentCell();
29514            }
29515        }
29516
29517        function absBox(elem) {
29518            var box = {top:elem.offsetTop, left:elem.offsetLeft, bottom:0, right:0, width:$(elem).outerWidth(), height:$(elem).outerHeight(), visible:true};
29519            box.bottom = box.top + box.height;
29520            box.right = box.left + box.width;
29521
29522            // walk up the tree
29523            var offsetParent = elem.offsetParent;
29524            while ((elem = elem.parentNode) != document.body) {
29525                if (box.visible && elem.scrollHeight != elem.offsetHeight && $(elem).css("overflowY") != "visible")
29526                    box.visible = box.bottom > elem.scrollTop && box.top < elem.scrollTop + elem.clientHeight;
29527
29528                if (box.visible && elem.scrollWidth != elem.offsetWidth && $(elem).css("overflowX") != "visible")
29529                    box.visible = box.right > elem.scrollLeft && box.left < elem.scrollLeft + elem.clientWidth;
29530
29531                box.left -= elem.scrollLeft;
29532                box.top -= elem.scrollTop;
29533
29534                if (elem === offsetParent) {
29535                    box.left += elem.offsetLeft;
29536                    box.top += elem.offsetTop;
29537                    offsetParent = elem.offsetParent;
29538                }
29539
29540                box.bottom = box.top + box.height;
29541                box.right = box.left + box.width;
29542            }
29543
29544            return box;
29545        }
29546
29547        function getCurrentCellPosition(){
29548            return absBox(currentCellNode);
29549        }
29550
29551        function getGridPosition(){
29552            return absBox($container[0])
29553        }
29554
29555        function handleCurrentCellPositionChange() {
29556            if (!currentCellNode) return;
29557            var cellBox;
29558
29559            if (self.onCurrentCellPositionChanged){
29560                cellBox = getCurrentCellPosition();
29561                self.onCurrentCellPositionChanged(cellBox);
29562            }
29563
29564            if (currentEditor) {
29565                cellBox = cellBox || getCurrentCellPosition();
29566                if (currentEditor.show && currentEditor.hide) {
29567                    if (!cellBox.visible)
29568                        currentEditor.hide();
29569                    else
29570                        currentEditor.show();
29571                }
29572
29573                if (currentEditor.position)
29574                    currentEditor.position(cellBox);
29575            }
29576        }
29577
29578        function getCellEditor() {
29579            return currentEditor;
29580        }
29581
29582        function getCurrentCell() {
29583            if (!currentCellNode)
29584                return null;
29585            else
29586                return {row: currentRow, cell: currentCell};
29587        }
29588
29589        function getCurrentCellNode() {
29590            return currentCellNode;
29591        }
29592
29593        function scrollRowIntoView(row, doPaging) {
29594            var rowAtTop = row * options.rowHeight;
29595            var rowAtBottom = (row + 1) * options.rowHeight - viewportH + (viewportHasHScroll?scrollbarDimensions.height:0);
29596
29597            // need to page down?
29598            if ((row + 1) * options.rowHeight > scrollTop + viewportH + offset) {
29599                scrollTo(doPaging ? rowAtTop : rowAtBottom);
29600                render();
29601            }
29602
29603            // or page up?
29604            else if (row * options.rowHeight < scrollTop + offset) {
29605                scrollTo(doPaging ? rowAtBottom : rowAtTop);
29606                render();
29607            }
29608        }
29609
29610        function gotoDir(dy, dx, rollover) {
29611            if (!currentCellNode || !options.enableCellNavigation) { return; }
29612            if (!options.editorLock.commitCurrentEdit()) { return; }
29613
29614            function selectableCellFilter() {
29615                return !columns[getSiblingIndex(this)].unselectable
29616            }
29617
29618            var nextRow = rowsCache[currentRow + dy];
29619            var nextCell = (nextRow && currentCell + dx >= 0)
29620                    ? $(nextRow).children().eq(currentCell+dx).filter(selectableCellFilter)
29621                    : null;
29622
29623            if (nextCell && !nextCell.length) {
29624                var nodes = $(nextRow).children()
29625                        .filter(function(index) { return (dx>0) ? index > currentCell + dx : index < currentCell + dx })
29626                        .filter(selectableCellFilter);
29627
29628                if (nodes && nodes.length) {
29629                nextCell = (dx>0)
29630                            ? nodes.eq(0)
29631                            : nodes.eq(nodes.length-1);
29632                }
29633            }
29634
29635            if (rollover && dy === 0 && !(nextRow && nextCell && nextCell.length)) {
29636                if (!nextCell || !nextCell.length) {
29637                    nextRow = rowsCache[currentRow + dy + ((dx>0)?1:-1)];
29638                    var nodes = $(nextRow).children().filter(selectableCellFilter);
29639                    if (dx > 0) {
29640                        nextCell = nextRow
29641                                ? nodes.eq(0)
29642                                : null;
29643                    }
29644                    else {
29645                        nextCell = nextRow
29646                                ? nodes.eq(nodes.length-1)
29647                                : null;
29648                    }
29649                }
29650            }
29651
29652            if (nextRow && nextCell && nextCell.length) {
29653                // if selecting the 'add new' row, start editing right away
29654                var row = parseInt($(nextRow).attr("row"), 10);
29655                var isAddNewRow = (row == defaultGetLength());
29656                scrollRowIntoView(row,!isAddNewRow);
29657                setSelectedCellAndRow(nextCell[0], isAddNewRow || options.autoEdit);
29658
29659                // if no editor was created, set the focus back on the cell
29660                if (!currentEditor) {
29661                    focusOnCurrentCell();
29662                }
29663            }
29664            else {
29665                focusOnCurrentCell();
29666            }
29667        }
29668
29669        function gotoCell(row, cell, forceEdit) {
29670            if (row > gridDataGetLength() || row < 0 || cell >= columns.length || cell < 0) { return; }
29671            if (!options.enableCellNavigation || columns[cell].unselectable) { return; }
29672
29673            if (!options.editorLock.commitCurrentEdit()) { return; }
29674
29675            scrollRowIntoView(row,false);
29676
29677            var newCell = null;
29678            if (!columns[cell].unselectable) {
29679                newCell = $(rowsCache[row]).children().eq(cell)[0];
29680            }
29681
29682            // if selecting the 'add new' row, start editing right away
29683            setSelectedCellAndRow(newCell, forceEdit || (row === gridDataGetLength()) || options.autoEdit);
29684
29685            // if no editor was created, set the focus back on the cell
29686            if (!currentEditor) {
29687                focusOnCurrentCell();
29688            }
29689        }
29690
29691        function navigateUp() {
29692            gotoDir(-1, 0, false);
29693        }
29694
29695        function navigateDown() {
29696            gotoDir(1, 0, false);
29697        }
29698
29699        function navigateLeft() {
29700            gotoDir(0, -1, false);
29701        }
29702
29703        function navigateRight() {
29704            gotoDir(0, 1, false);
29705        }
29706
29707        function navigatePrev() {
29708            gotoDir(0, -1, true);
29709        }
29710
29711        function navigateNext() {
29712            gotoDir(0, 1, true);
29713        }
29714
29715        //////////////////////////////////////////////////////////////////////////////////////////////
29716        // IEditor implementation for the editor lock
29717
29718        function commitCurrentEdit() {
29719            var item = gridDataGetItem(currentRow);
29720            var column = columns[currentCell];
29721
29722            if (currentEditor) {
29723                if (currentEditor.isValueChanged()) {
29724                    var validationResults = currentEditor.validate();
29725
29726                    if (validationResults.valid) {
29727                        if (currentRow < gridDataGetLength()) {
29728                            var editCommand = {
29729                                row: currentRow,
29730                                cell: currentCell,
29731                                editor: currentEditor,
29732                                serializedValue: currentEditor.serializeValue(),
29733                                prevSerializedValue: serializedEditorValue,
29734                                execute: function() {
29735                                    this.editor.applyValue(item,this.serializedValue);
29736                                    updateRow(this.row);
29737                                },
29738                                undo: function() {
29739                                    this.editor.applyValue(item,this.prevSerializedValue);
29740                                    updateRow(this.row);
29741                                }
29742                            };
29743
29744                            if (options.editCommandHandler) {
29745                                makeSelectedCellNormal();
29746                                options.editCommandHandler(item,column,editCommand);
29747
29748                            }
29749                            else {
29750                                editCommand.execute();
29751                                makeSelectedCellNormal();
29752                            }
29753
29754                            if (self.onCellChange) {
29755                                self.onCellChange(currentRow,currentCell,item);
29756                            }
29757                        }
29758                        else if (self.onAddNewRow) {
29759                            var newItem = {};
29760                            currentEditor.applyValue(newItem,currentEditor.serializeValue());
29761                            makeSelectedCellNormal();
29762                            self.onAddNewRow(newItem,column);
29763                        }
29764
29765                        // check whether the lock has been re-acquired by event handlers
29766                        return !options.editorLock.isActive();
29767                    }
29768                    else {
29769                        // TODO: remove and put in onValidationError handlers in examples
29770                        $(currentCellNode).addClass("invalid");
29771                        $(currentCellNode).stop(true,true).effect("highlight", {color:"red"}, 300);
29772
29773                        if (self.onValidationError) {
29774                            self.onValidationError(currentCellNode, validationResults, currentRow, currentCell, column);
29775                        }
29776
29777                        currentEditor.focus();
29778                        return false;
29779                    }
29780                }
29781
29782                makeSelectedCellNormal();
29783            }
29784            return true;
29785        }
29786
29787        function cancelCurrentEdit() {
29788            makeSelectedCellNormal();
29789            return true;
29790        }
29791
29792
29793        //////////////////////////////////////////////////////////////////////////////////////////////
29794        // Debug
29795
29796        this.debug = function() {
29797            var s = "";
29798
29799            s += ("\n" + "counter_rows_rendered:  " + counter_rows_rendered);
29800            s += ("\n" + "counter_rows_removed:  " + counter_rows_removed);
29801            s += ("\n" + "renderedRows:  " + renderedRows);
29802            s += ("\n" + "numVisibleRows:  " + numVisibleRows);
29803            s += ("\n" + "maxSupportedCssHeight:  " + maxSupportedCssHeight);
29804            s += ("\n" + "n(umber of pages):  " + n);
29805            s += ("\n" + "(current) page:  " + page);
29806            s += ("\n" + "page height (ph):  " + ph);
29807            s += ("\n" + "scrollDir:  " + scrollDir);
29808
29809            alert(s);
29810        };
29811
29812        // a debug helper to be able to access private members
29813        this.eval = function(expr) {
29814            return eval(expr);
29815        };
29816
29817        init();
29818
29819
29820        //////////////////////////////////////////////////////////////////////////////////////////////
29821        // Public API
29822
29823        $.extend(this, {
29824            "slickGridVersion": "1.4.3",
29825
29826            // Events
29827            "onSort":                null,
29828            "onHeaderContextMenu":   null,
29829            "onClick":               null,
29830            "onDblClick":            null,
29831            "onContextMenu":         null,
29832            "onKeyDown":             null,
29833            "onAddNewRow":           null,
29834            "onValidationError":     null,
29835            "onViewportChanged":     null,
29836            "onSelectedRowsChanged": null,
29837            "onColumnsReordered":    null,
29838            "onColumnsResized":      null,
29839            "onBeforeMoveRows":      null,
29840            "onMoveRows":            null,
29841            "onCellChange":          null,
29842            "onBeforeEditCell":      null,
29843            "onBeforeCellEditorDestroy":    null,
29844            "onBeforeDestroy":       null,
29845            "onCurrentCellChanged":  null,
29846            "onCurrentCellPositionChanged":  null,
29847            "onCellRangeSelected":   null,
29848
29849            // Methods
29850            "getColumns":          getColumns,
29851            "setColumns":          setColumns,
29852            "getOptions":          getOptions,
29853            "setOptions":          setOptions,
29854            "getData":             getData,
29855            "setData":             setData,
29856            "destroy":             destroy,
29857            "getColumnIndex":      getColumnIndex,
29858            "autosizeColumns":     autosizeColumns,
29859            "updateCell":          updateCell,
29860            "updateRow":           updateRow,
29861            "removeRow":           removeRow,
29862            "removeRows":          removeRows,
29863            "removeAllRows":       removeAllRows,
29864            "render":              render,
29865            "invalidate":          invalidate,
29866            "setHighlightedCells": setHighlightedCells,
29867            "flashCell":           flashCell,
29868            "getViewport":         getVisibleRange,
29869            "resizeCanvas":        resizeCanvas,
29870            "updateRowCount":      updateRowCount,
29871            "getCellFromPoint":    getCellFromPoint,
29872            "getCellFromEvent":    getCellFromEvent,
29873            "getCurrentCell":      getCurrentCell,
29874            "getCurrentCellNode":  getCurrentCellNode,
29875            "resetCurrentCell":    resetCurrentCell,
29876            "navigatePrev":        navigatePrev,
29877            "navigateNext":        navigateNext,
29878            "navigateUp":          navigateUp,
29879            "navigateDown":        navigateDown,
29880            "navigateLeft":        navigateLeft,
29881            "navigateRight":       navigateRight,
29882            "gotoCell":            gotoCell,
29883            "editCurrentCell":     makeSelectedCellEditable,
29884            "getCellEditor":       getCellEditor,
29885            "scrollRowIntoView":   scrollRowIntoView,
29886            "getSelectedRows":     getSelectedRows,
29887            "setSelectedRows":     setSelectedRows,
29888            "getSecondaryHeaderRow":    getSecondaryHeaderRow,
29889            "showSecondaryHeaderRow":   showSecondaryHeaderRow,
29890            "hideSecondaryHeaderRow":   hideSecondaryHeaderRow,
29891            "setSortColumn":       setSortColumn,
29892            "getCurrentCellPosition" : getCurrentCellPosition,
29893            "getGridPosition": getGridPosition,
29894
29895            // IEditor implementation
29896            "getEditController":    getEditController
29897        });
29898    }
29899
29900    // Slick.Grid
29901    $.extend(true, window, {
29902        Slick: {
29903            Grid: SlickGrid,
29904            EditorLock: EditorLock,
29905            GlobalEditorLock: new EditorLock()
29906        }
29907    });
29908}(jQuery));
29909/***
29910 * A simple observer pattern implementation.
29911 */
29912
29913function EventHelper() {
29914    this.handlers = [];
29915
29916    this.subscribe = function(fn) {
29917        this.handlers.push(fn);
29918    };
29919
29920    this.notify = function(args) {
29921        for (var i = 0; i < this.handlers.length; i++) {
29922            this.handlers[i].call(this, args);
29923        }
29924    };
29925
29926    return this;
29927}
29928
29929
29930(function($) {
29931    /***
29932     * A sample Model implementation.
29933     * Provides a filtered view of the underlying data.
29934     *
29935     * Relies on the data item having an "id" property uniquely identifying it.
29936     */
29937    function DataView() {
29938        var self = this;
29939
29940        // private
29941        var idProperty = "id";  // property holding a unique row id
29942        var items = [];			// data by index
29943        var rows = [];			// data by row
29944        var idxById = {};		// indexes by id
29945        var rowsById = null;	// rows by id; lazy-calculated
29946        var filter = null;		// filter function
29947        var updated = null; 	// updated item ids
29948        var suspend = false;	// suspends the recalculation
29949        var sortAsc = true;
29950        var sortComparer = null;
29951        var fastSortField = null;
29952
29953        var pagesize = 0;
29954        var pagenum = 0;
29955        var totalRows = 0;
29956
29957        // events
29958        var onRowCountChanged = new EventHelper();
29959        var onRowsChanged = new EventHelper();
29960        var onPagingInfoChanged = new EventHelper();
29961
29962
29963        function beginUpdate() {
29964            suspend = true;
29965        }
29966
29967        function endUpdate() {
29968            suspend = false;
29969            refresh();
29970        }
29971
29972        function refreshIdxById() {
29973            idxById = {};
29974            for (var i = 0,l = items.length; i < l; i++) {
29975                var id = items[i][idProperty];
29976                if (id == undefined || idxById[id] != undefined)
29977                    throw "Each data element must implement a unique 'id' property";
29978                idxById[id] = i;
29979            }
29980        }
29981
29982        function getItems() {
29983            return items;
29984        }
29985
29986        function setItems(data, objectIdProperty) {
29987            if (objectIdProperty !== undefined) idProperty = objectIdProperty;
29988            items = data;
29989            refreshIdxById();
29990            refresh();
29991        }
29992
29993        function setPagingOptions(args) {
29994            if (args.pageSize != undefined)
29995                pagesize = args.pageSize;
29996
29997            if (args.pageNum != undefined)
29998                pagenum = Math.min(args.pageNum, Math.ceil(totalRows / pagesize));
29999
30000            onPagingInfoChanged.notify(getPagingInfo());
30001
30002            refresh();
30003        }
30004
30005        function getPagingInfo() {
30006            return {pageSize:pagesize, pageNum:pagenum, totalRows:totalRows};
30007        }
30008
30009        function sort(comparer, ascending) {
30010            sortAsc = ascending;
30011            sortComparer = comparer;
30012            fastSortField = null;
30013            if (ascending === false) items.reverse();
30014            items.sort(comparer);
30015            if (ascending === false) items.reverse();
30016            refreshIdxById();
30017            refresh();
30018        }
30019
30020        /***
30021         * Provides a workaround for the extremely slow sorting in IE.
30022         * Does a [lexicographic] sort on a give column by temporarily overriding Object.prototype.toString
30023         * to return the value of that field and then doing a native Array.sort().
30024         */
30025        function fastSort(field, ascending) {
30026            sortAsc = ascending;
30027            fastSortField = field;
30028            sortComparer = null;
30029            var oldToString = Object.prototype.toString;
30030            Object.prototype.toString = (typeof field == "function")?field:function() { return this[field] };
30031            // an extra reversal for descending sort keeps the sort stable
30032            // (assuming a stable native sort implementation, which isn't true in some cases)
30033            if (ascending === false) items.reverse();
30034            items.sort();
30035            Object.prototype.toString = oldToString;
30036            if (ascending === false) items.reverse();
30037            refreshIdxById();
30038            refresh();
30039        }
30040
30041        function reSort() {
30042            if (sortComparer)
30043                sort(sortComparer,sortAsc);
30044            else if (fastSortField)
30045                fastSort(fastSortField,sortAsc);
30046        }
30047
30048        function setFilter(filterFn) {
30049            filter = filterFn;
30050            refresh();
30051        }
30052
30053        function getItemByIdx(i) {
30054            return items[i];
30055        }
30056
30057        function getIdxById(id) {
30058            return idxById[id];
30059        }
30060
30061        // calculate the lookup table on first call
30062        function getRowById(id) {
30063            if (!rowsById) {
30064                rowsById = {};
30065                for (var i = 0, l = rows.length; i < l; ++i) {
30066                    rowsById[rows[i][idProperty]] = i;
30067                }
30068            }
30069
30070            return rowsById[id];
30071        }
30072
30073        function getItemById(id) {
30074            return items[idxById[id]];
30075        }
30076
30077        function updateItem(id, item) {
30078            if (idxById[id] === undefined || id !== item[idProperty])
30079                throw "Invalid or non-matching id";
30080            items[idxById[id]] = item;
30081            if (!updated) updated = {};
30082            updated[id] = true;
30083            refresh();
30084        }
30085
30086        function insertItem(insertBefore, item) {
30087            items.splice(insertBefore, 0, item);
30088            refreshIdxById();  // TODO:  optimize
30089            refresh();
30090        }
30091
30092        function addItem(item) {
30093            items.push(item);
30094            refreshIdxById();  // TODO:  optimize
30095            refresh();
30096        }
30097
30098        function deleteItem(id) {
30099            if (idxById[id] === undefined)
30100                throw "Invalid id";
30101            items.splice(idxById[id], 1);
30102            refreshIdxById();  // TODO:  optimize
30103            refresh();
30104        }
30105
30106        function recalc(_items, _rows, _filter, _updated) {
30107            var diff = [];
30108            var items = _items, rows = _rows, filter = _filter, updated = _updated; // cache as local vars
30109
30110            rowsById = null;
30111
30112            // go over all items remapping them to rows on the fly
30113            // while keeping track of the differences and updating indexes
30114            var rl = rows.length;
30115            var currentRowIndex = 0;
30116            var currentPageIndex = 0;
30117            var item,id;
30118
30119            for (var i = 0, il = items.length; i < il; ++i) {
30120                item = items[i];
30121
30122                if (!filter || filter(item)) {
30123                    id = item[idProperty];
30124
30125                    if (!pagesize || (currentRowIndex >= pagesize * pagenum && currentRowIndex < pagesize * (pagenum + 1))) {
30126                        if (currentPageIndex >= rl || id != rows[currentPageIndex][idProperty] || (updated && updated[id])) {
30127                            diff[diff.length] = currentPageIndex;
30128                            rows[currentPageIndex] = item;
30129                        }
30130
30131                        currentPageIndex++;
30132                    }
30133
30134                    currentRowIndex++;
30135                }
30136            }
30137
30138            if (rl > currentPageIndex)
30139                rows.splice(currentPageIndex, rl - currentPageIndex);
30140
30141            totalRows = currentRowIndex;
30142
30143            return diff;
30144        }
30145
30146        function refresh() {
30147            if (suspend) return;
30148
30149            var countBefore = rows.length;
30150            var totalRowsBefore = totalRows;
30151
30152            var diff = recalc(items, rows, filter, updated); // pass as direct refs to avoid closure perf hit
30153
30154            // if the current page is no longer valid, go to last page and recalc
30155            // we suffer a performance penalty here, but the main loop (recalc) remains highly optimized
30156            if (pagesize && totalRows < pagenum * pagesize) {
30157                pagenum = Math.floor(totalRows / pagesize);
30158                diff = recalc(items, rows, filter, updated);
30159            }
30160
30161            updated = null;
30162
30163            if (totalRowsBefore != totalRows) onPagingInfoChanged.notify(getPagingInfo());
30164            if (countBefore != rows.length) onRowCountChanged.notify({previous:countBefore, current:rows.length});
30165            if (diff.length > 0) onRowsChanged.notify(diff);
30166        }
30167
30168
30169        return {
30170            // properties
30171            "rows":             rows,  // note: neither the array or the data in it should be modified directly
30172
30173            // methods
30174            "beginUpdate":      beginUpdate,
30175            "endUpdate":        endUpdate,
30176            "setPagingOptions": setPagingOptions,
30177            "getPagingInfo":    getPagingInfo,
30178            "getItems":         getItems,
30179            "setItems":         setItems,
30180            "setFilter":        setFilter,
30181            "sort":             sort,
30182            "fastSort":         fastSort,
30183            "reSort":           reSort,
30184            "getIdxById":       getIdxById,
30185            "getRowById":       getRowById,
30186            "getItemById":      getItemById,
30187            "getItemByIdx":     getItemByIdx,
30188            "refresh":          refresh,
30189            "updateItem":       updateItem,
30190            "insertItem":       insertItem,
30191            "addItem":          addItem,
30192            "deleteItem":       deleteItem,
30193
30194            // events
30195            "onRowCountChanged":    onRowCountChanged,
30196            "onRowsChanged":        onRowsChanged,
30197            "onPagingInfoChanged":  onPagingInfoChanged
30198        };
30199    }
30200
30201    // Slick.Data.DataView
30202    $.extend(true, window, { Slick: { Data: { DataView: DataView }}});
30203})(jQuery);
30204(function($) {
30205    function SlickGridPager(dataView, grid, $container)
30206    {
30207        var $status, $contextMenu;
30208
30209        function init()
30210        {
30211            dataView.onPagingInfoChanged.subscribe(function(pagingInfo) {
30212                updatePager(pagingInfo);
30213            });
30214
30215            constructPagerUI();
30216            updatePager(dataView.getPagingInfo());
30217        }
30218
30219		function getNavState()
30220		{
30221			var cannotLeaveEditMode = !Slick.GlobalEditorLock.commitCurrentEdit();
30222			var pagingInfo = dataView.getPagingInfo();
30223			var lastPage = Math.floor(pagingInfo.totalRows/pagingInfo.pageSize);
30224
30225            return {
30226                canGotoFirst:	!cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum > 0,
30227                canGotoLast:	!cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum != lastPage,
30228                canGotoPrev:	!cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum > 0,
30229                canGotoNext:	!cannotLeaveEditMode && pagingInfo.pageSize != 0 && pagingInfo.pageNum < lastPage,
30230                pagingInfo:		pagingInfo,
30231                lastPage:		lastPage
30232            }
30233        }
30234
30235        function setPageSize(n)
30236        {
30237            dataView.setPagingOptions({pageSize:n});
30238        }
30239
30240        function gotoFirst()
30241        {
30242            if (getNavState().canGotoFirst)
30243                dataView.setPagingOptions({pageNum: 0});
30244        }
30245
30246        function gotoLast()
30247        {
30248            var state = getNavState();
30249            if (state.canGotoLast)
30250                dataView.setPagingOptions({pageNum: state.lastPage});
30251        }
30252
30253        function gotoPrev()
30254        {
30255            var state = getNavState();
30256            if (state.canGotoPrev)
30257                dataView.setPagingOptions({pageNum: state.pagingInfo.pageNum-1});
30258        }
30259
30260        function gotoNext()
30261        {
30262            var state = getNavState();
30263            if (state.canGotoNext)
30264                dataView.setPagingOptions({pageNum: state.pagingInfo.pageNum+1});
30265        }
30266
30267        function constructPagerUI()
30268        {
30269            $container.empty();
30270
30271
30272            var $nav = $("<span class='slick-pager-nav' />").appendTo($container);
30273            var $settings = $("<span class='slick-pager-settings' />").appendTo($container);
30274            $status = $("<span class='slick-pager-status' />").appendTo($container);
30275
30276            $settings
30277                    .append("<span class='slick-pager-settings-expanded' style='display:none'>Show: <a data='-1'>Auto</a><a data=25>25</a><a data=50>50</a><a data=100>100</a></span>");
30278
30279            $settings.find("a[data]").click(function(e) {
30280                var pagesize = $(e.target).attr("data");
30281                if (pagesize != undefined)
30282                {
30283                    if (pagesize == -1)
30284                    {
30285                        var vp = grid.getViewport();
30286                        setPageSize((vp.bottom-vp.top) - 1);
30287                    }
30288                    else
30289                        setPageSize(parseInt(pagesize));
30290                }
30291            });
30292
30293            var icon_prefix = "<span class='ui-state-default ui-corner-all ui-icon-container'><span class='ui-icon ";
30294            var icon_suffix = "' /></span>";
30295
30296            $(icon_prefix + "ui-icon-lightbulb" + icon_suffix)
30297                    .click(function() { $(".slick-pager-settings-expanded").toggle() })
30298                    .appendTo($settings);
30299
30300            $(icon_prefix + "ui-icon-seek-first" + icon_suffix)
30301                    .click(gotoFirst)
30302                    .appendTo($nav);
30303
30304            $(icon_prefix + "ui-icon-seek-prev" + icon_suffix)
30305                    .click(gotoPrev)
30306                    .appendTo($nav);
30307
30308            $(icon_prefix + "ui-icon-seek-next" + icon_suffix)
30309                    .click(gotoNext)
30310                    .appendTo($nav);
30311
30312            $(icon_prefix + "ui-icon-seek-end" + icon_suffix)
30313                    .click(gotoLast)
30314                    .appendTo($nav);
30315
30316            $container.find(".ui-icon-container")
30317                    .hover(function() {
30318                        $(this).toggleClass("ui-state-hover");
30319                    });
30320
30321            $container.children().wrapAll("<div class='slick-pager' />");
30322        }
30323
30324
30325        function updatePager(pagingInfo)
30326        {
30327            var state = getNavState();
30328
30329            $container.find(".slick-pager-nav span").removeClass("ui-state-disabled");
30330            if (!state.canGotoFirst) $container.find(".ui-icon-seek-first").addClass("ui-state-disabled");
30331            if (!state.canGotoLast) $container.find(".ui-icon-seek-end").addClass("ui-state-disabled");
30332            if (!state.canGotoNext) $container.find(".ui-icon-seek-next").addClass("ui-state-disabled");
30333            if (!state.canGotoPrev) $container.find(".ui-icon-seek-prev").addClass("ui-state-disabled");
30334
30335
30336            if (pagingInfo.showAll)
30337                $status.text("Showing all " + pagingInfo.totalRows + " rows");
30338            else
30339                $status.text("Showing page " + (pagingInfo.pageNum+1) + " of " + (Math.floor(pagingInfo.totalRows/pagingInfo.pageSize)+1));
30340        }
30341
30342
30343
30344        init();
30345    }
30346
30347    // Slick.Controls.Pager
30348    $.extend(true, window, { Slick: { Controls: { Pager: SlickGridPager }}});
30349})(jQuery);
30350
30351
30352
30353/*!
30354 * Amplify Store - Persistent Client-Side Storage @VERSION
30355 *
30356 * Copyright 2011 appendTo LLC. (http://appendto.com/team)
30357 * Dual licensed under the MIT or GPL licenses.
30358 * http://appendto.com/open-source-licenses
30359 *
30360 * http://amplifyjs.com
30361 */
30362
30363(function( amplify, undefined ) {
30364
30365// MooTools Compatibility
30366JSON.stringify = JSON.stringify||JSON.encode;
30367JSON.parse = JSON.parse||JSON.decode;
30368
30369var store = amplify.store = function( key, value, options, type ) {
30370	var type = store.type;
30371	if ( options && options.type && options.type in store.types ) {
30372		type = options.type;
30373	}
30374	return store.types[ type ]( key, value, options || {} );
30375};
30376
30377store.types = {};
30378store.type = null;
30379store.addType = function( type, storage ) {
30380	if ( !store.type ) {
30381		store.type = type;
30382	}
30383
30384	store.types[ type ] = storage;
30385	store[ type ] = function( key, value, options ) {
30386		options = options || {};
30387		options.type = type;
30388		return store( key, value, options );
30389	};
30390}
30391store.error = function() {
30392	return "amplify.store quota exceeded";
30393};
30394
30395function createSimpleStorage( storageType, storage ) {
30396	var values = storage.__amplify__ ? JSON.parse( storage.__amplify__ ) : {};
30397	store.addType( storageType, function( key, value, options ) {
30398		var ret = value,
30399			now = (new Date()).getTime(),
30400			storedValue,
30401			parsed;
30402
30403		if ( !key ) {
30404			ret = {};
30405			for ( key in values ) {
30406				storedValue = storage[ key ];
30407				parsed = storedValue ? JSON.parse( storedValue ) : { expires: -1 };
30408				if ( parsed.expires && parsed.expires <= now ) {
30409					delete storage[ key ];
30410					delete values[ key ];
30411				} else {
30412					ret[ key.replace( /^__amplify__/, "" ) ] = parsed.data;
30413				}
30414			}
30415			storage.__amplify__ = JSON.stringify( values );
30416			return ret;
30417		}
30418
30419		// protect against overwriting built-in properties
30420		key = "__amplify__" + key;
30421
30422		if ( value === undefined ) {
30423			if ( values[ key ] ) {
30424				storedValue = storage[ key ];
30425				parsed = storedValue ? JSON.parse( storedValue ) : { expires: -1 };
30426				if ( parsed.expires && parsed.expires <= now ) {
30427					delete storage[ key ];
30428					delete values[ key ];
30429				} else {
30430					return parsed.data;
30431				}
30432			}
30433		} else {
30434			if ( value === null ) {
30435				delete storage[ key ];
30436				delete values[ key ];
30437			} else {
30438				parsed = JSON.stringify({
30439					data: value,
30440					expires: options.expires ? now + options.expires : null
30441				});
30442				try {
30443					storage[ key ] = parsed;
30444					values[ key ] = true;
30445				// quota exceeded
30446				} catch( error ) {
30447					// expire old data and try again
30448					store[ storageType ]();
30449					try {
30450						storage[ key ] = parsed;
30451						values[ key ] = true;
30452					} catch( error ) {
30453						throw store.error();
30454					}
30455				}
30456			}
30457		}
30458
30459		storage.__amplify__ = JSON.stringify( values );
30460		return ret;
30461	});
30462}
30463
30464// localStorage + sessionStorage
30465// IE 8+, Firefox 3.5+, Safari 4+, Chrome 4+, Opera 10.5+, iPhone 2+, Android 2+
30466for ( var webStorageType in { localStorage: 1, sessionStorage: 1 } ) {
30467	// try/catch for file protocol in Firefox
30468	try {
30469		if ( window[ webStorageType ].getItem ) {
30470			createSimpleStorage( webStorageType, window[ webStorageType ] );
30471		}
30472	} catch( e ) {}
30473}
30474
30475// globalStorage
30476// non-standard: Firefox 2+
30477// https://developer.mozilla.org/en/dom/storage#globalStorage
30478if ( window.globalStorage ) {
30479	createSimpleStorage( "globalStorage",
30480		window.globalStorage[ window.location.hostname ] );
30481	// Firefox 2.0 and 3.0 have sessionStorage and globalStorage
30482	// make sure we defualt to globalStorage
30483	// but don't default to globalStorage in 3.5+ which also has localStorage
30484	if ( store.type === "sessionStorage" ) {
30485		store.type = "globalStorage";
30486	}
30487}
30488
30489// userData
30490// non-standard: IE 5+
30491// http://msdn.microsoft.com/en-us/library/ms531424(v=vs.85).aspx
30492(function() {
30493	// append to html instead of body so we can do this from the head
30494	var div = document.createElement( "div" ),
30495		attrKey = "amplify",
30496		attrs;
30497	div.style.display = "none";
30498	document.getElementsByTagName( "head" )[ 0 ].appendChild( div );
30499	if ( div.addBehavior ) {
30500		div.addBehavior( "#default#userdata" );
30501		div.load( attrKey );
30502		attrs = div.getAttribute( attrKey ) ? JSON.parse( div.getAttribute( attrKey ) ) : {};
30503
30504		store.addType( "userData", function( key, value, options ) {
30505			var ret = value,
30506				now = (new Date()).getTime(),
30507				attr,
30508				parsed,
30509				prevValue;
30510
30511			if ( !key ) {
30512				ret = {};
30513				for ( key in attrs ) {
30514					attr = div.getAttribute( key );
30515					parsed = attr ? JSON.parse( attr ) : { expires: -1 };
30516					if ( parsed.expires && parsed.expires <= now ) {
30517						div.removeAttribute( key );
30518						delete attrs[ key ];
30519					} else {
30520						ret[ key ] = parsed.data;
30521					}
30522				}
30523				div.setAttribute( attrKey, JSON.stringify( attrs ) );
30524				div.save( attrKey );
30525				return ret;
30526			}
30527
30528			// convert invalid characters to dashes
30529			// http://www.w3.org/TR/REC-xml/#NT-Name
30530			// simplified to assume the starting character is valid
30531			// also removed colon as it is invalid in HTML attribute names
30532			key = key.replace( /[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u37f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/g, "-" );
30533
30534			if ( value === undefined ) {
30535				if ( key in attrs ) {
30536					attr = div.getAttribute( key );
30537					parsed = attr ? JSON.parse( attr ) : { expires: -1 };
30538					if ( parsed.expires && parsed.expires <= now ) {
30539						div.removeAttribute( key );
30540						delete attrs[ key ];
30541					} else {
30542						return parsed.data;
30543					}
30544				}
30545			} else {
30546				if ( value === null ) {
30547					div.removeAttribute( key );
30548					delete attrs[ key ];
30549				} else {
30550					// we need to get the previous value in case we need to rollback
30551					prevValue = div.getAttribute( key );
30552					parsed = JSON.stringify({
30553						data: value,
30554						expires: (options.expires ? (now + options.expires) : null)
30555					});
30556					div.setAttribute( key, parsed );
30557					attrs[ key ] = true;
30558				}
30559			}
30560
30561			div.setAttribute( attrKey, JSON.stringify( attrs ) );
30562			try {
30563				div.save( attrKey );
30564			// quota exceeded
30565			} catch ( error ) {
30566				// roll the value back to the previous value
30567				if ( prevValue === null ) {
30568					div.removeAttribute( key );
30569					delete attrs[ key ];
30570				} else {
30571					div.setAttribute( key, prevValue );
30572				}
30573
30574				// expire old data and try again
30575				store.userData();
30576				try {
30577					div.setAttribute( key, parsed );
30578					attrs[ key ] = true;
30579					div.save( attrKey );
30580				} catch ( error ) {
30581					// roll the value back to the previous value
30582					if ( prevValue === null ) {
30583						div.removeAttribute( key );
30584						delete attrs[ key ];
30585					} else {
30586						div.setAttribute( key, prevValue );
30587					}
30588					throw store.error();
30589				}
30590			}
30591			return ret;
30592		});
30593	}
30594}() );
30595
30596// in-memory storage
30597// fallback for all browsers to enable the API even if we can't persist data
30598createSimpleStorage( "memory", {} );
30599
30600}( this.amplify = this.amplify || {} ) );
30601/**
30602 * History.js jQuery Adapter
30603 * @author Benjamin Arthur Lupton <[email protected]>
30604 * @copyright 2010-2011 Benjamin Arthur Lupton <[email protected]>
30605 * @license New BSD License <http://creativecommons.org/licenses/BSD/>
30606 */
30607
30608// Closure
30609(function(window,undefined){
30610	// Localise Globals
30611	var
30612		History = window.History = window.History||{},
30613		jQuery = window.jQuery;
30614
30615	// Check Existence
30616	if ( typeof History.Adapter !== 'undefined' ) {
30617		throw new Error('History.js Adapter has already been loaded...');
30618	}
30619
30620	// Add the Adapter
30621	History.Adapter = {
30622		/**
30623		 * History.Adapter.bind(el,event,callback)
30624		 * @param {Element|Selector} el
30625		 * @param {String} event - custom and standard events
30626		 * @param {Function} callback
30627		 * @return
30628		 */
30629		bind: function(el,event,callback){
30630			jQuery(el).bind(event,callback);
30631		},
30632
30633		/**
30634		 * History.Adapter.trigger(el,event)
30635		 * @param {Element|Selector} el
30636		 * @param {String} event - custom and standard events
30637		 * @return
30638		 */
30639		trigger: function(el,event){
30640			jQuery(el).trigger(event);
30641		},
30642
30643		/**
30644		 * History.Adapter.trigger(el,event,data)
30645		 * @param {Function} callback
30646		 * @return
30647		 */
30648		onDomLoad: function(callback) {
30649			jQuery(callback);
30650		}
30651	};
30652
30653	// Try and Initialise History
30654	if ( typeof History.init !== 'undefined' ) {
30655		History.init();
30656	}
30657
30658})(window);
30659/**
30660 * History.js Core
30661 * @author Benjamin Arthur Lupton <[email protected]>
30662 * @copyright 2010-2011 Benjamin Arthur Lupton <[email protected]>
30663 * @license New BSD License <http://creativecommons.org/licenses/BSD/>
30664 */
30665
30666
30667(function(window,undefined){
30668	"use strict";
30669
30670	// --------------------------------------------------------------------------
30671	// Initialise
30672
30673	// Localise Globals
30674	var
30675		console = window.console||undefined, // Prevent a JSLint complain
30676		document = window.document, // Make sure we are using the correct document
30677		navigator = window.navigator, // Make sure we are using the correct navigator
30678		amplify = window.amplify||false, // Amplify.js
30679		setTimeout = window.setTimeout,
30680		clearTimeout = window.clearTimeout,
30681		setInterval = window.setInterval,
30682		clearInterval = window.clearInterval,
30683		JSON = window.JSON,
30684		History = window.History = window.History||{}, // Public History Object
30685		history = window.history; // Old History Object
30686
30687	// MooTools Compatibility
30688	JSON.stringify = JSON.stringify||JSON.encode;
30689	JSON.parse = JSON.parse||JSON.decode;
30690
30691	// Check Existence
30692	if ( typeof History.init !== 'undefined' ) {
30693		throw new Error('History.js Core has already been loaded...');
30694	}
30695
30696	// Initialise History
30697	History.init = function(){
vendor: 14,162 bytes, lines 30698-31222
30698		// Check Load Status of Adapter
30699		if ( typeof History.Adapter === 'undefined' ) {
30700			return false;
30701		}
30702
30703		// Check Load Status of Core
30704		if ( typeof History.initCore !== 'undefined' ) {
30705			History.initCore();
30706		}
30707
30708		// Check Load Status of HTML4 Support
30709		if ( typeof History.initHtml4 !== 'undefined' ) {
30710			History.initHtml4();
30711		}
30712
30713		// Return true
30714		return true;
30715	};
30716
30717	// --------------------------------------------------------------------------
30718	// Initialise Core
30719
30720	// Initialise Core
30721	History.initCore = function(){
30722		// Initialise
30723		if ( typeof History.initCore.initialized !== 'undefined' ) {
30724			// Already Loaded
30725			return false;
30726		}
30727		else {
30728			History.initCore.initialized = true;
30729		}
30730
30731		// ----------------------------------------------------------------------
30732		// Options
30733
30734		/**
30735		 * History.options
30736		 * Configurable options
30737		 */
30738		History.options = History.options||{};
30739
30740		/**
30741		 * History.options.hashChangeInterval
30742		 * How long should the interval be before hashchange checks
30743		 */
30744		History.options.hashChangeInterval = History.options.hashChangeInterval || 100;
30745
30746		/**
30747		 * History.options.safariPollInterval
30748		 * How long should the interval be before safari poll checks
30749		 */
30750		History.options.safariPollInterval = History.options.safariPollInterval || 500;
30751
30752		/**
30753		 * History.options.doubleCheckInterval
30754		 * How long should the interval be before we perform a double check
30755		 */
30756		History.options.doubleCheckInterval = History.options.doubleCheckInterval || 500;
30757
30758		/**
30759		 * History.options.storeInterval
30760		 * How long should we wait between store calls
30761		 */
30762		History.options.storeInterval = History.options.storeInterval || 1000;
30763
30764		/**
30765		 * History.options.busyDelay
30766		 * How long should we wait between busy events
30767		 */
30768		History.options.busyDelay = History.options.busyDelay || 250;
30769
30770		/**
30771		 * History.options.debug
30772		 * If true will enable debug messages to be logged
30773		 */
30774		History.options.debug = History.options.debug || false;
30775
30776		/**
30777		 * History.options.initialTitle
30778		 * What is the title of the initial state
30779		 */
30780		History.options.initialTitle = History.options.initialTitle || document.title;
30781
30782
30783		// ----------------------------------------------------------------------
30784		// Interval record
30785
30786		/**
30787		 * History.intervalList
30788		 * List of intervals set, to be cleared when document is unloaded.
30789		 */
30790		History.intervalList = [];
30791
30792		/**
30793		 * History.clearAllIntervals
30794		 * Clears all setInterval instances.
30795		 */
30796		History.clearAllIntervals = function(){
30797			var i, il = History.intervalList;
30798			if (typeof il !== "undefined" && il !== null) {
30799				for (i = 0; i < il.length; i++) {
30800					clearInterval(il[i]);
30801				}
30802				History.intervalList = null;
30803			}
30804		};
30805		History.Adapter.bind(window,"beforeunload",History.clearAllIntervals);
30806		History.Adapter.bind(window,"unload",History.clearAllIntervals);
30807
30808
30809		// ----------------------------------------------------------------------
30810		// Debug
30811
30812		/**
30813		 * History.debug(message,...)
30814		 * Logs the passed arguments if debug enabled
30815		 */
30816		History.debug = function(){
30817			if ( (History.options.debug||false) ) {
30818				History.log.apply(History,arguments);
30819			}
30820		};
30821
30822		/**
30823		 * History.log(message,...)
30824		 * Logs the passed arguments
30825		 */
30826		History.log = function(){
30827			// Prepare
30828			var
30829				consoleExists = !(typeof console === 'undefined' || typeof console.log === 'undefined' || typeof console.log.apply === 'undefined'),
30830				textarea = document.getElementById('log'),
30831				message,
30832				i,n
30833				;
30834
30835			// Write to Console
30836			if ( consoleExists ) {
30837				var args = Array.prototype.slice.call(arguments);
30838				message = args.shift();
30839				if ( typeof console.debug !== 'undefined' ) {
30840					console.debug.apply(console,[message,args]);
30841				}
30842				else {
30843					console.log.apply(console,[message,args]);
30844				}
30845			}
30846			else {
30847				message = ("\n"+arguments[0]+"\n");
30848			}
30849
30850			// Write to log
30851			for ( i=1,n=arguments.length; i<n; ++i ) {
30852				var arg = arguments[i];
30853				if ( typeof arg === 'object' && typeof JSON !== 'undefined' ) {
30854					try {
30855						arg = JSON.stringify(arg);
30856					}
30857					catch ( Exception ) {
30858						// Recursive Object
30859					}
30860				}
30861				message += "\n"+arg+"\n";
30862			}
30863
30864			// Textarea
30865			if ( textarea ) {
30866				textarea.value += message+"\n-----\n";
30867				textarea.scrollTop = textarea.scrollHeight - textarea.clientHeight;
30868			}
30869			// No Textarea, No Console
30870			else if ( !consoleExists ) {
30871				alert(message);
30872			}
30873
30874			// Return true
30875			return true;
30876		};
30877
30878		// ----------------------------------------------------------------------
30879		// Emulated Status
30880
30881		/**
30882		 * History.getInternetExplorerMajorVersion()
30883		 * Get's the major version of Internet Explorer
30884		 * @return {integer}
30885		 * @license Public Domain
30886		 * @author Benjamin Arthur Lupton <[email protected]>
30887		 * @author James Padolsey <https://gist.github.com/527683>
30888		 */
30889		History.getInternetExplorerMajorVersion = function(){
30890			var result = History.getInternetExplorerMajorVersion.cached =
30891					(typeof History.getInternetExplorerMajorVersion.cached !== 'undefined')
30892				?	History.getInternetExplorerMajorVersion.cached
30893				:	(function(){
30894						var v = 3,
30895								div = document.createElement('div'),
30896								all = div.getElementsByTagName('i');
30897						while ( (div.innerHTML = '<!--[if gt IE ' + (++v) + ']><i></i><![endif]-->') && all[0] ) {}
30898						return (v > 4) ? v : false;
30899					})()
30900				;
30901			return result;
30902		};
30903
30904		/**
30905		 * History.isInternetExplorer()
30906		 * Are we using Internet Explorer?
30907		 * @return {boolean}
30908		 * @license Public Domain
30909		 * @author Benjamin Arthur Lupton <[email protected]>
30910		 */
30911		History.isInternetExplorer = function(){
30912			var result =
30913				History.isInternetExplorer.cached =
30914				(typeof History.isInternetExplorer.cached !== 'undefined')
30915					?	History.isInternetExplorer.cached
30916					:	Boolean(History.getInternetExplorerMajorVersion())
30917				;
30918			return result;
30919		};
30920
30921		/**
30922		 * History.emulated
30923		 * Which features require emulating?
30924		 */
30925		History.emulated = {
30926			pushState: !Boolean(
30927				window.history && window.history.pushState && window.history.replaceState
30928				&& !(
30929					(/ Mobile\/([1-7][a-z]|(8([abcde]|f(1[0-8]))))/i).test(navigator.userAgent) /* disable for versions of iOS before version 4.3 (8F190) */
30930					|| (/AppleWebKit\/5([0-2]|3[0-2])/i).test(navigator.userAgent) /* disable for the mercury iOS browser, or at least older versions of the webkit engine */
30931				)
30932			),
30933			hashChange: Boolean(
30934				!(('onhashchange' in window) || ('onhashchange' in document))
30935				||
30936				(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8)
30937			)
30938		};
30939
30940		/**
30941		 * History.enabled
30942		 * Is History enabled?
30943		 */
30944		History.enabled = !History.emulated.pushState;
30945
30946		/**
30947		 * History.bugs
30948		 * Which bugs are present
30949		 */
30950		History.bugs = {
30951			/**
30952			 * Safari 5 and Safari iOS 4 fail to return to the correct state once a hash is replaced by a `replaceState` call
30953			 * https://bugs.webkit.org/show_bug.cgi?id=56249
30954			 */
30955			setHash: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
30956
30957			/**
30958			 * Safari 5 and Safari iOS 4 sometimes fail to apply the state change under busy conditions
30959			 * https://bugs.webkit.org/show_bug.cgi?id=42940
30960			 */
30961			safariPoll: Boolean(!History.emulated.pushState && navigator.vendor === 'Apple Computer, Inc.' && /AppleWebKit\/5([0-2]|3[0-3])/.test(navigator.userAgent)),
30962
30963			/**
30964			 * MSIE 6 and 7 sometimes do not apply a hash even it was told to (requiring a second call to the apply function)
30965			 */
30966			ieDoubleCheck: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 8),
30967
30968			/**
30969			 * MSIE 6 requires the entire hash to be encoded for the hashes to trigger the onHashChange event
30970			 */
30971			hashEscape: Boolean(History.isInternetExplorer() && History.getInternetExplorerMajorVersion() < 7)
30972		};
30973
30974		/**
30975		 * History.isEmptyObject(obj)
30976		 * Checks to see if the Object is Empty
30977		 * @param {Object} obj
30978		 * @return {boolean}
30979		 */
30980		History.isEmptyObject = function(obj) {
30981			for ( var name in obj ) {
30982				return false;
30983			}
30984			return true;
30985		};
30986
30987		/**
30988		 * History.cloneObject(obj)
30989		 * Clones a object
30990		 * @param {Object} obj
30991		 * @return {Object}
30992		 */
30993		History.cloneObject = function(obj) {
30994			var hash,newObj;
30995			if ( obj ) {
30996				hash = JSON.stringify(obj);
30997				newObj = JSON.parse(hash);
30998			}
30999			else {
31000				newObj = {};
31001			}
31002			return newObj;
31003		};
31004
31005		// ----------------------------------------------------------------------
31006		// URL Helpers
31007
31008		/**
31009		 * History.getRootUrl()
31010		 * Turns "http://mysite.com/dir/page.html?asd" into "http://mysite.com"
31011		 * @return {String} rootUrl
31012		 */
31013		History.getRootUrl = function(){
31014			// Create
31015			var rootUrl = document.location.protocol+'//'+(document.location.hostname||document.location.host);
31016			if ( document.location.port||false ) {
31017				rootUrl += ':'+document.location.port;
31018			}
31019			rootUrl += '/';
31020
31021			// Return
31022			return rootUrl;
31023		};
31024
31025		/**
31026		 * History.getBaseHref()
31027		 * Fetches the `href` attribute of the `<base href="...">` element if it exists
31028		 * @return {String} baseHref
31029		 */
31030		History.getBaseHref = function(){
31031			// Create
31032			var
31033				baseElements = document.getElementsByTagName('base'),
31034				baseElement = null,
31035				baseHref = '';
31036
31037			// Test for Base Element
31038			if ( baseElements.length === 1 ) {
31039				// Prepare for Base Element
31040				baseElement = baseElements[0];
31041				baseHref = baseElement.href.replace(/[^\/]+$/,'');
31042			}
31043
31044			// Adjust trailing slash
31045			baseHref = baseHref.replace(/\/+$/,'');
31046			if ( baseHref ) baseHref += '/';
31047
31048			// Return
31049			return baseHref;
31050		};
31051
31052		/**
31053		 * History.getBaseUrl()
31054		 * Fetches the baseHref or basePageUrl or rootUrl (whichever one exists first)
31055		 * @return {String} baseUrl
31056		 */
31057		History.getBaseUrl = function(){
31058			// Create
31059			var baseUrl = History.getBaseHref()||History.getBasePageUrl()||History.getRootUrl();
31060
31061			// Return
31062			return baseUrl;
31063		};
31064
31065		/**
31066		 * History.getPageUrl()
31067		 * Fetches the URL of the current page
31068		 * @return {String} pageUrl
31069		 */
31070		History.getPageUrl = function(){
31071			// Fetch
31072			var
31073				State = History.getState(false,false),
31074				stateUrl = (State||{}).url||document.location.href;
31075
31076			// Create
31077			var pageUrl = stateUrl.replace(/\/+$/,'').replace(/[^\/]+$/,function(part,index,string){
31078				return (/\./).test(part) ? part : part+'/';
31079			});
31080
31081			// Return
31082			return pageUrl;
31083		};
31084
31085		/**
31086		 * History.getBasePageUrl()
31087		 * Fetches the Url of the directory of the current page
31088		 * @return {String} basePageUrl
31089		 */
31090		History.getBasePageUrl = function(){
31091			// Create
31092			var basePageUrl = document.location.href.replace(/[#\?].*/,'').replace(/[^\/]+$/,function(part,index,string){
31093				return (/[^\/]$/).test(part) ? '' : part;
31094			}).replace(/\/+$/,'')+'/';
31095
31096			// Return
31097			return basePageUrl;
31098		};
31099
31100		/**
31101		 * History.getFullUrl(url)
31102		 * Ensures that we have an absolute URL and not a relative URL
31103		 * @param {string} url
31104		 * @param {Boolean} allowBaseHref
31105		 * @return {string} fullUrl
31106		 */
31107		History.getFullUrl = function(url,allowBaseHref){
31108			// Prepare
31109			var fullUrl = url, firstChar = url.substring(0,1);
31110			allowBaseHref = (typeof allowBaseHref === 'undefined') ? true : allowBaseHref;
31111
31112			// Check
31113			if ( /[a-z]+\:\/\//.test(url) ) {
31114				// Full URL
31115			}
31116			else if ( firstChar === '/' ) {
31117				// Root URL
31118				fullUrl = History.getRootUrl()+url.replace(/^\/+/,'');
31119			}
31120			else if ( firstChar === '#' ) {
31121				// Anchor URL
31122				fullUrl = History.getPageUrl().replace(/#.*/,'')+url;
31123			}
31124			else if ( firstChar === '?' ) {
31125				// Query URL
31126				fullUrl = History.getPageUrl().replace(/[\?#].*/,'')+url;
31127			}
31128			else {
31129				// Relative URL
31130				if ( allowBaseHref ) {
31131					fullUrl = History.getBaseUrl()+url.replace(/^(\.\/)+/,'');
31132				} else {
31133					fullUrl = History.getBasePageUrl()+url.replace(/^(\.\/)+/,'');
31134				}
31135				// We have an if condition above as we do not want hashes
31136				// which are relative to the baseHref in our URLs
31137				// as if the baseHref changes, then all our bookmarks
31138				// would now point to different locations
31139				// whereas the basePageUrl will always stay the same
31140			}
31141
31142			// Return
31143			return fullUrl.replace(/\#$/,'');
31144		};
31145
31146		/**
31147		 * History.getShortUrl(url)
31148		 * Ensures that we have a relative URL and not a absolute URL
31149		 * @param {string} url
31150		 * @return {string} url
31151		 */
31152		History.getShortUrl = function(url){
31153			// Prepare
31154			var shortUrl = url, baseUrl = History.getBaseUrl(), rootUrl = History.getRootUrl();
31155
31156			// Trim baseUrl
31157			if ( History.emulated.pushState ) {
31158				// We are in a if statement as when pushState is not emulated
31159				// The actual url these short urls are relative to can change
31160				// So within the same session, we the url may end up somewhere different
31161				shortUrl = shortUrl.replace(baseUrl,'');
31162			}
31163
31164			// Trim rootUrl
31165			shortUrl = shortUrl.replace(rootUrl,'/');
31166
31167			// Ensure we can still detect it as a state
31168			if ( History.isTraditionalAnchor(shortUrl) ) {
31169				shortUrl = './'+shortUrl;
31170			}
31171
31172			// Clean It
31173			shortUrl = shortUrl.replace(/^(\.\/)+/g,'./').replace(/\#$/,'');
31174
31175			// Return
31176			return shortUrl;
31177		};
31178
31179		// ----------------------------------------------------------------------
31180		// State Storage
31181
31182		/**
31183		 * History.store
31184		 * The store for all session specific data
31185		 */
31186		History.store = amplify ? (amplify.store('History.store')||{}) : {};
31187		History.store.idToState = History.store.idToState||{};
31188		History.store.urlToId = History.store.urlToId||{};
31189		History.store.stateToId = History.store.stateToId||{};
31190
31191		/**
31192		 * History.idToState
31193		 * 1-1: State ID to State Object
31194		 */
31195		History.idToState = History.idToState||{};
31196
31197		/**
31198		 * History.stateToId
31199		 * 1-1: State String to State ID
31200		 */
31201		History.stateToId = History.stateToId||{};
31202
31203		/**
31204		 * History.urlToId
31205		 * 1-1: State URL to State ID
31206		 */
31207		History.urlToId = History.urlToId||{};
31208
31209		/**
31210		 * History.storedStates
31211		 * Store the states in an array
31212		 */
31213		History.storedStates = History.storedStates||[];
31214
31215		/**
31216		 * History.savedStates
31217		 * Saved the states in an array
31218		 */
31219		History.savedStates = History.savedStates||[];
31220
31221		/**
31222		 * History.getState()
31223		 * Get an object containing the data, title and url of the current state
31224		 * @param {Boolean} friendly
31225		 * @param {Boolean} create
31226		 * @return {Object} State
31227		 */
31228		History.getState = function(friendly,create){
31229			// Prepare
31230			if ( typeof friendly === 'undefined' ) { friendly = true; }
31231			if ( typeof create === 'undefined' ) { create = true; }
31232
31233			// Fetch
31234			var State = History.getLastSavedState();
31235
31236			// Create
31237			if ( !State && create ) {
31238				State = History.createStateObject();
31239			}
31240
31241			// Adjust
31242			if ( friendly ) {
31243				State = History.cloneObject(State);
31244				State.url = State.cleanUrl||State.url;
31245			}
31246
31247			// Return
31248			return State;
31249		};
31250
31251		/**
31252		 * History.getIdByState(State)
31253		 * Gets a ID for a State
31254		 * @param {State} newState
31255		 * @return {String} id
31256		 */
31257		History.getIdByState = function(newState){
31258
31259			// Fetch ID
31260			var id = History.extractId(newState.url);
31261			if ( !id ) {
31262				// Find ID via State String
31263				var str = History.getStateString(newState);
31264				if ( typeof History.stateToId[str] !== 'undefined' ) {
31265					id = History.stateToId[str];
31266				}
31267				else if ( typeof History.store.stateToId[str] !== 'undefined' ) {
31268					id = History.store.stateToId[str];
31269				}
31270				else {
31271					// Generate a new ID
31272					while ( true ) {
31273						id = String(Math.floor(Math.random()*1000));
31274						if ( typeof History.idToState[id] === 'undefined' && typeof History.store.idToState[id] === 'undefined' ) {
31275							break;
31276						}
31277					}
31278
31279					// Apply the new State to the ID
31280					History.stateToId[str] = id;
31281					History.idToState[id] = newState;
31282				}
31283			}
31284
31285			// Return ID
31286			return id;
31287		};
31288
31289		/**
31290		 * History.normalizeState(State)
31291		 * Expands a State Object
31292		 * @param {object} State
31293		 * @return {object}
31294		 */
31295		History.normalizeState = function(oldState){
31296			// Prepare
31297			if ( !oldState || (typeof oldState !== 'object') ) {
31298				oldState = {};
31299			}
31300
31301			// Check
31302			if ( typeof oldState.normalized !== 'undefined' ) {
31303				return oldState;
31304			}
31305
31306			// Adjust
31307			if ( !oldState.data || (typeof oldState.data !== 'object') ) {
31308				oldState.data = {};
31309			}
31310
31311			// ----------------------------------------------------------------------
31312
31313			// Create
31314			var newState = {};
31315			newState.normalized = true;
31316			newState.title = oldState.title||'';
31317			newState.url = History.getFullUrl(History.unescapeString(oldState.url||document.location.href));
31318			newState.hash = History.getShortUrl(newState.url);
31319			newState.data = History.cloneObject(oldState.data);
31320
31321			// Fetch ID
31322			newState.id = History.getIdByState(newState);
31323
31324			// ----------------------------------------------------------------------
31325
31326			// Clean the URL
31327			newState.cleanUrl = newState.url.replace(/\??\&_suid.*/,'');
31328			newState.url = newState.cleanUrl;
31329
31330			// Check to see if we have more than just a url
31331			var dataNotEmpty = !History.isEmptyObject(newState.data);
31332
31333			// Apply
31334			if ( newState.title || dataNotEmpty ) {
31335				// Add ID to Hash
31336				newState.hash = History.getShortUrl(newState.url).replace(/\??\&_suid.*/,'');
31337				if ( !/\?/.test(newState.hash) ) {
31338					newState.hash += '?';
31339				}
31340				newState.hash += '&_suid='+newState.id;
31341			}
31342
31343			// Create the Hashed URL
31344			newState.hashedUrl = History.getFullUrl(newState.hash);
31345
31346			// ----------------------------------------------------------------------
31347
31348			// Update the URL if we have a duplicate
31349			if ( (History.emulated.pushState || History.bugs.safariPoll) && History.hasUrlDuplicate(newState) ) {
31350				newState.url = newState.hashedUrl;
31351			}
31352
31353			// ----------------------------------------------------------------------
31354
31355			// Return
31356			return newState;
31357		};
31358
31359		/**
31360		 * History.createStateObject(data,title,url)
31361		 * Creates a object based on the data, title and url state params
31362		 * @param {object} data
31363		 * @param {string} title
31364		 * @param {string} url
31365		 * @return {object}
31366		 */
31367		History.createStateObject = function(data,title,url){
31368			// Hashify
31369			var State = {
31370				'data': data,
31371				'title': title,
31372				'url': url
31373			};
31374
31375			// Expand the State
31376			State = History.normalizeState(State);
31377
31378			// Return object
31379			return State;
31380		};
31381
31382		/**
31383		 * History.getStateById(id)
31384		 * Get a state by it's UID
31385		 * @param {String} id
31386		 */
31387		History.getStateById = function(id){
31388			// Prepare
31389			id = String(id);
31390
31391			// Retrieve
31392			var State = History.idToState[id] || History.store.idToState[id] || undefined;
31393
31394			// Return State
31395			return State;
31396		};
31397
31398		/**
31399		 * Get a State's String
31400		 * @param {State} passedState
31401		 */
31402		History.getStateString = function(passedState){
31403			// Prepare
31404			var State = History.normalizeState(passedState);
31405
31406			// Clean
31407			var cleanedState = {
31408				data: State.data,
31409				title: passedState.title,
31410				url: passedState.url
31411			};
31412
31413			// Fetch
31414			var str = JSON.stringify(cleanedState);
31415
31416			// Return
31417			return str;
31418		};
31419
31420		/**
31421		 * Get a State's ID
31422		 * @param {State} passedState
31423		 * @return {String} id
31424		 */
31425		History.getStateId = function(passedState){
31426			// Prepare
31427			var State = History.normalizeState(passedState);
31428
31429			// Fetch
31430			var id = State.id;
31431
31432			// Return
31433			return id;
31434		};
31435
31436		/**
31437		 * History.getHashByState(State)
31438		 * Creates a Hash for the State Object
31439		 * @param {State} passedState
31440		 * @return {String} hash
31441		 */
31442		History.getHashByState = function(passedState){
31443			// Prepare
31444			var hash, State = History.normalizeState(passedState);
31445
31446			// Fetch
31447			hash = State.hash;
31448
31449			// Return
31450			return hash;
31451		};
31452
31453		/**
31454		 * History.extractId(url_or_hash)
31455		 * Get a State ID by it's URL or Hash
31456		 * @param {string} url_or_hash
31457		 * @return {string} id
31458		 */
31459		History.extractId = function ( url_or_hash ) {
31460			// Prepare
31461			var id;
31462
31463			// Extract
31464			var parts,url;
31465			parts = /(.*)\&_suid=([0-9]+)$/.exec(url_or_hash);
31466			url = parts ? (parts[1]||url_or_hash) : url_or_hash;
31467			id = parts ? String(parts[2]||'') : '';
31468
31469			// Return
31470			return id||false;
31471		};
31472
31473		/**
31474		 * History.isTraditionalAnchor
31475		 * Checks to see if the url is a traditional anchor or not
31476		 * @param {String} url_or_hash
31477		 * @return {Boolean}
31478		 */
31479		History.isTraditionalAnchor = function(url_or_hash){
31480			// Check
31481			var isTraditional = !(/[\/\?\.]/.test(url_or_hash));
31482
31483			// Return
31484			return isTraditional;
31485		};
31486
31487		/**
31488		 * History.extractState
31489		 * Get a State by it's URL or Hash
31490		 * @param {String} url_or_hash
31491		 * @return {State|null}
31492		 */
31493		History.extractState = function(url_or_hash,create){
31494			// Prepare
31495			var State = null;
31496			create = create||false;
31497
31498			// Fetch SUID
31499			var id = History.extractId(url_or_hash);
31500			if ( id ) {
31501				State = History.getStateById(id);
31502			}
31503
31504			// Fetch SUID returned no State
31505			if ( !State ) {
31506				// Fetch URL
31507				var url = History.getFullUrl(url_or_hash);
31508
31509				// Check URL
31510				id = History.getIdByUrl(url)||false;
31511				if ( id ) {
31512					State = History.getStateById(id);
31513				}
31514
31515				// Create State
31516				if ( !State && create && !History.isTraditionalAnchor(url_or_hash) ) {
31517					State = History.createStateObject(null,null,url);
31518				}
31519			}
31520
31521			// Return
31522			return State;
31523		};
31524
31525		/**
31526		 * History.getIdByUrl()
31527		 * Get a State ID by a State URL
31528		 */
31529		History.getIdByUrl = function(url){
31530			// Fetch
31531			var id = History.urlToId[url] || History.store.urlToId[url] || undefined;
31532
31533			// Return
31534			return id;
31535		};
31536
31537		/**
31538		 * History.getLastSavedState()
31539		 * Get an object containing the data, title and url of the current state
31540		 * @return {Object} State
31541		 */
31542		History.getLastSavedState = function(){
31543			return History.savedStates[History.savedStates.length-1]||undefined;
31544		};
31545
31546		/**
31547		 * History.getLastStoredState()
31548		 * Get an object containing the data, title and url of the current state
31549		 * @return {Object} State
31550		 */
31551		History.getLastStoredState = function(){
31552			return History.storedStates[History.storedStates.length-1]||undefined;
31553		};
31554
31555		/**
31556		 * History.hasUrlDuplicate
31557		 * Checks if a Url will have a url conflict
31558		 * @param {Object} newState
31559		 * @return {Boolean} hasDuplicate
31560		 */
31561		History.hasUrlDuplicate = function(newState) {
31562			// Prepare
31563			var hasDuplicate = false;
31564
31565			// Fetch
31566			var oldState = History.extractState(newState.url);
31567
31568			// Check
31569			hasDuplicate = oldState && oldState.id !== newState.id;
31570
31571			// Return
31572			return hasDuplicate;
31573		};
31574
31575		/**
31576		 * History.storeState
31577		 * Store a State
31578		 * @param {Object} newState
31579		 * @return {Object} newState
31580		 */
31581		History.storeState = function(newState){
31582			// Store the State
31583			History.urlToId[newState.url] = newState.id;
31584
31585			// Push the State
31586			History.storedStates.push(History.cloneObject(newState));
31587
31588			// Return newState
31589			return newState;
31590		};
31591
31592		/**
31593		 * History.isLastSavedState(newState)
31594		 * Tests to see if the state is the last state
31595		 * @param {Object} newState
31596		 * @return {boolean} isLast
31597		 */
31598		History.isLastSavedState = function(newState){
31599			// Prepare
31600			var isLast = false;
31601
31602			// Check
31603			if ( History.savedStates.length ) {
31604				var
31605					newId = newState.id,
31606					oldState = History.getLastSavedState(),
31607					oldId = oldState.id;
31608
31609				// Check
31610				isLast = (newId === oldId);
31611			}
31612
31613			// Return
31614			return isLast;
31615		};
31616
31617		/**
31618		 * History.saveState
31619		 * Push a State
31620		 * @param {Object} newState
31621		 * @return {boolean} changed
31622		 */
31623		History.saveState = function(newState){
31624			// Check Hash
31625			if ( History.isLastSavedState(newState) ) {
31626				return false;
31627			}
31628
31629			// Push the State
31630			History.savedStates.push(History.cloneObject(newState));
31631
31632			// Return true
31633			return true;
31634		};
31635
31636		/**
31637		 * History.getStateByIndex()
31638		 * Gets a state by the index
31639		 * @param {integer} index
31640		 * @return {Object}
31641		 */
31642		History.getStateByIndex = function(index){
31643			// Prepare
31644			var State = null;
31645
31646			// Handle
31647			if ( typeof index === 'undefined' ) {
31648				// Get the last inserted
31649				State = History.savedStates[History.savedStates.length-1];
31650			}
31651			else if ( index < 0 ) {
31652				// Get from the end
31653				State = History.savedStates[History.savedStates.length+index];
31654			}
31655			else {
31656				// Get from the beginning
31657				State = History.savedStates[index];
31658			}
31659
31660			// Return State
31661			return State;
31662		};
31663
31664		// ----------------------------------------------------------------------
31665		// Hash Helpers
31666
31667		/**
31668		 * History.getHash()
31669		 * Gets the current document hash
31670		 * @return {string}
31671		 */
31672		History.getHash = function(){
31673			var hash = History.unescapeHash(document.location.hash);
31674			return hash;
31675		};
31676
31677		/**
31678		 * History.unescapeString()
31679		 * Unescape a string
31680		 * @param {String} str
31681		 * @return {string}
31682		 */
31683		History.unescapeString = function(str){
31684			// Prepare
31685			var result = str;
31686
31687			// Unescape hash
31688			var tmp;
31689			while ( true ) {
31690				tmp = window.unescape(result);
31691				if ( tmp === result ) {
31692					break;
31693				}
31694				result = tmp;
31695			}
31696
31697			// Return result
31698			return result;
31699		};
31700
31701		/**
31702		 * History.unescapeHash()
31703		 * normalize and Unescape a Hash
31704		 * @param {String} hash
31705		 * @return {string}
31706		 */
31707		History.unescapeHash = function(hash){
31708			// Prepare
31709			var result = History.normalizeHash(hash);
31710
31711			// Unescape hash
31712			result = History.unescapeString(result);
31713
31714			// Return result
31715			return result;
31716		};
31717
31718		/**
31719		 * History.normalizeHash()
31720		 * normalize a hash across browsers
31721		 * @return {string}
31722		 */
31723		History.normalizeHash = function(hash){
31724			var result = hash.replace(/[^#]*#/,'').replace(/#.*/, '');
31725
31726			// Return result
31727			return result;
31728		};
31729
31730		/**
31731		 * History.setHash(hash)
31732		 * Sets the document hash
31733		 * @param {string} hash
31734		 * @return {History}
31735		 */
31736		History.setHash = function(hash,queue){
31737			// Handle Queueing
31738			if ( queue !== false && History.busy() ) {
31739				// Wait + Push to Queue
31740				//History.debug('History.setHash: we must wait', arguments);
31741				History.pushQueue({
31742					scope: History,
31743					callback: History.setHash,
31744					args: arguments,
31745					queue: queue
31746				});
31747				return false;
31748			}
31749
31750			// Log
31751			//History.debug('History.setHash: called',hash);
31752
31753			// Prepare
31754			var adjustedHash = History.escapeHash(hash);
31755
31756			// Make Busy + Continue
31757			History.busy(true);
31758
31759			// Check if hash is a state
31760			var State = History.extractState(hash,true);
31761			if ( State && !History.emulated.pushState ) {
31762				// Hash is a state so skip the setHash
31763				//History.debug('History.setHash: Hash is a state so skipping the hash set with a direct pushState call',arguments);
31764
31765				// PushState
31766				History.pushState(State.data,State.title,State.url,false);
31767			}
31768			else if ( document.location.hash !== adjustedHash ) {
31769				// Hash is a proper hash, so apply it
31770
31771				// Handle browser bugs
31772				if ( History.bugs.setHash ) {
31773					// Fix Safari Bug https://bugs.webkit.org/show_bug.cgi?id=56249
31774
31775					// Fetch the base page
31776					var pageUrl = History.getPageUrl();
31777
31778					// Safari hash apply
31779					History.pushState(null,null,pageUrl+'#'+adjustedHash,false);
31780				}
31781				else {
31782					// Normal hash apply
31783					document.location.hash = adjustedHash;
31784				}
31785			}
31786
31787			// Chain
31788			return History;
31789		};
31790
31791		/**
31792		 * History.escape()
31793		 * normalize and Escape a Hash
31794		 * @return {string}
31795		 */
31796		History.escapeHash = function(hash){
31797			var result = History.normalizeHash(hash);
31798
31799			// Escape hash
31800			result = window.escape(result);
31801
31802			// IE6 Escape Bug
31803			if ( !History.bugs.hashEscape ) {
31804				// Restore common parts
31805				result = result
31806					.replace(/\%21/g,'!')
31807					.replace(/\%26/g,'&')
31808					.replace(/\%3D/g,'=')
31809					.replace(/\%3F/g,'?');
31810			}
31811
31812			// Return result
31813			return result;
31814		};
31815
31816		/**
31817		 * History.getHashByUrl(url)
31818		 * Extracts the Hash from a URL
31819		 * @param {string} url
31820		 * @return {string} url
31821		 */
31822		History.getHashByUrl = function(url){
31823			// Extract the hash
31824			var hash = String(url)
31825				.replace(/([^#]*)#?([^#]*)#?(.*)/, '$2')
31826				;
31827
31828			// Unescape hash
31829			hash = History.unescapeHash(hash);
31830
31831			// Return hash
31832			return hash;
31833		};
31834
31835		/**
31836		 * History.setTitle(title)
31837		 * Applies the title to the document
31838		 * @param {State} newState
31839		 * @return {Boolean}
31840		 */
31841		History.setTitle = function(newState){
31842			// Prepare
31843			var title = newState.title;
31844
31845			// Initial
31846			if ( !title ) {
31847				var firstState = History.getStateByIndex(0);
31848				if ( firstState && firstState.url === newState.url ) {
31849					title = firstState.title||History.options.initialTitle;
31850				}
31851			}
31852
31853			// Apply
31854			try {
31855				document.getElementsByTagName('title')[0].innerHTML = title.replace('<','&lt;').replace('>','&gt;').replace(' & ',' &amp; ');
31856			}
31857			catch ( Exception ) { }
31858			document.title = title;
31859
31860			// Chain
31861			return History;
31862		};
31863
31864		// ----------------------------------------------------------------------
31865		// Queueing
31866
31867		/**
31868		 * History.queues
31869		 * The list of queues to use
31870		 * First In, First Out
31871		 */
31872		History.queues = [];
31873
31874		/**
31875		 * History.busy(value)
31876		 * @param {boolean} value [optional]
31877		 * @return {boolean} busy
31878		 */
31879		History.busy = function(value){
31880			// Apply
31881			if ( typeof value !== 'undefined' ) {
31882				//History.debug('History.busy: changing ['+(History.busy.flag||false)+'] to ['+(value||false)+']', History.queues.length);
31883				History.busy.flag = value;
31884			}
31885			// Default
31886			else if ( typeof History.busy.flag === 'undefined' ) {
31887				History.busy.flag = false;
31888			}
31889
31890			// Queue
31891			if ( !History.busy.flag ) {
31892				// Execute the next item in the queue
31893				clearTimeout(History.busy.timeout);
31894				var fireNext = function(){
31895					if ( History.busy.flag ) return;
31896					for ( var i=History.queues.length-1; i >= 0; --i ) {
31897						var queue = History.queues[i];
31898						if ( queue.length === 0 ) continue;
31899						var item = queue.shift();
31900						History.fireQueueItem(item);
31901						History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
31902					}
31903				};
31904				History.busy.timeout = setTimeout(fireNext,History.options.busyDelay);
31905			}
31906
31907			// Return
31908			return History.busy.flag;
31909		};
31910
31911		/**
31912		 * History.fireQueueItem(item)
31913		 * Fire a Queue Item
31914		 * @param {Object} item
31915		 * @return {Mixed} result
31916		 */
31917		History.fireQueueItem = function(item){
31918			return item.callback.apply(item.scope||History,item.args||[]);
31919		};
31920
31921		/**
31922		 * History.pushQueue(callback,args)
31923		 * Add an item to the queue
31924		 * @param {Object} item [scope,callback,args,queue]
31925		 */
31926		History.pushQueue = function(item){
31927			// Prepare the queue
31928			History.queues[item.queue||0] = History.queues[item.queue||0]||[];
31929
31930			// Add to the queue
31931			History.queues[item.queue||0].push(item);
31932
31933			// Chain
31934			return History;
31935		};
31936
31937		/**
31938		 * History.queue (item,queue), (func,queue), (func), (item)
31939		 * Either firs the item now if not busy, or adds it to the queue
31940		 */
31941		History.queue = function(item,queue){
31942			// Prepare
31943			if ( typeof item === 'function' ) {
31944				item = {
31945					callback: item
31946				};
31947			}
31948			if ( typeof queue !== 'undefined' ) {
31949				item.queue = queue;
31950			}
31951
31952			// Handle
31953			if ( History.busy() ) {
31954				History.pushQueue(item);
31955			} else {
31956				History.fireQueueItem(item);
31957			}
31958
31959			// Chain
31960			return History;
31961		};
31962
31963		/**
31964		 * History.clearQueue()
31965		 * Clears the Queue
31966		 */
31967		History.clearQueue = function(){
31968			History.busy.flag = false;
31969			History.queues = [];
31970			return History;
31971		};
31972
31973
31974		// ----------------------------------------------------------------------
31975		// IE Bug Fix
31976
31977		/**
31978		 * History.stateChanged
31979		 * States whether or not the state has changed since the last double check was initialised
31980		 */
31981		History.stateChanged = false;
31982
31983		/**
31984		 * History.doubleChecker
31985		 * Contains the timeout used for the double checks
31986		 */
31987		History.doubleChecker = false;
31988
31989		/**
31990		 * History.doubleCheckComplete()
31991		 * Complete a double check
31992		 * @return {History}
31993		 */
31994		History.doubleCheckComplete = function(){
31995			// Update
31996			History.stateChanged = true;
31997
31998			// Clear
31999			History.doubleCheckClear();
32000
32001			// Chain
32002			return History;
32003		};
32004
32005		/**
32006		 * History.doubleCheckClear()
32007		 * Clear a double check
32008		 * @return {History}
32009		 */
32010		History.doubleCheckClear = function(){
32011			// Clear
32012			if ( History.doubleChecker ) {
32013				clearTimeout(History.doubleChecker);
32014				History.doubleChecker = false;
32015			}
32016
32017			// Chain
32018			return History;
32019		};
32020
32021		/**
32022		 * History.doubleCheck()
32023		 * Create a double check
32024		 * @return {History}
32025		 */
32026		History.doubleCheck = function(tryAgain){
32027			// Reset
32028			History.stateChanged = false;
32029			History.doubleCheckClear();
32030
32031			// Fix IE6,IE7 bug where calling history.back or history.forward does not actually change the hash (whereas doing it manually does)
32032			// Fix Safari 5 bug where sometimes the state does not change: https://bugs.webkit.org/show_bug.cgi?id=42940
32033			if ( History.bugs.ieDoubleCheck ) {
32034				// Apply Check
32035				History.doubleChecker = setTimeout(
32036					function(){
32037						History.doubleCheckClear();
32038						if ( !History.stateChanged ) {
32039							//History.debug('History.doubleCheck: State has not yet changed, trying again', arguments);
32040							// Re-Attempt
32041							tryAgain();
32042						}
32043						return true;
32044					},
32045					History.options.doubleCheckInterval
32046				);
32047			}
32048
32049			// Chain
32050			return History;
32051		};
32052
32053		// ----------------------------------------------------------------------
32054		// Safari Bug Fix
32055
32056		/**
32057		 * History.safariStatePoll()
32058		 * Poll the current state
32059		 * @return {History}
32060		 */
32061		History.safariStatePoll = function(){
32062			// Poll the URL
32063
32064			// Get the Last State which has the new URL
32065			var
32066				urlState = History.extractState(document.location.href),
32067				newState;
32068
32069			// Check for a difference
32070			if ( !History.isLastSavedState(urlState) ) {
32071				newState = urlState;
32072			}
32073			else {
32074				return;
32075			}
32076
32077			// Check if we have a state with that url
32078			// If not create it
32079			if ( !newState ) {
32080				//History.debug('History.safariStatePoll: new');
32081				newState = History.createStateObject();
32082			}
32083
32084			// Apply the New State
32085			//History.debug('History.safariStatePoll: trigger');
32086			History.Adapter.trigger(window,'popstate');
32087
32088			// Chain
32089			return History;
32090		};
32091
32092		// ----------------------------------------------------------------------
32093		// State Aliases
32094
32095		/**
32096		 * History.back(queue)
32097		 * Send the browser history back one item
32098		 * @param {Integer} queue [optional]
32099		 */
32100		History.back = function(queue){
32101			//History.debug('History.back: called', arguments);
32102
32103			// Handle Queueing
32104			if ( queue !== false && History.busy() ) {
32105				// Wait + Push to Queue
32106				//History.debug('History.back: we must wait', arguments);
32107				History.pushQueue({
32108					scope: History,
32109					callback: History.back,
32110					args: arguments,
32111					queue: queue
32112				});
32113				return false;
32114			}
32115
32116			// Make Busy + Continue
32117			History.busy(true);
32118
32119			// Fix certain browser bugs that prevent the state from changing
32120			History.doubleCheck(function(){
32121				History.back(false);
32122			});
32123
32124			// Go back
32125			history.go(-1);
32126
32127			// End back closure
32128			return true;
32129		};
32130
32131		/**
32132		 * History.forward(queue)
32133		 * Send the browser history forward one item
32134		 * @param {Integer} queue [optional]
32135		 */
32136		History.forward = function(queue){
32137			//History.debug('History.forward: called', arguments);
32138
32139			// Handle Queueing
32140			if ( queue !== false && History.busy() ) {
32141				// Wait + Push to Queue
32142				//History.debug('History.forward: we must wait', arguments);
32143				History.pushQueue({
32144					scope: History,
32145					callback: History.forward,
32146					args: arguments,
32147					queue: queue
32148				});
32149				return false;
32150			}
32151
32152			// Make Busy + Continue
32153			History.busy(true);
32154
32155			// Fix certain browser bugs that prevent the state from changing
32156			History.doubleCheck(function(){
32157				History.forward(false);
32158			});
32159
32160			// Go forward
32161			history.go(1);
32162
32163			// End forward closure
32164			return true;
32165		};
32166
32167		/**
32168		 * History.go(index,queue)
32169		 * Send the browser history back or forward index times
32170		 * @param {Integer} queue [optional]
32171		 */
32172		History.go = function(index,queue){
32173			//History.debug('History.go: called', arguments);
32174
32175			// Prepare
32176			var i;
32177
32178			// Handle
32179			if ( index > 0 ) {
32180				// Forward
32181				for ( i=1; i<=index; ++i ) {
32182					History.forward(queue);
32183				}
32184			}
32185			else if ( index < 0 ) {
32186				// Backward
32187				for ( i=-1; i>=index; --i ) {
32188					History.back(queue);
32189				}
32190			}
32191			else {
32192				throw new Error('History.go: History.go requires a positive or negative integer passed.');
32193			}
32194
32195			// Chain
32196			return History;
32197		};
32198
32199
32200		// ----------------------------------------------------------------------
32201		// Initialise
32202
32203		/**
32204		 * Create the initial State
32205		 */
32206		History.saveState(History.storeState(History.extractState(document.location.href,true)));
32207
32208		/**
32209		 * Bind for Saving Store
32210		 */
32211		if ( amplify ) {
32212			History.onUnload = function(){
32213				// Prepare
32214				var
32215					currentStore = amplify.store('History.store')||{},
32216					item;
32217
32218				// Ensure
32219				currentStore.idToState = currentStore.idToState || {};
32220				currentStore.urlToId = currentStore.urlToId || {};
32221				currentStore.stateToId = currentStore.stateToId || {};
32222
32223				// Sync
32224				for ( item in History.idToState ) {
32225					if ( !History.idToState.hasOwnProperty(item) ) {
32226						continue;
32227					}
32228					currentStore.idToState[item] = History.idToState[item];
32229				}
32230				for ( item in History.urlToId ) {
32231					if ( !History.urlToId.hasOwnProperty(item) ) {
32232						continue;
32233					}
32234					currentStore.urlToId[item] = History.urlToId[item];
32235				}
32236				for ( item in History.stateToId ) {
32237					if ( !History.stateToId.hasOwnProperty(item) ) {
32238						continue;
32239					}
32240					currentStore.stateToId[item] = History.stateToId[item];
32241				}
32242
32243				// Update
32244				History.store = currentStore;
32245
32246				// Store
32247				amplify.store('History.store',currentStore);
32248			};
32249			// For Internet Explorer
32250			History.intervalList.push(setInterval(History.onUnload,History.options.storeInterval));
32251			// For Other Browsers
32252			History.Adapter.bind(window,'beforeunload',History.onUnload);
32253			History.Adapter.bind(window,'unload',History.onUnload);
32254			// Both are enabled for consistency
32255		}
32256
32257
32258		// ----------------------------------------------------------------------
32259		// HTML5 State Support
32260
32261		if ( History.emulated.pushState ) {
32262			/*
32263			 * Provide Skeleton for HTML4 Browsers
32264			 */
32265
32266			// Prepare
32267			var emptyFunction = function(){};
32268			History.pushState = History.pushState||emptyFunction;
32269			History.replaceState = History.replaceState||emptyFunction;
32270		}
32271		else {
32272			/*
32273			 * Use native HTML5 History API Implementation
32274			 */
32275
32276			/**
32277			 * History.onPopState(event,extra)
32278			 * Refresh the Current State
32279			 */
32280			History.onPopState = function(event){
32281				// Reset the double check
32282				History.doubleCheckComplete();
32283
32284				// Check for a Hash, and handle apporiatly
32285				var currentHash	= History.getHash();
32286				if ( currentHash ) {
32287					// Expand Hash
32288					var currentState = History.extractState(currentHash||document.location.href,true);
32289					if ( currentState ) {
32290						// We were able to parse it, it must be a State!
32291						// Let's forward to replaceState
32292						//History.debug('History.onPopState: state anchor', currentHash, currentState);
32293						History.replaceState(currentState.data, currentState.title, currentState.url, false);
32294					}
32295					else {
32296						// Traditional Anchor
32297						//History.debug('History.onPopState: traditional anchor', currentHash);
32298						History.Adapter.trigger(window,'anchorchange');
32299						History.busy(false);
32300					}
32301
32302					// We don't care for hashes
32303					History.expectedStateId = false;
32304					return false;
32305				}
32306
32307				// Prepare
32308				var newState = false;
32309
32310				// Prepare
32311				event = event||{};
32312				if ( typeof event.state === 'undefined' ) {
32313					// jQuery
32314					if ( typeof event.originalEvent !== 'undefined' && typeof event.originalEvent.state !== 'undefined' ) {
32315						event.state = event.originalEvent.state||false;
32316					}
32317					// MooTools
32318					else if ( typeof event.event !== 'undefined' && typeof event.event.state !== 'undefined' ) {
32319						event.state = event.event.state||false;
32320					}
32321				}
32322
32323				// Ensure
32324				event.state = (event.state||false);
32325
32326				// Fetch State
32327				if ( event.state ) {
32328					// Vanilla: Back/forward button was used
32329					newState = History.getStateById(event.state);
32330				}
32331				else if ( History.expectedStateId ) {
32332					// Vanilla: A new state was pushed, and popstate was called manually
32333					newState = History.getStateById(History.expectedStateId);
32334				}
32335				else {
32336					// Initial State
32337					newState = History.extractState(document.location.href);
32338				}
32339
32340				// The State did not exist in our store
32341				if ( !newState ) {
32342					// Regenerate the State
32343					newState = History.createStateObject(null,null,document.location.href);
32344				}
32345
32346				// Clean
32347				History.expectedStateId = false;
32348
32349				// Check if we are the same state
32350				if ( History.isLastSavedState(newState) ) {
32351					// There has been no change (just the page's hash has finally propagated)
32352					//History.debug('History.onPopState: no change', newState, History.savedStates);
32353					History.busy(false);
32354					return false;
32355				}
32356
32357				// Store the State
32358				History.storeState(newState);
32359				History.saveState(newState);
32360
32361				// Force update of the title
32362				History.setTitle(newState);
32363
32364				// Fire Our Event
32365				History.Adapter.trigger(window,'statechange');
32366				History.busy(false);
32367
32368				// Return true
32369				return true;
32370			};
32371			History.Adapter.bind(window,'popstate',History.onPopState);
32372
32373			/**
32374			 * History.pushState(data,title,url)
32375			 * Add a new State to the history object, become it, and trigger onpopstate
32376			 * We have to trigger for HTML4 compatibility
32377			 * @param {object} data
32378			 * @param {string} title
32379			 * @param {string} url
32380			 * @return {true}
32381			 */
32382			History.pushState = function(data,title,url,queue){
32383				//History.debug('History.pushState: called', arguments);
32384
32385				// Check the State
32386				if ( History.getHashByUrl(url) && History.emulated.pushState ) {
32387					throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
32388				}
32389
32390				// Handle Queueing
32391				if ( queue !== false && History.busy() ) {
32392					// Wait + Push to Queue
32393					//History.debug('History.pushState: we must wait', arguments);
32394					History.pushQueue({
32395						scope: History,
32396						callback: History.pushState,
32397						args: arguments,
32398						queue: queue
32399					});
32400					return false;
32401				}
32402
32403				// Make Busy + Continue
32404				History.busy(true);
32405
32406				// Create the newState
32407				var newState = History.createStateObject(data,title,url);
32408
32409				// Check it
32410				if ( History.isLastSavedState(newState) ) {
32411					// Won't be a change
32412					History.busy(false);
32413				}
32414				else {
32415					// Store the newState
32416					History.storeState(newState);
32417					History.expectedStateId = newState.id;
32418
32419					// Push the newState
32420					history.pushState(newState.id,newState.title,newState.url);
32421
32422					// Fire HTML5 Event
32423					History.Adapter.trigger(window,'popstate');
32424				}
32425
32426				// End pushState closure
32427				return true;
32428			};
32429
32430			/**
32431			 * History.replaceState(data,title,url)
32432			 * Replace the State and trigger onpopstate
32433			 * We have to trigger for HTML4 compatibility
32434			 * @param {object} data
32435			 * @param {string} title
32436			 * @param {string} url
32437			 * @return {true}
32438			 */
32439			History.replaceState = function(data,title,url,queue){
32440				//History.debug('History.replaceState: called', arguments);
32441
32442				// Check the State
32443				if ( History.getHashByUrl(url) && History.emulated.pushState ) {
32444					throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
32445				}
32446
32447				// Handle Queueing
32448				if ( queue !== false && History.busy() ) {
32449					// Wait + Push to Queue
32450					//History.debug('History.replaceState: we must wait', arguments);
32451					History.pushQueue({
32452						scope: History,
32453						callback: History.replaceState,
32454						args: arguments,
32455						queue: queue
32456					});
32457					return false;
32458				}
32459
32460				// Make Busy + Continue
32461				History.busy(true);
32462
32463				// Create the newState
32464				var newState = History.createStateObject(data,title,url);
32465
32466				// Check it
32467				if ( History.isLastSavedState(newState) ) {
32468					// Won't be a change
32469					History.busy(false);
32470				}
32471				else {
32472					// Store the newState
32473					History.storeState(newState);
32474					History.expectedStateId = newState.id;
32475
32476					// Push the newState
32477					history.replaceState(newState.id,newState.title,newState.url);
32478
32479					// Fire HTML5 Event
32480					History.Adapter.trigger(window,'popstate');
32481				}
32482
32483				// End replaceState closure
32484				return true;
32485			};
32486
32487			// Be aware, the following is only for native pushState implementations
32488			// If you are wanting to include something for all browsers
32489			// Then include it above this if block
32490
32491			/**
32492			 * Setup Safari Fix
32493			 */
32494			if ( History.bugs.safariPoll ) {
32495				History.intervalList.push(setInterval(History.safariStatePoll, History.options.safariPollInterval));
32496			}
32497
32498			/**
32499			 * Ensure Cross Browser Compatibility
32500			 */
32501			if ( navigator.vendor === 'Apple Computer, Inc.' || (navigator.appCodeName||'') === 'Mozilla' ) {
32502				/**
32503				 * Fix Safari HashChange Issue
32504				 */
32505
32506				// Setup Alias
32507				History.Adapter.bind(window,'hashchange',function(){
32508					History.Adapter.trigger(window,'popstate');
32509				});
32510
32511				// Initialise Alias
32512				if ( History.getHash() ) {
32513					History.Adapter.onDomLoad(function(){
32514						History.Adapter.trigger(window,'hashchange');
32515					});
32516				}
32517			}
32518
32519		} // !History.emulated.pushState
32520
32521	}; // History.initCore
32522
32523	// Try and Initialise History
32524	History.init();
32525
32526})(window);
32527/**
32528 * History.js HTML4 Support
32529 * Depends on the HTML5 Support
32530 * @author Benjamin Arthur Lupton <[email protected]>
32531 * @copyright 2010-2011 Benjamin Arthur Lupton <[email protected]>
32532 * @license New BSD License <http://creativecommons.org/licenses/BSD/>
32533 */
32534
32535
32536(function(window,undefined){
32537	"use strict";
32538
32539	// --------------------------------------------------------------------------
32540	// Initialise
32541
32542	// Localise Globals
32543	var
32544		document = window.document, // Make sure we are using the correct document
32545		setTimeout = window.setTimeout||setTimeout,
32546		clearTimeout = window.clearTimeout||clearTimeout,
32547		setInterval = window.setInterval||setInterval,
32548		History = window.History = window.History||{}; // Public History Object
32549
32550	// Check Existence
32551	if ( typeof History.initHtml4 !== 'undefined' ) {
32552		throw new Error('History.js HTML4 Support has already been loaded...');
32553	}
32554
32555	// --------------------------------------------------------------------------
32556	// Initialise HTML4 Support
32557
32558	// Initialise HTML4 Support
32559	History.initHtml4 = function(){
32560		// Initialise
32561		if ( typeof History.initHtml4.initialized !== 'undefined' ) {
32562			// Already Loaded
32563			return false;
32564		}
32565		else {
32566			History.initHtml4.initialized = true;
32567		}
32568
32569		// ----------------------------------------------------------------------
32570		// Properties
32571
32572		/**
32573		 * History.enabled
32574		 * Is History enabled?
32575		 */
32576		History.enabled = true;
32577
32578
32579		// ----------------------------------------------------------------------
32580		// Hash Storage
32581
32582		/**
32583		 * History.savedHashes
32584		 * Store the hashes in an array
32585		 */
32586		History.savedHashes = [];
32587
32588		/**
32589		 * History.isLastHash(newHash)
32590		 * Checks if the hash is the last hash
32591		 * @param {string} newHash
32592		 * @return {boolean} true
32593		 */
32594		History.isLastHash = function(newHash){
32595			// Prepare
32596			var oldHash = History.getHashByIndex();
32597
32598			// Check
32599			var isLast = newHash === oldHash;
32600
32601			// Return isLast
32602			return isLast;
32603		};
32604
32605		/**
32606		 * History.saveHash(newHash)
32607		 * Push a Hash
32608		 * @param {string} newHash
32609		 * @return {boolean} true
32610		 */
32611		History.saveHash = function(newHash){
32612			// Check Hash
32613			if ( History.isLastHash(newHash) ) {
32614				return false;
32615			}
32616
32617			// Push the Hash
32618			History.savedHashes.push(newHash);
32619
32620			// Return true
32621			return true;
32622		};
32623
32624		/**
32625		 * History.getHashByIndex()
32626		 * Gets a hash by the index
32627		 * @param {integer} index
32628		 * @return {string}
32629		 */
32630		History.getHashByIndex = function(index){
32631			// Prepare
32632			var hash = null;
32633
32634			// Handle
32635			if ( typeof index === 'undefined' ) {
32636				// Get the last inserted
32637				hash = History.savedHashes[History.savedHashes.length-1];
32638			}
32639			else if ( index < 0 ) {
32640				// Get from the end
32641				hash = History.savedHashes[History.savedHashes.length+index];
32642			}
32643			else {
32644				// Get from the beginning
32645				hash = History.savedHashes[index];
32646			}
32647
32648			// Return hash
32649			return hash;
32650		};
32651
32652		// ----------------------------------------------------------------------
32653		// Discarded States
32654
32655		/**
32656		 * History.discardedHashes
32657		 * A hashed array of discarded hashes
32658		 */
32659		History.discardedHashes = {};
32660
32661		/**
32662		 * History.discardedStates
32663		 * A hashed array of discarded states
32664		 */
32665		History.discardedStates = {};
32666
32667		/**
32668		 * History.discardState(State)
32669		 * Discards the state by ignoring it through History
32670		 * @param {object} State
32671		 * @return {true}
32672		 */
32673		History.discardState = function(discardedState,forwardState,backState){
32674			//History.debug('History.discardState', arguments);
32675			// Prepare
32676			var discardedStateHash = History.getHashByState(discardedState);
32677
32678			// Create Discard Object
32679			var discardObject = {
32680				'discardedState': discardedState,
32681				'backState': backState,
32682				'forwardState': forwardState
32683			};
32684
32685			// Add to DiscardedStates
32686			History.discardedStates[discardedStateHash] = discardObject;
32687
32688			// Return true
32689			return true;
32690		};
32691
32692		/**
32693		 * History.discardHash(hash)
32694		 * Discards the hash by ignoring it through History
32695		 * @param {string} hash
32696		 * @return {true}
32697		 */
32698		History.discardHash = function(discardedHash,forwardState,backState){
32699			//History.debug('History.discardState', arguments);
32700			// Create Discard Object
32701			var discardObject = {
32702				'discardedHash': discardedHash,
32703				'backState': backState,
32704				'forwardState': forwardState
32705			};
32706
32707			// Add to discardedHash
32708			History.discardedHashes[discardedHash] = discardObject;
32709
32710			// Return true
32711			return true;
32712		};
32713
32714		/**
32715		 * History.discardState(State)
32716		 * Checks to see if the state is discarded
32717		 * @param {object} State
32718		 * @return {bool}
32719		 */
32720		History.discardedState = function(State){
32721			// Prepare
32722			var StateHash = History.getHashByState(State);
32723
32724			// Check
32725			var discarded = History.discardedStates[StateHash]||false;
32726
32727			// Return true
32728			return discarded;
32729		};
32730
32731		/**
32732		 * History.discardedHash(hash)
32733		 * Checks to see if the state is discarded
32734		 * @param {string} State
32735		 * @return {bool}
32736		 */
32737		History.discardedHash = function(hash){
32738			// Check
32739			var discarded = History.discardedHashes[hash]||false;
32740
32741			// Return true
32742			return discarded;
32743		};
32744
32745		/**
32746		 * History.recycleState(State)
32747		 * Allows a discarded state to be used again
32748		 * @param {object} data
32749		 * @param {string} title
32750		 * @param {string} url
32751		 * @return {true}
32752		 */
32753		History.recycleState = function(State){
32754			//History.debug('History.recycleState', arguments);
32755			// Prepare
32756			var StateHash = History.getHashByState(State);
32757
32758			// Remove from DiscardedStates
32759			if ( History.discardedState(State) ) {
32760				delete History.discardedStates[StateHash];
32761			}
32762
32763			// Return true
32764			return true;
32765		};
32766
32767		// ----------------------------------------------------------------------
32768		// HTML4 HashChange Support
32769
32770		if ( History.emulated.hashChange ) {
32771			/*
32772			 * We must emulate the HTML4 HashChange Support by manually checking for hash changes
32773			 */
32774
32775			/**
32776			 * History.hashChangeInit()
32777			 * Init the HashChange Emulation
32778			 */
32779			History.hashChangeInit = function(){
32780				// Define our Checker Function
32781				History.checkerFunction = null;
32782
32783				// Define some variables that will help in our checker function
32784				var
32785					lastDocumentHash = '';
32786
32787				// Handle depending on the browser
32788				if ( History.isInternetExplorer() ) {
32789					// IE6 and IE7
32790					// We need to use an iframe to emulate the back and forward buttons
32791
32792					// Create iFrame
32793					var
32794						iframeId = 'historyjs-iframe',
32795						iframe = document.createElement('iframe');
32796
32797					// Adjust iFarme
32798					iframe.setAttribute('id', iframeId);
32799					iframe.style.display = 'none';
32800
32801					// Append iFrame
32802					document.body.appendChild(iframe);
32803
32804					// Create initial history entry
32805					iframe.contentWindow.document.open();
32806					iframe.contentWindow.document.close();
32807
32808					// Define some variables that will help in our checker function
32809					var
32810						lastIframeHash = '',
32811						checkerRunning = false;
32812
32813					// Define the checker function
32814					History.checkerFunction = function(){
32815						// Check Running
32816						if ( checkerRunning ) {
32817							return false;
32818						}
32819
32820						// Update Running
32821						checkerRunning = true;
32822
32823						// Fetch
32824						var
32825							documentHash = History.getHash()||'',
32826							iframeHash = History.unescapeHash(iframe.contentWindow.document.location.hash)||'';
32827
32828						// The Document Hash has changed (application caused)
32829						if ( documentHash !== lastDocumentHash ) {
32830							// Equalise
32831							lastDocumentHash = documentHash;
32832
32833							// Create a history entry in the iframe
32834							if ( iframeHash !== documentHash ) {
32835								//History.debug('hashchange.checker: iframe hash change', 'documentHash (new):', documentHash, 'iframeHash (old):', iframeHash);
32836
32837								// Equalise
32838								lastIframeHash = iframeHash = documentHash;
32839
32840								// Create History Entry
32841								iframe.contentWindow.document.open();
32842								iframe.contentWindow.document.close();
32843
32844								// Update the iframe's hash
32845								iframe.contentWindow.document.location.hash = History.escapeHash(documentHash);
32846							}
32847
32848							// Trigger Hashchange Event
32849							History.Adapter.trigger(window,'hashchange');
32850						}
32851
32852						// The iFrame Hash has changed (back button caused)
32853						else if ( iframeHash !== lastIframeHash ) {
32854							//History.debug('hashchange.checker: iframe hash out of sync', 'iframeHash (new):', iframeHash, 'documentHash (old):', documentHash);
32855
32856							// Equalise
32857							lastIframeHash = iframeHash;
32858
32859							// Update the Hash
32860							History.setHash(iframeHash,false);
32861						}
32862
32863						// Reset Running
32864						checkerRunning = false;
32865
32866						// Return true
32867						return true;
32868					};
32869				}
32870				else {
32871					// We are not IE
32872					// Firefox 1 or 2, Opera
32873
32874					// Define the checker function
32875					History.checkerFunction = function(){
32876						// Prepare
32877						var documentHash = History.getHash();
32878
32879						// The Document Hash has changed (application caused)
32880						if ( documentHash !== lastDocumentHash ) {
32881							// Equalise
32882							lastDocumentHash = documentHash;
32883
32884							// Trigger Hashchange Event
32885							History.Adapter.trigger(window,'hashchange');
32886						}
32887
32888						// Return true
32889						return true;
32890					};
32891				}
32892
32893				// Apply the checker function
32894				History.intervalList.push(setInterval(History.checkerFunction, History.options.hashChangeInterval));
32895
32896				// Done
32897				return true;
32898			}; // History.hashChangeInit
32899
32900			// Bind hashChangeInit
32901			History.Adapter.onDomLoad(History.hashChangeInit);
32902
32903		} // History.emulated.hashChange
32904
32905
32906		// ----------------------------------------------------------------------
32907		// HTML5 State Support
32908
32909		if ( History.emulated.pushState ) {
32910			/*
32911			 * We must emulate the HTML5 State Management by using HTML4 HashChange
32912			 */
32913
32914			/**
32915			 * History.onHashChange(event)
32916			 * Trigger HTML5's window.onpopstate via HTML4 HashChange Support
32917			 */
32918			History.onHashChange = function(event){
32919				//History.debug('History.onHashChange', arguments);
32920
32921				// Prepare
32922				var
32923					currentUrl						= ((event && event.newURL) || document.location.href),
32924					currentHash						= History.getHashByUrl(currentUrl),
32925					currentState					= null,
32926					currentStateHash			= null,
32927					currentStateHashExits	= null;
32928
32929				// Check if we are the same state
32930				if ( History.isLastHash(currentHash) ) {
32931					// There has been no change (just the page's hash has finally propagated)
32932					//History.debug('History.onHashChange: no change');
32933					History.busy(false);
32934					return false;
32935				}
32936
32937				// Reset the double check
32938				History.doubleCheckComplete();
32939
32940				// Store our location for use in detecting back/forward direction
32941				History.saveHash(currentHash);
32942
32943				// Expand Hash
32944				if ( currentHash && History.isTraditionalAnchor(currentHash) ) {
32945					//History.debug('History.onHashChange: traditional anchor', currentHash);
32946					// Traditional Anchor Hash
32947					History.Adapter.trigger(window,'anchorchange');
32948					History.busy(false);
32949					return false;
32950				}
32951
32952				// Create State
32953				currentState = History.extractState(History.getFullUrl(currentHash||document.location.href,false),true);
32954
32955				// Check if we are the same state
32956				if ( History.isLastSavedState(currentState) ) {
32957					//History.debug('History.onHashChange: no change');
32958					// There has been no change (just the page's hash has finally propagated)
32959					History.busy(false);
32960					return false;
32961				}
32962
32963				// Create the state Hash
32964				currentStateHash = History.getHashByState(currentState);
32965
32966				// Check if we are DiscardedState
32967				var discardObject = History.discardedState(currentState);
32968				if ( discardObject ) {
32969					// Ignore this state as it has been discarded and go back to the state before it
32970					if ( History.getHashByIndex(-2) === History.getHashByState(discardObject.forwardState) ) {
32971						// We are going backwards
32972						//History.debug('History.onHashChange: go backwards');
32973						History.back(false);
32974					} else {
32975						// We are going forwards
32976						//History.debug('History.onHashChange: go forwards');
32977						History.forward(false);
32978					}
32979					return false;
32980				}
32981
32982				// Push the new HTML5 State
32983				//History.debug('History.onHashChange: success hashchange');
32984				History.pushState(currentState.data,currentState.title,currentState.url,false);
32985
32986				// End onHashChange closure
32987				return true;
32988			};
32989			History.Adapter.bind(window,'hashchange',History.onHashChange);
32990
32991			/**
32992			 * History.pushState(data,title,url)
32993			 * Add a new State to the history object, become it, and trigger onpopstate
32994			 * We have to trigger for HTML4 compatibility
32995			 * @param {object} data
32996			 * @param {string} title
32997			 * @param {string} url
32998			 * @return {true}
32999			 */
33000			History.pushState = function(data,title,url,queue){
33001				//History.debug('History.pushState: called', arguments);
33002
33003				// Check the State
33004				if ( History.getHashByUrl(url) ) {
33005					throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
33006				}
33007
33008				// Handle Queueing
33009				if ( queue !== false && History.busy() ) {
33010					// Wait + Push to Queue
33011					//History.debug('History.pushState: we must wait', arguments);
33012					History.pushQueue({
33013						scope: History,
33014						callback: History.pushState,
33015						args: arguments,
33016						queue: queue
33017					});
33018					return false;
33019				}
33020
33021				// Make Busy
33022				History.busy(true);
33023
33024				// Fetch the State Object
33025				var
33026					newState = History.createStateObject(data,title,url),
33027					newStateHash = History.getHashByState(newState),
33028					oldState = History.getState(false),
33029					oldStateHash = History.getHashByState(oldState),
33030					html4Hash = History.getHash();
33031
33032				// Store the newState
33033				History.storeState(newState);
33034				History.expectedStateId = newState.id;
33035
33036				// Recycle the State
33037				History.recycleState(newState);
33038
33039				// Force update of the title
33040				History.setTitle(newState);
33041
33042				// Check if we are the same State
33043				if ( newStateHash === oldStateHash ) {
33044					//History.debug('History.pushState: no change', newStateHash);
33045					History.busy(false);
33046					return false;
33047				}
33048
33049				// Update HTML4 Hash
33050				if ( newStateHash !== html4Hash && newStateHash !== History.getShortUrl(document.location.href) ) {
33051					//History.debug('History.pushState: update hash', newStateHash, html4Hash);
33052					History.setHash(newStateHash,false);
33053					return false;
33054				}
33055
33056				// Update HTML5 State
33057				History.saveState(newState);
33058
33059				// Fire HTML5 Event
33060				//History.debug('History.pushState: trigger popstate');
33061				History.Adapter.trigger(window,'statechange');
33062				History.busy(false);
33063
33064				// End pushState closure
33065				return true;
33066			};
33067
33068			/**
33069			 * History.replaceState(data,title,url)
33070			 * Replace the State and trigger onpopstate
33071			 * We have to trigger for HTML4 compatibility
33072			 * @param {object} data
33073			 * @param {string} title
33074			 * @param {string} url
33075			 * @return {true}
33076			 */
33077			History.replaceState = function(data,title,url,queue){
33078				//History.debug('History.replaceState: called', arguments);
33079
33080				// Check the State
33081				if ( History.getHashByUrl(url) ) {
33082					throw new Error('History.js does not support states with fragement-identifiers (hashes/anchors).');
33083				}
33084
33085				// Handle Queueing
33086				if ( queue !== false && History.busy() ) {
33087					// Wait + Push to Queue
33088					//History.debug('History.replaceState: we must wait', arguments);
33089					History.pushQueue({
33090						scope: History,
33091						callback: History.replaceState,
33092						args: arguments,
33093						queue: queue
33094					});
33095					return false;
33096				}
33097
33098				// Make Busy
33099				History.busy(true);
33100
33101				// Fetch the State Objects
33102				var
33103					newState        = History.createStateObject(data,title,url),
33104					oldState        = History.getState(false),
33105					previousState   = History.getStateByIndex(-2);
33106
33107				// Discard Old State
33108				History.discardState(oldState,newState,previousState);
33109
33110				// Alias to PushState
33111				History.pushState(newState.data,newState.title,newState.url,false);
33112
33113				// End replaceState closure
33114				return true;
33115			};
33116
33117			/**
33118			 * Ensure initial state is handled correctly
33119			 */
33120			if ( History.getHash() && !History.emulated.hashChange ) {
33121				History.Adapter.onDomLoad(function(){
33122					History.Adapter.trigger(window,'hashchange');
33123				});
33124			}
33125
33126		} // History.emulated.pushState
33127
33128	}; // History.initHtml4
33129
33130	// Try and Initialise History
33131	History.init();
33132
33133}
33133)(window);
33134
33135
33136
33137
33138
33139/*
33140
33141	jQuery Tags Input Plugin 1.3.3
33142	
33143	Copyright (c) 2011 XOXCO, Inc
33144	
33145	Documentation for this plugin lives here:
33146	http://xoxco.com/clickable/jquery-tags-input
33147	
33148	Licensed under the MIT license:
33149	http://www.opensource.org/licenses/mit-license.php
33150
33151	[email protected]
33152
33153*/
33154
33155
33156(function($) {
33157
33158	var delimiter = new Array();
33159	var tags_callbacks = new Array();
33160	$.fn.doAutosize = function(o){
33161	    var minWidth = $(this).data('minwidth'),
33162	        maxWidth = $(this).data('maxwidth'),
33163	        val = '',
33164	        input = $(this),
33165	        testSubject = $('#'+$(this).data('tester_id'));
33166	
33167	    if (val === (val = input.val())) {return;}
33168	
33169	    // Enter new content into testSubject
33170	    var escaped = val.replace(/&/g, '&amp;').replace(/\s/g,' ').replace(/</g, '&lt;').replace(/>/g, '&gt;');
33171	    testSubject.html(escaped);
33172	    // Calculate new width + whether to change
33173	    var testerWidth = testSubject.width(),
33174	        newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
33175	        currentWidth = input.width(),
33176	        isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
33177	                             || (newWidth > minWidth && newWidth < maxWidth);
33178	
33179	    // Animate width
33180	    if (isValidWidthChange) {
33181	        input.width(newWidth);
33182	    }
33183
33184
33185  };
33186  $.fn.resetAutosize = function(options){
33187    // alert(JSON.stringify(options));
33188    var minWidth =  $(this).data('minwidth') || options.minInputWidth || $(this).width(),
33189        maxWidth = $(this).data('maxwidth') || options.maxInputWidth || ($(this).closest('.tagsinput').width() - options.inputPadding),
33190        val = '',
33191        input = $(this),
33192        testSubject = $('<tester/>').css({
33193            position: 'absolute',
33194            top: -9999,
33195            left: -9999,
33196            width: 'auto',
33197            fontSize: input.css('fontSize'),
33198            fontFamily: input.css('fontFamily'),
33199            fontWeight: input.css('fontWeight'),
33200            letterSpacing: input.css('letterSpacing'),
33201            whiteSpace: 'nowrap'
33202        }),
33203        testerId = $(this).attr('id')+'_autosize_tester';
33204    if(! $('#'+testerId).length > 0){
33205      testSubject.attr('id', testerId);
33206      testSubject.appendTo('body');
33207    }
33208
33209    input.data('minwidth', minWidth);
33210    input.data('maxwidth', maxWidth);
33211    input.data('tester_id', testerId);
33212    input.css('width', minWidth);
33213  };
33214  
33215	$.fn.addTag = function(value,options) {
33216			options = jQuery.extend({focus:false,callback:true},options);
33217			this.each(function() { 
33218				var id = $(this).attr('id');
33219
33220				var tagslist = $(this).val().split(delimiter[id]);
33221				if (tagslist[0] == '') { 
33222					tagslist = new Array();
33223				}
33224
33225				value = jQuery.trim(value);
33226		
33227				if (options.unique) {
33228					var skipTag = $(this).tagExist(value);
33229					if(skipTag == true) {
33230					    //Marks fake input as not_valid to let styling it
33231    				    $('#'+id+'_tag').addClass('not_valid');
33232    				}
33233				} else {
33234					var skipTag = false; 
33235				}
33236				
33237				if (value !='' && skipTag != true) { 
33238                    $('<span>').addClass('tag').append(
33239                        $('<span>').text(value).append('&nbsp;&nbsp;'),
33240                        $('<a>', {
33241                            href  : '#',
33242                            title : 'Removing tag',
33243                            text  : 'x'
33244                        }).click(function () {
33245                            return $('#' + id).removeTag(escape(value));
33246                        })
33247                    ).insertBefore('#' + id + '_addTag');
33248
33249					tagslist.push(value);
33250				
33251					$('#'+id+'_tag').val('');
33252					if (options.focus) {
33253						$('#'+id+'_tag').focus();
33254					} else {		
33255						$('#'+id+'_tag').blur();
33256					}
33257					
33258					$.fn.tagsInput.updateTagsField(this,tagslist);
33259					
33260					if (options.callback && tags_callbacks[id] && tags_callbacks[id]['onAddTag']) {
33261						var f = tags_callbacks[id]['onAddTag'];
33262						f.call(this, value);
33263					}
33264					if(tags_callbacks[id] && tags_callbacks[id]['onChange'])
33265					{
33266						var i = tagslist.length;
33267						var f = tags_callbacks[id]['onChange'];
33268						f.call(this, $(this), tagslist[i-1]);
33269					}					
33270				}
33271		
33272			});		
33273			
33274			return false;
33275		};
33276		
33277	$.fn.removeTag = function(value) { 
33278			value = unescape(value);
33279			this.each(function() { 
33280				var id = $(this).attr('id');
33281	
33282				var old = $(this).val().split(delimiter[id]);
33283					
33284				$('#'+id+'_tagsinput .tag').remove();
33285				str = '';
33286				for (i=0; i< old.length; i++) { 
33287					if (old[i]!=value) { 
33288						str = str + delimiter[id] +old[i];
33289					}
33290				}
33291				
33292				$.fn.tagsInput.importTags(this,str);
33293
33294				if (tags_callbacks[id] && tags_callbacks[id]['onRemoveTag']) {
33295					var f = tags_callbacks[id]['onRemoveTag'];
33296					f.call(this, value);
33297				}
33298
33299			    // davidf: making default text visible again.
33300			    if ($(this).val().length == 0) {
33301				var fake_input = $j("#" + $j(this).attr('id') + "_tag");
33302				fake_input.blur();
33303			    }
33304			});
33305					
33306			return false;
33307		};
33308	
33309	$.fn.tagExist = function(val) {
33310		var id = $(this).attr('id');
33311		var tagslist = $(this).val().split(delimiter[id]);
33312		return (jQuery.inArray(val, tagslist) >= 0); //true when tag exists, false when not
33313	};
33314	
33315	// clear all existing tags and import new ones from a string
33316	$.fn.importTags = function(str) {
33317                id = $(this).attr('id');
33318		$('#'+id+'_tagsinput .tag').remove();
33319		$.fn.tagsInput.importTags(this,str);
33320	}
33321		
33322	$.fn.tagsInput = function(options) { 
33323    var settings = jQuery.extend({
33324      interactive:true,
33325      defaultText:'add a tag',
33326      minChars:0,
33327      width:'300px',
33328      height:'100px',
33329      autocomplete: {selectFirst: false },
33330      'hide':true,
33331      'delimiter':',',
33332      'unique':true,
33333      removeWithBackspace:true,
33334      placeholderColor:'#666666',
33335      autosize: true,
33336      comfortZone: 20,
33337      inputPadding: 6*2
33338    },options);
33339
33340		this.each(function() { 
33341			if (settings.hide) { 
33342				$(this).hide();				
33343			}
33344			var id = $(this).attr('id');
33345			if (!id || delimiter[$(this).attr('id')]) {
33346				id = $(this).attr('id', 'tags' + new Date().getTime()).attr('id');
33347            }
33348			
33349			var data = jQuery.extend({
33350				pid:id,
33351				real_input: '#'+id,
33352				holder: '#'+id+'_tagsinput',
33353				input_wrapper: '#'+id+'_addTag',
33354				fake_input: '#'+id+'_tag'
33355			},settings);
33356	
33357			delimiter[id] = data.delimiter;
33358			
33359			if (settings.onAddTag || settings.onRemoveTag || settings.onChange) {
33360				tags_callbacks[id] = new Array();
33361				tags_callbacks[id]['onAddTag'] = settings.onAddTag;
33362				tags_callbacks[id]['onRemoveTag'] = settings.onRemoveTag;
33363				tags_callbacks[id]['onChange'] = settings.onChange;
33364			}
33365	
33366			var markup = '<div id="'+id+'_tagsinput" class="tagsinput"><div id="'+id+'_addTag">';
33367			
33368			if (settings.interactive) {
33369				markup = markup + '<input id="'+id+'_tag" value="" data-default="'+settings.defaultText+'" />';
33370			}
33371			
33372			markup = markup + '</div><div class="tags_clear"></div></div>';
33373			
33374			$(markup).insertAfter(this);
33375
33376			$(data.holder).css('width',settings.width);
33377			$(data.holder).css('min-height',settings.height);
33378			$(data.holder).css('height','100%');
33379	
33380			if ($(data.real_input).val()!='') { 
33381				$.fn.tagsInput.importTags($(data.real_input),$(data.real_input).val());
33382			}		
33383			if (settings.interactive) { 
33384				$(data.fake_input).val($(data.fake_input).attr('data-default'));
33385				$(data.fake_input).css('color',settings.placeholderColor);
33386		        $(data.fake_input).resetAutosize(settings);
33387		
33388				$(data.holder).bind('click',data,function(event) {
33389					$(event.data.fake_input).focus();
33390				});
33391			
33392				$(data.fake_input).bind('focus',data,function(event) {
33393					if ($(event.data.fake_input).val()==$(event.data.fake_input).attr('data-default')) { 
33394						$(event.data.fake_input).val('');
33395					}
33396					$(event.data.fake_input).css('color','#000000');		
33397				});
33398
33399			        // davidf: reinstate the default text 
33400				$(data.fake_input).bind('blur',data,function(event) {
33401				    var last_tag = $(this).closest('.tagsinput').find('.tag:last').text();
33402				    if (last_tag == '') {
33403					$(event.data.fake_input).val($(event.data.fake_input).attr('data-default'));
33404					$(data.fake_input).css('color',settings.placeholderColor);
33405				    }
33406				});
33407						
33408				if (settings.autocomplete_url != undefined) {
33409					autocomplete_options = {source: settings.autocomplete_url};
33410					for (attrname in settings.autocomplete) { 
33411						autocomplete_options[attrname] = settings.autocomplete[attrname]; 
33412					}
33413				
33414					if (jQuery.Autocompleter !== undefined) {
33415						$(data.fake_input).autocomplete(settings.autocomplete_url, settings.autocomplete);
33416						$(data.fake_input).bind('result',data,function(event,data,formatted) {
33417							if (data) {
33418								$('#'+id).addTag(data[0] + "",{focus:true,unique:(settings.unique)});
33419							}
33420					  	});
33421					} else if (jQuery.ui.autocomplete !== undefined) {
33422						$(data.fake_input).autocomplete(autocomplete_options);
33423						$(data.fake_input).bind('autocompleteselect',data,function(event,ui) {
33424							$(event.data.real_input).addTag(ui.item.value,{focus:true,unique:(settings.unique)});
33425							return false;
33426						});
33427					}
33428				
33429					
33430				} else {
33431						// if a user tabs out of the field, create a new tag
33432						// this is only available if autocomplete is not used.
33433						$(data.fake_input).bind('blur',data,function(event) { 
33434							var d = $(this).attr('data-default');
33435							if ($(event.data.fake_input).val()!='' && $(event.data.fake_input).val()!=d) { 
33436								if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
33437									$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
33438							} else {
33439								$(event.data.fake_input).val($(event.data.fake_input).attr('data-default'));
33440								$(event.data.fake_input).css('color',settings.placeholderColor);
33441							}
33442							return false;
33443						});
33444				
33445				}
33446				// if user types a comma, create a new tag
33447				$(data.fake_input).bind('keypress',data,function(event) {
33448					if (event.which==event.data.delimiter.charCodeAt(0) || event.which==13 ) {
33449					    event.preventDefault();
33450						if( (event.data.minChars <= $(event.data.fake_input).val().length) && (!event.data.maxChars || (event.data.maxChars >= $(event.data.fake_input).val().length)) )
33451							$(event.data.real_input).addTag($(event.data.fake_input).val(),{focus:true,unique:(settings.unique)});
33452					  	$(event.data.fake_input).resetAutosize(settings);
33453						return false;
33454					} else if (event.data.autosize) {
33455			            $(event.data.fake_input).doAutosize(settings);
33456            
33457          			}
33458				});
33459				//Delete last tag on backspace
33460				data.removeWithBackspace && $(data.fake_input).bind('keydown', function(event)
33461				{
33462					if(event.keyCode == 8 && $(this).val() == '')
33463					{
33464						 event.preventDefault();
33465						 var last_tag = $(this).closest('.tagsinput').find('.tag:last').text();
33466						 var id = $(this).attr('id').replace(/_tag$/, '');
33467						 last_tag = last_tag.replace(/[\s]+x$/, '');
33468						 $('#' + id).removeTag(escape(last_tag));
33469						 $(this).trigger('focus');
33470					}
33471				});
33472				$(data.fake_input).blur();
33473				
33474				//Removes the not_valid class when user changes the value of the fake input
33475				if(data.unique) {
33476				    $(data.fake_input).keydown(function(event){
33477				        if(event.keyCode == 8 || String.fromCharCode(event.which).match(/\w+|[áéíóúÁÉÍÓÚñÑ,/]+/)) {
33478				            $(this).removeClass('not_valid');
33479				        }
33480				    });
33481				}
33482			} // if settings.interactive
33483		});
33484			
33485		return this;
33486	
33487	};
33488	
33489	$.fn.tagsInput.updateTagsField = function(obj,tagslist) { 
33490		var id = $(obj).attr('id');
33491		$(obj).val(tagslist.join(delimiter[id]));
33492	};
33493	
33494	$.fn.tagsInput.importTags = function(obj,val) {			
33495		$(obj).val('');
33496		var id = $(obj).attr('id');
33497		var tags = val.split(delimiter[id]);
33498		for (i=0; i<tags.length; i++) { 
33499			$(obj).addTag(tags[i],{focus:false,callback:false});
33500		}
33501		if(tags_callbacks[id] && tags_callbacks[id]['onChange'])
33502		{
33503			var f = tags_callbacks[id]['onChange'];
33504			f.call(obj, obj, tags[i]);
33505		}
33506	};
33507
33508    $.fn.tagsInput.destroy = function( ) {
33509
33510        delimiter = new Array();
33511        tags_callbacks = new Array();
33512    };
33513
33514})(jQuery);
33515/*
33516 * jsTree 1.0-rc3
33517 * http://jstree.com/
33518 *
33519 * Copyright (c) 2010 Ivan Bozhanov (vakata.com)
33520 *
33521 * Licensed same as jquery - under the terms of either the MIT License or the GPL Version 2 License
33522 *   http://www.opensource.org/licenses/mit-license.php
33523 *   http://www.gnu.org/licenses/gpl.html
33524 *
33525 * $Date: 2011-02-09 01:17:14 +0200 (ср, 09 февр 2011) $
33526 * $Revision: 236 $
33527 */
33528
33529/*jslint browser: true, onevar: true, undef: true, bitwise: true, strict: true */
33530/*global window : false, clearInterval: false, clearTimeout: false, document: false, setInterval: false, setTimeout: false, jQuery: false, navigator: false, XSLTProcessor: false, DOMParser: false, XMLSerializer: false*/
33531
33532
33533"use strict";
33534
33535// top wrapper to prevent multiple inclusion (is this OK?)
33536(function () { if(jQuery && jQuery.jstree) { return; }
33537	var is_ie6 = false, is_ie7 = false, is_ff2 = false;
33538
33539/* 
33540 * jsTree core
33541 */
33542(function ($) {
33543	// Common functions not related to jsTree 
33544	// decided to move them to a `vakata` "namespace"
33545	$.vakata = {};
33546	// CSS related functions
33547	$.vakata.css = {
33548		get_css : function(rule_name, delete_flag, sheet) {
33549			rule_name = rule_name.toLowerCase();
33550			var css_rules = sheet.cssRules || sheet.rules,
33551				j = 0;
33552			do {
33553				if(css_rules.length && j > css_rules.length + 5) { return false; }
33554				if(css_rules[j].selectorText && css_rules[j].selectorText.toLowerCase() == rule_name) {
33555					if(delete_flag === true) {
33556						if(sheet.removeRule) { sheet.removeRule(j); }
33557						if(sheet.deleteRule) { sheet.deleteRule(j); }
33558						return true;
33559					}
33560					else { return css_rules[j]; }
33561				}
33562			}
33563			while (css_rules[++j]);
33564			return false;
33565		},
33566		add_css : function(rule_name, sheet) {
33567			if($.jstree.css.get_css(rule_name, false, sheet)) { return false; }
33568			if(sheet.insertRule) { sheet.insertRule(rule_name + ' { }', 0); } else { sheet.addRule(rule_name, null, 0); }
33569			return $.vakata.css.get_css(rule_name);
33570		},
33571		remove_css : function(rule_name, sheet) { 
33572			return $.vakata.css.get_css(rule_name, true, sheet); 
33573		},
33574		add_sheet : function(opts) {
33575			var tmp = false, is_new = true;
33576			if(opts.str) {
33577				if(opts.title) { tmp = $("style[id='" + opts.title + "-stylesheet']")[0]; }
33578				if(tmp) { is_new = false; }
33579				else {
33580					tmp = document.createElement("style");
33581					tmp.setAttribute('type',"text/css");
33582					if(opts.title) { tmp.setAttribute("id", opts.title + "-stylesheet"); }
33583				}
33584				if(tmp.styleSheet) {
33585					if(is_new) { 
33586						document.getElementsByTagName("head")[0].appendChild(tmp); 
33587						tmp.styleSheet.cssText = opts.str; 
33588					}
33589					else {
33590						tmp.styleSheet.cssText = tmp.styleSheet.cssText + " " + opts.str; 
33591					}
33592				}
33593				else {
33594					tmp.appendChild(document.createTextNode(opts.str));
33595					document.getElementsByTagName("head")[0].appendChild(tmp);
33596				}
33597				return tmp.sheet || tmp.styleSheet;
33598			}
33599			if(opts.url) {
33600				if(document.createStyleSheet) {
33601					try { tmp = document.createStyleSheet(opts.url); } catch (e) { }
33602				}
33603				else {
33604					tmp			= document.createElement('link');
33605					tmp.rel		= 'stylesheet';
33606					tmp.type	= 'text/css';
33607					tmp.media	= "all";
33608					tmp.href	= opts.url;
33609					document.getElementsByTagName("head")[0].appendChild(tmp);
33610					return tmp.styleSheet;
33611				}
33612			}
33613		}
33614	};
33615
33616	// private variables 
33617	var instances = [],			// instance array (used by $.jstree.reference/create/focused)
33618		focused_instance = -1,	// the index in the instance array of the currently focused instance
33619		plugins = {},			// list of included plugins
33620		prepared_move = {};		// for the move_node function
33621
33622	// jQuery plugin wrapper (thanks to jquery UI widget function)
33623	$.fn.jstree = function (settings) {
33624		var isMethodCall = (typeof settings == 'string'), // is this a method call like $().jstree("open_node")
33625			args = Array.prototype.slice.call(arguments, 1), 
33626			returnValue = this;
33627
33628		// if a method call execute the method on all selected instances
33629		if(isMethodCall) {
33630			if(settings.substring(0, 1) == '_') { return returnValue; }
33631			this.each(function() {
33632				var instance = instances[$.data(this, "jstree_instance_id")],
33633					methodValue = (instance && $.isFunction(instance[settings])) ? instance[settings].apply(instance, args) : instance;
33634					if(typeof methodValue !== "undefined" && (settings.indexOf("is_") === 0 || (methodValue !== true && methodValue !== false))) { returnValue = methodValue; return false; }
33635			});
33636		}
33637		else {
33638			this.each(function() {
33639				// extend settings and allow for multiple hashes and $.data
33640				var instance_id = $.data(this, "jstree_instance_id"),
33641					a = [],
33642					b = settings ? $.extend({}, true, settings) : {},
33643					c = $(this), 
33644					s = false, 
33645					t = [];
33646				a = a.concat(args);
33647				if(c.data("jstree")) { a.push(c.data("jstree")); }
33648				b = a.length ? $.extend.apply(null, [true, b].concat(a)) : b;
33649
33650				// if an instance already exists, destroy it first
33651				if(typeof instance_id !== "undefined" && instances[instance_id]) { instances[instance_id].destroy(); }
33652				// push a new empty object to the instances array
33653				instance_id = parseInt(instances.push({}),10) - 1;
33654				// store the jstree instance id to the container element
33655				$.data(this, "jstree_instance_id", instance_id);
33656				// clean up all plugins
33657				b.plugins = $.isArray(b.plugins) ? b.plugins : $.jstree.defaults.plugins.slice();
33658				b.plugins.unshift("core");
33659				// only unique plugins
33660				b.plugins = b.plugins.sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(",");
33661
33662				// extend defaults with passed data
33663				s = $.extend(true, {}, $.jstree.defaults, b);
33664				s.plugins = b.plugins;
33665				$.each(plugins, function (i, val) { 
33666					if($.inArray(i, s.plugins) === -1) { s[i] = null; delete s[i]; } 
33667					else { t.push(i); }
33668				});
33669				s.plugins = t;
33670
33671				// push the new object to the instances array (at the same time set the default classes to the container) and init
33672				instances[instance_id] = new $.jstree._instance(instance_id, $(this).addClass("jstree jstree-" + instance_id), s); 
33673				// init all activated plugins for this instance
33674				$.each(instances[instance_id]._get_settings().plugins, function (i, val) { instances[instance_id].data[val] = {}; });
33675				$.each(instances[instance_id]._get_settings().plugins, function (i, val) { if(plugins[val]) { plugins[val].__init.apply(instances[instance_id]); } });
33676				// initialize the instance
33677				setTimeout(function() { if(instances[instance_id]) { instances[instance_id].init(); } }, 0);
33678			});
33679		}
33680		// return the jquery selection (or if it was a method call that returned a value - the returned value)
33681		return returnValue;
33682	};
33683	// object to store exposed functions and objects
33684	$.jstree = {
33685		defaults : {
33686			plugins : []
33687		},
33688		_focused : function () { return instances[focused_instance] || null; },
33689		_reference : function (needle) { 
33690			// get by instance id
33691			if(instances[needle]) { return instances[needle]; }
33692			// get by DOM (if still no luck - return null
33693			var o = $(needle); 
33694			if(!o.length && typeof needle === "string") { o = $("#" + needle); }
33695			if(!o.length) { return null; }
33696			return instances[o.closest(".jstree").data("jstree_instance_id")] || null; 
33697		},
33698		_instance : function (index, container, settings) { 
33699			// for plugins to store data in
33700			this.data = { core : {} };
33701			this.get_settings	= function () { return $.extend(true, {}, settings); };
33702			this._get_settings	= function () { return settings; };
33703			this.get_index		= function () { return index; };
33704			this.get_container	= function () { return container; };
33705			this.get_container_ul = function () { return container.children("ul:eq(0)"); };
33706			this._set_settings	= function (s) { 
33707				settings = $.extend(true, {}, settings, s);
33708			};
33709		},
33710		_fn : { },
33711		plugin : function (pname, pdata) {
33712			pdata = $.extend({}, {
33713				__init		: $.noop, 
33714				__destroy	: $.noop,
33715				_fn			: {},
33716				defaults	: false
33717			}, pdata);
33718			plugins[pname] = pdata;
33719
33720			$.jstree.defaults[pname] = pdata.defaults;
33721			$.each(pdata._fn, function (i, val) {
33722				val.plugin		= pname;
33723				val.old			= $.jstree._fn[i];
33724				$.jstree._fn[i] = function () {
33725					var rslt,
33726						func = val,
33727						args = Array.prototype.slice.call(arguments),
33728						evnt = new $.Event("before.jstree"),
33729						rlbk = false;
33730
33731					if(this.data.core.locked === true && i !== "unlock" && i !== "is_locked") { return; }
33732
33733					// Check if function belongs to the included plugins of this instance
33734					do {
33735						if(func && func.plugin && $.inArray(func.plugin, this._get_settings().plugins) !== -1) { break; }
33736						func = func.old;
33737					} while(func);
33738					if(!func) { return; }
33739
33740					// context and function to trigger events, then finally call the function
33741					if(i.indexOf("_") === 0) {
33742						rslt = func.apply(this, args);
33743					}
33744					else {
33745						rslt = this.get_container().triggerHandler(evnt, { "func" : i, "inst" : this, "args" : args, "plugin" : func.plugin });
33746						if(rslt === false) { return; }
33747						if(typeof rslt !== "undefined") { args = rslt; }
33748
33749						rslt = func.apply(
33750							$.extend({}, this, { 
33751								__callback : function (data) { 
33752									this.get_container().triggerHandler( i + '.jstree', { "inst" : this, "args" : args, "rslt" : data, "rlbk" : rlbk });
33753								},
33754								__rollback : function () { 
33755									rlbk = this.get_rollback();
33756									return rlbk;
33757								},
33758								__call_old : function (replace_arguments) {
33759									return func.old.apply(this, (replace_arguments ? Array.prototype.slice.call(arguments, 1) : args ) );
33760								}
33761							}), args);
33762					}
33763
33764					// return the result
33765					return rslt;
33766				};
33767				$.jstree._fn[i].old = val.old;
33768				$.jstree._fn[i].plugin = pname;
33769			});
33770		},
33771		rollback : function (rb) {
33772			if(rb) {
33773				if(!$.isArray(rb)) { rb = [ rb ]; }
33774				$.each(rb, function (i, val) {
33775					instances[val.i].set_rollback(val.h, val.d);
33776				});
33777			}
33778		}
33779	};
33780	// set the prototype for all instances
33781	$.jstree._fn = $.jstree._instance.prototype = {};
33782
33783	// load the css when DOM is ready
33784	$(function() {
33785		// code is copied from jQuery ($.browser is deprecated + there is a bug in IE)
33786		var u = navigator.userAgent.toLowerCase(),
33787			v = (u.match( /.+?(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
33788			css_string = '' + 
33789				'.jstree ul, .jstree li { display:block; margin:0 0 0 0; padding:0 0 0 0; list-style-type:none; } ' + 
33790				'.jstree li { display:block; min-height:18px; line-height:18px; white-space:nowrap; margin-left:18px; min-width:18px; } ' + 
33791				'.jstree-rtl li { margin-left:0; margin-right:18px; } ' + 
33792				'.jstree > ul > li { margin-left:0px; } ' + 
33793				'.jstree-rtl > ul > li { margin-right:0px; } ' + 
33794				'.jstree ins { display:inline-block; text-decoration:none; width:18px; height:18px; margin:0 0 0 0; padding:0; } ' + 
33795				'.jstree a { display:inline-block; line-height:16px; height:16px; color:black; white-space:nowrap; text-decoration:none; padding:1px 2px; margin:0; } ' + 
33796				'.jstree a:focus { outline: none; } ' + 
33797				'.jstree a > ins { height:16px; width:16px; } ' + 
33798				'.jstree a > .jstree-icon { margin-right:3px; } ' + 
33799				'.jstree-rtl a > .jstree-icon { margin-left:3px; margin-right:0; } ' + 
33800				'li.jstree-open > ul { display:block; } ' + 
33801				'li.jstree-closed > ul { display:none; } ';
33802		// Correct IE 6 (does not support the > CSS selector)
33803		if(/msie/.test(u) && parseInt(v, 10) == 6) { 
33804			is_ie6 = true;
33805
33806			// fix image flicker and lack of caching
33807			try {
33808				document.execCommand("BackgroundImageCache", false, true);
33809			} catch (err) { }
33810
33811			css_string += '' + 
33812				'.jstree li { height:18px; margin-left:0; margin-right:0; } ' + 
33813				'.jstree li li { margin-left:18px; } ' + 
33814				'.jstree-rtl li li { margin-left:0px; margin-right:18px; } ' + 
33815				'li.jstree-open ul { display:block; } ' + 
33816				'li.jstree-closed ul { display:none !important; } ' + 
33817				'.jstree li a { display:inline; border-width:0 !important; padding:0px 2px !important; } ' + 
33818				'.jstree li a ins { height:16px; width:16px; margin-right:3px; } ' + 
33819				'.jstree-rtl li a ins { margin-right:0px; margin-left:3px; } ';
33820		}
33821		// Correct IE 7 (shifts anchor nodes onhover)
33822		if(/msie/.test(u) && parseInt(v, 10) == 7) { 
33823			is_ie7 = true;
33824			css_string += '.jstree li a { border-width:0 !important; padding:0px 2px !important; } ';
33825		}
33826		// correct ff2 lack of display:inline-block
33827		if(!/compatible/.test(u) && /mozilla/.test(u) && parseFloat(v, 10) < 1.9) {
33828			is_ff2 = true;
33829			css_string += '' + 
33830				'.jstree ins { display:-moz-inline-box; } ' + 
33831				'.jstree li { line-height:12px; } ' + // WHY??
33832				'.jstree a { display:-moz-inline-box; } ' + 
33833				'.jstree .jstree-no-icons .jstree-checkbox { display:-moz-inline-stack !important; } ';
33834				/* this shouldn't be here as it is theme specific */
33835		}
33836		// the default stylesheet
33837		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
33838	});
33839
33840	// core functions (open, close, create, update, delete)
33841	$.jstree.plugin("core", {
33842		__init : function () {
33843			this.data.core.locked = false;
33844			this.data.core.to_open = this.get_settings().core.initially_open;
33845			this.data.core.to_load = this.get_settings().core.initially_load;
33846		},
33847		defaults : { 
33848			html_titles	: false,
33849			animation	: 500,
33850			initially_open : [],
33851			initially_load : [],
33852			open_parents : true,
33853			notify_plugins : true,
33854			rtl			: false,
33855			load_open	: false,
33856			strings		: {
33857				loading		: "Loading ...",
33858				new_node	: "New node",
33859				multiple_selection : "Multiple selection"
33860			}
33861		},
33862		_fn : { 
33863			init	: function () { 
33864				this.set_focus(); 
33865				if(this._get_settings().core.rtl) {
33866					this.get_container().addClass("jstree-rtl").css("direction", "rtl");
33867				}
33868				this.get_container().html("<ul><li class='jstree-last jstree-leaf'><ins>&#160;</ins><a class='jstree-loading' href='#'><ins class='jstree-icon'>&#160;</ins>" + this._get_string("loading") + "</a></li></ul>");
33869				this.data.core.li_height = this.get_container_ul().find("li.jstree-closed, li.jstree-leaf").eq(0).height() || 18;
33870
33871				this.get_container()
33872					.delegate("li > ins", "click.jstree", $.proxy(function (event) {
33873							var trgt = $(event.target);
33874							// if(trgt.is("ins") && event.pageY - trgt.offset().top < this.data.core.li_height) { this.toggle_node(trgt); }
33875							this.toggle_node(trgt);
33876						}, this))
33877					.bind("mousedown.jstree", $.proxy(function () { 
33878							this.set_focus(); // This used to be setTimeout(set_focus,0) - why?
33879						}, this))
33880					.bind("dblclick.jstree", function (event) { 
33881						var sel;
33882						if(document.selection && document.selection.empty) { document.selection.empty(); }
33883						else {
33884							if(window.getSelection) {
33885								sel = window.getSelection();
33886								try { 
33887									sel.removeAllRanges();
33888									sel.collapse();
33889								} catch (err) { }
33890							}
33891						}
33892					});
33893				if(this._get_settings().core.notify_plugins) {
33894					this.get_container()
33895						.bind("load_node.jstree", $.proxy(function (e, data) { 
33896								var o = this._get_node(data.rslt.obj),
33897									t = this;
33898								if(o === -1) { o = this.get_container_ul(); }
33899								if(!o.length) { return; }
33900								o.find("li").each(function () {
33901									var th = $(this);
33902									if(th.data("jstree")) {
33903										$.each(th.data("jstree"), function (plugin, values) {
33904											if(t.data[plugin] && $.isFunction(t["_" + plugin + "_notify"])) {
33905												t["_" + plugin + "_notify"].call(t, th, values);
33906											}
33907										});
33908									}
33909								});
33910							}, this));
33911				}
33912				if(this._get_settings().core.load_open) {
33913					this.get_container()
33914						.bind("load_node.jstree", $.proxy(function (e, data) { 
33915								var o = this._get_node(data.rslt.obj),
33916									t = this;
33917								if(o === -1) { o = this.get_container_ul(); }
33918								if(!o.length) { return; }
33919								o.find("li.jstree-open:not(:has(ul))").each(function () {
33920									t.load_node(this, $.noop, $.noop);
33921								});
33922							}, this));
33923				}
33924				this.__callback();
33925				this.load_node(-1, function () { this.loaded(); this.reload_nodes(); });
33926			},
33927			destroy	: function () { 
33928				var i,
33929					n = this.get_index(),
33930					s = this._get_settings(),
33931					_this = this;
33932
33933				$.each(s.plugins, function (i, val) {
33934					try { plugins[val].__destroy.apply(_this); } catch(err) { }
33935				});
33936				this.__callback();
33937				// set focus to another instance if this one is focused
33938				if(this.is_focused()) { 
33939					for(i in instances) { 
33940						if(instances.hasOwnProperty(i) && i != n) { 
33941							instances[i].set_focus(); 
33942							break; 
33943						} 
33944					}
33945				}
33946				// if no other instance found
33947				if(n === focused_instance) { focused_instance = -1; }
33948				// remove all traces of jstree in the DOM (only the ones set using jstree*) and cleans all events
33949				this.get_container()
33950					.unbind(".jstree")
33951					.undelegate(".jstree")
33952					.removeData("jstree_instance_id")
33953					.find("[class^='jstree']")
33954						.andSelf()
33955						.attr("class", function () { return this.className.replace(/jstree[^ ]*|$/ig,''); });
33956				$(document)
33957					.unbind(".jstree-" + n)
33958					.undelegate(".jstree-" + n);
33959				// remove the actual data
33960				instances[n] = null;
33961				delete instances[n];
33962			},
33963
33964			_core_notify : function (n, data) {
33965				if(data.opened) {
33966					this.open_node(n, false, true);
33967				}
33968			},
33969
33970			lock : function () {
33971				this.data.core.locked = true;
33972				this.get_container().children("ul").addClass("jstree-locked").css("opacity","0.7");
33973				this.__callback({});
33974			},
33975			unlock : function () {
33976				this.data.core.locked = false;
33977				this.get_container().children("ul").removeClass("jstree-locked").css("opacity","1");
33978				this.__callback({});
33979			},
33980			is_locked : function () { return this.data.core.locked; },
33981			save_opened : function () {
33982				var _this = this;
33983				this.data.core.to_open = [];
33984				this.get_container_ul().find("li.jstree-open").each(function () { 
33985					if(this.id) { _this.data.core.to_open.push("#" + this.id.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:")); }
33986				});
33987				this.__callback(_this.data.core.to_open);
33988			},
33989			save_loaded : function () { },
33990			reload_nodes : function (is_callback) {
33991				var _this = this,
33992					done = true,
33993					current = [],
33994					remaining = [];
33995				if(!is_callback) { 
33996					this.data.core.reopen = false; 
33997					this.data.core.refreshing = true; 
33998					this.data.core.to_open = $.map($.makeArray(this.data.core.to_open), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); });
33999					this.data.core.to_load = $.map($.makeArray(this.data.core.to_load), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); });
34000					if(this.data.core.to_open.length) {
34001						this.data.core.to_load = this.data.core.to_load.concat(this.data.core.to_open);
34002					}
34003				}
34004				if(this.data.core.to_load.length) {
34005					$.each(this.data.core.to_load, function (i, val) {
34006						if(val == "#") { return true; }
34007						if($(val).length) { current.push(val); }
34008						else { remaining.push(val); }
34009					});
34010					if(current.length) {
34011						this.data.core.to_load = remaining;
34012						$.each(current, function (i, val) { 
34013							if(!_this._is_loaded(val)) {
34014								_this.load_node(val, function () { _this.reload_nodes(true); }, function () { _this.reload_nodes(true); });
34015								done = false;
34016							}
34017						});
34018					}
34019				}
34020				if(this.data.core.to_open.length) {
34021					$.each(this.data.core.to_open, function (i, val) {
34022						_this.open_node(val, false, true); 
34023					});
34024				}
34025				if(done) { 
34026					// TODO: find a more elegant approach to syncronizing returning requests
34027					if(this.data.core.reopen) { clearTimeout(this.data.core.reopen); }
34028					this.data.core.reopen = setTimeout(function () { _this.__callback({}, _this); }, 50);
34029					this.data.core.refreshing = false;
34030					this.reopen();
34031				}
34032			},
34033			reopen : function () {
34034				var _this = this;
34035				if(this.data.core.to_open.length) {
34036					$.each(this.data.core.to_open, function (i, val) {
34037						_this.open_node(val, false, true); 
34038					});
34039				}
34040				this.__callback({});
34041			},
34042			refresh : function (obj) {
34043				var _this = this;
34044				this.save_opened();
34045				if(!obj) { obj = -1; }
34046				obj = this._get_node(obj);
34047				if(!obj) { obj = -1; }
34048				if(obj !== -1) { obj.children("UL").remove(); }
34049				else { this.get_container_ul().empty(); }
34050				this.load_node(obj, function () { _this.__callback({ "obj" : obj}); _this.reload_nodes(); });
34051			},
34052			// Dummy function to fire after the first load (so that there is a jstree.loaded event)
34053			loaded	: function () { 
34054				this.__callback(); 
34055			},
34056			// deal with focus
34057			set_focus	: function () { 
34058				if(this.is_focused()) { return; }
34059				var f = $.jstree._focused();
34060				if(f) { f.unset_focus(); }
34061
34062				this.get_container().addClass("jstree-focused"); 
34063				focused_instance = this.get_index(); 
34064				this.__callback();
34065			},
34066			is_focused	: function () { 
34067				return focused_instance == this.get_index(); 
34068			},
34069			unset_focus	: function () {
34070				if(this.is_focused()) {
34071					this.get_container().removeClass("jstree-focused"); 
34072					focused_instance = -1; 
34073				}
34074				this.__callback();
34075			},
34076
34077			// traverse
34078			_get_node		: function (obj) { 
34079				var $obj = $(obj, this.get_container()); 
34080				if($obj.is(".jstree") || obj == -1) { return -1; } 
34081				$obj = $obj.closest("li", this.get_container()); 
34082				return $obj.length ? $obj : false; 
34083			},
34084			_get_next		: function (obj, strict) {
34085				obj = this._get_node(obj);
34086				if(obj === -1) { return this.get_container().find("> ul > li:first-child"); }
34087				if(!obj.length) { return false; }
34088				if(strict) { return (obj.nextAll("li").size() > 0) ? obj.nextAll("li:eq(0)") : false; }
34089
34090				if(obj.hasClass("jstree-open")) { return obj.find("li:eq(0)"); }
34091				else if(obj.nextAll("li").size() > 0) { return obj.nextAll("li:eq(0)"); }
34092				else { return obj.parentsUntil(".jstree","li").next("li").eq(0); }
34093			},
34094			_get_prev		: function (obj, strict) {
34095				obj = this._get_node(obj);
34096				if(obj === -1) { return this.get_container().find("> ul > li:last-child"); }
34097				if(!obj.length) { return false; }
34098				if(strict) { return (obj.prevAll("li").length > 0) ? obj.prevAll("li:eq(0)") : false; }
34099
34100				if(obj.prev("li").length) {
34101					obj = obj.prev("li").eq(0);
34102					while(obj.hasClass("jstree-open")) { obj = obj.children("ul:eq(0)").children("li:last"); }
34103					return obj;
34104				}
34105				else { var o = obj.parentsUntil(".jstree","li:eq(0)"); return o.length ? o : false; }
34106			},
34107			_get_parent		: function (obj) {
34108				obj = this._get_node(obj);
34109				if(obj == -1 || !obj.length) { return false; }
34110				var o = obj.parentsUntil(".jstree", "li:eq(0)");
34111				return o.length ? o : -1;
34112			},
34113			_get_children	: function (obj) {
34114				obj = this._get_node(obj);
34115				if(obj === -1) { return this.get_container().children("ul:eq(0)").children("li"); }
34116				if(!obj.length) { return false; }
34117				return obj.children("ul:eq(0)").children("li");
34118			},
34119			get_path		: function (obj, id_mode) {
34120				var p = [],
34121					_this = this;
34122				obj = this._get_node(obj);
34123				if(obj === -1 || !obj || !obj.length) { return false; }
34124				obj.parentsUntil(".jstree", "li").each(function () {
34125					p.push( id_mode ? this.id : _this.get_text(this) );
34126				});
34127				p.reverse();
34128				p.push( id_mode ? obj.attr("id") : this.get_text(obj) );
34129				return p;
34130			},
34131
34132			// string functions
34133			_get_string : function (key) {
34134				return this._get_settings().core.strings[key] || key;
34135			},
34136
34137			is_open		: function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-open"); },
34138			is_closed	: function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-closed"); },
34139			is_leaf		: function (obj) { obj = this._get_node(obj); return obj && obj !== -1 && obj.hasClass("jstree-leaf"); },
34140			correct_state	: function (obj) {
34141				obj = this._get_node(obj);
34142				if(!obj || obj === -1) { return false; }
34143				obj.removeClass("jstree-closed jstree-open").addClass("jstree-leaf").children("ul").remove();
34144				this.__callback({ "obj" : obj });
34145			},
34146			// open/close
34147			open_node	: function (obj, callback, skip_animation) {
34148
34149				obj = this._get_node(obj);
34150				if(!obj.length) { return false; }
34151				if(!obj.hasClass("jstree-closed")) { if(callback) { callback.call(); } return false; }
34152				var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation,
34153					t = this;
34154				if(!this._is_loaded(obj)) {
34155					obj.children("a").addClass("jstree-loading");
34156					this.load_node(obj, function () { t.open_node(obj, callback, skip_animation); }, callback);
34157				}
34158				else {
34159					if(this._get_settings().core.open_parents) {
34160						obj.parentsUntil(".jstree",".jstree-closed").each(function () {
34161							t.open_node(this, false, true);
34162						});
34163					}
34164					if(s) { obj.children("ul").css("display","none"); }
34165					obj.removeClass("jstree-closed").addClass("jstree-open").children("a").removeClass("jstree-loading");
34166					if(s) { obj.children("ul").stop(true, true).slideDown(s, function () { this.style.display = ""; t.after_open(obj); }); }
34167					else { t.after_open(obj); }
34168					this.__callback({ "obj" : obj });
34169					if(callback) { callback.call(); }
34170				}
34171			},
34172			after_open	: function (obj) { this.__callback({ "obj" : obj }); },
34173			close_node	: function (obj, skip_animation) {
34174				obj = this._get_node(obj);
34175				var s = skip_animation || is_ie6 ? 0 : this._get_settings().core.animation,
34176					t = this;
34177				if(!obj.length || !obj.hasClass("jstree-open")) { return false; }
34178				if(s) { obj.children("ul").attr("style","display:block !important"); }
34179				obj.removeClass("jstree-open").addClass("jstree-closed");
34180				if(s) { obj.children("ul").stop(true, true).slideUp(s, function () { this.style.display = ""; t.after_close(obj); }); }
34181				else { t.after_close(obj); }
34182				this.__callback({ "obj" : obj });
34183			},
34184			after_close	: function (obj) { this.__callback({ "obj" : obj }); },
34185			toggle_node	: function (obj) {
34186				obj = this._get_node(obj);
34187				if(obj.hasClass("jstree-closed")) { return this.open_node(obj); }
34188				if(obj.hasClass("jstree-open")) { return this.close_node(obj); }
34189			},
34190			open_all	: function (obj, do_animation, original_obj) {
34191				obj = obj ? this._get_node(obj) : -1;
34192				if(!obj || obj === -1) { obj = this.get_container_ul(); }
34193				if(original_obj) { 
34194					obj = obj.find("li.jstree-closed");
34195				}
34196				else {
34197					original_obj = obj;
34198					if(obj.is(".jstree-closed")) { obj = obj.find("li.jstree-closed").andSelf(); }
34199					else { obj = obj.find("li.jstree-closed"); }
34200				}
34201				var _this = this;
34202				obj.each(function () { 
34203					var __this = this; 
34204					if(!_this._is_loaded(this)) { _this.open_node(this, function() { _this.open_all(__this, do_animation, original_obj); }, !do_animation); }
34205					else { _this.open_node(this, false, !do_animation); }
34206				});
34207				// so that callback is fired AFTER all nodes are open
34208				if(original_obj.find('li.jstree-closed').length === 0) { this.__callback({ "obj" : original_obj }); }
34209			},
34210			close_all	: function (obj, do_animation) {
34211				var _this = this;
34212				obj = obj ? this._get_node(obj) : this.get_container();
34213				if(!obj || obj === -1) { obj = this.get_container_ul(); }
34214				obj.find("li.jstree-open").andSelf().each(function () { _this.close_node(this, !do_animation); });
34215				this.__callback({ "obj" : obj });
34216			},
34217			clean_node	: function (obj) {
34218				obj = obj && obj != -1 ? $(obj) : this.get_container_ul();
34219				obj = obj.is("li") ? obj.find("li").andSelf() : obj.find("li");
34220				obj.removeClass("jstree-last")
34221					.filter("li:last-child").addClass("jstree-last").end()
34222					.filter(":has(li)")
34223						.not(".jstree-open").removeClass("jstree-leaf").addClass("jstree-closed");
34224				obj.not(".jstree-open, .jstree-closed").addClass("jstree-leaf").children("ul").remove();
34225				this.__callback({ "obj" : obj });
34226			},
34227			// rollback
34228			get_rollback : function () { 
34229				this.__callback();
34230				return { i : this.get_index(), h : this.get_container().children("ul").clone(true), d : this.data }; 
34231			},
34232			set_rollback : function (html, data) {
34233				this.get_container().empty().append(html);
34234				this.data = data;
34235				this.__callback();
34236			},
34237			// Dummy functions to be overwritten by any datastore plugin included
34238			load_node	: function (obj, s_call, e_call) { this.__callback({ "obj" : obj }); },
34239			_is_loaded	: function (obj) { return true; },
34240
34241			// Basic operations: create
34242			create_node	: function (obj, position, js, callback, is_loaded) {
34243				obj = this._get_node(obj);
34244				position = typeof position === "undefined" ? "last" : position;
34245				var d = $("<li />"),
34246					s = this._get_settings().core,
34247					tmp;
34248
34249				if(obj !== -1 && !obj.length) { return false; }
34250				if(!is_loaded && !this._is_loaded(obj)) { this.load_node(obj, function () { this.create_node(obj, position, js, callback, true); }); return false; }
34251
34252				this.__rollback();
34253
34254				if(typeof js === "string") { js = { "data" : js }; }
34255				if(!js) { js = {}; }
34256				if(js.attr) { d.attr(js.attr); }
34257				if(js.metadata) { d.data(js.metadata); }
34258				if(js.state) { d.addClass("jstree-" + js.state); }
34259				if(!js.data) { js.data = this._get_string("new_node"); }
34260				if(!$.isArray(js.data)) { tmp = js.data; js.data = []; js.data.push(tmp); }
34261				$.each(js.data, function (i, m) {
34262					tmp = $("<a />");
34263					if($.isFunction(m)) { m = m.call(this, js); }
34264					if(typeof m == "string") { tmp.attr('href','#')[ s.html_titles ? "html" : "text" ](m); }
34265					else {
34266						if(!m.attr) { m.attr = {}; }
34267						if(!m.attr.href) { m.attr.href = '#'; }
34268						tmp.attr(m.attr)[ s.html_titles ? "html" : "text" ](m.title);
34269						if(m.language) { tmp.addClass(m.language); }
34270					}
34271					tmp.prepend("<ins class='jstree-icon'>&#160;</ins>");
34272					if(!m.icon && js.icon) { m.icon = js.icon; }
34273					if(m.icon) { 
34274						if(m.icon.indexOf("/") === -1) { tmp.children("ins").addClass(m.icon); }
34275						else { tmp.children("ins").css("background","url('" + m.icon + "') center center no-repeat"); }
34276					}
34277					d.append(tmp);
34278				});
34279				d.prepend("<ins class='jstree-icon'>&#160;</ins>");
34280				if(obj === -1) {
34281					obj = this.get_container();
34282					if(position === "before") { position = "first"; }
34283					if(position === "after") { position = "last"; }
34284				}
34285				switch(position) {
34286					case "before": obj.before(d); tmp = this._get_parent(obj); break;
34287					case "after" : obj.after(d);  tmp = this._get_parent(obj); break;
34288					case "inside":
34289					case "first" :
34290						if(!obj.children("ul").length) { obj.append("<ul />"); }
34291						obj.children("ul").prepend(d);
34292						tmp = obj;
34293						break;
34294					case "last":
34295						if(!obj.children("ul").length) { obj.append("<ul />"); }
34296						obj.children("ul").append(d);
34297						tmp = obj;
34298						break;
34299					default:
34300						if(!obj.children("ul").length) { obj.append("<ul />"); }
34301						if(!position) { position = 0; }
34302						tmp = obj.children("ul").children("li").eq(position);
34303						if(tmp.length) { tmp.before(d); }
34304						else { obj.children("ul").append(d); }
34305						tmp = obj;
34306						break;
34307				}
34308				if(tmp === -1 || tmp.get(0) === this.get_container().get(0)) { tmp = -1; }
34309				this.clean_node(tmp);
34310				this.__callback({ "obj" : d, "parent" : tmp });
34311				if(callback) { callback.call(this, d); }
34312				return d;
34313			},
34314			// Basic operations: rename (deal with text)
34315			get_text	: function (obj) {
34316				obj = this._get_node(obj);
34317				if(!obj.length) { return false; }
34318				var s = this._get_settings().core.html_titles;
34319				obj = obj.children("a:eq(0)");
34320				if(s) {
34321					obj = obj.clone();
34322					obj.children("INS").remove();
34323					return obj.html();
34324				}
34325				else {
34326					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
34327					return obj.nodeValue;
34328				}
34329			},
34330			set_text	: function (obj, val) {
34331				obj = this._get_node(obj);
34332				if(!obj.length) { return false; }
34333				obj = obj.children("a:eq(0)");
34334				if(this._get_settings().core.html_titles) {
34335					var tmp = obj.children("INS").clone();
34336					obj.html(val).prepend(tmp);
34337					this.__callback({ "obj" : obj, "name" : val });
34338					return true;
34339				}
34340				else {
34341					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
34342					this.__callback({ "obj" : obj, "name" : val });
34343					return (obj.nodeValue = val);
34344				}
34345			},
34346			rename_node : function (obj, val) {
34347				obj = this._get_node(obj);
34348				this.__rollback();
34349				if(obj && obj.length && this.set_text.apply(this, Array.prototype.slice.call(arguments))) { this.__callback({ "obj" : obj, "name" : val }); }
34350			},
34351			// Basic operations: deleting nodes
34352			delete_node : function (obj) {
34353				obj = this._get_node(obj);
34354				if(!obj.length) { return false; }
34355				this.__rollback();
34356				var p = this._get_parent(obj), prev = $([]), t = this;
34357				obj.each(function () {
34358					prev = prev.add(t._get_prev(this));
34359				});
34360				obj = obj.detach();
34361				if(p !== -1 && p.find("> ul > li").length === 0) {
34362					p.removeClass("jstree-open jstree-closed").addClass("jstree-leaf");
34363				}
34364				this.clean_node(p);
34365				this.__callback({ "obj" : obj, "prev" : prev, "parent" : p });
34366				return obj;
34367			},
34368			prepare_move : function (o, r, pos, cb, is_cb) {
34369				var p = {};
34370
34371				p.ot = $.jstree._reference(o) || this;
34372				p.o = p.ot._get_node(o);
34373				p.r = r === - 1 ? -1 : this._get_node(r);
34374				p.p = (typeof pos === "undefined" || pos === false) ? "last" : pos; // TODO: move to a setting
34375				if(!is_cb && prepared_move.o && prepared_move.o[0] === p.o[0] && prepared_move.r[0] === p.r[0] && prepared_move.p === p.p) {
34376					this.__callback(prepared_move);
34377					if(cb) { cb.call(this, prepared_move); }
34378					return;
34379				}
34380				p.ot = $.jstree._reference(p.o) || this;
34381				p.rt = $.jstree._reference(p.r) || this; // r === -1 ? p.ot : $.jstree._reference(p.r) || this
34382				if(p.r === -1 || !p.r) {
34383					p.cr = -1;
34384					switch(p.p) {
34385						case "first":
34386						case "before":
34387						case "inside":
34388							p.cp = 0; 
34389							break;
34390						case "after":
34391						case "last":
34392							p.cp = p.rt.get_container().find(" > ul > li").length; 
34393							break;
34394						default:
34395							p.cp = p.p;
34396							break;
34397					}
34398				}
34399				else {
34400					if(!/^(before|after)$/.test(p.p) && !this._is_loaded(p.r)) {
34401						return this.load_node(p.r, function () { this.prepare_move(o, r, pos, cb, true); });
34402					}
34403					switch(p.p) {
34404						case "before":
34405							p.cp = p.r.index();
34406							p.cr = p.rt._get_parent(p.r);
34407							break;
34408						case "after":
34409							p.cp = p.r.index() + 1;
34410							p.cr = p.rt._get_parent(p.r);
34411							break;
34412						case "inside":
34413						case "first":
34414							p.cp = 0;
34415							p.cr = p.r;
34416							break;
34417						case "last":
34418							p.cp = p.r.find(" > ul > li").length; 
34419							p.cr = p.r;
34420							break;
34421						default: 
34422							p.cp = p.p;
34423							p.cr = p.r;
34424							break;
34425					}
34426				}
34427				p.np = p.cr == -1 ? p.rt.get_container() : p.cr;
34428				p.op = p.ot._get_parent(p.o);
34429				p.cop = p.o.index();
34430				if(p.op === -1) { p.op = p.ot ? p.ot.get_container() : this.get_container(); }
34431				if(!/^(before|after)$/.test(p.p) && p.op && p.np && p.op[0] === p.np[0] && p.o.index() < p.cp) { p.cp++; }
34432				//if(p.p === "before" && p.op && p.np && p.op[0] === p.np[0] && p.o.index() < p.cp) { p.cp--; }
34433				p.or = p.np.find(" > ul > li:nth-child(" + (p.cp + 1) + ")");
34434				prepared_move = p;
34435				this.__callback(prepared_move);
34436				if(cb) { cb.call(this, prepared_move); }
34437			},
34438			check_move : function () {
34439				var obj = prepared_move, ret = true, r = obj.r === -1 ? this.get_container() : obj.r;
34440				if(!obj || !obj.o || obj.or[0] === obj.o[0]) { return false; }
34441				if(obj.op && obj.np && obj.op[0] === obj.np[0] && obj.cp - 1 === obj.o.index()) { return false; }
34442				obj.o.each(function () { 
34443					if(r.parentsUntil(".jstree", "li").andSelf().index(this) !== -1) { ret = false; return false; }
34444				});
34445				return ret;
34446			},
34447			move_node : function (obj, ref, position, is_copy, is_prepared, skip_check) {
34448				if(!is_prepared) { 
34449					return this.prepare_move(obj, ref, position, function (p) {
34450						this.move_node(p, false, false, is_copy, true, skip_check);
34451					});
34452				}
34453				if(is_copy) { 
34454					prepared_move.cy = true;
34455				}
34456				if(!skip_check && !this.check_move()) { return false; }
34457
34458				this.__rollback();
34459				var o = false;
34460				if(is_copy) {
34461					o = obj.o.clone(true);
34462					o.find("*[id]").andSelf().each(function () {
34463						if(this.id) { this.id = "copy_" + this.id; }
34464					});
34465				}
34466				else { o = obj.o; }
34467
34468				if(obj.or.length) { obj.or.before(o); }
34469				else { 
34470					if(!obj.np.children("ul").length) { $("<ul />").appendTo(obj.np); }
34471					obj.np.children("ul:eq(0)").append(o); 
34472				}
34473
34474				try { 
34475					obj.ot.clean_node(obj.op);
34476					obj.rt.clean_node(obj.np);
34477					if(!obj.op.find("> ul > li").length) {
34478						obj.op.removeClass("jstree-open jstree-closed").addClass("jstree-leaf").children("ul").remove();
34479					}
34480				} catch (e) { }
34481
34482				if(is_copy) { 
34483					prepared_move.cy = true;
34484					prepared_move.oc = o; 
34485				}
34486				this.__callback(prepared_move);
34487				return prepared_move;
34488			},
34489			_get_move : function () { return prepared_move; }
34490		}
34491	});
34492})(jQuery);
34493//*/
34494
34495/* 
34496 * jsTree ui plugin
34497 * This plugins handles selecting/deselecting/hovering/dehovering nodes
34498 */
34499(function ($) {
34500	var scrollbar_width, e1, e2;
34501	$(function() {
34502		if (/msie/.test(navigator.userAgent.toLowerCase())) {
34503			e1 = $('<textarea cols="10" rows="2"></textarea>').css({ position: 'absolute', top: -1000, left: 0 }).appendTo('body');
34504			e2 = $('<textarea cols="10" rows="2" style="overflow: hidden;"></textarea>').css({ position: 'absolute', top: -1000, left: 0 }).appendTo('body');
34505			scrollbar_width = e1.width() - e2.width();
34506			e1.add(e2).remove();
34507		} 
34508		else {
34509			e1 = $('<div />').css({ width: 100, height: 100, overflow: 'auto', position: 'absolute', top: -1000, left: 0 })
34510					.prependTo('body').append('<div />').find('div').css({ width: '100%', height: 200 });
34511			scrollbar_width = 100 - e1.width();
34512			e1.parent().remove();
34513		}
34514	});
34515	$.jstree.plugin("ui", {
34516		__init : function () { 
34517			this.data.ui.selected = $(); 
34518			this.data.ui.last_selected = false; 
34519			this.data.ui.hovered = null;
34520			this.data.ui.to_select = this.get_settings().ui.initially_select;
34521
34522			this.get_container()
34523				.delegate("a", "click.jstree", $.proxy(function (event) {
34524						event.preventDefault();
34525						event.currentTarget.blur();
34526						if(!$(event.currentTarget).hasClass("jstree-loading")) {
34527							this.select_node(event.currentTarget, true, event);
34528						}
34529					}, this))
34530				.delegate("a", "mouseenter.jstree", $.proxy(function (event) {
34531						if(!$(event.currentTarget).hasClass("jstree-loading")) {
34532							this.hover_node(event.target);
34533						}
34534					}, this))
34535				.delegate("a", "mouseleave.jstree", $.proxy(function (event) {
34536						if(!$(event.currentTarget).hasClass("jstree-loading")) {
34537							this.dehover_node(event.target);
34538						}
34539					}, this))
34540				.bind("reopen.jstree", $.proxy(function () { 
34541						this.reselect();
34542					}, this))
34543				.bind("get_rollback.jstree", $.proxy(function () { 
34544						this.dehover_node();
34545						this.save_selected();
34546					}, this))
34547				.bind("set_rollback.jstree", $.proxy(function () { 
34548						this.reselect();
34549					}, this))
34550				.bind("close_node.jstree", $.proxy(function (event, data) { 
34551						var s = this._get_settings().ui,
34552							obj = this._get_node(data.rslt.obj),
34553							clk = (obj && obj.length) ? obj.children("ul").find("a.jstree-clicked") : $(),
34554							_this = this;
34555						if(s.selected_parent_close === false || !clk.length) { return; }
34556						clk.each(function () { 
34557							_this.deselect_node(this);
34558							if(s.selected_parent_close === "select_parent") { _this.select_node(obj); }
34559						});
34560					}, this))
34561				.bind("delete_node.jstree", $.proxy(function (event, data) { 
34562						var s = this._get_settings().ui.select_prev_on_delete,
34563							obj = this._get_node(data.rslt.obj),
34564							clk = (obj && obj.length) ? obj.find("a.jstree-clicked") : [],
34565							_this = this;
34566						clk.each(function () { _this.deselect_node(this); });
34567						if(s && clk.length) { 
34568							data.rslt.prev.each(function () { 
34569								if(this.parentNode) { _this.select_node(this); return false; /* if return false is removed all prev nodes will be selected */}
34570							});
34571						}
34572					}, this))
34573				.bind("move_node.jstree", $.proxy(function (event, data) { 
34574						if(data.rslt.cy) { 
34575							data.rslt.oc.find("a.jstree-clicked").removeClass("jstree-clicked");
34576						}
34577					}, this));
34578		},
34579		defaults : {
34580			select_limit : -1, // 0, 1, 2 ... or -1 for unlimited
34581			select_multiple_modifier : "ctrl", // on, or ctrl, shift, alt
34582			select_range_modifier : "shift",
34583			selected_parent_close : "select_parent", // false, "deselect", "select_parent"
34584			selected_parent_open : true,
34585			select_prev_on_delete : true,
34586			disable_selecting_children : false,
34587			initially_select : []
34588		},
34589		_fn : { 
34590			_get_node : function (obj, allow_multiple) {
34591				if(typeof obj === "undefined" || obj === null) { return allow_multiple ? this.data.ui.selected : this.data.ui.last_selected; }
34592				var $obj = $(obj, this.get_container()); 
34593				if($obj.is(".jstree") || obj == -1) { return -1; } 
34594				$obj = $obj.closest("li", this.get_container()); 
34595				return $obj.length ? $obj : false; 
34596			},
34597			_ui_notify : function (n, data) {
34598				if(data.selected) {
34599					this.select_node(n, false);
34600				}
34601			},
34602			save_selected : function () {
34603				var _this = this;
34604				this.data.ui.to_select = [];
34605				this.data.ui.selected.each(function () { if(this.id) { _this.data.ui.to_select.push("#" + this.id.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:")); } });
34606				this.__callback(this.data.ui.to_select);
34607			},
34608			reselect : function () {
34609				var _this = this,
34610					s = this.data.ui.to_select;
34611				s = $.map($.makeArray(s), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); });
34612				// this.deselect_all(); WHY deselect, breaks plugin state notifier?
34613				$.each(s, function (i, val) { if(val && val !== "#") { _this.select_node(val); } });
34614				this.data.ui.selected = this.data.ui.selected.filter(function () { return this.parentNode; });
34615				this.__callback();
34616			},
34617			refresh : function (obj) {
34618				this.save_selected();
34619				return this.__call_old();
34620			},
34621			hover_node : function (obj) {
34622				obj = this._get_node(obj);
34623				if(!obj.length) { return false; }
34624				//if(this.data.ui.hovered && obj.get(0) === this.data.ui.hovered.get(0)) { return; }
34625				if(!obj.hasClass("jstree-hovered")) { this.dehover_node(); }
34626				this.data.ui.hovered = obj.children("a").addClass("jstree-hovered").parent();
34627				this._fix_scroll(obj);
34628				this.__callback({ "obj" : obj });
34629			},
34630			dehover_node : function () {
34631				var obj = this.data.ui.hovered, p;
34632				if(!obj || !obj.length) { return false; }
34633				p = obj.children("a").removeClass("jstree-hovered").parent();
34634				if(this.data.ui.hovered[0] === p[0]) { this.data.ui.hovered = null; }
34635				this.__callback({ "obj" : obj });
34636			},
34637			select_node : function (obj, check, e) {
34638				obj = this._get_node(obj);
34639				if(obj == -1 || !obj || !obj.length) { return false; }
34640				var s = this._get_settings().ui,
34641					is_multiple = (s.select_multiple_modifier == "on" || (s.select_multiple_modifier !== false && e && e[s.select_multiple_modifier + "Key"])),
34642					is_range = (s.select_range_modifier !== false && e && e[s.select_range_modifier + "Key"] && this.data.ui.last_selected && this.data.ui.last_selected[0] !== obj[0] && this.data.ui.last_selected.parent()[0] === obj.parent()[0]),
34643					is_selected = this.is_selected(obj),
34644					proceed = true,
34645					t = this;
34646				if(check) {
34647					if(s.disable_selecting_children && is_multiple && 
34648						(
34649							(obj.parentsUntil(".jstree","li").children("a.jstree-clicked").length) ||
34650							(obj.children("ul").find("a.jstree-clicked:eq(0)").length)
34651						)
34652					) {
34653						return false;
34654					}
34655					proceed = false;
34656					switch(!0) {
34657						case (is_range):
34658							this.data.ui.last_selected.addClass("jstree-last-selected");
34659							obj = obj[ obj.index() < this.data.ui.last_selected.index() ? "nextUntil" : "prevUntil" ](".jstree-last-selected").andSelf();
34660							if(s.select_limit == -1 || obj.length < s.select_limit) {
34661								this.data.ui.last_selected.removeClass("jstree-last-selected");
34662								this.data.ui.selected.each(function () {
34663									if(this !== t.data.ui.last_selected[0]) { t.deselect_node(this); }
34664								});
34665								is_selected = false;
34666								proceed = true;
34667							}
34668							else {
34669								proceed = false;
34670							}
34671							break;
34672						case (is_selected && !is_multiple): 
34673							this.deselect_all();
34674							is_selected = false;
34675							proceed = true;
34676							break;
34677						case (!is_selected && !is_multiple): 
34678							if(s.select_limit == -1 || s.select_limit > 0) {
34679								this.deselect_all();
34680								proceed = true;
34681							}
34682							break;
34683						case (is_selected && is_multiple): 
34684							this.deselect_node(obj);
34685							break;
34686						case (!is_selected && is_multiple): 
34687							if(s.select_limit == -1 || this.data.ui.selected.length + 1 <= s.select_limit) { 
34688								proceed = true;
34689							}
34690							break;
34691					}
34692				}
34693				if(proceed && !is_selected) {
34694					if(!is_range) { this.data.ui.last_selected = obj; }
34695					obj.children("a").addClass("jstree-clicked");
34696					if(s.selected_parent_open) {
34697						obj.parents(".jstree-closed").each(function () { t.open_node(this, false, true); });
34698					}
34699					this.data.ui.selected = this.data.ui.selected.add(obj);
34700					this._fix_scroll(obj.eq(0));
34701					this.__callback({ "obj" : obj, "e" : e });
34702				}
34703			},
34704			_fix_scroll : function (obj) {
34705				var c = this.get_container()[0], t;
34706				if(c.scrollHeight > c.offsetHeight) {
34707					obj = this._get_node(obj);
34708					if(!obj || obj === -1 || !obj.length || !obj.is(":visible")) { return; }
34709					t = obj.offset().top - this.get_container().offset().top;
34710					if(t < 0) { 
34711						c.scrollTop = c.scrollTop + t - 1; 
34712					}
34713					if(t + this.data.core.li_height + (c.scrollWidth > c.offsetWidth ? scrollbar_width : 0) > c.offsetHeight) { 
34714						c.scrollTop = c.scrollTop + (t - c.offsetHeight + this.data.core.li_height + 1 + (c.scrollWidth > c.offsetWidth ? scrollbar_width : 0)); 
34715					}
34716				}
34717			},
34718			deselect_node : function (obj) {
34719				obj = this._get_node(obj);
34720				if(!obj.length) { return false; }
34721				if(this.is_selected(obj)) {
34722					obj.children("a").removeClass("jstree-clicked");
34723					this.data.ui.selected = this.data.ui.selected.not(obj);
34724					if(this.data.ui.last_selected.get(0) === obj.get(0)) { this.data.ui.last_selected = this.data.ui.selected.eq(0); }
34725					this.__callback({ "obj" : obj });
34726				}
34727			},
34728			toggle_select : function (obj) {
34729				obj = this._get_node(obj);
34730				if(!obj.length) { return false; }
34731				if(this.is_selected(obj)) { this.deselect_node(obj); }
34732				else { this.select_node(obj); }
34733			},
34734			is_selected : function (obj) { return this.data.ui.selected.index(this._get_node(obj)) >= 0; },
34735			get_selected : function (context) { 
34736				return context ? $(context).find("a.jstree-clicked").parent() : this.data.ui.selected; 
34737			},
34738			deselect_all : function (context) {
34739				var ret = context ? $(context).find("a.jstree-clicked").parent() : this.get_container().find("a.jstree-clicked").parent();
34740				ret.children("a.jstree-clicked").removeClass("jstree-clicked");
34741				this.data.ui.selected = $([]);
34742				this.data.ui.last_selected = false;
34743				this.__callback({ "obj" : ret });
34744			}
34745		}
34746	});
34747	// include the selection plugin by default
34748	$.jstree.defaults.plugins.push("ui");
34749})(jQuery);
34750//*/
34751
34752/* 
34753 * jsTree CRRM plugin
34754 * Handles creating/renaming/removing/moving nodes by user interaction.
34755 */
34756(function ($) {
34757	$.jstree.plugin("crrm", { 
34758		__init : function () {
34759			this.get_container()
34760				.bind("move_node.jstree", $.proxy(function (e, data) {
34761					if(this._get_settings().crrm.move.open_onmove) {
34762						var t = this;
34763						data.rslt.np.parentsUntil(".jstree").andSelf().filter(".jstree-closed").each(function () {
34764							t.open_node(this, false, true);
34765						});
34766					}
34767				}, this));
34768		},
34769		defaults : {
34770			input_width_limit : 200,
34771			move : {
34772				always_copy			: false, // false, true or "multitree"
34773				open_onmove			: true,
34774				default_position	: "last",
34775				check_move			: function (m) { return true; }
34776			}
34777		},
34778		_fn : {
34779			_show_input : function (obj, callback) {
34780				obj = this._get_node(obj);
34781				var rtl = this._get_settings().core.rtl,
34782					w = this._get_settings().crrm.input_width_limit,
34783					w1 = obj.children("ins").width(),
34784					w2 = obj.find("> a:visible > ins").width() * obj.find("> a:visible > ins").length,
34785					t = this.get_text(obj),
34786					h1 = $("<div />", { css : { "position" : "absolute", "top" : "-200px", "left" : (rtl ? "0px" : "-1000px"), "visibility" : "hidden" } }).appendTo("body"),
34787					h2 = obj.css("position","relative").append(
34788					$("<input />", { 
34789						"value" : t,
34790						"class" : "jstree-rename-input",
34791						// "size" : t.length,
34792						"css" : {
34793							"padding" : "0",
34794							"border" : "1px solid silver",
34795							"position" : "absolute",
34796							"left"  : (rtl ? "auto" : (w1 + w2 + 4) + "px"),
34797							"right" : (rtl ? (w1 + w2 + 4) + "px" : "auto"),
34798							"top" : "0px",
34799							"height" : (this.data.core.li_height - 2) + "px",
34800							"lineHeight" : (this.data.core.li_height - 2) + "px",
34801							"width" : "150px" // will be set a bit further down
34802						},
34803						"blur" : $.proxy(function () {
34804							var i = obj.children(".jstree-rename-input"),
34805								v = i.val();
34806							if(v === "") { v = t; }
34807							h1.remove();
34808							i.remove(); // rollback purposes
34809							this.set_text(obj,t); // rollback purposes
34810							this.rename_node(obj, v);
34811							callback.call(this, obj, v, t);
34812							obj.css("position","");
34813						}, this),
34814						"keyup" : function (event) {
34815							var key = event.keyCode || event.which;
34816							if(key == 27) { this.value = t; this.blur(); return; }
34817							else if(key == 13) { this.blur(); return; }
34818							else {
34819								h2.width(Math.min(h1.text("pW" + this.value).width(),w));
34820							}
34821						},
34822						"keypress" : function(event) {
34823							var key = event.keyCode || event.which;
34824							if(key == 13) { return false; }
34825						}
34826					})
34827				).children(".jstree-rename-input"); 
34828				this.set_text(obj, "");
34829				h1.css({
34830						fontFamily		: h2.css('fontFamily')		|| '',
34831						fontSize		: h2.css('fontSize')		|| '',
34832						fontWeight		: h2.css('fontWeight')		|| '',
34833						fontStyle		: h2.css('fontStyle')		|| '',
34834						fontStretch		: h2.css('fontStretch')		|| '',
34835						fontVariant		: h2.css('fontVariant')		|| '',
34836						letterSpacing	: h2.css('letterSpacing')	|| '',
34837						wordSpacing		: h2.css('wordSpacing')		|| ''
34838				});
34839				h2.width(Math.min(h1.text("pW" + h2[0].value).width(),w))[0].select();
34840			},
34841			rename : function (obj) {
34842				obj = this._get_node(obj);
34843				this.__rollback();
34844				var f = this.__callback;
34845				this._show_input(obj, function (obj, new_name, old_name) { 
34846					f.call(this, { "obj" : obj, "new_name" : new_name, "old_name" : old_name });
34847				});
34848			},
34849			create : function (obj, position, js, callback, skip_rename) {
34850				var t, _this = this;
34851				obj = this._get_node(obj);
34852				if(!obj) { obj = -1; }
34853				this.__rollback();
34854				t = this.create_node(obj, position, js, function (t) {
34855					var p = this._get_parent(t),
34856						pos = $(t).index();
34857					if(callback) { callback.call(this, t); }
34858					if(p.length && p.hasClass("jstree-closed")) { this.open_node(p, false, true); }
34859					if(!skip_rename) { 
34860						this._show_input(t, function (obj, new_name, old_name) { 
34861							_this.__callback({ "obj" : obj, "name" : new_name, "parent" : p, "position" : pos });
34862						});
34863					}
34864					else { _this.__callback({ "obj" : t, "name" : this.get_text(t), "parent" : p, "position" : pos }); }
34865				});
34866				return t;
34867			},
34868			remove : function (obj) {
34869				obj = this._get_node(obj, true);
34870				var p = this._get_parent(obj), prev = this._get_prev(obj);
34871				this.__rollback();
34872				obj = this.delete_node(obj);
34873				if(obj !== false) { this.__callback({ "obj" : obj, "prev" : prev, "parent" : p }); }
34874			},
34875			check_move : function () {
34876				if(!this.__call_old()) { return false; }
34877				var s = this._get_settings().crrm.move;
34878				if(!s.check_move.call(this, this._get_move())) { return false; }
34879				return true;
34880			},
34881			move_node : function (obj, ref, position, is_copy, is_prepared, skip_check) {
34882				var s = this._get_settings().crrm.move;
34883				if(!is_prepared) { 
34884					if(typeof position === "undefined") { position = s.default_position; }
34885					if(position === "inside" && !s.default_position.match(/^(before|after)$/)) { position = s.default_position; }
34886					return this.__call_old(true, obj, ref, position, is_copy, false, skip_check);
34887				}
34888				// if the move is already prepared
34889				if(s.always_copy === true || (s.always_copy === "multitree" && obj.rt.get_index() !== obj.ot.get_index() )) {
34890					is_copy = true;
34891				}
34892				this.__call_old(true, obj, ref, position, is_copy, true, skip_check);
34893			},
34894
34895			cut : function (obj) {
34896				obj = this._get_node(obj, true);
34897				if(!obj || !obj.length) { return false; }
34898				this.data.crrm.cp_nodes = false;
34899				this.data.crrm.ct_nodes = obj;
34900				this.__callback({ "obj" : obj });
34901			},
34902			copy : function (obj) {
34903				obj = this._get_node(obj, true);
34904				if(!obj || !obj.length) { return false; }
34905				this.data.crrm.ct_nodes = false;
34906				this.data.crrm.cp_nodes = obj;
34907				this.__callback({ "obj" : obj });
34908			},
34909			paste : function (obj) { 
34910				obj = this._get_node(obj);
34911				if(!obj || !obj.length) { return false; }
34912				var nodes = this.data.crrm.ct_nodes ? this.data.crrm.ct_nodes : this.data.crrm.cp_nodes;
34913				if(!this.data.crrm.ct_nodes && !this.data.crrm.cp_nodes) { return false; }
34914				if(this.data.crrm.ct_nodes) { this.move_node(this.data.crrm.ct_nodes, obj); this.data.crrm.ct_nodes = false; }
34915				if(this.data.crrm.cp_nodes) { this.move_node(this.data.crrm.cp_nodes, obj, false, true); }
34916				this.__callback({ "obj" : obj, "nodes" : nodes });
34917			}
34918		}
34919	});
34920	// include the crr plugin by default
34921	// $.jstree.defaults.plugins.push("crrm");
34922})(jQuery);
34923//*/
34924
34925/* 
34926 * jsTree themes plugin
34927 * Handles loading and setting themes, as well as detecting path to themes, etc.
34928 */
34929(function ($) {
34930	var themes_loaded = [];
34931	// this variable stores the path to the themes folder - if left as false - it will be autodetected
34932	$.jstree._themes = false;
34933	$.jstree.plugin("themes", {
34934		__init : function () { 
34935			this.get_container()
34936				.bind("init.jstree", $.proxy(function () {
34937						var s = this._get_settings().themes;
34938						this.data.themes.dots = s.dots; 
34939						this.data.themes.icons = s.icons; 
34940						this.set_theme(s.theme, s.url);
34941					}, this))
34942				.bind("loaded.jstree", $.proxy(function () {
34943						// bound here too, as simple HTML tree's won't honor dots & icons otherwise
34944						if(!this.data.themes.dots) { this.hide_dots(); }
34945						else { this.show_dots(); }
34946						if(!this.data.themes.icons) { this.hide_icons(); }
34947						else { this.show_icons(); }
34948					}, this));
34949		},
34950		defaults : { 
34951			theme : "default", 
34952			url : false,
34953			dots : true,
34954			icons : true
34955		},
34956		_fn : {
34957			set_theme : function (theme_name, theme_url) {
34958				if(!theme_name) { return false; }
34959				if(!theme_url) { theme_url = $.jstree._themes + theme_name + '/style.css'; }
34960				if($.inArray(theme_url, themes_loaded) == -1) {
34961//					$.vakata.css.add_sheet({ "url" : theme_url });
34962//					themes_loaded.push(theme_url);
34963				}
34964				if(this.data.themes.theme != theme_name) {
34965					this.get_container().removeClass('jstree-' + this.data.themes.theme);
34966					this.data.themes.theme = theme_name;
34967				}
34968				this.get_container().addClass('jstree-' + theme_name);
34969				if(!this.data.themes.dots) { this.hide_dots(); }
34970				else { this.show_dots(); }
34971				if(!this.data.themes.icons) { this.hide_icons(); }
34972				else { this.show_icons(); }
34973				this.__callback();
34974			},
34975			get_theme	: function () { return this.data.themes.theme; },
34976
34977			show_dots	: function () { this.data.themes.dots = true; this.get_container().children("ul").removeClass("jstree-no-dots"); },
34978			hide_dots	: function () { this.data.themes.dots = false; this.get_container().children("ul").addClass("jstree-no-dots"); },
34979			toggle_dots	: function () { if(this.data.themes.dots) { this.hide_dots(); } else { this.show_dots(); } },
34980
34981			show_icons	: function () { this.data.themes.icons = true; this.get_container().children("ul").removeClass("jstree-no-icons"); },
34982			hide_icons	: function () { this.data.themes.icons = false; this.get_container().children("ul").addClass("jstree-no-icons"); },
34983			toggle_icons: function () { if(this.data.themes.icons) { this.hide_icons(); } else { this.show_icons(); } }
34984		}
34985	});
34986	// autodetect themes path
34987	$(function () {
34988		if($.jstree._themes === false) {
34989			$("script").each(function () { 
34990				if(this.src.toString().match(/jquery\.jstree[^\/]*?\.js(\?.*)?$/)) { 
34991					$.jstree._themes = this.src.toString().replace(/jquery\.jstree[^\/]*?\.js(\?.*)?$/, "") + 'themes/'; 
34992					return false; 
34993				}
34994			});
34995		}
34996		if($.jstree._themes === false) { $.jstree._themes = "themes/"; }
34997	});
34998	// include the themes plugin by default
34999	$.jstree.defaults.plugins.push("themes");
35000})(jQuery);
35001//*/
35002
35003/*
35004 * jsTree hotkeys plugin
35005 * Enables keyboard navigation for all tree instances
35006 * Depends on the jstree ui & jquery hotkeys plugins
35007 */
35008(function ($) {
35009	var bound = [];
35010	function exec(i, event) {
35011		var f = $.jstree._focused(), tmp;
35012		if(f && f.data && f.data.hotkeys && f.data.hotkeys.enabled) { 
35013			tmp = f._get_settings().hotkeys[i];
35014			if(tmp) { return tmp.call(f, event); }
35015		}
35016	}
35017	$.jstree.plugin("hotkeys", {
35018		__init : function () {
35019			if(typeof $.hotkeys === "undefined") { throw "jsTree hotkeys: jQuery hotkeys plugin not included."; }
35020			if(!this.data.ui) { throw "jsTree hotkeys: jsTree UI plugin not included."; }
35021			$.each(this._get_settings().hotkeys, function (i, v) {
35022				if(v !== false && $.inArray(i, bound) == -1) {
35023					$(document).bind("keydown", i, function (event) { return exec(i, event); });
35024					bound.push(i);
35025				}
35026			});
35027			this.get_container()
35028				.bind("lock.jstree", $.proxy(function () {
35029						if(this.data.hotkeys.enabled) { this.data.hotkeys.enabled = false; this.data.hotkeys.revert = true; }
35030					}, this))
35031				.bind("unlock.jstree", $.proxy(function () {
35032						if(this.data.hotkeys.revert) { this.data.hotkeys.enabled = true; }
35033					}, this));
35034			this.enable_hotkeys();
35035		},
35036		defaults : {
35037			"up" : function () { 
35038				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
35039				this.hover_node(this._get_prev(o));
35040				return false; 
35041			},
35042			"ctrl+up" : function () { 
35043				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
35044				this.hover_node(this._get_prev(o));
35045				return false; 
35046			},
35047			"shift+up" : function () { 
35048				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
35049				this.hover_node(this._get_prev(o));
35050				return false; 
35051			},
35052			"down" : function () { 
35053				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
35054				this.hover_node(this._get_next(o));
35055				return false;
35056			},
35057			"ctrl+down" : function () { 
35058				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
35059				this.hover_node(this._get_next(o));
35060				return false;
35061			},
35062			"shift+down" : function () { 
35063				var o = this.data.ui.hovered || this.data.ui.last_selected || -1;
35064				this.hover_node(this._get_next(o));
35065				return false;
35066			},
35067			"left" : function () { 
35068				var o = this.data.ui.hovered || this.data.ui.last_selected;
35069				if(o) {
35070					if(o.hasClass("jstree-open")) { this.close_node(o); }
35071					else { this.hover_node(this._get_prev(o)); }
35072				}
35073				return false;
35074			},
35075			"ctrl+left" : function () { 
35076				var o = this.data.ui.hovered || this.data.ui.last_selected;
35077				if(o) {
35078					if(o.hasClass("jstree-open")) { this.close_node(o); }
35079					else { this.hover_node(this._get_prev(o)); }
35080				}
35081				return false;
35082			},
35083			"shift+left" : function () { 
35084				var o = this.data.ui.hovered || this.data.ui.last_selected;
35085				if(o) {
35086					if(o.hasClass("jstree-open")) { this.close_node(o); }
35087					else { this.hover_node(this._get_prev(o)); }
35088				}
35089				return false;
35090			},
35091			"right" : function () { 
35092				var o = this.data.ui.hovered || this.data.ui.last_selected;
35093				if(o && o.length) {
35094					if(o.hasClass("jstree-closed")) { this.open_node(o); }
35095					else { this.hover_node(this._get_next(o)); }
35096				}
35097				return false;
35098			},
35099			"ctrl+right" : function () { 
35100				var o = this.data.ui.hovered || this.data.ui.last_selected;
35101				if(o && o.length) {
35102					if(o.hasClass("jstree-closed")) { this.open_node(o); }
35103					else { this.hover_node(this._get_next(o)); }
35104				}
35105				return false;
35106			},
35107			"shift+right" : function () { 
35108				var o = this.data.ui.hovered || this.data.ui.last_selected;
35109				if(o && o.length) {
35110					if(o.hasClass("jstree-closed")) { this.open_node(o); }
35111					else { this.hover_node(this._get_next(o)); }
35112				}
35113				return false;
35114			},
35115			"space" : function () { 
35116				if(this.data.ui.hovered) { this.data.ui.hovered.children("a:eq(0)").click(); } 
35117				return false; 
35118			},
35119			"ctrl+space" : function (event) { 
35120				event.type = "click";
35121				if(this.data.ui.hovered) { this.data.ui.hovered.children("a:eq(0)").trigger(event); } 
35122				return false; 
35123			},
35124			"shift+space" : function (event) { 
35125				event.type = "click";
35126				if(this.data.ui.hovered) { this.data.ui.hovered.children("a:eq(0)").trigger(event); } 
35127				return false; 
35128			},
35129			"f2" : function () { this.rename(this.data.ui.hovered || this.data.ui.last_selected); },
35130			"del" : function () { this.remove(this.data.ui.hovered || this._get_node(null)); }
35131		},
35132		_fn : {
35133			enable_hotkeys : function () {
35134				this.data.hotkeys.enabled = true;
35135			},
35136			disable_hotkeys : function () {
35137				this.data.hotkeys.enabled = false;
35138			}
35139		}
35140	});
35141})(jQuery);
35142//*/
35143
35144/* 
35145 * jsTree JSON plugin
35146 * The JSON data store. Datastores are build by overriding the `load_node` and `_is_loaded` functions.
35147 */
35148(function ($) {
35149	$.jstree.plugin("json_data", {
35150		__init : function() {
35151			var s = this._get_settings().json_data;
35152			if(s.progressive_unload) {
35153				this.get_container().bind("after_close.jstree", function (e, data) {
35154					data.rslt.obj.children("ul").remove();
35155				});
35156			}
35157		},
35158		defaults : { 
35159			// `data` can be a function:
35160			//  * accepts two arguments - node being loaded and a callback to pass the result to
35161			//  * will be executed in the current tree's scope & ajax won't be supported
35162			data : false, 
35163			ajax : false,
35164			correct_state : true,
35165			progressive_render : false,
35166			progressive_unload : false
35167		},
35168		_fn : {
35169			load_node : function (obj, s_call, e_call) { var _this = this; this.load_node_json(obj, function () { _this.__callback({ "obj" : _this._get_node(obj) }); s_call.call(this); }, e_call); },
35170			_is_loaded : function (obj) { 
35171				var s = this._get_settings().json_data;
35172				obj = this._get_node(obj); 
35173				return obj == -1 || !obj || (!s.ajax && !s.progressive_render && !$.isFunction(s.data)) || obj.is(".jstree-open, .jstree-leaf") || obj.children("ul").children("li").length > 0;
35174			},
35175			refresh : function (obj) {
35176				obj = this._get_node(obj);
35177				var s = this._get_settings().json_data;
35178				if(obj && obj !== -1 && s.progressive_unload && ($.isFunction(s.data) || !!s.ajax)) {
35179					obj.removeData("jstree_children");
35180				}
35181				return this.__call_old();
35182			},
35183			load_node_json : function (obj, s_call, e_call) {
35184				var s = this.get_settings().json_data, d,
35185					error_func = function () {},
35186					success_func = function () {};
35187				obj = this._get_node(obj);
35188
35189				if(obj && obj !== -1 && (s.progressive_render || s.progressive_unload) && !obj.is(".jstree-open, .jstree-leaf") && obj.children("ul").children("li").length === 0 && obj.data("jstree_children")) {
35190					d = this._parse_json(obj.data("jstree_children"), obj);
35191					if(d) {
35192						obj.append(d);
35193						if(!s.progressive_unload) { obj.removeData("jstree_children"); }
35194					}
35195					this.clean_node(obj);
35196					if(s_call) { s_call.call(this); }
35197					return;
35198				}
35199
35200				if(obj && obj !== -1) {
35201					if(obj.data("jstree_is_loading")) { return; }
35202					else { obj.data("jstree_is_loading",true); }
35203				}
35204				switch(!0) {
35205					case (!s.data && !s.ajax): throw "Neither data nor ajax settings supplied.";
35206					// function option added here for easier model integration (also supporting async - see callback)
35207					case ($.isFunction(s.data)):
35208						s.data.call(this, obj, $.proxy(function (d) {
35209							d = this._parse_json(d, obj);
35210							if(!d) { 
35211								if(obj === -1 || !obj) {
35212									if(s.correct_state) { this.get_container().children("ul").empty(); }
35213								}
35214								else {
35215									obj.children("a.jstree-loading").removeClass("jstree-loading");
35216									obj.removeData("jstree_is_loading");
35217									if(s.correct_state) { this.correct_state(obj); }
35218								}
35219								if(e_call) { e_call.call(this); }
35220							}
35221							else {
35222								if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
35223								else { obj.append(d).children("a.jstree-loading").removeClass("jstree-loading"); obj.removeData("jstree_is_loading"); }
35224								this.clean_node(obj);
35225								if(s_call) { s_call.call(this); }
35226							}
35227						}, this));
35228						break;
35229					case (!!s.data && !s.ajax) || (!!s.data && !!s.ajax && (!obj || obj === -1)):
35230						if(!obj || obj == -1) {
35231							d = this._parse_json(s.data, obj);
35232							if(d) {
35233								this.get_container().children("ul").empty().append(d.children());
35234								this.clean_node();
35235							}
35236							else { 
35237								if(s.correct_state) { this.get_container().children("ul").empty(); }
35238							}
35239						}
35240						if(s_call) { s_call.call(this); }
35241						break;
35242					case (!s.data && !!s.ajax) || (!!s.data && !!s.ajax && obj && obj !== -1):
35243						error_func = function (x, t, e) {
35244							var ef = this.get_settings().json_data.ajax.error; 
35245							if(ef) { ef.call(this, x, t, e); }
35246							if(obj != -1 && obj.length) {
35247								obj.children("a.jstree-loading").removeClass("jstree-loading");
35248								obj.removeData("jstree_is_loading");
35249								if(t === "success" && s.correct_state) { this.correct_state(
35249obj); }
35250							}
35251							else {
35252								if(t === "success" && s.correct_state) { this.get_container().children("ul").empty(); }
35253							}
35254							if(e_call) { e_call.call(this); }
35255						};
35256						success_func = function (d, t, x) {
35257							var sf = this.get_settings().json_data.ajax.success; 
35258							if(sf) { d = sf.call(this,d,t,x) || d; }
35259							if(d === "" || (d && d.toString && d.toString().replace(/^[\s\n]+$/,"") === "") || (!$.isArray(d) && !$.isPlainObject(d))) {
35260								return error_func.call(this, x, t, "");
35261							}
35262							d = this._parse_json(d, obj);
35263							if(d) {
35264								if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
35265								else { obj.append(d).children("a.jstree-loading").removeClass("jstree-loading"); obj.removeData("jstree_is_loading"); }
35266								this.clean_node(obj);
35267								if(s_call) { s_call.call(this); }
35268							}
35269							else {
35270								if(obj === -1 || !obj) {
35271									if(s.correct_state) { 
35272										this.get_container().children("ul").empty(); 
35273										if(s_call) { s_call.call(this); }
35274									}
35275								}
35276								else {
35277									obj.children("a.jstree-loading").removeClass("jstree-loading");
35278									obj.removeData("jstree_is_loading");
35279									if(s.correct_state) { 
35280										this.correct_state(obj);
35281										if(s_call) { s_call.call(this); } 
35282									}
35283								}
35284							}
35285						};
35286						s.ajax.context = this;
35287						s.ajax.error = error_func;
35288						s.ajax.success = success_func;
35289						if(!s.ajax.dataType) { s.ajax.dataType = "json"; }
35290						if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, obj); }
35291						if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, obj); }
35292						$.ajax(s.ajax);
35293						break;
35294				}
35295			},
35296			_parse_json : function (js, obj, is_callback) {
35297				var d = false, 
35298					p = this._get_settings(),
35299					s = p.json_data,
35300					t = p.core.html_titles,
35301					tmp, i, j, ul1, ul2;
35302
35303				if(!js) { return d; }
35304				if(s.progressive_unload && obj && obj !== -1) { 
35305					obj.data("jstree_children", d);
35306				}
35307				if($.isArray(js)) {
35308					d = $();
35309					if(!js.length) { return false; }
35310					for(i = 0, j = js.length; i < j; i++) {
35311						tmp = this._parse_json(js[i], obj, true);
35312						if(tmp.length) { d = d.add(tmp); }
35313					}
35314				}
35315				else {
35316					if(typeof js == "string") { js = { data : js }; }
35317					if(!js.data && js.data !== "") { return d; }
35318					d = $("<li />");
35319					if(js.attr) { d.attr(js.attr); }
35320					if(js.metadata) { d.data(js.metadata); }
35321					if(js.state) { d.addClass("jstree-" + js.state); }
35322					if(!$.isArray(js.data)) { tmp = js.data; js.data = []; js.data.push(tmp); }
35323					$.each(js.data, function (i, m) {
35324						tmp = $("<a />");
35325						if($.isFunction(m)) { m = m.call(this, js); }
35326						if(typeof m == "string") { tmp.attr('href','#')[ t ? "html" : "text" ](m); }
35327						else {
35328							if(!m.attr) { m.attr = {}; }
35329							if(!m.attr.href) { m.attr.href = '#'; }
35330							tmp.attr(m.attr)[ t ? "html" : "text" ](m.title);
35331							if(m.language) { tmp.addClass(m.language); }
35332						}
35333						tmp.prepend("<ins class='jstree-icon'>&#160;</ins>");
35334						if(!m.icon && js.icon) { m.icon = js.icon; }
35335						if(m.icon) { 
35336							if(m.icon.indexOf("/") === -1) { tmp.children("ins").addClass(m.icon); }
35337							else { tmp.children("ins").css("background","url('" + m.icon + "') center center no-repeat"); }
35338						}
35339						d.append(tmp);
35340					});
35341					d.prepend("<ins class='jstree-icon'>&#160;</ins>");
35342					if(js.children) { 
35343						if(s.progressive_render && js.state !== "open") {
35344							d.addClass("jstree-closed").data("jstree_children", js.children);
35345						}
35346						else {
35347							if(s.progressive_unload) { d.data("jstree_children", js.children); }
35348							if($.isArray(js.children) && js.children.length) {
35349								tmp = this._parse_json(js.children, obj, true);
35350								if(tmp.length) {
35351									ul2 = $("<ul />");
35352									ul2.append(tmp);
35353									d.append(ul2);
35354								}
35355							}
35356						}
35357					}
35358				}
35359				if(!is_callback) {
35360					ul1 = $("<ul />");
35361					ul1.append(d);
35362					d = ul1;
35363				}
35364				return d;
35365			},
35366			get_json : function (obj, li_attr, a_attr, is_callback) {
35367				var result = [], 
35368					s = this._get_settings(), 
35369					_this = this,
35370					tmp1, tmp2, li, a, t, lang;
35371				obj = this._get_node(obj);
35372				if(!obj || obj === -1) { obj = this.get_container().find("> ul > li"); }
35373				li_attr = $.isArray(li_attr) ? li_attr : [ "id", "class" ];
35374				if(!is_callback && this.data.types) { li_attr.push(s.types.type_attr); }
35375				a_attr = $.isArray(a_attr) ? a_attr : [ ];
35376
35377				obj.each(function () {
35378					li = $(this);
35379					tmp1 = { data : [] };
35380					if(li_attr.length) { tmp1.attr = { }; }
35381					$.each(li_attr, function (i, v) { 
35382						tmp2 = li.attr(v); 
35383						if(tmp2 && tmp2.length && tmp2.replace(/jstree[^ ]*/ig,'').length) {
35384							tmp1.attr[v] = (" " + tmp2).replace(/ jstree[^ ]*/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,""); 
35385						}
35386					});
35387					if(li.hasClass("jstree-open")) { tmp1.state = "open"; }
35388					if(li.hasClass("jstree-closed")) { tmp1.state = "closed"; }
35389					if(li.data()) { tmp1.metadata = li.data(); }
35390					a = li.children("a");
35391					a.each(function () {
35392						t = $(this);
35393						if(
35394							a_attr.length || 
35395							$.inArray("languages", s.plugins) !== -1 || 
35396							t.children("ins").get(0).style.backgroundImage.length || 
35397							(t.children("ins").get(0).className && t.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').length)
35398						) { 
35399							lang = false;
35400							if($.inArray("languages", s.plugins) !== -1 && $.isArray(s.languages) && s.languages.length) {
35401								$.each(s.languages, function (l, lv) {
35402									if(t.hasClass(lv)) {
35403										lang = lv;
35404										return false;
35405									}
35406								});
35407							}
35408							tmp2 = { attr : { }, title : _this.get_text(t, lang) }; 
35409							$.each(a_attr, function (k, z) {
35410								tmp2.attr[z] = (" " + (t.attr(z) || "")).replace(/ jstree[^ ]*/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"");
35411							});
35412							if($.inArray("languages", s.plugins) !== -1 && $.isArray(s.languages) && s.languages.length) {
35413								$.each(s.languages, function (k, z) {
35414									if(t.hasClass(z)) { tmp2.language = z; return true; }
35415								});
35416							}
35417							if(t.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/^\s+$/ig,"").length) {
35418								tmp2.icon = t.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"");
35419							}
35420							if(t.children("ins").get(0).style.backgroundImage.length) {
35421								tmp2.icon = t.children("ins").get(0).style.backgroundImage.replace("url(","").replace(")","");
35422							}
35423						}
35424						else {
35425							tmp2 = _this.get_text(t);
35426						}
35427						if(a.length > 1) { tmp1.data.push(tmp2); }
35428						else { tmp1.data = tmp2; }
35429					});
35430					li = li.find("> ul > li");
35431					if(li.length) { tmp1.children = _this.get_json(li, li_attr, a_attr, true); }
35432					result.push(tmp1);
35433				});
35434				return result;
35435			}
35436		}
35437	});
35438})(jQuery);
35439//*/
35440
35441/* 
35442 * jsTree languages plugin
35443 * Adds support for multiple language versions in one tree
35444 * This basically allows for many titles coexisting in one node, but only one of them being visible at any given time
35445 * This is useful for maintaining the same structure in many languages (hence the name of the plugin)
35446 */
35447(function ($) {
35448	$.jstree.plugin("languages", {
35449		__init : function () { this._load_css();  },
35450		defaults : [],
35451		_fn : {
35452			set_lang : function (i) { 
35453				var langs = this._get_settings().languages,
35454					st = false,
35455					selector = ".jstree-" + this.get_index() + ' a';
35456				if(!$.isArray(langs) || langs.length === 0) { return false; }
35457				if($.inArray(i,langs) == -1) {
35458					if(!!langs[i]) { i = langs[i]; }
35459					else { return false; }
35460				}
35461				if(i == this.data.languages.current_language) { return true; }
35462				st = $.vakata.css.get_css(selector + "." + this.data.languages.current_language, false, this.data.languages.language_css);
35463				if(st !== false) { st.style.display = "none"; }
35464				st = $.vakata.css.get_css(selector + "." + i, false, this.data.languages.language_css);
35465				if(st !== false) { st.style.display = ""; }
35466				this.data.languages.current_language = i;
35467				this.__callback(i);
35468				return true;
35469			},
35470			get_lang : function () {
35471				return this.data.languages.current_language;
35472			},
35473			_get_string : function (key, lang) {
35474				var langs = this._get_settings().languages,
35475					s = this._get_settings().core.strings;
35476				if($.isArray(langs) && langs.length) {
35477					lang = (lang && $.inArray(lang,langs) != -1) ? lang : this.data.languages.current_language;
35478				}
35479				if(s[lang] && s[lang][key]) { return s[lang][key]; }
35480				if(s[key]) { return s[key]; }
35481				return key;
35482			},
35483			get_text : function (obj, lang) {
35484				obj = this._get_node(obj) || this.data.ui.last_selected;
35485				if(!obj.size()) { return false; }
35486				var langs = this._get_settings().languages,
35487					s = this._get_settings().core.html_titles;
35488				if($.isArray(langs) && langs.length) {
35489					lang = (lang && $.inArray(lang,langs) != -1) ? lang : this.data.languages.current_language;
35490					obj = obj.children("a." + lang);
35491				}
35492				else { obj = obj.children("a:eq(0)"); }
35493				if(s) {
35494					obj = obj.clone();
35495					obj.children("INS").remove();
35496					return obj.html();
35497				}
35498				else {
35499					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
35500					return obj.nodeValue;
35501				}
35502			},
35503			set_text : function (obj, val, lang) {
35504				obj = this._get_node(obj) || this.data.ui.last_selected;
35505				if(!obj.size()) { return false; }
35506				var langs = this._get_settings().languages,
35507					s = this._get_settings().core.html_titles,
35508					tmp;
35509				if($.isArray(langs) && langs.length) {
35510					lang = (lang && $.inArray(lang,langs) != -1) ? lang : this.data.languages.current_language;
35511					obj = obj.children("a." + lang);
35512				}
35513				else { obj = obj.children("a:eq(0)"); }
35514				if(s) {
35515					tmp = obj.children("INS").clone();
35516					obj.html(val).prepend(tmp);
35517					this.__callback({ "obj" : obj, "name" : val, "lang" : lang });
35518					return true;
35519				}
35520				else {
35521					obj = obj.contents().filter(function() { return this.nodeType == 3; })[0];
35522					this.__callback({ "obj" : obj, "name" : val, "lang" : lang });
35523					return (obj.nodeValue = val);
35524				}
35525			},
35526			_load_css : function () {
35527				var langs = this._get_settings().languages,
35528					str = "/* languages css */",
35529					selector = ".jstree-" + this.get_index() + ' a',
35530					ln;
35531				if($.isArray(langs) && langs.length) {
35532					this.data.languages.current_language = langs[0];
35533					for(ln = 0; ln < langs.length; ln++) {
35534						str += selector + "." + langs[ln] + " {";
35535						if(langs[ln] != this.data.languages.current_language) { str += " display:none; "; }
35536						str += " } ";
35537					}
35538					this.data.languages.language_css = $.vakata.css.add_sheet({ 'str' : str, 'title' : "jstree-languages" });
35539				}
35540			},
35541			create_node : function (obj, position, js, callback) {
35542				var t = this.__call_old(true, obj, position, js, function (t) {
35543					var langs = this._get_settings().languages,
35544						a = t.children("a"),
35545						ln;
35546					if($.isArray(langs) && langs.length) {
35547						for(ln = 0; ln < langs.length; ln++) {
35548							if(!a.is("." + langs[ln])) {
35549								t.append(a.eq(0).clone().removeClass(langs.join(" ")).addClass(langs[ln]));
35550							}
35551						}
35552						a.not("." + langs.join(", .")).remove();
35553					}
35554					if(callback) { callback.call(this, t); }
35555				});
35556				return t;
35557			}
35558		}
35559	});
35560})(jQuery);
35561//*/
35562
35563/*
35564 * jsTree cookies plugin
35565 * Stores the currently opened/selected nodes in a cookie and then restores them
35566 * Depends on the jquery.cookie plugin
35567 */
35568(function ($) {
35569	$.jstree.plugin("cookies", {
35570		__init : function () {
35571			if(typeof $.cookie === "undefined") { throw "jsTree cookie: jQuery cookie plugin not included."; }
35572
35573			var s = this._get_settings().cookies,
35574				tmp;
35575			if(!!s.save_loaded) {
35576				tmp = $.cookie(s.save_loaded);
35577				if(tmp && tmp.length) { this.data.core.to_load = tmp.split(","); }
35578			}
35579			if(!!s.save_opened) {
35580				tmp = $.cookie(s.save_opened);
35581				if(tmp && tmp.length) { this.data.core.to_open = tmp.split(","); }
35582			}
35583			if(!!s.save_selected) {
35584				tmp = $.cookie(s.save_selected);
35585				if(tmp && tmp.length && this.data.ui) { this.data.ui.to_select = tmp.split(","); }
35586			}
35587			this.get_container()
35588				.one( ( this.data.ui ? "reselect" : "reopen" ) + ".jstree", $.proxy(function () {
35589					this.get_container()
35590						.bind("open_node.jstree close_node.jstree select_node.jstree deselect_node.jstree", $.proxy(function (e) { 
35591								if(this._get_settings().cookies.auto_save) { this.save_cookie((e.handleObj.namespace + e.handleObj.type).replace("jstree","")); }
35592							}, this));
35593				}, this));
35594		},
35595		defaults : {
35596			save_loaded		: "jstree_load",
35597			save_opened		: "jstree_open",
35598			save_selected	: "jstree_select",
35599			auto_save		: true,
35600			cookie_options	: {}
35601		},
35602		_fn : {
35603			save_cookie : function (c) {
35604				if(this.data.core.refreshing) { return; }
35605				var s = this._get_settings().cookies;
35606				if(!c) { // if called manually and not by event
35607					if(s.save_loaded) {
35608						this.save_loaded();
35609						$.cookie(s.save_loaded, this.data.core.to_load.join(","), s.cookie_options);
35610					}
35611					if(s.save_opened) {
35612						this.save_opened();
35613						$.cookie(s.save_opened, this.data.core.to_open.join(","), s.cookie_options);
35614					}
35615					if(s.save_selected && this.data.ui) {
35616						this.save_selected();
35617						$.cookie(s.save_selected, this.data.ui.to_select.join(","), s.cookie_options);
35618					}
35619					return;
35620				}
35621				switch(c) {
35622					case "open_node":
35623					case "close_node":
35624						if(!!s.save_opened) { 
35625							this.save_opened(); 
35626							$.cookie(s.save_opened, this.data.core.to_open.join(","), s.cookie_options); 
35627						}
35628						if(!!s.save_loaded) { 
35629							this.save_loaded(); 
35630							$.cookie(s.save_loaded, this.data.core.to_load.join(","), s.cookie_options); 
35631						}
35632						break;
35633					case "select_node":
35634					case "deselect_node":
35635						if(!!s.save_selected && this.data.ui) { 
35636							this.save_selected(); 
35637							$.cookie(s.save_selected, this.data.ui.to_select.join(","), s.cookie_options); 
35638						}
35639						break;
35640				}
35641			}
35642		}
35643	});
35644	// include cookies by default
35645	// $.jstree.defaults.plugins.push("cookies");
35646})(jQuery);
35647//*/
35648
35649/*
35650 * jsTree sort plugin
35651 * Sorts items alphabetically (or using any other function)
35652 */
35653(function ($) {
35654	$.jstree.plugin("sort", {
35655		__init : function () {
35656			this.get_container()
35657				.bind("load_node.jstree", $.proxy(function (e, data) {
35658						var obj = this._get_node(data.rslt.obj);
35659						obj = obj === -1 ? this.get_container().children("ul") : obj.children("ul");
35660						this.sort(obj);
35661					}, this))
35662				.bind("rename_node.jstree create_node.jstree create.jstree", $.proxy(function (e, data) {
35663						this.sort(data.rslt.obj.parent());
35664					}, this))
35665				.bind("move_node.jstree", $.proxy(function (e, data) {
35666						var m = data.rslt.np == -1 ? this.get_container() : data.rslt.np;
35667						this.sort(m.children("ul"));
35668					}, this));
35669		},
35670		defaults : function (a, b) { return this.get_text(a) > this.get_text(b) ? 1 : -1; },
35671		_fn : {
35672			sort : function (obj) {
35673				var s = this._get_settings().sort,
35674					t = this;
35675				obj.append($.makeArray(obj.children("li")).sort($.proxy(s, t)));
35676				obj.find("> li > ul").each(function() { t.sort($(this)); });
35677				this.clean_node(obj);
35678			}
35679		}
35680	});
35681})(jQuery);
35682//*/
35683
35684/*
35685 * jsTree DND plugin
35686 * Drag and drop plugin for moving/copying nodes
35687 */
35688(function ($) {
35689	var o = false,
35690		r = false,
35691		m = false,
35692		ml = false,
35693		sli = false,
35694		sti = false,
35695		dir1 = false,
35696		dir2 = false,
35697		last_pos = false;
35698	$.vakata.dnd = {
35699		is_down : false,
35700		is_drag : false,
35701		helper : false,
35702		scroll_spd : 10,
35703		init_x : 0,
35704		init_y : 0,
35705		threshold : 5,
35706		helper_left : 5,
35707		helper_top : 10,
35708		user_data : {},
35709
35710		drag_start : function (e, data, html) { 
35711			if($.vakata.dnd.is_drag) { $.vakata.drag_stop({}); }
35712			try {
35713				e.currentTarget.unselectable = "on";
35714				e.currentTarget.onselectstart = function() { return false; };
35715				if(e.currentTarget.style) { e.currentTarget.style.MozUserSelect = "none"; }
35716			} catch(err) { }
35717			$.vakata.dnd.init_x = e.pageX;
35718			$.vakata.dnd.init_y = e.pageY;
35719			$.vakata.dnd.user_data = data;
35720			$.vakata.dnd.is_down = true;
35721			$.vakata.dnd.helper = $("<div id='vakata-dragged' />").html(html); //.fadeTo(10,0.25);
35722			$(document).bind("mousemove", $.vakata.dnd.drag);
35723			$(document).bind("mouseup", $.vakata.dnd.drag_stop);
35724			return false;
35725		},
35726		drag : function (e) { 
35727			if(!$.vakata.dnd.is_down) { return; }
35728			if(!$.vakata.dnd.is_drag) {
35729				if(Math.abs(e.pageX - $.vakata.dnd.init_x) > 5 || Math.abs(e.pageY - $.vakata.dnd.init_y) > 5) { 
35730					$.vakata.dnd.helper.appendTo("body");
35731					$.vakata.dnd.is_drag = true;
35732					$(document).triggerHandler("drag_start.vakata", { "event" : e, "data" : $.vakata.dnd.user_data });
35733				}
35734				else { return; }
35735			}
35736
35737			// maybe use a scrolling parent element instead of document?
35738			if(e.type === "mousemove") { // thought of adding scroll in order to move the helper, but mouse poisition is n/a
35739				var d = $(document), t = d.scrollTop(), l = d.scrollLeft();
35740				if(e.pageY - t < 20) { 
35741					if(sti && dir1 === "down") { clearInterval(sti); sti = false; }
35742					if(!sti) { dir1 = "up"; sti = setInterval(function () { $(document).scrollTop($(document).scrollTop() - $.vakata.dnd.scroll_spd); }, 150); }
35743				}
35744				else { 
35745					if(sti && dir1 === "up") { clearInterval(sti); sti = false; }
35746				}
35747				if($(window).height() - (e.pageY - t) < 20) {
35748					if(sti && dir1 === "up") { clearInterval(sti); sti = false; }
35749					if(!sti) { dir1 = "down"; sti = setInterval(function () { $(document).scrollTop($(document).scrollTop() + $.vakata.dnd.scroll_spd); }, 150); }
35750				}
35751				else { 
35752					if(sti && dir1 === "down") { clearInterval(sti); sti = false; }
35753				}
35754
35755				if(e.pageX - l < 20) {
35756					if(sli && dir2 === "right") { clearInterval(sli); sli = false; }
35757					if(!sli) { dir2 = "left"; sli = setInterval(function () { $(document).scrollLeft($(document).scrollLeft() - $.vakata.dnd.scroll_spd); }, 150); }
35758				}
35759				else { 
35760					if(sli && dir2 === "left") { clearInterval(sli); sli = false; }
35761				}
35762				if($(window).width() - (e.pageX - l) < 20) {
35763					if(sli && dir2 === "left") { clearInterval(sli); sli = false; }
35764					if(!sli) { dir2 = "right"; sli = setInterval(function () { $(document).scrollLeft($(document).scrollLeft() + $.vakata.dnd.scroll_spd); }, 150); }
35765				}
35766				else { 
35767					if(sli && dir2 === "right") { clearInterval(sli); sli = false; }
35768				}
35769			}
35770
35771			$.vakata.dnd.helper.css({ left : (e.pageX + $.vakata.dnd.helper_left) + "px", top : (e.pageY + $.vakata.dnd.helper_top) + "px" });
35772			$(document).triggerHandler("drag.vakata", { "event" : e, "data" : $.vakata.dnd.user_data });
35773		},
35774		drag_stop : function (e) {
35775			if(sli) { clearInterval(sli); }
35776			if(sti) { clearInterval(sti); }
35777			$(document).unbind("mousemove", $.vakata.dnd.drag);
35778			$(document).unbind("mouseup", $.vakata.dnd.drag_stop);
35779			$(document).triggerHandler("drag_stop.vakata", { "event" : e, "data" : $.vakata.dnd.user_data });
35780			$.vakata.dnd.helper.remove();
35781			$.vakata.dnd.init_x = 0;
35782			$.vakata.dnd.init_y = 0;
35783			$.vakata.dnd.user_data = {};
35784			$.vakata.dnd.is_down = false;
35785			$.vakata.dnd.is_drag = false;
35786		}
35787	};
35788	$(function() {
35789		var css_string = '#vakata-dragged { display:block; margin:0 0 0 0; padding:4px 4px 4px 24px; position:absolute; top:-2000px; line-height:16px; z-index:10000; } ';
35790		$.vakata.css.add_sheet({ str : css_string, title : "vakata" });
35791	});
35792
35793	$.jstree.plugin("dnd", {
35794		__init : function () {
35795			this.data.dnd = {
35796				active : false,
35797				after : false,
35798				inside : false,
35799				before : false,
35800				off : false,
35801				prepared : false,
35802				w : 0,
35803				to1 : false,
35804				to2 : false,
35805				cof : false,
35806				cw : false,
35807				ch : false,
35808				i1 : false,
35809				i2 : false,
35810				mto : false
35811			};
35812			this.get_container()
35813				.bind("mouseenter.jstree", $.proxy(function (e) {
35814						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
35815							if(this.data.themes) {
35816								m.attr("class", "jstree-" + this.data.themes.theme); 
35817								if(ml) { ml.attr("class", "jstree-" + this.data.themes.theme); }
35818								$.vakata.dnd.helper.attr("class", "jstree-dnd-helper jstree-" + this.data.themes.theme);
35819							}
35820							//if($(e.currentTarget).find("> ul > li").length === 0) {
35821							if(e.currentTarget === e.target && $.vakata.dnd.user_data.obj && $($.vakata.dnd.user_data.obj).length && $($.vakata.dnd.user_data.obj).parents(".jstree:eq(0)")[0] !== e.target) { // node should not be from the same tree
35822								var tr = $.jstree._reference(e.target), dc;
35823								if(tr.data.dnd.foreign) {
35824									dc = tr._get_settings().dnd.drag_check.call(this, { "o" : o, "r" : tr.get_container(), is_root : true });
35825									if(dc === true || dc.inside === true || dc.before === true || dc.after === true) {
35826										$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
35827									}
35828								}
35829								else {
35830									tr.prepare_move(o, tr.get_container(), "last");
35831									if(tr.check_move()) {
35832										$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
35833									}
35834								}
35835							}
35836						}
35837					}, this))
35838				.bind("mouseup.jstree", $.proxy(function (e) {
35839						//if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && $(e.currentTarget).find("> ul > li").length === 0) {
35840						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && e.currentTarget === e.target && $.vakata.dnd.user_data.obj && $($.vakata.dnd.user_data.obj).length && $($.vakata.dnd.user_data.obj).parents(".jstree:eq(0)")[0] !== e.target) { // node should not be from the same tree
35841							var tr = $.jstree._reference(e.currentTarget), dc;
35842							if(tr.data.dnd.foreign) {
35843								dc = tr._get_settings().dnd.drag_check.call(this, { "o" : o, "r" : tr.get_container(), is_root : true });
35844								if(dc === true || dc.inside === true || dc.before === true || dc.after === true) {
35845									tr._get_settings().dnd.drag_finish.call(this, { "o" : o, "r" : tr.get_container(), is_root : true });
35846								}
35847							}
35848							else {
35849								tr.move_node(o, tr.get_container(), "last", e[tr._get_settings().dnd.copy_modifier + "Key"]);
35850							}
35851						}
35852					}, this))
35853				.bind("mouseleave.jstree", $.proxy(function (e) {
35854						if(e.relatedTarget && e.relatedTarget.id && e.relatedTarget.id === "jstree-marker-line") {
35855							return false; 
35856						}
35857						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
35858							if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
35859							if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
35860							if(this.data.dnd.to1) { clearTimeout(this.data.dnd.to1); }
35861							if(this.data.dnd.to2) { clearTimeout(this.data.dnd.to2); }
35862							if($.vakata.dnd.helper.children("ins").hasClass("jstree-ok")) {
35863								$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
35864							}
35865						}
35866					}, this))
35867				.bind("mousemove.jstree", $.proxy(function (e) {
35868						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
35869							var cnt = this.get_container()[0];
35870
35871							// Horizontal scroll
35872							if(e.pageX + 24 > this.data.dnd.cof.left + this.data.dnd.cw) {
35873								if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
35874								this.data.dnd.i1 = setInterval($.proxy(function () { this.scrollLeft += $.vakata.dnd.scroll_spd; }, cnt), 100);
35875							}
35876							else if(e.pageX - 24 < this.data.dnd.cof.left) {
35877								if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
35878								this.data.dnd.i1 = setInterval($.proxy(function () { this.scrollLeft -= $.vakata.dnd.scroll_spd; }, cnt), 100);
35879							}
35880							else {
35881								if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
35882							}
35883
35884							// Vertical scroll
35885							if(e.pageY + 24 > this.data.dnd.cof.top + this.data.dnd.ch) {
35886								if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
35887								this.data.dnd.i2 = setInterval($.proxy(function () { this.scrollTop += $.vakata.dnd.scroll_spd; }, cnt), 100);
35888							}
35889							else if(e.pageY - 24 < this.data.dnd.cof.top) {
35890								if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
35891								this.data.dnd.i2 = setInterval($.proxy(function () { this.scrollTop -= $.vakata.dnd.scroll_spd; }, cnt), 100);
35892							}
35893							else {
35894								if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
35895							}
35896
35897						}
35898					}, this))
35899				.bind("scroll.jstree", $.proxy(function (e) { 
35900						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && m && ml) {
35901							m.hide();
35902							ml.hide();
35903						}
35904					}, this))
35905				.delegate("a", "mousedown.jstree", $.proxy(function (e) { 
35906						if(e.which === 1) {
35907							this.start_drag(e.currentTarget, e);
35908							return false;
35909						}
35910					}, this))
35911				.delegate("a", "mouseenter.jstree", $.proxy(function (e) { 
35912						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
35913							this.dnd_enter(e.currentTarget);
35914						}
35915					}, this))
35916				.delegate("a", "mousemove.jstree", $.proxy(function (e) { 
35917						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
35918							if(!r || !r.length || r.children("a")[0] !== e.currentTarget) {
35919								this.dnd_enter(e.currentTarget);
35920							}
35921							if(typeof this.data.dnd.off.top === "undefined") { this.data.dnd.off = $(e.target).offset(); }
35922							this.data.dnd.w = (e.pageY - (this.data.dnd.off.top || 0)) % this.data.core.li_height;
35923							if(this.data.dnd.w < 0) { this.data.dnd.w += this.data.core.li_height; }
35924							this.dnd_show();
35925						}
35926					}, this))
35927				.delegate("a", "mouseleave.jstree", $.proxy(function (e) { 
35928						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
35929							if(e.relatedTarget && e.relatedTarget.id && e.relatedTarget.id === "jstree-marker-line") {
35930								return false; 
35931							}
35932								if(m) { m.hide(); }
35933								if(ml) { ml.hide(); }
35934							/*
35935							var ec = $(e.currentTarget).closest("li"), 
35936								er = $(e.relatedTarget).closest("li");
35937							if(er[0] !== ec.prev()[0] && er[0] !== ec.next()[0]) {
35938								if(m) { m.hide(); }
35939								if(ml) { ml.hide(); }
35940							}
35941							*/
35942							this.data.dnd.mto = setTimeout( 
35943								(function (t) { return function () { t.dnd_leave(e); }; })(this),
35944							0);
35945						}
35946					}, this))
35947				.delegate("a", "mouseup.jstree", $.proxy(function (e) { 
35948						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree) {
35949							this.dnd_finish(e);
35950						}
35951					}, this));
35952
35953			$(document)
35954				.bind("drag_stop.vakata", $.proxy(function () {
35955						if(this.data.dnd.to1) { clearTimeout(this.data.dnd.to1); }
35956						if(this.data.dnd.to2) { clearTimeout(this.data.dnd.to2); }
35957						if(this.data.dnd.i1) { clearInterval(this.data.dnd.i1); }
35958						if(this.data.dnd.i2) { clearInterval(this.data.dnd.i2); }
35959						this.data.dnd.after		= false;
35960						this.data.dnd.before	= false;
35961						this.data.dnd.inside	= false;
35962						this.data.dnd.off		= false;
35963						this.data.dnd.prepared	= false;
35964						this.data.dnd.w			= false;
35965						this.data.dnd.to1		= false;
35966						this.data.dnd.to2		= false;
35967						this.data.dnd.i1		= false;
35968						this.data.dnd.i2		= false;
35969						this.data.dnd.active	= false;
35970						this.data.dnd.foreign	= false;
35971						if(m) { m.css({ "top" : "-2000px" }); }
35972						if(ml) { ml.css({ "top" : "-2000px" }); }
35973					}, this))
35974				.bind("drag_start.vakata", $.proxy(function (e, data) {
35975						if(data.data.jstree) { 
35976							var et = $(data.event.target);
35977							if(et.closest(".jstree").hasClass("jstree-" + this.get_index())) {
35978								this.dnd_enter(et);
35979							}
35980						}
35981					}, this));
35982				/*
35983				.bind("keydown.jstree-" + this.get_index() + " keyup.jstree-" + this.get_index(), $.proxy(function(e) {
35984						if($.vakata.dnd.is_drag && $.vakata.dnd.user_data.jstree && !this.data.dnd.foreign) {
35985							var h = $.vakata.dnd.helper.children("ins");
35986							if(e[this._get_settings().dnd.copy_modifier + "Key"] && h.hasClass("jstree-ok")) {
35987								h.parent().html(h.parent().html().replace(/ \(Copy\)$/, "") + " (Copy)");
35988							} 
35989							else {
35990								h.parent().html(h.parent().html().replace(/ \(Copy\)$/, ""));
35991							}
35992						}
35993					}, this)); */
35994
35995
35996
35997			var s = this._get_settings().dnd;
35998			if(s.drag_target) {
35999				$(document)
36000					.delegate(s.drag_target, "mousedown.jstree-" + this.get_index(), $.proxy(function (e) {
36001						o = e.target;
36002						$.vakata.dnd.drag_start(e, { jstree : true, obj : e.target }, "<ins class='jstree-icon'></ins>" + $(e.target).text() );
36003						if(this.data.themes) { 
36004							if(m) { m.attr("class", "jstree-" + this.data.themes.theme); }
36005							if(ml) { ml.attr("class", "jstree-" + this.data.themes.theme); }
36006							$.vakata.dnd.helper.attr("class", "jstree-dnd-helper jstree-" + this.data.themes.theme); 
36007						}
36008						$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
36009						var cnt = this.get_container();
36010						this.data.dnd.cof = cnt.offset();
36011						this.data.dnd.cw = parseInt(cnt.width(),10);
36012						this.data.dnd.ch = parseInt(cnt.height(),10);
36013						this.data.dnd.foreign = true;
36014						e.preventDefault();
36015					}, this));
36016			}
36017			if(s.drop_target) {
36018				$(document)
36019					.delegate(s.drop_target, "mouseenter.jstree-" + this.get_index(), $.proxy(function (e) {
36020							if(this.data.dnd.active && this._get_settings().dnd.drop_check.call(this, { "o" : o, "r" : $(e.target), "e" : e })) {
36021								$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
36022							}
36023						}, this))
36024					.delegate(s.drop_target, "mouseleave.jstree-" + this.get_index(), $.proxy(function (e) {
36025							if(this.data.dnd.active) {
36026								$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
36027							}
36028						}, this))
36029					.delegate(s.drop_target, "mouseup.jstree-" + this.get_index(), $.proxy(function (e) {
36030							if(this.data.dnd.active && $.vakata.dnd.helper.children("ins").hasClass("jstree-ok")) {
36031								this._get_settings().dnd.drop_finish.call(this, { "o" : o, "r" : $(e.target), "e" : e });
36032							}
36033						}, this));
36034			}
36035		},
36036		defaults : {
36037			copy_modifier	: "ctrl",
36038			check_timeout	: 100,
36039			open_timeout	: 500,
36040			drop_target		: ".jstree-drop",
36041			drop_check		: function (data) { return true; },
36042			drop_finish		: $.noop,
36043			drag_target		: ".jstree-draggable",
36044			drag_finish		: $.noop,
36045			drag_check		: function (data) { return { after : false, before : false, inside : true }; }
36046		},
36047		_fn : {
36048			dnd_prepare : function () {
36049				if(!r || !r.length) { return; }
36050				this.data.dnd.off = r.offset();
36051				if(this._get_settings().core.rtl) {
36052					this.data.dnd.off.right = this.data.dnd.off.left + r.width();
36053				}
36054				if(this.data.dnd.foreign) {
36055					var a = this._get_settings().dnd.drag_check.call(this, { "o" : o, "r" : r });
36056					this.data.dnd.after = a.after;
36057					this.data.dnd.before = a.before;
36058					this.data.dnd.inside = a.inside;
36059					this.data.dnd.prepared = true;
36060					return this.dnd_show();
36061				}
36062				this.prepare_move(o, r, "before");
36063				this.data.dnd.before = this.check_move();
36064				this.prepare_move(o, r, "after");
36065				this.data.dnd.after = this.check_move();
36066				if(this._is_loaded(r)) {
36067					this.prepare_move(o, r, "inside");
36068					this.data.dnd.inside = this.check_move();
36069				}
36070				else {
36071					this.data.dnd.inside = false;
36072				}
36073				this.data.dnd.prepared = true;
36074				return this.dnd_show();
36075			},
36076			dnd_show : function () {
36077				if(!this.data.dnd.prepared) { return; }
36078				var o = ["before","inside","after"],
36079					r = false,
36080					rtl = this._get_settings().core.rtl,
36081					pos;
36082				if(this.data.dnd.w < this.data.core.li_height/3) { o = ["before","inside","after"]; }
36083				else if(this.data.dnd.w <= this.data.core.li_height*2/3) {
36084					o = this.data.dnd.w < this.data.core.li_height/2 ? ["inside","before","after"] : ["inside","after","before"];
36085				}
36086				else { o = ["after","inside","before"]; }
36087				$.each(o, $.proxy(function (i, val) { 
36088					if(this.data.dnd[val]) {
36089						$.vakata.dnd.helper.children("ins").attr("class","jstree-ok");
36090						r = val;
36091						return false;
36092					}
36093				}, this));
36094				if(r === false) { $.vakata.dnd.helper.children("ins").attr("class","jstree-invalid"); }
36095				
36096				pos = rtl ? (this.data.dnd.off.right - 18) : (this.data.dnd.off.left + 10);
36097				switch(r) {
36098					case "before":
36099						m.css({ "left" : pos + "px", "top" : (this.data.dnd.off.top - 6) + "px" }).show();
36100						if(ml) { ml.css({ "left" : (pos + 8) + "px", "top" : (this.data.dnd.off.top - 1) + "px" }).show(); }
36101						break;
36102					case "after":
36103						m.css({ "left" : pos + "px", "top" : (this.data.dnd.off.top + this.data.core.li_height - 6) + "px" }).show();
36104						if(ml) { ml.css({ "left" : (pos + 8) + "px", "top" : (this.data.dnd.off.top + this.data.core.li_height - 1) + "px" }).show(); }
36105						break;
36106					case "inside":
36107						m.css({ "left" : pos + ( rtl ? -4 : 4) + "px", "top" : (this.data.dnd.off.top + this.data.core.li_height/2 - 5) + "px" }).show();
36108						if(ml) { ml.hide(); }
36109						break;
36110					default:
36111						m.hide();
36112						if(ml) { ml.hide(); }
36113						break;
36114				}
36115				last_pos = r;
36116				return r;
36117			},
36118			dnd_open : function () {
36119				this.data.dnd.to2 = false;
36120				this.open_node(r, $.proxy(this.dnd_prepare,this), true);
36121			},
36122			dnd_finish : function (e) {
36123				if(this.data.dnd.foreign) {
36124					if(this.data.dnd.after || this.data.dnd.before || this.data.dnd.inside) {
36125						this._get_settings().dnd.drag_finish.call(this, { "o" : o, "r" : r, "p" : last_pos });
36126					}
36127				}
36128				else {
36129					this.dnd_prepare();
36130					this.move_node(o, r, last_pos, e[this._get_settings().dnd.copy_modifier + "Key"]);
36131				}
36132				o = false;
36133				r = false;
36134				m.hide();
36135				if(ml) { ml.hide(); }
36136			},
36137			dnd_enter : function (obj) {
36138				if(this.data.dnd.mto) { 
36139					clearTimeout(this.data.dnd.mto);
36140					this.data.dnd.mto = false;
36141				}
36142				var s = this._get_settings().dnd;
36143				this.data.dnd.prepared = false;
36144				r = this._get_node(obj);
36145				if(s.check_timeout) { 
36146					// do the calculations after a minimal timeout (users tend to drag quickly to the desired location)
36147					if(this.data.dnd.to1) { clearTimeout(this.data.dnd.to1); }
36148					this.data.dnd.to1 = setTimeout($.proxy(this.dnd_prepare, this), s.check_timeout); 
36149				}
36150				else { 
36151					this.dnd_prepare(); 
36152				}
36153				if(s.open_timeout) { 
36154					if(this.data.dnd.to2) { clearTimeout(this.data.dnd.to2); }
36155					if(r && r.length && r.hasClass("jstree-closed")) { 
36156						// if the node is closed - open it, then recalculate
36157						this.data.dnd.to2 = setTimeout($.proxy(this.dnd_open, this), s.open_timeout);
36158					}
36159				}
36160				else {
36161					if(r && r.length && r.hasClass("jstree-closed")) { 
36162						this.dnd_open();
36163					}
36164				}
36165			},
36166			dnd_leave : function (e) {
36167				this.data.dnd.after		= false;
36168				this.data.dnd.before	= false;
36169				this.data.dnd.inside	= false;
36170				$.vakata.dnd.helper.children("ins").attr("class","jstree-invalid");
36171				m.hide();
36172				if(ml) { ml.hide(); }
36173				if(r && r[0] === e.target.parentNode) {
36174					if(this.data.dnd.to1) {
36175						clearTimeout(this.data.dnd.to1);
36176						this.data.dnd.to1 = false;
36177					}
36178					if(this.data.dnd.to2) {
36179						clearTimeout(this.data.dnd.to2);
36180						this.data.dnd.to2 = false;
36181					}
36182				}
36183			},
36184			start_drag : function (obj, e) {
36185				o = this._get_node(obj);
36186				if(this.data.ui && this.is_selected(o)) { o = this._get_node(null, true); }
36187				var dt = o.length > 1 ? this._get_string("multiple_selection") : this.get_text(o),
36188					cnt = this.get_container();
36189				if(!this._get_settings().core.html_titles) { dt = dt.replace(/</ig,"&lt;").replace(/>/ig,"&gt;"); }
36190				$.vakata.dnd.drag_start(e, { jstree : true, obj : o }, "<ins class='jstree-icon'></ins>" + dt );
36191				if(this.data.themes) { 
36192					if(m) { m.attr("class", "jstree-" + this.data.themes.theme); }
36193					if(ml) { ml.attr("class", "jstree-" + this.data.themes.theme); }
36194					$.vakata.dnd.helper.attr("class", "jstree-dnd-helper jstree-" + this.data.themes.theme); 
36195				}
36196				this.data.dnd.cof = cnt.offset();
36197				this.data.dnd.cw = parseInt(cnt.width(),10);
36198				this.data.dnd.ch = parseInt(cnt.height(),10);
36199				this.data.dnd.active = true;
36200			}
36201		}
36202	});
36203	$(function() {
36204		var css_string = '' + 
36205			'#vakata-dragged ins { display:block; text-decoration:none; width:16px; height:16px; margin:0 0 0 0; padding:0; position:absolute; top:4px; left:4px; ' + 
36206			' -moz-border-radius:4px; border-radius:4px; -webkit-border-radius:4px; ' +
36207			'} ' + 
36208			'#vakata-dragged .jstree-ok { background:green; } ' + 
36209			'#vakata-dragged .jstree-invalid { background:red; } ' + 
36210			'#jstree-marker { padding:0; margin:0; font-size:12px; overflow:hidden; height:12px; width:8px; position:absolute; top:-30px; z-index:10001; background-repeat:no-repeat; display:none; background-color:transparent; text-shadow:1px 1px 1px white; color:black; line-height:10px; } ' + 
36211			'#jstree-marker-line { padding:0; margin:0; line-height:0%; font-size:1px; overflow:hidden; height:1px; width:100px; position:absolute; top:-30px; z-index:10000; background-repeat:no-repeat; display:none; background-color:#456c43; ' + 
36212			' cursor:pointer; border:1px solid #eeeeee; border-left:0; -moz-box-shadow: 0px 0px 2px #666; -webkit-box-shadow: 0px 0px 2px #666; box-shadow: 0px 0px 2px #666; ' + 
36213			' -moz-border-radius:1px; border-radius:1px; -webkit-border-radius:1px; ' +
36214			'}' + 
36215			'';
36216		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
36217		m = $("<div />").attr({ id : "jstree-marker" }).hide().html("&raquo;")
36218			.bind("mouseleave mouseenter", function (e) { 
36219				m.hide();
36220				ml.hide();
36221				e.preventDefault(); 
36222				e.stopImmediatePropagation(); 
36223				return false; 
36224			})
36225			.appendTo("body");
36226		ml = $("<div />").attr({ id : "jstree-marker-line" }).hide()
36227			.bind("mouseup", function (e) { 
36228				if(r && r.length) { 
36229					r.children("a").trigger(e); 
36230					e.preventDefault(); 
36231					e.stopImmediatePropagation(); 
36232					return false; 
36233				} 
36234			})
36235			.bind("mouseleave", function (e) { 
36236				var rt = $(e.relatedTarget);
36237				if(rt.is(".jstree") || rt.closest(".jstree").length === 0) {
36238					if(r && r.length) { 
36239						r.children("a").trigger(e); 
36240						m.hide();
36241						ml.hide();
36242						e.preventDefault(); 
36243						e.stopImmediatePropagation(); 
36244						return false; 
36245					}
36246				}
36247			})
36248			.appendTo("body");
36249		$(document).bind("drag_start.vakata", function (e, data) {
36250			if(data.data.jstree) { m.show(); if(ml) { ml.show(); } }
36251		});
36252		$(document).bind("drag_stop.vakata", function (e, data) {
36253			if(data.data.jstree) { m.hide(); if(ml) { ml.hide(); } }
36254		});
36255	});
36256})(jQuery);
36257//*/
36258
36259/*
36260 * jsTree checkbox plugin
36261 * Inserts checkboxes in front of every node
36262 * Depends on the ui plugin
36263 * DOES NOT WORK NICELY WITH MULTITREE DRAG'N'DROP
36264 */
36265(function ($) {
36266	$.jstree.plugin("checkbox", {
36267		__init : function () {
36268			this.data.checkbox.noui = this._get_settings().checkbox.override_ui;
36269			if(this.data.ui && this.data.checkbox.noui) {
36270				this.select_node = this.deselect_node = this.deselect_all = $.noop;
36271				this.get_selected = this.get_checked;
36272			}
36273
36274			this.get_container()
36275				.bind("open_node.jstree create_node.jstree clean_node.jstree refresh.jstree", $.proxy(function (e, data) { 
36276						this._prepare_checkboxes(data.rslt.obj);
36277					}, this))
36278				.bind("loaded.jstree", $.proxy(function (e) {
36279						this._prepare_checkboxes();
36280					}, this))
36281				.delegate( (this.data.ui && this.data.checkbox.noui ? "a" : "ins.jstree-checkbox") , "click.jstree", $.proxy(function (e) {
36282						e.preventDefault();
36283						if(this._get_node(e.target).hasClass("jstree-checked")) { this.uncheck_node(e.target); }
36284						else { this.check_node(e.target); }
36285						if(this.data.ui && this.data.checkbox.noui) {
36286							this.save_selected();
36287							if(this.data.cookies) { this.save_cookie("select_node"); }
36288						}
36289						else {
36290							e.stopImmediatePropagation();
36291							return false;
36292						}
36293					}, this));
36294		},
36295		defaults : {
36296			override_ui : false,
36297			two_state : false,
36298			real_checkboxes : false,
36299			checked_parent_open : true,
36300			real_checkboxes_names : function (n) { return [ ("check_" + (n[0].id || Math.ceil(Math.random() * 10000))) , 1]; }
36301		},
36302		__destroy : function () {
36303			this.get_container()
36304				.find("input.jstree-real-checkbox").removeClass("jstree-real-checkbox").end()
36305				.find("ins.jstree-checkbox").remove();
36306		},
36307		_fn : {
36308			_checkbox_notify : function (n, data) {
36309				if(data.checked) {
36310					this.check_node(n, false);
36311				}
36312			},
36313			_prepare_checkboxes : function (obj) {
36314				obj = !obj || obj == -1 ? this.get_container().find("> ul > li") : this._get_node(obj);
36315				if(obj === false) { return; } // added for removing root nodes
36316				var c, _this = this, t, ts = this._get_settings().checkbox.two_state, rc = this._get_settings().checkbox.real_checkboxes, rcn = this._get_settings().checkbox.real_checkboxes_names;
36317				obj.each(function () {
36318					t = $(this);
36319					c = t.is("li") && (t.hasClass("jstree-checked") || (rc && t.children(":checked").length)) ? "jstree-checked" : "jstree-unchecked";
36320					t.find("li").andSelf().each(function () {
36321						var $t = $(this), nm;
36322						$t.children("a" + (_this.data.languages ? "" : ":eq(0)") ).not(":has(.jstree-checkbox)").prepend("<ins class='jstree-checkbox'>&#160;</ins>").parent().not(".jstree-checked, .jstree-unchecked").addClass( ts ? "jstree-unchecked" : c );
36323						if(rc) {
36324							if(!$t.children(":checkbox").length) {
36325								nm = rcn.call(_this, $t);
36326								$t.prepend("<input type='checkbox' class='jstree-real-checkbox' id='" + nm[0] + "' name='" + nm[0] + "' value='" + nm[1] + "' />");
36327							}
36328							else {
36329								$t.children(":checkbox").addClass("jstree-real-checkbox");
36330							}
36331						}
36332						if(!ts) {
36333							if(c === "jstree-checked" || $t.hasClass("jstree-checked") || $t.children(':checked').length) {
36334								$t.find("li").andSelf().addClass("jstree-checked").children(":checkbox").prop("checked", true);
36335							}
36336						}
36337						else {
36338							if($t.hasClass("jstree-checked") || $t.children(':checked').length) {
36339								$t.addClass("jstree-checked").children(":checkbox").prop("checked", true);
36340							}
36341						}
36342					});
36343				});
36344				if(!ts) {
36345					obj.find(".jstree-checked").parent().parent().each(function () { _this._repair_state(this); }); 
36346				}
36347			},
36348			change_state : function (obj, state) {
36349				obj = this._get_node(obj);
36350				var coll = false, rc = this._get_settings().checkbox.real_checkboxes;
36351				if(!obj || obj === -1) { return false; }
36352				state = (state === false || state === true) ? state : obj.hasClass("jstree-checked");
36353				if(this._get_settings().checkbox.two_state) {
36354					if(state) { 
36355						obj.removeClass("jstree-checked").addClass("jstree-unchecked"); 
36356						if(rc) { obj.children(":checkbox").prop("checked", false); }
36357					}
36358					else { 
36359						obj.removeClass("jstree-unchecked").addClass("jstree-checked"); 
36360						if(rc) { obj.children(":checkbox").prop("checked", true); }
36361					}
36362				}
36363				else {
36364					if(state) { 
36365						coll = obj.find("li").andSelf();
36366						if(!coll.filter(".jstree-checked, .jstree-undetermined").length) { return false; }
36367						coll.removeClass("jstree-checked jstree-undetermined").addClass("jstree-unchecked"); 
36368						if(rc) { coll.children(":checkbox").prop("checked", false); }
36369					}
36370					else { 
36371						coll = obj.find("li").andSelf();
36372						if(!coll.filter(".jstree-unchecked, .jstree-undetermined").length) { return false; }
36373						coll.removeClass("jstree-unchecked jstree-undetermined").addClass("jstree-checked"); 
36374						if(rc) { coll.children(":checkbox").prop("checked", true); }
36375						if(this.data.ui) { this.data.ui.last_selected = obj; }
36376						this.data.checkbox.last_selected = obj;
36377					}
36378					obj.parentsUntil(".jstree", "li").each(function () {
36379						var $this = $(this);
36380						if(state) {
36381							if($this.children("ul").children("li.jstree-checked, li.jstree-undetermined").length) {
36382								$this.parentsUntil(".jstree", "li").andSelf().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined");
36383								if(rc) { $this.parentsUntil(".jstree", "li").andSelf().children(":checkbox").prop("checked", false); }
36384								return false;
36385							}
36386							else {
36387								$this.removeClass("jstree-checked jstree-undetermined").addClass("jstree-unchecked");
36388								if(rc) { $this.children(":checkbox").prop("checked", false); }
36389							}
36390						}
36391						else {
36392							if($this.children("ul").children("li.jstree-unchecked, li.jstree-undetermined").length) {
36393								$this.parentsUntil(".jstree", "li").andSelf().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined");
36394								if(rc) { $this.parentsUntil(".jstree", "li").andSelf().children(":checkbox").prop("checked", false); }
36395								return false;
36396							}
36397							else {
36398								$this.removeClass("jstree-unchecked jstree-undetermined").addClass("jstree-checked");
36399								if(rc) { $this.children(":checkbox").prop("checked", true); }
36400							}
36401						}
36402					});
36403				}
36404				if(this.data.ui && this.data.checkbox.noui) { this.data.ui.selected = this.get_checked(); }
36405				this.__callback(obj);
36406				return true;
36407			},
36408			check_node : function (obj) {
36409				if(this.change_state(obj, false)) { 
36410					obj = this._get_node(obj);
36411					if(this._get_settings().checkbox.checked_parent_open) {
36412						var t = this;
36413						obj.parents(".jstree-closed").each(function () { t.open_node(this, false, true); });
36414					}
36415					this.__callback({ "obj" : obj }); 
36416				}
36417			},
36418			uncheck_node : function (obj) {
36419				if(this.change_state(obj, true)) { this.__callback({ "obj" : this._get_node(obj) }); }
36420			},
36421			check_all : function () {
36422				var _this = this, 
36423					coll = this._get_settings().checkbox.two_state ? this.get_container_ul().find("li") : this.get_container_ul().children("li");
36424				coll.each(function () {
36425					_this.change_state(this, false);
36426				});
36427				this.__callback();
36428			},
36429			uncheck_all : function () {
36430				var _this = this,
36431					coll = this._get_settings().checkbox.two_state ? this.get_container_ul().find("li") : this.get_container_ul().children("li");
36432				coll.each(function () {
36433					_this.change_state(this, true);
36434				});
36435				this.__callback();
36436			},
36437
36438			is_checked : function(obj) {
36439				obj = this._get_node(obj);
36440				return obj.length ? obj.is(".jstree-checked") : false;
36441			},
36442			get_checked : function (obj, get_all) {
36443				obj = !obj || obj === -1 ? this.get_container() : this._get_node(obj);
36444				return get_all || this._get_settings().checkbox.two_state ? obj.find(".jstree-checked") : obj.find("> ul > .jstree-checked, .jstree-undetermined > ul > .jstree-checked");
36445			},
36446			get_unchecked : function (obj, get_all) { 
36447				obj = !obj || obj === -1 ? this.get_container() : this._get_node(obj);
36448				return get_all || this._get_settings().checkbox.two_state ? obj.find(".jstree-unchecked") : obj.find("> ul > .jstree-unchecked, .jstree-undetermined > ul > .jstree-unchecked");
36449			},
36450
36451			show_checkboxes : function () { this.get_container().children("ul").removeClass("jstree-no-checkboxes"); },
36452			hide_checkboxes : function () { this.get_container().children("ul").addClass("jstree-no-checkboxes"); },
36453
36454			_repair_state : function (obj) {
36455				obj = this._get_node(obj);
36456				if(!obj.length) { return; }
36457				if(this._get_settings().checkbox.two_state) {
36458					obj.find('li').andSelf().not('.jstree-checked').removeClass('jstree-undetermined').addClass('jstree-unchecked').children(':checkbox').prop('checked', true);
36459					return;
36460				}
36461				var rc = this._get_settings().checkbox.real_checkboxes,
36462					a = obj.find("> ul > .jstree-checked").length,
36463					b = obj.find("> ul > .jstree-undetermined").length,
36464					c = obj.find("> ul > li").length;
36465				if(c === 0) { if(obj.hasClass("jstree-undetermined")) { this.change_state(obj, false); } }
36466				else if(a === 0 && b === 0) { this.change_state(obj, true); }
36467				else if(a === c) { this.change_state(obj, false); }
36468				else { 
36469					obj.parentsUntil(".jstree","li").andSelf().removeClass("jstree-checked jstree-unchecked").addClass("jstree-undetermined");
36470					if(rc) { obj.parentsUntil(".jstree", "li").andSelf().children(":checkbox").prop("checked", false); }
36471				}
36472			},
36473			reselect : function () {
36474				if(this.data.ui && this.data.checkbox.noui) { 
36475					var _this = this,
36476						s = this.data.ui.to_select;
36477					s = $.map($.makeArray(s), function (n) { return "#" + n.toString().replace(/^#/,"").replace(/\\\//g,"/").replace(/\//g,"\\\/").replace(/\\\./g,".").replace(/\./g,"\\.").replace(/\:/g,"\\:"); });
36478					this.deselect_all();
36479					$.each(s, function (i, val) { _this.check_node(val); });
36480					this.__callback();
36481				}
36482				else { 
36483					this.__call_old(); 
36484				}
36485			},
36486			save_loaded : function () {
36487				var _this = this;
36488				this.data.core.to_load = [];
36489				this.get_container_ul().find("li.jstree-closed.jstree-undetermined").each(function () {
36490					if(this.id) { _this.data.core.to_load.push("#" + this.id); }
36491				});
36492			}
36493		}
36494	});
36495	$(function() {
36496		var css_string = '.jstree .jstree-real-checkbox { display:none; } ';
36497		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
36498	});
36499})(jQuery);
36500//*/
36501
36502/* 
36503 * jsTree XML plugin
36504 * The XML data store. Datastores are build by overriding the `load_node` and `_is_loaded` functions.
36505 */
36506(function ($) {
36507	$.vakata.xslt = function (xml, xsl, callback) {
36508		var rs = "", xm, xs, processor, support;
36509		// TODO: IE9 no XSLTProcessor, no document.recalc
36510		if(document.recalc) {
36511			xm = document.createElement('xml');
36512			xs = document.createElement('xml');
36513			xm.innerHTML = xml;
36514			xs.innerHTML = xsl;
36515			$("body").append(xm).append(xs);
36516			setTimeout( (function (xm, xs, callback) {
36517				return function () {
36518					callback.call(null, xm.transformNode(xs.XMLDocument));
36519					setTimeout( (function (xm, xs) { return function () { $(xm).remove(); $(xs).remove(); }; })(xm, xs), 200);
36520				};
36521			})(xm, xs, callback), 100);
36522			return true;
36523		}
36524		if(typeof window.DOMParser !== "undefined" && typeof window.XMLHttpRequest !== "undefined" && typeof window.XSLTProcessor === "undefined") {
36525			xml = new DOMParser().parseFromString(xml, "text/xml");
36526			xsl = new DOMParser().parseFromString(xsl, "text/xml");
36527			// alert(xml.transformNode());
36528			// callback.call(null, new XMLSerializer().serializeToString(rs));
36529			
36530		}
36531		if(typeof window.DOMParser !== "undefined" && typeof window.XMLHttpRequest !== "undefined" && typeof window.XSLTProcessor !== "undefined") {
36532			processor = new XSLTProcessor();
36533			support = $.isFunction(processor.transformDocument) ? (typeof window.XMLSerializer !== "undefined") : true;
36534			if(!support) { return false; }
36535			xml = new DOMParser().parseFromString(xml, "text/xml");
36536			xsl = new DOMParser().parseFromString(xsl, "text/xml");
36537			if($.isFunction(processor.transformDocument)) {
36538				rs = document.implementation.createDocument("", "", null);
36539				processor.transformDocument(xml, xsl, rs, null);
36540				callback.call(null, new XMLSerializer().serializeToString(rs));
36541				return true;
36542			}
36543			else {
36544				processor.importStylesheet(xsl);
36545				rs = processor.transformToFragment(xml, document);
36546				callback.call(null, $("<div />").append(rs).html());
36547				return true;
36548			}
36549		}
36550		return false;
36551	};
36552	var xsl = {
36553		'nest' : '<' + '?xml version="1.0" encoding="utf-8" ?>' + 
36554			'<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >' + 
36555			'<xsl:output method="html" encoding="utf-8" omit-xml-declaration="yes" standalone="no" indent="no" media-type="text/html" />' + 
36556			'<xsl:template match="/">' + 
36557			'	<xsl:call-template name="nodes">' + 
36558			'		<xsl:with-param name="node" select="/root" />' + 
36559			'	</xsl:call-template>' + 
36560			'</xsl:template>' + 
36561			'<xsl:template name="nodes">' + 
36562			'	<xsl:param name="node" />' + 
36563			'	<ul>' + 
36564			'	<xsl:for-each select="$node/item">' + 
36565			'		<xsl:variable name="children" select="count(./item) &gt; 0" />' + 
36566			'		<li>' + 
36567			'			<xsl:attribute name="class">' + 
36568			'				<xsl:if test="position() = last()">jstree-last </xsl:if>' + 
36569			'				<xsl:choose>' + 
36570			'					<xsl:when test="@state = \'open\'">jstree-open </xsl:when>' + 
36571			'					<xsl:when test="$children or @hasChildren or @state = \'closed\'">jstree-closed </xsl:when>' + 
36572			'					<xsl:otherwise>jstree-leaf </xsl:otherwise>' + 
36573			'				</xsl:choose>' + 
36574			'				<xsl:value-of select="@class" />' + 
36575			'			</xsl:attribute>' + 
36576			'			<xsl:for-each select="@*">' + 
36577			'				<xsl:if test="name() != \'class\' and name() != \'state\' and name() != \'hasChildren\'">' + 
36578			'					<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
36579			'				</xsl:if>' + 
36580			'			</xsl:for-each>' + 
36581			'	<ins class="jstree-icon"><xsl:text>&#xa0;</xsl:text></ins>' + 
36582			'			<xsl:for-each select="content/name">' + 
36583			'				<a>' + 
36584			'				<xsl:attribute name="href">' + 
36585			'					<xsl:choose>' + 
36586			'					<xsl:when test="@href"><xsl:value-of select="@href" /></xsl:when>' + 
36587			'					<xsl:otherwise>#</xsl:otherwise>' + 
36588			'					</xsl:choose>' + 
36589			'				</xsl:attribute>' + 
36590			'				<xsl:attribute name="class"><xsl:value-of select="@lang" /> <xsl:value-of select="@class" /></xsl:attribute>' + 
36591			'				<xsl:attribute name="style"><xsl:value-of select="@style" />
36591</xsl:attribute>' + 
36592			'				<xsl:for-each select="@*">' + 
36593			'					<xsl:if test="name() != \'style\' and name() != \'class\' and name() != \'href\'">' + 
36594			'						<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
36595			'					</xsl:if>' + 
36596			'				</xsl:for-each>' + 
36597			'					<ins>' + 
36598			'						<xsl:attribute name="class">jstree-icon ' + 
36599			'							<xsl:if test="string-length(attribute::icon) > 0 and not(contains(@icon,\'/\'))"><xsl:value-of select="@icon" /></xsl:if>' + 
36600			'						</xsl:attribute>' + 
36601			'						<xsl:if test="string-length(attribute::icon) > 0 and contains(@icon,\'/\')"><xsl:attribute name="style">background:url(<xsl:value-of select="@icon" />) center center no-repeat;</xsl:attribute></xsl:if>' + 
36602			'						<xsl:text>&#xa0;</xsl:text>' + 
36603			'					</ins>' + 
36604			'					<xsl:copy-of select="./child::node()" />' + 
36605			'				</a>' + 
36606			'			</xsl:for-each>' + 
36607			'			<xsl:if test="$children or @hasChildren"><xsl:call-template name="nodes"><xsl:with-param name="node" select="current()" /></xsl:call-template></xsl:if>' + 
36608			'		</li>' + 
36609			'	</xsl:for-each>' + 
36610			'	</ul>' + 
36611			'</xsl:template>' + 
36612			'</xsl:stylesheet>',
36613
36614		'flat' : '<' + '?xml version="1.0" encoding="utf-8" ?>' + 
36615			'<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >' + 
36616			'<xsl:output method="html" encoding="utf-8" omit-xml-declaration="yes" standalone="no" indent="no" media-type="text/xml" />' + 
36617			'<xsl:template match="/">' + 
36618			'	<ul>' + 
36619			'	<xsl:for-each select="//item[not(@parent_id) or @parent_id=0 or not(@parent_id = //item/@id)]">' + /* the last `or` may be removed */
36620			'		<xsl:call-template name="nodes">' + 
36621			'			<xsl:with-param name="node" select="." />' + 
36622			'			<xsl:with-param name="is_last" select="number(position() = last())" />' + 
36623			'		</xsl:call-template>' + 
36624			'	</xsl:for-each>' + 
36625			'	</ul>' + 
36626			'</xsl:template>' + 
36627			'<xsl:template name="nodes">' + 
36628			'	<xsl:param name="node" />' + 
36629			'	<xsl:param name="is_last" />' + 
36630			'	<xsl:variable name="children" select="count(//item[@parent_id=$node/attribute::id]) &gt; 0" />' + 
36631			'	<li>' + 
36632			'	<xsl:attribute name="class">' + 
36633			'		<xsl:if test="$is_last = true()">jstree-last </xsl:if>' + 
36634			'		<xsl:choose>' + 
36635			'			<xsl:when test="@state = \'open\'">jstree-open </xsl:when>' + 
36636			'			<xsl:when test="$children or @hasChildren or @state = \'closed\'">jstree-closed </xsl:when>' + 
36637			'			<xsl:otherwise>jstree-leaf </xsl:otherwise>' + 
36638			'		</xsl:choose>' + 
36639			'		<xsl:value-of select="@class" />' + 
36640			'	</xsl:attribute>' + 
36641			'	<xsl:for-each select="@*">' + 
36642			'		<xsl:if test="name() != \'parent_id\' and name() != \'hasChildren\' and name() != \'class\' and name() != \'state\'">' + 
36643			'		<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
36644			'		</xsl:if>' + 
36645			'	</xsl:for-each>' + 
36646			'	<ins class="jstree-icon"><xsl:text>&#xa0;</xsl:text></ins>' + 
36647			'	<xsl:for-each select="content/name">' + 
36648			'		<a>' + 
36649			'		<xsl:attribute name="href">' + 
36650			'			<xsl:choose>' + 
36651			'			<xsl:when test="@href"><xsl:value-of select="@href" /></xsl:when>' + 
36652			'			<xsl:otherwise>#</xsl:otherwise>' + 
36653			'			</xsl:choose>' + 
36654			'		</xsl:attribute>' + 
36655			'		<xsl:attribute name="class"><xsl:value-of select="@lang" /> <xsl:value-of select="@class" /></xsl:attribute>' + 
36656			'		<xsl:attribute name="style"><xsl:value-of select="@style" /></xsl:attribute>' + 
36657			'		<xsl:for-each select="@*">' + 
36658			'			<xsl:if test="name() != \'style\' and name() != \'class\' and name() != \'href\'">' + 
36659			'				<xsl:attribute name="{name()}"><xsl:value-of select="." /></xsl:attribute>' + 
36660			'			</xsl:if>' + 
36661			'		</xsl:for-each>' + 
36662			'			<ins>' + 
36663			'				<xsl:attribute name="class">jstree-icon ' + 
36664			'					<xsl:if test="string-length(attribute::icon) > 0 and not(contains(@icon,\'/\'))"><xsl:value-of select="@icon" /></xsl:if>' + 
36665			'				</xsl:attribute>' + 
36666			'				<xsl:if test="string-length(attribute::icon) > 0 and contains(@icon,\'/\')"><xsl:attribute name="style">background:url(<xsl:value-of select="@icon" />) center center no-repeat;</xsl:attribute></xsl:if>' + 
36667			'				<xsl:text>&#xa0;</xsl:text>' + 
36668			'			</ins>' + 
36669			'			<xsl:copy-of select="./child::node()" />' + 
36670			'		</a>' + 
36671			'	</xsl:for-each>' + 
36672			'	<xsl:if test="$children">' + 
36673			'		<ul>' + 
36674			'		<xsl:for-each select="//item[@parent_id=$node/attribute::id]">' + 
36675			'			<xsl:call-template name="nodes">' + 
36676			'				<xsl:with-param name="node" select="." />' + 
36677			'				<xsl:with-param name="is_last" select="number(position() = last())" />' + 
36678			'			</xsl:call-template>' + 
36679			'		</xsl:for-each>' + 
36680			'		</ul>' + 
36681			'	</xsl:if>' + 
36682			'	</li>' + 
36683			'</xsl:template>' + 
36684			'</xsl:stylesheet>'
36685	},
36686	escape_xml = function(string) {
36687		return string
36688			.toString()
36689			.replace(/&/g, '&amp;')
36690			.replace(/</g, '&lt;')
36691			.replace(/>/g, '&gt;')
36692			.replace(/"/g, '&quot;')
36693			.replace(/'/g, '&apos;');
36694	};
36695	$.jstree.plugin("xml_data", {
36696		defaults : { 
36697			data : false,
36698			ajax : false,
36699			xsl : "flat",
36700			clean_node : false,
36701			correct_state : true,
36702			get_skip_empty : false,
36703			get_include_preamble : true
36704		},
36705		_fn : {
36706			load_node : function (obj, s_call, e_call) { var _this = this; this.load_node_xml(obj, function () { _this.__callback({ "obj" : _this._get_node(obj) }); s_call.call(this); }, e_call); },
36707			_is_loaded : function (obj) { 
36708				var s = this._get_settings().xml_data;
36709				obj = this._get_node(obj);
36710				return obj == -1 || !obj || (!s.ajax && !$.isFunction(s.data)) || obj.is(".jstree-open, .jstree-leaf") || obj.children("ul").children("li").size() > 0;
36711			},
36712			load_node_xml : function (obj, s_call, e_call) {
36713				var s = this.get_settings().xml_data,
36714					error_func = function () {},
36715					success_func = function () {};
36716
36717				obj = this._get_node(obj);
36718				if(obj && obj !== -1) {
36719					if(obj.data("jstree_is_loading")) { return; }
36720					else { obj.data("jstree_is_loading",true); }
36721				}
36722				switch(!0) {
36723					case (!s.data && !s.ajax): throw "Neither data nor ajax settings supplied.";
36724					case ($.isFunction(s.data)):
36725						s.data.call(this, obj, $.proxy(function (d) {
36726							this.parse_xml(d, $.proxy(function (d) {
36727								if(d) {
36728									d = d.replace(/ ?xmlns="[^"]*"/ig, "");
36729									if(d.length > 10) {
36730										d = $(d);
36731										if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
36732										else { obj.children("a.jstree-loading").removeClass("jstree-loading"); obj.append(d); obj.removeData("jstree_is_loading"); }
36733										if(s.clean_node) { this.clean_node(obj); }
36734										if(s_call) { s_call.call(this); }
36735									}
36736									else {
36737										if(obj && obj !== -1) { 
36738											obj.children("a.jstree-loading").removeClass("jstree-loading");
36739											obj.removeData("jstree_is_loading");
36740											if(s.correct_state) { 
36741												this.correct_state(obj);
36742												if(s_call) { s_call.call(this); } 
36743											}
36744										}
36745										else {
36746											if(s.correct_state) { 
36747												this.get_container().children("ul").empty();
36748												if(s_call) { s_call.call(this); } 
36749											}
36750										}
36751									}
36752								}
36753							}, this));
36754						}, this));
36755						break;
36756					case (!!s.data && !s.ajax) || (!!s.data && !!s.ajax && (!obj || obj === -1)):
36757						if(!obj || obj == -1) {
36758							this.parse_xml(s.data, $.proxy(function (d) {
36759								if(d) {
36760									d = d.replace(/ ?xmlns="[^"]*"/ig, "");
36761									if(d.length > 10) {
36762										d = $(d);
36763										this.get_container().children("ul").empty().append(d.children());
36764										if(s.clean_node) { this.clean_node(obj); }
36765										if(s_call) { s_call.call(this); }
36766									}
36767								}
36768								else { 
36769									if(s.correct_state) { 
36770										this.get_container().children("ul").empty(); 
36771										if(s_call) { s_call.call(this); }
36772									}
36773								}
36774							}, this));
36775						}
36776						break;
36777					case (!s.data && !!s.ajax) || (!!s.data && !!s.ajax && obj && obj !== -1):
36778						error_func = function (x, t, e) {
36779							var ef = this.get_settings().xml_data.ajax.error; 
36780							if(ef) { ef.call(this, x, t, e); }
36781							if(obj !== -1 && obj.length) {
36782								obj.children("a.jstree-loading").removeClass("jstree-loading");
36783								obj.removeData("jstree_is_loading");
36784								if(t === "success" && s.correct_state) { this.correct_state(obj); }
36785							}
36786							else {
36787								if(t === "success" && s.correct_state) { this.get_container().children("ul").empty(); }
36788							}
36789							if(e_call) { e_call.call(this); }
36790						};
36791						success_func = function (d, t, x) {
36792							d = x.responseText;
36793							var sf = this.get_settings().xml_data.ajax.success; 
36794							if(sf) { d = sf.call(this,d,t,x) || d; }
36795							if(d === "" || (d && d.toString && d.toString().replace(/^[\s\n]+$/,"") === "")) {
36796								return error_func.call(this, x, t, "");
36797							}
36798							this.parse_xml(d, $.proxy(function (d) {
36799								if(d) {
36800									d = d.replace(/ ?xmlns="[^"]*"/ig, "");
36801									if(d.length > 10) {
36802										d = $(d);
36803										if(obj === -1 || !obj) { this.get_container().children("ul").empty().append(d.children()); }
36804										else { obj.children("a.jstree-loading").removeClass("jstree-loading"); obj.append(d); obj.removeData("jstree_is_loading"); }
36805										if(s.clean_node) { this.clean_node(obj); }
36806										if(s_call) { s_call.call(this); }
36807									}
36808									else {
36809										if(obj && obj !== -1) { 
36810											obj.children("a.jstree-loading").removeClass("jstree-loading");
36811											obj.removeData("jstree_is_loading");
36812											if(s.correct_state) { 
36813												this.correct_state(obj);
36814												if(s_call) { s_call.call(this); } 
36815											}
36816										}
36817										else {
36818											if(s.correct_state) { 
36819												this.get_container().children("ul").empty();
36820												if(s_call) { s_call.call(this); } 
36821											}
36822										}
36823									}
36824								}
36825							}, this));
36826						};
36827						s.ajax.context = this;
36828						s.ajax.error = error_func;
36829						s.ajax.success = success_func;
36830						if(!s.ajax.dataType) { s.ajax.dataType = "xml"; }
36831						if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, obj); }
36832						if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, obj); }
36833						$.ajax(s.ajax);
36834						break;
36835				}
36836			},
36837			parse_xml : function (xml, callback) {
36838				var s = this._get_settings().xml_data;
36839				$.vakata.xslt(xml, xsl[s.xsl], callback);
36840			},
36841			get_xml : function (tp, obj, li_attr, a_attr, is_callback) {
36842				var result = "", 
36843					s = this._get_settings(), 
36844					_this = this,
36845					tmp1, tmp2, li, a, lang;
36846				if(!tp) { tp = "flat"; }
36847				if(!is_callback) { is_callback = 0; }
36848				obj = this._get_node(obj);
36849				if(!obj || obj === -1) { obj = this.get_container().find("> ul > li"); }
36850				li_attr = $.isArray(li_attr) ? li_attr : [ "id", "class" ];
36851				if(!is_callback && this.data.types && $.inArray(s.types.type_attr, li_attr) === -1) { li_attr.push(s.types.type_attr); }
36852
36853				a_attr = $.isArray(a_attr) ? a_attr : [ ];
36854
36855				if(!is_callback) { 
36856					if(s.xml_data.get_include_preamble) { 
36857						result += '<' + '?xml version="1.0" encoding="UTF-8"?' + '>'; 
36858					}
36859					result += "<root>"; 
36860				}
36861				obj.each(function () {
36862					result += "<item";
36863					li = $(this);
36864					$.each(li_attr, function (i, v) { 
36865						var t = li.attr(v);
36866						if(!s.xml_data.get_skip_empty || typeof t !== "undefined") {
36867							result += " " + v + "=\"" + escape_xml((" " + (t || "")).replace(/ jstree[^ ]*/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"")) + "\""; 
36868						}
36869					});
36870					if(li.hasClass("jstree-open")) { result += " state=\"open\""; }
36871					if(li.hasClass("jstree-closed")) { result += " state=\"closed\""; }
36872					if(tp === "flat") { result += " parent_id=\"" + escape_xml(is_callback) + "\""; }
36873					result += ">";
36874					result += "<content>";
36875					a = li.children("a");
36876					a.each(function () {
36877						tmp1 = $(this);
36878						lang = false;
36879						result += "<name";
36880						if($.inArray("languages", s.plugins) !== -1) {
36881							$.each(s.languages, function (k, z) {
36882								if(tmp1.hasClass(z)) { result += " lang=\"" + escape_xml(z) + "\""; lang = z; return false; }
36883							});
36884						}
36885						if(a_attr.length) { 
36886							$.each(a_attr, function (k, z) {
36887								var t = tmp1.attr(z);
36888								if(!s.xml_data.get_skip_empty || typeof t !== "undefined") {
36889									result += " " + z + "=\"" + escape_xml((" " + t || "").replace(/ jstree[^ ]*/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"")) + "\"";
36890								}
36891							});
36892						}
36893						if(tmp1.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/^\s+$/ig,"").length) {
36894							result += ' icon="' + escape_xml(tmp1.children("ins").get(0).className.replace(/jstree[^ ]*|$/ig,'').replace(/\s+$/ig," ").replace(/^ /,"").replace(/ $/,"")) + '"';
36895						}
36896						if(tmp1.children("ins").get(0).style.backgroundImage.length) {
36897							result += ' icon="' + escape_xml(tmp1.children("ins").get(0).style.backgroundImage.replace("url(","").replace(")","").replace(/'/ig,"").replace(/"/ig,"")) + '"';
36898						}
36899						result += ">";
36900						result += "<![CDATA[" + _this.get_text(tmp1, lang) + "]]>";
36901						result += "</name>";
36902					});
36903					result += "</content>";
36904					tmp2 = li[0].id || true;
36905					li = li.find("> ul > li");
36906					if(li.length) { tmp2 = _this.get_xml(tp, li, li_attr, a_attr, tmp2); }
36907					else { tmp2 = ""; }
36908					if(tp == "nest") { result += tmp2; }
36909					result += "</item>";
36910					if(tp == "flat") { result += tmp2; }
36911				});
36912				if(!is_callback) { result += "</root>"; }
36913				return result;
36914			}
36915		}
36916	});
36917})(jQuery);
36918//*/
36919
36920/*
36921 * jsTree search plugin
36922 * Enables both sync and async search on the tree
36923 * DOES NOT WORK WITH JSON PROGRESSIVE RENDER
36924 */
36925(function ($) {
36926	$.expr[':'].jstree_contains = function(a,i,m){
36927		return (a.textContent || a.innerText || "").toLowerCase().indexOf(m[3].toLowerCase())>=0;
36928	};
36929	$.expr[':'].jstree_title_contains = function(a,i,m) {
36930		return (a.getAttribute("title") || "").toLowerCase().indexOf(m[3].toLowerCase())>=0;
36931	};
36932	$.jstree.plugin("search", {
36933		__init : function () {
36934			this.data.search.str = "";
36935			this.data.search.result = $();
36936			if(this._get_settings().search.show_only_matches) {
36937				this.get_container()
36938					.bind("search.jstree", function (e, data) {
36939						$(this).children("ul").find("li").hide().removeClass("jstree-last");
36940						data.rslt.nodes.parentsUntil(".jstree").andSelf().show()
36941							.filter("ul").each(function () { $(this).children("li:visible").eq(-1).addClass("jstree-last"); });
36942					})
36943					.bind("clear_search.jstree", function () {
36944						$(this).children("ul").find("li").css("display","").end().end().jstree("clean_node", -1);
36945					});
36946			}
36947		},
36948		defaults : {
36949			ajax : false,
36950			search_method : "jstree_contains", // for case insensitive - jstree_contains
36951			show_only_matches : false
36952		},
36953		_fn : {
36954			search : function (str, skip_async) {
36955				if($.trim(str) === "") { this.clear_search(); return; }
36956				var s = this.get_settings().search, 
36957					t = this,
36958					error_func = function () { },
36959					success_func = function () { };
36960				this.data.search.str = str;
36961
36962				if(!skip_async && s.ajax !== false && this.get_container_ul().find("li.jstree-closed:not(:has(ul)):eq(0)").length > 0) {
36963					this.search.supress_callback = true;
36964					error_func = function () { };
36965					success_func = function (d, t, x) {
36966						var sf = this.get_settings().search.ajax.success; 
36967						if(sf) { d = sf.call(this,d,t,x) || d; }
36968						this.data.search.to_open = d;
36969						this._search_open();
36970					};
36971					s.ajax.context = this;
36972					s.ajax.error = error_func;
36973					s.ajax.success = success_func;
36974					if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, str); }
36975					if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, str); }
36976					if(!s.ajax.data) { s.ajax.data = { "search_string" : str }; }
36977					if(!s.ajax.dataType || /^json/.exec(s.ajax.dataType)) { s.ajax.dataType = "json"; }
36978					$.ajax(s.ajax);
36979					return;
36980				}
36981				if(this.data.search.result.length) { this.clear_search(); }
36982				this.data.search.result = this.get_container().find("a" + (this.data.languages ? "." + this.get_lang() : "" ) + ":" + (s.search_method) + "(" + this.data.search.str + ")");
36983				this.data.search.result.addClass("jstree-search").parent().parents(".jstree-closed").each(function () {
36984					t.open_node(this, false, true);
36985				});
36986				this.__callback({ nodes : this.data.search.result, str : str });
36987			},
36988			clear_search : function (str) {
36989				this.data.search.result.removeClass("jstree-search");
36990				this.__callback(this.data.search.result);
36991				this.data.search.result = $();
36992			},
36993			_search_open : function (is_callback) {
36994				var _this = this,
36995					done = true,
36996					current = [],
36997					remaining = [];
36998				if(this.data.search.to_open.length) {
36999					$.each(this.data.search.to_open, function (i, val) {
37000						if(val == "#") { return true; }
37001						if($(val).length && $(val).is(".jstree-closed")) { current.push(val); }
37002						else { remaining.push(val); }
37003					});
37004					if(current.length) {
37005						this.data.search.to_open = remaining;
37006						$.each(current, function (i, val) { 
37007							_this.open_node(val, function () { _this._search_open(true); }); 
37008						});
37009						done = false;
37010					}
37011				}
37012				if(done) { this.search(this.data.search.str, true); }
37013			}
37014		}
37015	});
37016})(jQuery);
37017//*/
37018
37019/* 
37020 * jsTree contextmenu plugin
37021 */
37022(function ($) {
37023	$.vakata.context = {
37024		hide_on_mouseleave : false,
37025
37026		cnt		: $("<div id='vakata-contextmenu' />"),
37027		vis		: false,
37028		tgt		: false,
37029		par		: false,
37030		func	: false,
37031		data	: false,
37032		rtl		: false,
37033		show	: function (s, t, x, y, d, p, rtl) {
37034			$.vakata.context.rtl = !!rtl;
37035			var html = $.vakata.context.parse(s), h, w;
37036			if(!html) { return; }
37037			$.vakata.context.vis = true;
37038			$.vakata.context.tgt = t;
37039			$.vakata.context.par = p || t || null;
37040			$.vakata.context.data = d || null;
37041			$.vakata.context.cnt
37042				.html(html)
37043				.css({ "visibility" : "hidden", "display" : "block", "left" : 0, "top" : 0 });
37044
37045			if($.vakata.context.hide_on_mouseleave) {
37046				$.vakata.context.cnt
37047					.one("mouseleave", function(e) { $.vakata.context.hide(); });
37048			}
37049
37050			h = $.vakata.context.cnt.height();
37051			w = $.vakata.context.cnt.width();
37052			if(x + w > $(document).width()) { 
37053				x = $(document).width() - (w + 5); 
37054				$.vakata.context.cnt.find("li > ul").addClass("right"); 
37055			}
37056			if(y + h > $(document).height()) { 
37057				y = y - (h + t[0].offsetHeight); 
37058				$.vakata.context.cnt.find("li > ul").addClass("bottom"); 
37059			}
37060
37061			$.vakata.context.cnt
37062				.css({ "left" : x, "top" : y })
37063				.find("li:has(ul)")
37064					.bind("mouseenter", function (e) { 
37065						var w = $(document).width(),
37066							h = $(document).height(),
37067							ul = $(this).children("ul").show(); 
37068						if(w !== $(document).width()) { ul.toggleClass("right"); }
37069						if(h !== $(document).height()) { ul.toggleClass("bottom"); }
37070					})
37071					.bind("mouseleave", function (e) { 
37072						$(this).children("ul").hide(); 
37073					})
37074					.end()
37075				.css({ "visibility" : "visible" })
37076				.show();
37077			$(document).triggerHandler("context_show.vakata");
37078		},
37079		hide	: function () {
37080			$.vakata.context.vis = false;
37081			$.vakata.context.cnt.attr("class","").css({ "visibility" : "hidden" });
37082			$(document).triggerHandler("context_hide.vakata");
37083		},
37084		parse	: function (s, is_callback) {
37085			if(!s) { return false; }
37086			var str = "",
37087				tmp = false,
37088				was_sep = true;
37089			if(!is_callback) { $.vakata.context.func = {}; }
37090			str += "<ul>";
37091			$.each(s, function (i, val) {
37092				if(!val) { return true; }
37093				$.vakata.context.func[i] = val.action;
37094				if(!was_sep && val.separator_before) {
37095					str += "<li class='vakata-separator vakata-separator-before'></li>";
37096				}
37097				was_sep = false;
37098				str += "<li class='" + (val._class || "") + (val._disabled ? " jstree-contextmenu-disabled " : "") + "'><ins ";
37099				if(val.icon && val.icon.indexOf("/") === -1) { str += " class='" + val.icon + "' "; }
37100				if(val.icon && val.icon.indexOf("/") !== -1) { str += " style='background:url(" + val.icon + ") center center no-repeat;' "; }
37101				str += ">&#160;</ins><a href='#' rel='" + i + "'>";
37102				if(val.submenu) {
37103					str += "<span style='float:" + ($.vakata.context.rtl ? "left" : "right") + ";'>&raquo;</span>";
37104				}
37105				str += val.label + "</a>";
37106				if(val.submenu) {
37107					tmp = $.vakata.context.parse(val.submenu, true);
37108					if(tmp) { str += tmp; }
37109				}
37110				str += "</li>";
37111				if(val.separator_after) {
37112					str += "<li class='vakata-separator vakata-separator-after'></li>";
37113					was_sep = true;
37114				}
37115			});
37116			str = str.replace(/<li class\='vakata-separator vakata-separator-after'\><\/li\>$/,"");
37117			str += "</ul>";
37118			$(document).triggerHandler("context_parse.vakata");
37119			return str.length > 10 ? str : false;
37120		},
37121		exec	: function (i) {
37122			if($.isFunction($.vakata.context.func[i])) {
37123				// if is string - eval and call it!
37124				$.vakata.context.func[i].call($.vakata.context.data, $.vakata.context.par);
37125				return true;
37126			}
37127			else { return false; }
37128		}
37129	};
37130	$(function () {
37131		var css_string = '' + 
37132			'#vakata-contextmenu { display:block; visibility:hidden; left:0; top:-200px; position:absolute; margin:0; padding:0; min-width:180px; background:#ebebeb; border:1px solid silver; z-index:10000; *width:180px; } ' + 
37133			'#vakata-contextmenu ul { min-width:180px; *width:180px; } ' + 
37134			'#vakata-contextmenu ul, #vakata-contextmenu li { margin:0; padding:0; list-style-type:none; display:block; } ' + 
37135			'#vakata-contextmenu li { line-height:20px; min-height:20px; position:relative; padding:0px; } ' + 
37136			'#vakata-contextmenu li a { padding:1px 6px; line-height:17px; display:block; text-decoration:none; margin:1px 1px 0 1px; } ' + 
37137			'#vakata-contextmenu li ins { float:left; width:16px; height:16px; text-decoration:none; margin-right:2px; } ' + 
37138			'#vakata-contextmenu li a:hover, #vakata-contextmenu li.vakata-hover > a { background:gray; color:white; } ' + 
37139			'#vakata-contextmenu li ul { display:none; position:absolute; top:-2px; left:100%; background:#ebebeb; border:1px solid gray; } ' + 
37140			'#vakata-contextmenu .right { right:100%; left:auto; } ' + 
37141			'#vakata-contextmenu .bottom { bottom:-1px; top:auto; } ' + 
37142			'#vakata-contextmenu li.vakata-separator { min-height:0; height:1px; line-height:1px; font-size:1px; overflow:hidden; margin:0 2px; background:silver; /* border-top:1px solid #fefefe; */ padding:0; } ';
37143		$.vakata.css.add_sheet({ str : css_string, title : "vakata" });
37144		$.vakata.context.cnt
37145			.delegate("a","click", function (e) { e.preventDefault(); })
37146			.delegate("a","mouseup", function (e) {
37147				if(!$(this).parent().hasClass("jstree-contextmenu-disabled") && $.vakata.context.exec($(this).attr("rel"))) {
37148					$.vakata.context.hide();
37149				}
37150				else { $(this).blur(); }
37151			})
37152			.delegate("a","mouseover", function () {
37153				$.vakata.context.cnt.find(".vakata-hover").removeClass("vakata-hover");
37154			})
37155			.appendTo("body");
37156		$(document).bind("mousedown", function (e) { if($.vakata.context.vis && !$.contains($.vakata.context.cnt[0], e.target)) { $.vakata.context.hide(); } });
37157		if(typeof $.hotkeys !== "undefined") {
37158			$(document)
37159				.bind("keydown", "up", function (e) { 
37160					if($.vakata.context.vis) { 
37161						var o = $.vakata.context.cnt.find("ul:visible").last().children(".vakata-hover").removeClass("vakata-hover").prevAll("li:not(.vakata-separator)").first();
37162						if(!o.length) { o = $.vakata.context.cnt.find("ul:visible").last().children("li:not(.vakata-separator)").last(); }
37163						o.addClass("vakata-hover");
37164						e.stopImmediatePropagation(); 
37165						e.preventDefault();
37166					} 
37167				})
37168				.bind("keydown", "down", function (e) { 
37169					if($.vakata.context.vis) { 
37170						var o = $.vakata.context.cnt.find("ul:visible").last().children(".vakata-hover").removeClass("vakata-hover").nextAll("li:not(.vakata-separator)").first();
37171						if(!o.length) { o = $.vakata.context.cnt.find("ul:visible").last().children("li:not(.vakata-separator)").first(); }
37172						o.addClass("vakata-hover");
37173						e.stopImmediatePropagation(); 
37174						e.preventDefault();
37175					} 
37176				})
37177				.bind("keydown", "right", function (e) { 
37178					if($.vakata.context.vis) { 
37179						$.vakata.context.cnt.find(".vakata-hover").children("ul").show().children("li:not(.vakata-separator)").removeClass("vakata-hover").first().addClass("vakata-hover");
37180						e.stopImmediatePropagation(); 
37181						e.preventDefault();
37182					} 
37183				})
37184				.bind("keydown", "left", function (e) { 
37185					if($.vakata.context.vis) { 
37186						$.vakata.context.cnt.find(".vakata-hover").children("ul").hide().children(".vakata-separator").removeClass("vakata-hover");
37187						e.stopImmediatePropagation(); 
37188						e.preventDefault();
37189					} 
37190				})
37191				.bind("keydown", "esc", function (e) { 
37192					$.vakata.context.hide(); 
37193					e.preventDefault();
37194				})
37195				.bind("keydown", "space", function (e) { 
37196					$.vakata.context.cnt.find(".vakata-hover").last().children("a").click();
37197					e.preventDefault();
37198				});
37199		}
37200	});
37201
37202	$.jstree.plugin("contextmenu", {
37203		__init : function () {
37204			this.get_container()
37205				.delegate("a", "contextmenu.jstree", $.proxy(function (e) {
37206						e.preventDefault();
37207						if(!$(e.currentTarget).hasClass("jstree-loading")) {
37208							this.show_contextmenu(e.currentTarget, e.pageX, e.pageY);
37209						}
37210					}, this))
37211				.delegate("a", "click.jstree", $.proxy(function (e) {
37212						if(this.data.contextmenu) {
37213							$.vakata.context.hide();
37214						}
37215					}, this))
37216				.bind("destroy.jstree", $.proxy(function () {
37217						// TODO: move this to descruct method
37218						if(this.data.contextmenu) {
37219							$.vakata.context.hide();
37220						}
37221					}, this));
37222			$(document).bind("context_hide.vakata", $.proxy(function () { this.data.contextmenu = false; }, this));
37223		},
37224		defaults : { 
37225			select_node : false, // requires UI plugin
37226			show_at_node : true,
37227			items : { // Could be a function that should return an object like this one
37228				"create" : {
37229					"separator_before"	: false,
37230					"separator_after"	: true,
37231					"label"				: "Create",
37232					"action"			: function (obj) { this.create(obj); }
37233				},
37234				"rename" : {
37235					"separator_before"	: false,
37236					"separator_after"	: false,
37237					"label"				: "Rename",
37238					"action"			: function (obj) { this.rename(obj); }
37239				},
37240				"remove" : {
37241					"separator_before"	: false,
37242					"icon"				: false,
37243					"separator_after"	: false,
37244					"label"				: "Delete",
37245					"action"			: function (obj) { if(this.is_selected(obj)) { this.remove(); } else { this.remove(obj); } }
37246				},
37247				"ccp" : {
37248					"separator_before"	: true,
37249					"icon"				: false,
37250					"separator_after"	: false,
37251					"label"				: "Edit",
37252					"action"			: false,
37253					"submenu" : { 
37254						"cut" : {
37255							"separator_before"	: false,
37256							"separator_after"	: false,
37257							"label"				: "Cut",
37258							"action"			: function (obj) { this.cut(obj); }
37259						},
37260						"copy" : {
37261							"separator_before"	: false,
37262							"icon"				: false,
37263							"separator_after"	: false,
37264							"label"				: "Copy",
37265							"action"			: function (obj) { this.copy(obj); }
37266						},
37267						"paste" : {
37268							"separator_before"	: false,
37269							"icon"				: false,
37270							"separator_after"	: false,
37271							"label"				: "Paste",
37272							"action"			: function (obj) { this.paste(obj); }
37273						}
37274					}
37275				}
37276			}
37277		},
37278		_fn : {
37279			show_contextmenu : function (obj, x, y) {
37280				obj = this._get_node(obj);
37281				var s = this.get_settings().contextmenu,
37282					a = obj.children("a:visible:eq(0)"),
37283					o = false,
37284					i = false;
37285				if(s.select_node && this.data.ui && !this.is_selected(obj)) {
37286					this.deselect_all();
37287					this.select_node(obj, true);
37288				}
37289				if(s.show_at_node || typeof x === "undefined" || typeof y === "undefined") {
37290					o = a.offset();
37291					x = o.left;
37292					y = o.top + this.data.core.li_height;
37293				}
37294				i = obj.data("jstree") && obj.data("jstree").contextmenu ? obj.data("jstree").contextmenu : s.items;
37295				if($.isFunction(i)) { i = i.call(this, obj); }
37296				this.data.contextmenu = true;
37297				$.vakata.context.show(i, a, x, y, this, obj, this._get_settings().core.rtl);
37298				if(this.data.themes) { $.vakata.context.cnt.attr("class", "jstree-" + this.data.themes.theme + "-context"); }
37299			}
37300		}
37301	});
37302})(jQuery);
37303//*/
37304
37305/* 
37306 * jsTree types plugin
37307 * Adds support types of nodes
37308 * You can set an attribute on each li node, that represents its type.
37309 * According to the type setting the node may get custom icon/validation rules
37310 */
37311(function ($) {
37312	$.jstree.plugin("types", {
37313		__init : function () {
37314			var s = this._get_settings().types;
37315			this.data.types.attach_to = [];
37316			this.get_container()
37317				.bind("init.jstree", $.proxy(function () { 
37318						var types = s.types, 
37319							attr  = s.type_attr, 
37320							icons_css = "", 
37321							_this = this;
37322
37323						$.each(types, function (i, tp) {
37324							$.each(tp, function (k, v) { 
37325								if(!/^(max_depth|max_children|icon|valid_children)$/.test(k)) { _this.data.types.attach_to.push(k); }
37326							});
37327							if(!tp.icon) { return true; }
37328							if( tp.icon.image || tp.icon.position) {
37329								if(i == "default")	{ icons_css += '.jstree-' + _this.get_index() + ' a > .jstree-icon { '; }
37330								else				{ icons_css += '.jstree-' + _this.get_index() + ' li[' + attr + '="' + i + '"] > a > .jstree-icon { '; }
37331								if(tp.icon.image)	{ icons_css += ' background-image:url(' + tp.icon.image + '); '; }
37332								if(tp.icon.position){ icons_css += ' background-position:' + tp.icon.position + '; '; }
37333								else				{ icons_css += ' background-position:0 0; '; }
37334								icons_css += '} ';
37335							}
37336						});
37337						if(icons_css !== "") { $.vakata.css.add_sheet({ 'str' : icons_css, title : "jstree-types" }); }
37338					}, this))
37339				.bind("before.jstree", $.proxy(function (e, data) { 
37340						var s, t, 
37341							o = this._get_settings().types.use_data ? this._get_node(data.args[0]) : false, 
37342							d = o && o !== -1 && o.length ? o.data("jstree") : false;
37343						if(d && d.types && d.types[data.func] === false) { e.stopImmediatePropagation(); return false; }
37344						if($.inArray(data.func, this.data.types.attach_to) !== -1) {
37345							if(!data.args[0] || (!data.args[0].tagName && !data.args[0].jquery)) { return; }
37346							s = this._get_settings().types.types;
37347							t = this._get_type(data.args[0]);
37348							if(
37349								( 
37350									(s[t] && typeof s[t][data.func] !== "undefined") || 
37351									(s["default"] && typeof s["default"][data.func] !== "undefined") 
37352								) && this._check(data.func, data.args[0]) === false
37353							) {
37354								e.stopImmediatePropagation();
37355								return false;
37356							}
37357						}
37358					}, this));
37359			if(is_ie6) {
37360				this.get_container()
37361					.bind("load_node.jstree set_type.jstree", $.proxy(function (e, data) {
37362							var r = data && data.rslt && data.rslt.obj && data.rslt.obj !== -1 ? this._get_node(data.rslt.obj).parent() : this.get_container_ul(),
37363								c = false,
37364								s = this._get_settings().types;
37365							$.each(s.types, function (i, tp) {
37366								if(tp.icon && (tp.icon.image || tp.icon.position)) {
37367									c = i === "default" ? r.find("li > a > .jstree-icon") : r.find("li[" + s.type_attr + "='" + i + "'] > a > .jstree-icon");
37368									if(tp.icon.image) { c.css("backgroundImage","url(" + tp.icon.image + ")"); }
37369									c.css("backgroundPosition", tp.icon.position || "0 0");
37370								}
37371							});
37372						}, this));
37373			}
37374		},
37375		defaults : {
37376			// defines maximum number of root nodes (-1 means unlimited, -2 means disable max_children checking)
37377			max_children		: -1,
37378			// defines the maximum depth of the tree (-1 means unlimited, -2 means disable max_depth checking)
37379			max_depth			: -1,
37380			// defines valid node types for the root nodes
37381			valid_children		: "all",
37382
37383			// whether to use $.data
37384			use_data : false, 
37385			// where is the type stores (the rel attribute of the LI element)
37386			type_attr : "rel",
37387			// a list of types
37388			types : {
37389				// the default type
37390				"default" : {
37391					"max_children"	: -1,
37392					"max_depth"		: -1,
37393					"valid_children": "all"
37394
37395					// Bound functions - you can bind any other function here (using boolean or function)
37396					//"select_node"	: true
37397				}
37398			}
37399		},
37400		_fn : {
37401			_types_notify : function (n, data) {
37402				if(data.type && this._get_settings().types.use_data) {
37403					this.set_type(data.type, n);
37404				}
37405			},
37406			_get_type : function (obj) {
37407				obj = this._get_node(obj);
37408				return (!obj || !obj.length) ? false : obj.attr(this._get_settings().types.type_attr) || "default";
37409			},
37410			set_type : function (str, obj) {
37411				obj = this._get_node(obj);
37412				var ret = (!obj.length || !str) ? false : obj.attr(this._get_settings().types.type_attr, str);
37413				if(ret) { this.__callback({ obj : obj, type : str}); }
37414				return ret;
37415			},
37416			_check : function (rule, obj, opts) {
37417				obj = this._get_node(obj);
37418				var v = false, t = this._get_type(obj), d = 0, _this = this, s = this._get_settings().types, data = false;
37419				if(obj === -1) { 
37420					if(!!s[rule]) { v = s[rule]; }
37421					else { return; }
37422				}
37423				else {
37424					if(t === false) { return; }
37425					data = s.use_data ? obj.data("jstree") : false;
37426					if(data && data.types && typeof data.types[rule] !== "undefined") { v = data.types[rule]; }
37427					else if(!!s.types[t] && typeof s.types[t][rule] !== "undefined") { v = s.types[t][rule]; }
37428					else if(!!s.types["default"] && typeof s.types["default"][rule] !== "undefined") { v = s.types["default"][rule]; }
37429				}
37430				if($.isFunction(v)) { v = v.call(this, obj); }
37431				if(rule === "max_depth" && obj !== -1 && opts !== false && s.max_depth !== -2 && v !== 0) {
37432					// also include the node itself - otherwise if root node it is not checked
37433					obj.children("a:eq(0)").parentsUntil(".jstree","li").each(function (i) {
37434						// check if current depth already exceeds global tree depth
37435						if(s.max_depth !== -1 && s.max_depth - (i + 1) <= 0) { v = 0; return false; }
37436						d = (i === 0) ? v : _this._check(rule, this, false);
37437						// check if current node max depth is already matched or exceeded
37438						if(d !== -1 && d - (i + 1) <= 0) { v = 0; return false; }
37439						// otherwise - set the max depth to the current value minus current depth
37440						if(d >= 0 && (d - (i + 1) < v || v < 0) ) { v = d - (i + 1); }
37441						// if the global tree depth exists and it minus the nodes calculated so far is less than `v` or `v` is unlimited
37442						if(s.max_depth >= 0 && (s.max_depth - (i + 1) < v || v < 0) ) { v = s.max_depth - (i + 1); }
37443					});
37444				}
37445				return v;
37446			},
37447			check_move : function () {
37448				if(!this.__call_old()) { return false; }
37449				var m  = this._get_move(),
37450					s  = m.rt._get_settings().types,
37451					mc = m.rt._check("max_children", m.cr),
37452					md = m.rt._check("max_depth", m.cr),
37453					vc = m.rt._check("valid_children", m.cr),
37454					ch = 0, d = 1, t;
37455
37456				if(vc === "none") { return false; } 
37457				if($.isArray(vc) && m.ot && m.ot._get_type) {
37458					m.o.each(function () {
37459						if($.inArray(m.ot._get_type(this), vc) === -1) { d = false; return false; }
37460					});
37461					if(d === false) { return false; }
37462				}
37463				if(s.max_children !== -2 && mc !== -1) {
37464					ch = m.cr === -1 ? this.get_container().find("> ul > li").not(m.o).length : m.cr.find("> ul > li").not(m.o).length;
37465					if(ch + m.o.length > mc) { return false; }
37466				}
37467				if(s.max_depth !== -2 && md !== -1) {
37468					d = 0;
37469					if(md === 0) { return false; }
37470					if(typeof m.o.d === "undefined") {
37471						// TODO: deal with progressive rendering and async when checking max_depth (how to know the depth of the moved node)
37472						t = m.o;
37473						while(t.length > 0) {
37474							t = t.find("> ul > li");
37475							d ++;
37476						}
37477						m.o.d = d;
37478					}
37479					if(md - m.o.d < 0) { return false; }
37480				}
37481				return true;
37482			},
37483			create_node : function (obj, position, js, callback, is_loaded, skip_check) {
37484				if(!skip_check && (is_loaded || this._is_loaded(obj))) {
37485					var p  = (typeof position == "string" && position.match(/^before|after$/i) && obj !== -1) ? this._get_parent(obj) : this._get_node(obj),
37486						s  = this._get_settings().types,
37487						mc = this._check("max_children", p),
37488						md = this._check("max_depth", p),
37489						vc = this._check("valid_children", p),
37490						ch;
37491					if(typeof js === "string") { js = { data : js }; }
37492					if(!js) { js = {}; }
37493					if(vc === "none") { return false; } 
37494					if($.isArray(vc)) {
37495						if(!js.attr || !js.attr[s.type_attr]) { 
37496							if(!js.attr) { js.attr = {}; }
37497							js.attr[s.type_attr] = vc[0]; 
37498						}
37499						else {
37500							if($.inArray(js.attr[s.type_attr], vc) === -1) { return false; }
37501						}
37502					}
37503					if(s.max_children !== -2 && mc !== -1) {
37504						ch = p === -1 ? this.get_container().find("> ul > li").length : p.find("> ul > li").length;
37505						if(ch + 1 > mc) { return false; }
37506					}
37507					if(s.max_depth !== -2 && md !== -1 && (md - 1) < 0) { return false; }
37508				}
37509				return this.__call_old(true, obj, position, js, callback, is_loaded, skip_check);
37510			}
37511		}
37512	});
37513})(jQuery);
37514//*/
37515
37516/* 
37517 * jsTree HTML plugin
37518 * The HTML data store. Datastores are build by replacing the `load_node` and `_is_loaded` functions.
37519 */
37520(function ($) {
37521	$.jstree.plugin("html_data", {
37522		__init : function () { 
37523			// this used to use html() and clean the whitespace, but this way any attached data was lost
37524			this.data.html_data.original_container_html = this.get_container().find(" > ul > li").clone(true);
37525			// remove white space from LI node - otherwise nodes appear a bit to the right
37526			this.data.html_data.original_container_html.find("li").andSelf().contents().filter(function() { return this.nodeType == 3; }).remove();
37527		},
37528		defaults : { 
37529			data : false,
37530			ajax : false,
37531			correct_state : true
37532		},
37533		_fn : {
37534			load_node : function (obj, s_call, e_call) { var _this = this; this.load_node_html(obj, function () { _this.__callback({ "obj" : _this._get_node(obj) }); s_call.call(this); }, e_call); },
37535			_is_loaded : function (obj) { 
37536				obj = this._get_node(obj); 
37537				return obj == -1 || !obj || (!this._get_settings().html_data.ajax && !$.isFunction(this._get_settings().html_data.data)) || obj.is(".jstree-open, .jstree-leaf") || obj.children("ul").children("li").size() > 0;
37538			},
37539			load_node_html : function (obj, s_call, e_call) {
37540				var d,
37541					s = this.get_settings().html_data,
37542					error_func = function () {},
37543					success_func = function () {};
37544				obj = this._get_node(obj);
37545				if(obj && obj !== -1) {
37546					if(obj.data("jstree_is_loading")) { return; }
37547					else { obj.data("jstree_is_loading",true); }
37548				}
37549				switch(!0) {
37550					case ($.isFunction(s.data)):
37551						s.data.call(this, obj, $.proxy(function (d) {
37552							if(d && d !== "" && d.toString && d.toString().replace(/^[\s\n]+$/,"") !== "") {
37553								d = $(d);
37554								if(!d.is("ul")) { d = $("<ul />").append(d); }
37555								if(obj == -1 || !obj) { this.get_container().children("ul").empty().append(d.children()).find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); }
37556								else { obj.children("a.jstree-loading").removeClass("jstree-loading"); obj.append(d).children("ul").find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); obj.removeData("jstree_is_loading"); }
37557								this.clean_node(obj);
37558								if(s_call) { s_call.call(this); }
37559							}
37560							else {
37561								if(obj && obj !== -1) {
37562									obj.children("a.jstree-loading").removeClass("jstree-loading");
37563									obj.removeData("jstree_is_loading");
37564									if(s.correct_state) { 
37565										this.correct_state(obj);
37566										if(s_call) { s_call.call(this); } 
37567									}
37568								}
37569								else {
37570									if(s.correct_state) { 
37571										this.get_container().children("ul").empty();
37572										if(s_call) { s_call.call(this); } 
37573									}
37574								}
37575							}
37576						}, this));
37577						break;
37578					case (!s.data && !s.ajax):
37579						if(!obj || obj == -1) {
37580							this.get_container()
37581								.children("ul").empty()
37582								.append(this.data.html_data.original_container_html)
37583								.find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end()
37584								.filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon");
37585							this.clean_node();
37586						}
37587						if(s_call) { s_call.call(this); }
37588						break;
37589					case (!!s.data && !s.ajax) || (!!s.data && !!s.ajax && (!obj || obj === -1)):
37590						if(!obj || obj == -1) {
37591							d = $(s.data);
37592							if(!d.is("ul")) { d = $("<ul />").append(d); }
37593							this.get_container()
37594								.children("ul").empty().append(d.children())
37595								.find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end()
37596								.filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon");
37597							this.clean_node();
37598						}
37599						if(s_call) { s_call.call(this); }
37600						break;
37601					case (!s.data && !!s.ajax) || (!!s.data && !!s.ajax && obj && obj !== -1):
37602						obj = this._get_node(obj);
37603						error_func = function (x, t, e) {
37604							var ef = this.get_settings().html_data.ajax.error; 
37605							if(ef) { ef.call(this, x, t, e); }
37606							if(obj != -1 && obj.length) {
37607								obj.children("a.jstree-loading").removeClass("jstree-loading");
37608								obj.removeData("jstree_is_loading");
37609								if(t === "success" && s.correct_state) { this.correct_state(obj); }
37610							}
37611							else {
37612								if(t === "success" && s.correct_state) { this.get_container().children("ul").empty(); }
37613							}
37614							if(e_call) { e_call.call(this); }
37615						};
37616						success_func = function (d, t, x) {
37617							var sf = this.get_settings().html_data.ajax.success; 
37618							if(sf) { d = sf.call(this,d,t,x) || d; }
37619							if(d === "" || (d && d.toString && d.toString().replace(/^[\s\n]+$/,"") === "")) {
37620								return error_func.call(this, x, t, "");
37621							}
37622							if(d) {
37623								d = $(d);
37624								if(!d.is("ul")) { d = $("<ul />").append(d); }
37625								if(obj == -1 || !obj) { this.get_container().children("ul").empty().append(d.children()).find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); }
37626								else { obj.children("a.jstree-loading").removeClass("jstree-loading"); obj.append(d).children("ul").find("li, a").filter(function () { return !this.firstChild || !this.firstChild.tagName || this.firstChild.tagName !== "INS"; }).prepend("<ins class='jstree-icon'>&#160;</ins>").end().filter("a").children("ins:first-child").not(".jstree-icon").addClass("jstree-icon"); obj.removeData("jstree_is_loading"); }
37627								this.clean_node(obj);
37628								if(s_call) { s_call.call(this); }
37629							}
37630							else {
37631								if(obj && obj !== -1) {
37632									obj.children("a.jstree-loading").removeClass("jstree-loading");
37633									obj.removeData("jstree_is_loading");
37634									if(s.correct_state) { 
37635										this.correct_state(obj);
37636										if(s_call) { s_call.call(this); } 
37637									}
37638								}
37639								else {
37640									if(s.correct_state) { 
37641										this.get_container().children("ul").empty();
37642										if(s_call) { s_call.call(this); } 
37643									}
37644								}
37645							}
37646						};
37647						s.ajax.context = this;
37648						s.ajax.error = error_func;
37649						s.ajax.success = success_func;
37650						if(!s.ajax.dataType) { s.ajax.dataType = "html"; }
37651						if($.isFunction(s.ajax.url)) { s.ajax.url = s.ajax.url.call(this, obj); }
37652						if($.isFunction(s.ajax.data)) { s.ajax.data = s.ajax.data.call(this, obj); }
37653						$.ajax(s.ajax);
37654						break;
37655				}
37656			}
37657		}
37658	});
37659	// include the HTML data plugin by default
37660	$.jstree.defaults.plugins.push("html_data");
37661})(jQuery);
37662//*/
37663
37664/* 
37665 * jsTree themeroller plugin
37666 * Adds support for jQuery UI themes. Include this at the end of your plugins list, also make sure "themes" is not included.
37667 */
37668(function ($) {
37669	$.jstree.plugin("themeroller", {
37670		__init : function () {
37671			var s = this._get_settings().themeroller;
37672			this.get_container()
37673				.addClass("ui-widget-content")
37674				.addClass("jstree-themeroller")
37675				.delegate("a","mouseenter.jstree", function (e) {
37676					if(!$(e.currentTarget).hasClass("jstree-loading")) {
37677						$(this).addClass(s.item_h);
37678					}
37679				})
37680				.delegate("a","mouseleave.jstree", function () {
37681					$(this).removeClass(s.item_h);
37682				})
37683				.bind("init.jstree", $.proxy(function (e, data) { 
37684						data.inst.get_container().find("> ul > li > .jstree-loading > ins").addClass("ui-icon-refresh");
37685						this._themeroller(data.inst.get_container().find("> ul > li"));
37686					}, this))
37687				.bind("open_node.jstree create_node.jstree", $.proxy(function (e, data) { 
37688						this._themeroller(data.rslt.obj);
37689					}, this))
37690				.bind("loaded.jstree refresh.jstree", $.proxy(function (e) {
37691						this._themeroller();
37692					}, this))
37693				.bind("close_node.jstree", $.proxy(function (e, data) {
37694						this._themeroller(data.rslt.obj);
37695					}, this))
37696				.bind("delete_node.jstree", $.proxy(function (e, data) {
37697						this._themeroller(data.rslt.parent);
37698					}, this))
37699				.bind("correct_state.jstree", $.proxy(function (e, data) {
37700						data.rslt.obj
37701							.children("ins.jstree-icon").removeClass(s.opened + " " + s.closed + " ui-icon").end()
37702							.find("> a > ins.ui-icon")
37703								.filter(function() { 
37704									return this.className.toString()
37705										.replace(s.item_clsd,"").replace(s.item_open,"").replace(s.item_leaf,"")
37706										.indexOf("ui-icon-") === -1; 
37707								}).removeClass(s.item_open + " " + s.item_clsd).addClass(s.item_leaf || "jstree-no-icon");
37708					}, this))
37709				.bind("select_node.jstree", $.proxy(function (e, data) {
37710						data.rslt.obj.children("a").addClass(s.item_a);
37711					}, this))
37712				.bind("deselect_node.jstree deselect_all.jstree", $.proxy(function (e, data) {
37713						this.get_container()
37714							.find("a." + s.item_a).removeClass(s.item_a).end()
37715							.find("a.jstree-clicked").addClass(s.item_a);
37716					}, this))
37717				.bind("dehover_node.jstree", $.proxy(function (e, data) {
37718						data.rslt.obj.children("a").removeClass(s.item_h);
37719					}, this))
37720				.bind("hover_node.jstree", $.proxy(function (e, data) {
37721						this.get_container()
37722							.find("a." + s.item_h).not(data.rslt.obj).removeClass(s.item_h);
37723						data.rslt.obj.children("a").addClass(s.item_h);
37724					}, this))
37725				.bind("move_node.jstree", $.proxy(function (e, data) {
37726						this._themeroller(data.rslt.o);
37727						this._themeroller(data.rslt.op);
37728					}, this));
37729		},
37730		__destroy : function () {
37731			var s = this._get_settings().themeroller,
37732				c = [ "ui-icon" ];
37733			$.each(s, function (i, v) {
37734				v = v.split(" ");
37735				if(v.length) { c = c.concat(v); }
37736			});
37737			this.get_container()
37738				.removeClass("ui-widget-content")
37739				.find("." + c.join(", .")).removeClass(c.join(" "));
37740		},
37741		_fn : {
37742			_themeroller : function (obj) {
37743				var s = this._get_settings().themeroller;
37744				obj = !obj || obj == -1 ? this.get_container_ul() : this._get_node(obj).parent();
37745				obj
37746					.find("li.jstree-closed")
37747						.children("ins.jstree-icon").removeClass(s.opened).addClass("ui-icon " + s.closed).end()
37748						.children("a").addClass(s.item)
37749							.children("ins.jstree-icon").addClass("ui-icon")
37750								.filter(function() { 
37751									return this.className.toString()
37752										.replace(s.item_clsd,"").replace(s.item_open,"").replace(s.item_leaf,"")
37753										.indexOf("ui-icon-") === -1; 
37754								}).removeClass(s.item_leaf + " " + s.item_open).addClass(s.item_clsd || "jstree-no-icon")
37755								.end()
37756							.end()
37757						.end()
37758					.end()
37759					.find("li.jstree-open")
37760						.children("ins.jstree-icon").removeClass(s.closed).addClass("ui-icon " + s.opened).end()
37761						.children("a").addClass(s.item)
37762							.children("ins.jstree-icon").addClass("ui-icon")
37763								.filter(function() { 
37764									return this.className.toString()
37765										.replace(s.item_clsd,"").replace(s.item_open,"").replace(s.item_leaf,"")
37766										.indexOf("ui-icon-") === -1; 
37767								}).removeClass(s.item_leaf + " " + s.item_clsd).addClass(s.item_open || "jstree-no-icon")
37768								.end()
37769							.end()
37770						.end()
37771					.end()
37772					.find("li.jstree-leaf")
37773						.children("ins.jstree-icon").removeClass(s.closed + " ui-icon " + s.opened).end()
37774						.children("a").addClass(s.item)
37775							.children("ins.jstree-icon").addClass("ui-icon")
37776								.filter(function() { 
37777									return this.className.toString()
37778										.replace(s.item_clsd,"").replace(s.item_open,"").replace(s.item_leaf,"")
37779										.indexOf("ui-icon-") === -1; 
37780								}).removeClass(s.item_clsd + " " + s.item_open).addClass(s.item_leaf || "jstree-no-icon");
37781			}
37782		},
37783		defaults : {
37784			"opened"	: "ui-icon-triangle-1-se",
37785			"closed"	: "ui-icon-triangle-1-e",
37786			"item"		: "ui-state-default",
37787			"item_h"	: "ui-state-hover",
37788			"item_a"	: "ui-state-active",
37789			"item_open"	: "ui-icon-folder-open",
37790			"item_clsd"	: "ui-icon-folder-collapsed",
37791			"item_leaf"	: "ui-icon-document"
37792		}
37793	});
37794	$(function() {
37795		var css_string = '' + 
37796			'.jstree-themeroller .ui-icon { overflow:visible; } ' + 
37797			'.jstree-themeroller a { padding:0 2px; } ' + 
37798			'.jstree-themeroller .jstree-no-icon { display:none; }';
37799		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
37800	});
37801})(jQuery);
37802//*/
37803
37804/* 
37805 * jsTree unique plugin
37806 * Forces different names amongst siblings (still a bit experimental)
37807 * NOTE: does not check language versions (it will not be possible to have nodes with the same title, even in different languages)
37808 */
37809(function ($) {
37810	$.jstree.plugin("unique", {
37811		__init : function () {
37812			this.get_container()
37813				.bind("before.jstree", $.proxy(function (e, data) { 
37814						var nms = [], res = true, p, t;
37815						if(data.func == "move_node") {
37816							// obj, ref, position, is_copy, is_prepared, skip_check
37817							if(data.args[4] === true) {
37818								if(data.args[0].o && data.args[0].o.length) {
37819									data.args[0].o.children("a").each(function () { nms.push($(this).text().replace(/^\s+/g,"")); });
37820									res = this._check_unique(nms, data.args[0].np.find("> ul > li").not(data.args[0].o), "move_node");
37821								}
37822							}
37823						}
37824						if(data.func == "create_node") {
37825							// obj, position, js, callback, is_loaded
37826							if(data.args[4] || this._is_loaded(data.args[0])) {
37827								p = this._get_node(data.args[0]);
37828								if(data.args[1] && (data.args[1] === "before" || data.args[1] === "after")) {
37829									p = this._get_parent(data.args[0]);
37830									if(!p || p === -1) { p = this.get_container(); }
37831								}
37832								if(typeof data.args[2] === "string") { nms.push(data.args[2]); }
37833								else if(!data.args[2] || !data.args[2].data) { nms.push(this._get_string("new_node")); }
37834								else { nms.push(data.args[2].data); }
37835								res = this._check_unique(nms, p.find("> ul > li"), "create_node");
37836							}
37837						}
37838						if(data.func == "rename_node") {
37839							// obj, val
37840							nms.push(data.args[1]);
37841							t = this._get_node(data.args[0]);
37842							p = this._get_parent(t);
37843							if(!p || p === -1) { p = this.get_container(); }
37844							res = this._check_unique(nms, p.find("> ul > li").not(t), "rename_node");
37845						}
37846						if(!res) {
37847							e.stopPropagation();
37848							return false;
37849						}
37850					}, this));
37851		},
37852		defaults : { 
37853			error_callback : $.noop
37854		},
37855		_fn : { 
37856			_check_unique : function (nms, p, func) {
37857				var cnms = [];
37858				p.children("a").each(function () { cnms.push($(this).text().replace(/^\s+/g,"")); });
37859				if(!cnms.length || !nms.length) { return true; }
37860				cnms = cnms.sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(",");
37861				if((cnms.length + nms.length) != cnms.concat(nms).sort().join(",,").replace(/(,|^)([^,]+)(,,\2)+(,|$)/g,"$1$2$4").replace(/,,+/g,",").replace(/,$/,"").split(",").length) {
37862					this._get_settings().unique.error_callback.call(null, nms, p, func);
37863					return false;
37864				}
37865				return true;
37866			},
37867			check_move : function () {
37868				if(!this.__call_old()) { return false; }
37869				var p = this._get_move(), nms = [];
37870				if(p.o && p.o.length) {
37871					p.o.children("a").each(function () { nms.push($(this).text().replace(/^\s+/g,"")); });
37872					return this._check_unique(nms, p.np.find("> ul > li").not(p.o), "check_move");
37873				}
37874				return true;
37875			}
37876		}
37877	});
37878})(jQuery);
37879//*/
37880
37881/*
37882 * jsTree wholerow plugin
37883 * Makes select and hover work on the entire width of the node
37884 * MAY BE HEAVY IN LARGE DOM
37885 */
37886(function ($) {
37887	$.jstree.plugin("wholerow", {
37888		__init : function () {
37889			if(!this.data.ui) { throw "jsTree wholerow: jsTree UI plugin not included."; }
37890			this.data.wholerow.html = false;
37891			this.data.wholerow.to = false;
37892			this.get_container()
37893				.bind("init.jstree", $.proxy(function (e, data) { 
37894						this._get_settings().core.animation = 0;
37895					}, this))
37896				.bind("open_node.jstree create_node.jstree clean_node.jstree loaded.jstree", $.proxy(function (e, data) { 
37897						this._prepare_wholerow_span( data && data.rslt && data.rslt.obj ? data.rslt.obj : -1 );
37898					}, this))
37899				.bind("search.jstree clear_search.jstree reopen.jstree after_open.jstree after_close.jstree create_node.jstree delete_node.jstree clean_node.jstree", $.proxy(function (e, data) { 
37900						if(this.data.to) { clearTimeout(this.data.to); }
37901						this.data.to = setTimeout( (function (t, o) { return function() { t._prepare_wholerow_ul(o); }; })(this,  data && data.rslt && data.rslt.obj ? data.rslt.obj : -1), 0);
37902					}, this))
37903				.bind("deselect_all.jstree", $.proxy(function (e, data) { 
37904						this.get_container().find(" > .jstree-wholerow .jstree-clicked").removeClass("jstree-clicked " + (this.data.themeroller ? this._get_settings().themeroller.item_a : "" ));
37905					}, this))
37906				.bind("select_node.jstree deselect_node.jstree ", $.proxy(function (e, data) { 
37907						data.rslt.obj.each(function () { 
37908							var ref = data.inst.get_container().find(" > .jstree-wholerow li:visible:eq(" + ( parseInt((($(this).offset().top - data.inst.get_container().offset().top + data.inst.get_container()[0].scrollTop) / data.inst.data.core.li_height),10)) + ")");
37909							// ref.children("a")[e.type === "select_node" ? "addClass" : "removeClass"]("jstree-clicked");
37910							ref.children("a").attr("class",data.rslt.obj.children("a").attr("class"));
37911						});
37912					}, this))
37913				.bind("hover_node.jstree dehover_node.jstree", $.proxy(function (e, data) { 
37914						this.get_container().find(" > .jstree-wholerow .jstree-hovered").removeClass("jstree-hovered " + (this.data.themeroller ? this._get_settings().themeroller.item_h : "" ));
37915						if(e.type === "hover_node") {
37916							var ref = this.get_container().find(" > .jstree-wholerow li:visible:eq(" + ( parseInt(((data.rslt.obj.offset().top - this.get_container().offset().top + this.get_container()[0].scrollTop) / this.data.core.li_height),10)) + ")");
37917							// ref.children("a").addClass("jstree-hovered");
37918							ref.children("a").attr("class",data.rslt.obj.children(".jstree-hovered").attr("class"));
37919						}
37920					}, this))
37921				.delegate(".jstree-wholerow-span, ins.jstree-icon, li", "click.jstree", function (e) {
37922						var n = $(e.currentTarget);
37923						if(e.target.tagName === "A" || (e.target.tagName === "INS" && n.closest("li").is(".jstree-open, .jstree-closed"))) { return; }
37924						n.closest("li").children("a:visible:eq(0)").click();
37925						e.stopImmediatePropagation();
37926					})
37927				.delegate("li", "mouseover.jstree", $.proxy(function (e) {
37928						e.stopImmediatePropagation();
37929						if($(e.currentTarget).children(".jstree-hovered, .jstree-clicked").length) { return false; }
37930						this.hover_node(e.currentTarget);
37931						return false;
37932					}, this))
37933				.delegate("li", "mouseleave.jstree", $.proxy(function (e) {
37934						if($(e.currentTarget).children("a").hasClass("jstree-hovered").length) { return; }
37935						this.dehover_node(e.currentTarget);
37936					}, this));
37937			if(is_ie7 || is_ie6) {
37938				$.vakata.css.add_sheet({ str : ".jstree-" + this.get_index() + " { position:relative; } ", title : "jstree" });
37939			}
37940		},
37941		defaults : {
37942		},
37943		__destroy : function () {
37944			this.get_container().children(".jstree-wholerow").remove();
37945			this.get_container().find(".jstree-wholerow-span").remove();
37946		},
37947		_fn : {
37948			_prepare_wholerow_span : function (obj) {
37949				obj = !obj || obj == -1 ? this.get_container().find("> ul > li") : this._get_node(obj);
37950				if(obj === false) { return; } // added for removing root nodes
37951				obj.each(function () {
37952					$(this).find("li").andSelf().each(function () {
37953						var $t = $(this);
37954						if($t.children(".jstree-wholerow-span").length) { return true; }
37955						$t.prepend("<span class='jstree-wholerow-span' style='width:" + ($t.parentsUntil(".jstree","li").length * 18) + "px;'>&#160;</span>");
37956					});
37957				});
37958			},
37959			_prepare_wholerow_ul : function () {
37960				var o = this.get_container().children("ul").eq(0), h = o.html();
37961				o.addClass("jstree-wholerow-real");
37962				if(this.data.wholerow.last_html !== h) {
37963					this.data.wholerow.last_html = h;
37964					this.get_container().children(".jstree-wholerow").remove();
37965					this.get_container().append(
37966						o.clone().removeClass("jstree-wholerow-real")
37967							.wrapAll("<div class='jstree-wholerow' />").parent()
37968							.width(o.parent()[0].scrollWidth)
37969							.css("top", (o.height() + ( is_ie7 ? 5 : 0)) * -1 )
37970							.find("li[id]").each(function () { this.removeAttribute("id"); }).end()
37971					);
37972				}
37973			}
37974		}
37975	});
37976	$(function() {
37977		var css_string = '' + 
37978			'.jstree .jstree-wholerow-real { position:relative; z-index:1; } ' + 
37979			'.jstree .jstree-wholerow-real li { cursor:pointer; } ' + 
37980			'.jstree .jstree-wholerow-real a { border-left-color:transparent !important; border-right-color:transparent !important; } ' + 
37981			'.jstree .jstree-wholerow { position:relative; z-index:0; height:0; } ' + 
37982			'.jstree .jstree-wholerow ul, .jstree .jstree-wholerow li { width:100%; } ' + 
37983			'.jstree .jstree-wholerow, .jstree .jstree-wholerow ul, .jstree .jstree-wholerow li, .jstree .jstree-wholerow a { margin:0 !important; padding:0 !important; } ' + 
37984			'.jstree .jstree-wholerow, .jstree .jstree-wholerow ul, .jstree .jstree-wholerow li { background:transparent !important; }' + 
37985			'.jstree .jstree-wholerow ins, .jstree .jstree-wholerow span, .jstree .jstree-wholerow input { display:none !important; }' + 
37986			'.jstree .jstree-wholerow a, .jstree .jstree-wholerow a:hover { text-indent:-9999px; !important; width:100%; padding:0 !important; border-right-width:0px !important; border-left-width:0px !important; } ' + 
37987			'.jstree .jstree-wholerow-span { position:absolute; left:0; margin:0px; padding:0; height:18px; border-width:0; padding:0; z-index:0; }';
37988		if(is_ff2) {
37989			css_string += '' + 
37990				'.jstree .jstree-wholerow a { display:block; height:18px; margin:0; padding:0; border:0; } ' + 
37991				'.jstree .jstree-wholerow-real a { border-color:transparent !important; } ';
37992		}
37993		if(is_ie7 || is_ie6) {
37994			css_string += '' + 
37995				'.jstree .jstree-wholerow, .jstree .jstree-wholerow li, .jstree .jstree-wholerow ul, .jstree .jstree-wholerow a { margin:0; padding:0; line-height:18px; } ' + 
37996				'.jstree .jstree-wholerow a { display:block; height:18px; line-height:18px; overflow:hidden; } ';
37997		}
37998		$.vakata.css.add_sheet({ str : css_string, title : "jstree" });
37999	});
38000})(jQuery);
38001//*/
38002
38003/*
38004* jsTree model plugin
38005* This plugin gets jstree to use a class model to retrieve data, creating great dynamism
38006*/
38007(function ($) {
38008	var nodeInterface = ["getChildren","getChildrenCount","getAttr","getName","getProps"],
38009		validateInterface = function(obj, inter) {
38010			var valid = true;
38011			obj = obj || {};
38012			inter = [].concat(inter);
38013			$.each(inter, function (i, v) {
38014				if(!$.isFunction(obj[v])) { valid = false; return false; }
38015			});
38016			return valid;
38017		};
38018	$.jstree.plugin("model", {
38019		__init : function () {
38020			if(!this.data.json_data) { throw "jsTree model: jsTree json_data plugin not included."; }
38021			this._get_settings().json_data.data = function (n, b) {
38022				var obj = (n == -1) ? this._get_settings().model.object : n.data("jstree_model");
38023				if(!validateInterface(obj, nodeInterface)) { return b.call(null, false); }
38024				if(this._get_settings().model.async) {
38025					obj.getChildren($.proxy(function (data) {
38026						this.model_done(data, b);
38027					}, this));
38028				}
38029				else {
38030					this.model_done(obj.getChildren(), b);
38031				}
38032			};
38033		},
38034		defaults : {
38035			object : false,
38036			id_prefix : false,
38037			async : false
38038		},
38039		_fn : {
38040			model_done : function (data, callback) {
38041				var ret = [], 
38042					s = this._get_settings(),
38043					_this = this;
38044
38045				if(!$.isArray(data)) { data = [data]; }
38046				$.each(data, function (i, nd) {
38047					var r = nd.getProps() || {};
38048					r.attr = nd.getAttr() || {};
38049					if(nd.getChildrenCount()) { r.state = "closed"; }
38050					r.data = nd.getName();
38051					if(!$.isArray(r.data)) { r.data = [r.data]; }
38052					if(_this.data.types && $.isFunction(nd.getType)) {
38053						r.attr[s.types.type_attr] = nd.getType();
38054					}
38055					if(r.attr.id && s.model.id_prefix) { r.attr.id = s.model.id_prefix + r.attr.id; }
38056					if(!r.metadata) { r.metadata = { }; }
38057					r.metadata.jstree_model = nd;
38058					ret.push(r);
38059				});
38060				callback.call(null, ret);
38061			}
38062		}
38063	});
38064})(jQuery);
38065//*/
38066
38067})();
38068/**
38069 * Provides UI & data management for an ontology tree view.
38070 *
38071 * Requires AIBS.DATA.API_DAO and will throw if it is not present.
38072 *
38073 * All of the data access methods are contained within this widget.
38074 *
38075 * <p></p>
38076 * @class
38077 * @param container:        The jquery selector for (or jquery object that is) the container for this widget.
38078 * @param ontology:         An object that should contain at least these fields:
38079 *                              ontology_id: The database ID of the ontology to show.
38080 *                              root_node_id:The database ID of the node that is the parent of all the nodes to be displayed.
38081 *                              in this control.  The root node will not itself be visible.
38082 * @param data_service_url  The base url for the data service API
38083 * @param options:          A hash of the following (work in progress):
38084 * <ul>
38085 * <li>
38086 *     <b>included_fields</b>:  Fields that are included in the RMA request for structure nodes.  Default = "acronym,color_hex_triplet,hemisphere_id,id,name"
38087 * </li>
38088 * <li>
38089 *     <b>animation_time</b>:   The time in milliseconds to animate a node opening.  Default = 100.
38090 * </li>
38091 * <li>
38092 *     <b>on_ready</b>:  A callback function invoked when data has been fetched and is ready to use.  on_ready(this, data).
38093 * </li>
38094 * <li>
38095 *     <b>on_error</b>:  A callback function invoked when an error is encountered while fetching data. on_error(this, error).
38096 * </li>
38097 * <li>
38098 *     <b>section_thickness_in_microns</b>:  Default = 100.
38099 * </li>
38100 * <li>
38101 *     <b>sort_field</b>:  Default = section_number
38102 * </li>
38103 * <li>
38104 *     <b>sort_direction</b>:  Default = 'asc'
38105 * </li>
38106 * <li>
38107 *     <b>others</b>:  TBD
38108 * </li>
38109 * </ul>
38110**/
38111
38112function ontology_widget(container, ontology, data_service_url, options) {
38113
38114    this.container = $j(container);
38115    this.ontology_id = ontology.id;
38116    this.root_node_id = ontology.root_structure_id;
38117    this.data_service_url = data_service_url;
38118    this.settings = {
38119
38120        "multi_select":true,
38121        "ontology_data":null,
38122        "included_fields":"acronym,color_hex_triplet,hemisphere_id,id,name",
38123        "animation_time":1000,
38124
38125        "themes":{
38126            "theme":"classic",
38127            "url":"/assets/aiclient/vendor/jstree/themes/classic/style.css",
38128            "dots":true,
38129            "icons":false
38130        },
38131
38132        on_node_error:null,
38133        on_node_select:null,
38134        on_node_load:null,
38135        on_node_open:null,
38136        on_tree_create:null,
38137        on_tree_load:null
38138    };
38139    $j.extend(true, this.settings, options);
38140
38141
38142    /**
38143     * Open the tree to the given structure and select it.  A node_select event will
38144     * be raised.
38145     *
38146     * @param structure_id
38147     */
38148    this.open_structure = function(structure_id) {
38149
38150
38151    };
38152
38153    /**
38154     *  Apply class names to apply to nodes by structure_id
38155     *
38156     * @param structure_id   A hash indexed by structure_id and returning class name.
38157     */
38158    this.set_structure_states = function(structure_states) {
38159
38160
38161    };
38162
38163    this.select_none = function() {
38164
38165        if(_inner_container)
38166            _inner_container.jstree('deselect_all');
38167    };
38168
38169    this.scroll_to_structure = function() {
38170
38171    };
38172
38173    this.highlight_none = function() {
38174
38175    };
38176
38177
38178    this.select_at_highlight = function() {
38179
38180    };
38181
38182
38183    this.remove_highlight = function() {
38184
38185
38186    };
38187
38188    /**
38189     * Opens the given path.  The final node will be selected and a select event will be raised.
38190     *
38191     * @param path  It is expected that this path will have the form '/1/2/3/4/'.  Note
38192     * the leading and trailing slashes.
38193     *
38194     * @param target_id The ID of the final structure in the path. This structure will be
38195     *                  selected when it is exposed.
38196     */
38197    this.open_structure_path = function(path, target_id) {
38198
38199        _center_select = true;
38200        _prevent_select_signal = true;
38201        _opening_target_structure = target_id;
38202        open_structure_id_path(path);
38203
38204    }
38205
38206    this.deselect_structure = function(structure_id) {
38207
38208        var node = $j('#'+structure_id + _id);
38209        _inner_container.jstree('deselect_node', node);
38210    }
38211
38212
38213    var _self = this;
38214    var _id = this.container.attr('id');
38215
38216    var _outer_container = null;
38217    var _inner_container = null;
38218    var _search_container = null;
38219
38220    var _opening_path = null;
38221    var _opening_target_structure = 0;
38222    var _select_node = null;
38223    var _center_select = false;
38224    var _prevent_select_signal = false;
38225
38226
38227
38228    function build_scaffold() {
38229
38230        var html = "<div id='" + _id + "_outer_container' class='_ontology_outer_container'>";
38231        html += "<div id='" + _id + "_inner_container' class='_ontology_inner_container'></div>";
38232        html += "</div>";
38233
38234        $j(html).appendTo(_self.container);
38235        _outer_container = $j("#" + _id + "_outer_container");
38236        _inner_container = $j("#" + _id + "_inner_container");
38237    }
38238
38239    function open_structure_id_path(path, prevent_signal) {
38240
38241        if(prevent_signal)
38242            _prevent_select_signal = true;
38243
38244        var path_len = path.length;
38245        if(!path || path_len < 3)
38246            return;
38247
38248        // get rid of the leading and trailing slashes and turn the string into an array.
38249        _opening_path = path.slice(1,path_len-1).split('/');
38250
38251        // all of the paths start with the root node which is always open, so get rid of it.
38252        _opening_path.shift();
38253        open_branch();
38254    }
38255
38256    // opens (& removes) the top node in the _opening_path array.
38257    // Because fetching the node may happen asynchronously,
38258    // we can't just recurse through the tree here.  This function
38259    // will be called again when the node's 'opened' event fires
38260    // so that the next node in the _opening_path array can
38261    // also be opened, and so forth.
38262    function open_branch() {
38263
38264        if(!_opening_path || !_opening_path.length)
38265            return;
38266
38267        var structure_id = _opening_path.shift();
38268        var node = $j('#'+structure_id + _id);
38269
38270        // if we've reached the target structure we don't
38271        // need to see it's children, so stop here.
38272        if(structure_id == _opening_target_structure) {
38273
38274            if(!_self.settings.multi_select)
38275                _inner_container.jstree('deselect_all');
38276	    
38277	    // davidf: jstree will deselect a node that is already
38278	    // selected.  assuming that's not what we want here.
38279	    if (!_inner_container.jstree('is_selected', node)) {
38280		_inner_container.jstree('select_node', node, true);
38281	    }
38282
38283            if(_center_select) {
38284                center_selection(node);
38285                _center_select = false;
38286            }
38287
38288            _opening_path = null;
38289            _select_node = null;
38290            _opening_target_structure = 0;
38291            return;
38292        }
38293
38294        // we got this far, so open the node.  This may invoke an
38295        // ajax call to fetch mode node data. If it does, this open_branch
38296        // function will be called again by the node's 'open' event handler.
38297        var is_open = _inner_container.jstree('is_open', node);
38298        _inner_container.jstree('open_node', node);
38299
38300        // if the node was already open we're not going to get an 'opened'
38301        // event, so keep opening here
38302        if(is_open)
38303            open_branch();
38304    }
38305
38306    // Create the jsTree widget, bind handlers, etc.
38307    function build_ontology() {
38308
38309        var config = {
38310            "json_data":{},
38311            "core":{
38312                "html_titles":true,
38313                "animation":80
38314            },
38315            "ui":{
38316                "select_limit":_self.settings.multi_select ? -1 : 1,
38317                "select_multiple_modifier":_self.settings.multi_select ? "on" : "ctrl"
38318            },
38319            "themes":_self.settings.themes,
38320            "plugins":[ "themes", "json_data", "ui"]
38321        };
38322
38323        if (_self.settings.ontology_data)
38324            config.json_data.data = do_data;//_self.settings.ontology_data;
38325
38326        _inner_container.jstree(config)
38327            .bind("select_node.jstree", function (e, data) {
38328
38329                if (_self.settings.on_node_select && !_prevent_select_signal)
38330                    _self.settings.on_node_select(data.rslt.obj.data("id").replace(_id,''), true);
38331
38332                _prevent_select_signal = false;
38333            })
38334            .bind("deselect_node.jstree", function (e, data) {
38335
38336                if (_self.settings.on_node_select && !_prevent_select_signal)
38337                    _self.settings.on_node_select(data.rslt.obj.data("id").replace(_id,''), false);
38338
38339                _prevent_select_signal = false;
38340            })
38341            .bind("load_node.jstree", function (e, data) {
38342
38343                var container;
38344
38345                if (data.rslt.obj != -1)
38346                    container = data.rslt.obj;
38347                else
38348                    container = data.inst.get_container();
38349
38350                // we need some info from the node data to properly color it.
38351                decorate_nodes(container);
38352
38353                if (_self.settings.on_node_load)
38354                    _self.settings.on_node_load(container);
38355            })
38356            .bind("open_node.jstree", function (e, data) {
38357
38358                open_branch();
38359                if (_self.settings.on_node_open)
38360                    _self.settings.on_node_open(data.rslt.obj.attr("id"));
38361            })
38362            .bind("create.jstree", function (e, data) {
38363
38364                if (_self.settings.on_tree_create)
38365                    _self.settings.on_tree_create(data);
38366            })
38367            .bind("loaded.jstree", function (e, data) {
38368
38369                if (_self.settings.on_tree_load)
38370                    _self.settings.on_tree_load(data);
38371            });
38372    }
38373
38374    function do_data(node, callback) {
38375
38376        var id = node == -1 ? _self.root_node_id : node.attr("id").replace(_id,'');
38377        var data = _self.settings.ontology_data(id);
38378
38379        callback(format_node_data(data, []));
38380    }
38381
38382    function center_selection(node) {
38383
38384        var pos = node.position();
38385        if(!pos)
38386            return;
38387
38388        var scroll = _self.container.scrollTop();
38389        var center_pos = _self.container.height()/2;
38390        var node_top = pos.top;
38391        // ignoring the height of the element; it's only 10px or so.
38392        var new_scroll = scroll - (center_pos - node_top);
38393        _self.container.scrollTop(new_scroll);
38394    }
38395
38396
38397    // Sets the color of the node.  The color value is available as an attribute of
38398    // the node because we included the color_hex_triplet field in our RMA for fetching nodes,
38399    // and then we specified that the color_hex_triplet data be attached to the node as an
38400    // attribute in the format_node_data callback (see below.)
38401    function decorate_nodes(container) {
38402
38403        $j("#" + container.attr("id") +  " ins").each(function(index, el) {
38404
38405            var a = $j(el.parentNode).find("a").first();
38406            if(a && a.length) {
38407
38408                var color = a.attr("color");
38409                if(color)
38410                    $j(el).css({backgroundColor:color, border:'0px solid #f00', width:'14px', height:'14px'});
38411
38412                var anno_state = a.attr('annotation_state');
38413                if(anno_state)
38414                    $j(a).addClass(anno_state);
38415            }
38416        });
38417    }
38418
38419    // Transforms the data returned by the ajax call into
38420    // the format expected by the jsTree widget.
38421    function format_node_data(data) {
38422
38423         if(!data)
38424             return([]);
38425
38426         var formatted_nodes = [];
38427         for(var i = 0; i < data.length; i++) {
38428
38429            var state = data[i].child_structures && data[i].child_structures.length ? "closed" : "none";
38430            formatted_nodes.push({
38431                data:{
38432                    title:"<b>"+data[i].acronym+"</b>&nbsp;&nbsp;" + data[i].name,
38433                    // include the color as an attribute.
38434                    attr:{
38435                        color:'#'+data[i].color,
38436                        annotation_state:false//data[i].annotation_state
38437                    }
38438                },
38439                state:state,
38440                attr:{id:(data[i].structure_id+_id)},
38441                metadata:{
38442                    id:(data[i].structure_id+_id)
38443                }
38444            });
38445         }
38446
38447         return(formatted_nodes);
38448    }
38449
38450    function init() {
38451
38452        build_scaffold();
38453        build_ontology();
38454    }
38455
38456    init();
38457}
38458;
38459
38460AIBS.UI.ontology_tag_container = (function($j) {
38461
38462    return (
38463
38464        function(container, options) {
38465
38466            this.container = $j(container);
38467            this.settings = {
38468                on_autocomplete:null,
38469                on_tag_added:null,
38470                on_tag_removed:null,
38471		default_text:''
38472            };
38473            $j.extend(true, this.settings, options);
38474
38475
38476            /**
38477             * add a tag to the container
38478             * @param tag {object} Like this: {label:'foo', value:'bar'}
38479             */
38480            this.add_tag = function(tag) {
38481
38482                this.container.addTag(tag);
38483            }
38484
38485            /**
38486             * remove a tag from the container
38487             * @param tag {object} Like this: {label:'foo', value:'bar'}
38488             */
38489            this.remove_tag = function(tag) {
38490
38491                this.container.removeTag(tag);
38492            }
38493
38494            /**
38495             * get an array of all the tags in the container
38496             * @returns {array} Array of tag objects
38497             */
38498            this.get_tags = function() {
38499
38500                return(this.get_displayed_text().split(';'));
38501            }
38502
38503            /**
38504             * returns the text that's displayed in the container, which in some renderings
38505             * might be different than the set of labels in the tag collection.
38506             */
38507            this.get_displayed_text = function() {
38508
38509                return(this.container.val());
38510            }
38511
38512            /**
38513             * removes all tags from the collection
38514             */
38515            this.clear = function() {
38516
38517                this.container.importTags('');
38518            }
38519
38520            this.get_container = function() {
38521
38522                // after the tagsinput control is instantiated the original container is
38523                // no longer viable.
38524                return(this.actual_container);
38525            }
38526
38527            this.destroy = function() {
38528
38529                 _self.container.tagsInput.destroy();
38530            }
38531
38532            /**
38533             * @private
38534             * @scope AIBS.UI.tag_container
38535             */
38536
38537            var _self = this;
38538
38539
38540            function init() {
38541                _self.container.tagsInput({
38542                    width:_self.container.outerWidth(),
38543                    height:_self.container.outerHeight(),
38544                    defaultText:_self.settings.default_text,
38545                    delimiter:';',
38546                    onAddTag:do_add_tag,
38547                    onRemoveTag:do_remove_tag,
38548                    // the autocomplete_url field must be included or the autocomplete:{} field will be ignored.
38549                    autocomplete_url:'',
38550                    autocomplete:{
38551                        source:_self.settings.on_autocomplete,
38552                        autoFocus:false,
38553                        position:{
38554                            collision:'flip'
38555                        }
38556                    }
38557                });
38558                // the tagsinput control takes over the original control, so re-adjust here to reflect reality.
38559                _self.actual_container = $j('#' + _self.container.attr('id') + '_tagsinput');
38560            }
38561
38562            function do_add_tag(tag_list) {
38563
38564                if(_self.settings.on_tag_added)
38565                    _self.settings.on_tag_added(tag_list);
38566            }
38567
38568            function do_remove_tag(tag_list) {
38569
38570                if(_self.settings.on_tag_removed)
38571                    _self.settings.on_tag_removed(tag_list);
38572            }
38573
38574            init();
38575        }
38576
38577        );
38578
38579
38580})(jQuery);
38581(function ($) {
38582
38583    var methods = {
38584        init:function (ontology_model, options) {
38585
38586            var settings = {
38587                height:400,
38588                width:null,
38589                view_options:{},
38590		default_text:'',
38591                title_text:'Click below to select',
38592                close_text:'Close',
38593                delimiters:[';', ','],
38594                contains:true,
38595                initial_acronyms:'',
38596                horizontal_align:'left',
38597                closer:'icon',
38598                close_icon:'ui-icon-closethick',
38599                limit_single_selection:false,
38600                default_open_nodes:{"TH":true, "CTX":true, "MBmot":true, "MY":true},
38601                on_open:null,
38602                on_close:null,
38603                on_add_structure:null,
38604                on_remove_structure:null,
38605		on_ready:null,
38606		tag_key:'acronym',
38607		zIndex: 2000
38608            };
38609            if (options)
38610                $.extend(settings, options);
38611
38612
38613            return this.each(function () {
38614
38615                var $this = $(this);
38616                var _data = $this.data('ontology_autocomplete');
38617
38618                if (!_data) {
38619
38620                    var _id = Math.floor(Math.random() * 10001);
38621                    var _list_class = "_ontology_autocomplete_div";
38622                    var _list_container = null;
38623                    var _inner_container = null;
38624                    var _visible = false;
38625                    var _ontology_view = null;
38626                    var _model = ontology_model;
38627                    var _tag_container = null;
38628                    var _self_click = false;
38629                    var _adding_tag = false;
38630                    var _first_open = true;
38631                    var _previous_top = 0;
38632                    var _scrolling = false;
38633                    var _scroll_count = 0;
38634                    var _close_button = null;
38635                    var _replacement_container = null;
38636
38637
38638                    function destroy() {
38639
38640                        if(_tag_container) {
38641                            _tag_container.destroy();
38642                            delete _tag_container;
38643                        }
38644                    }
38645
38646                    function setup_events() {
38647
38648                        _list_container.bind('click', do_self_click);
38649
38650                        /*  This bit sets up listeners for focus and click events in order
38651                            to close the ontology drop down if the user clicks outside of this control.
38652                         */
38653
38654                        // IE <= 8 sauce. Old IE does not support this property,
38655                        // neither does it support window.click.  Note that this
38656                        // property is not strictly related to this implementation; it's
38657                        // just a reliable indicator of old IE.
38658                        if($.support.changeBubbles)
38659                            $(window).bind('click', do_blur);
38660                        else
38661                            $(document).bind('click', do_blur);
38662
38663                        // multiple input elements are created and destroyed as the users
38664                        // enters text in the tag container. We don't want to close the dropdown in these cases.
38665                        $(_tag_container.container[0].parentNode).find('input').bind('focus', show);
38666                        $(_tag_container.container[0].parentNode).find('input').bind('click', do_self_click);
38667                        _replacement_container = $('#'+_tag_container.container.attr('id')+'_tagsinput');
38668                        _replacement_container.live('focus', show);
38669                        _replacement_container.live('click', do_self_click);
38670
38671                        // If the browser size changes we need to reposition the drop downs, which are
38672                        // absolutely positioned.
38673                        $(window).bind('resize', set_position);
38674
38675                        /* the input is typically ~20px high, but the default scroll is 40px.  This bit resets the
38676                         scroll top so that it's 20 * number_of_scrolls.
38677                         */
38678                        _replacement_container.bind('scroll', function(event) {
38679
38680                            if(_scrolling)
38681                                return;
38682
38683                            var cont = $(this);
38684                            var new_top = cont.scrollTop();
38685
38686                            if(new_top > _previous_top)
38687                                _scroll_count++;
38688                            else if(new_top < _previous_top)
38689                                _scroll_count--;
38690
38691                            var wanted_top = _scroll_count*20;
38692
38693                            if(new_top != wanted_top) {
38694
38695                                _scrolling = true;
38696                                cont.scrollTop(wanted_top);
38697                                _scrolling = false;
38698                            }
38699
38700                            _previous_top = cont.scrollTop();
38701                        });
38702                    }
38703
38704                    function setup_view() {
38705
38706                       _ontology_view = new ontology_widget(_inner_container, {}, '', {
38707                            ontology_data:_model.get_structure_children,
38708                            multi_select:!settings.limit_single_selection,
38709                           on_node_select:do_node_select,
38710			   on_tree_load:do_tree_load
38711                       });
38712                       _tag_container = new AIBS.UI.ontology_tag_container($this, {
38713                           foo:'goo',
38714                           on_autocomplete:_model.structure_autocomplete,
38715                           on_tag_added:do_add_tag,
38716                           on_tag_removed:do_remove_tag,
38717			   default_text: settings.default_text
38718                       });
38719                    }
38720
38721                    function do_add_tag(tag) {
38722
38723                        if(_adding_tag)
38724                            return;
38725
38726                        _adding_tag = true;
38727
38728                        if(check_tags(tag)) {
38729                            var node = _model.node_by_acronym(tag);
38730
38731                            // the node_by_acronym is case-insensitive, but we want the '
38732                            // actual tag to be properly cased, so remove the user's tag and create a new one.
38733                            // But if we're in single-select mode, just clear the tag container.
38734                            if(settings.limit_single_selection)
38735                                _tag_container.clear();
38736                            else
38737                                _tag_container.remove_tag(tag);
38738
38739                            _tag_container.add_tag(node[settings.tag_key]);//.acronym);
38740			    
38741                            _ontology_view.open_structure_path(node.structure_id_path, node.structure_id);
38742
38743                            structure_selection_change(node, true);
38744                        }
38745                        _adding_tag = false;
38746                    }
38747
38748                    function do_remove_tag(tag) {
38749
38750                        if(_adding_tag)
38751                            return;
38752
38753                        var node = _model.node_by_acronym(tag);
38754                        if(node) {
38755                            _ontology_view.deselect_structure(node.structure_id);
38756                            structure_selection_change(node, false);
38757                        }
38758                    }
38759
38760                    function get_control_id() {
38761
38762                        return("_oa_wrapper" + _id);
38763                    }
38764
38765                    function build_scaffold() {
38766
38767                        // this is the containing div for the drop-down list
38768                        var html = "<div id='_oa_wrapper" + _id + "' class='ui-widget " + _list_class + "'>";
38769                        html += "<div id='_oa_" + _id + "' class='ui-widget _ontology_autocomplete_inner_div'>";
38770                        html += "<div id='_oa_" + _id + "_closer' title='" + settings.close_text + "'></div>";
38771                        html += "</div>";
38772
38773                        _list_container = $(html);
38774                        _list_container.appendTo(document.body)
38775                                        .hide();
38776
38777                        _inner_container = $('#_oa_' + _id).scroll(do_list_scroll);
38778
38779                        _close_button = $('#_oa_' + _id + '_closer');
38780                        _close_button.button({
38781                            icons:{
38782                                primary:settings.close_icon
38783                            },
38784                            text:false
38785                        }).css({
38786                            height:'20px',
38787                            width:'20px',
38788                            margin:'2px',
38789                            backgroundColor:'#fff',
38790                            position:'absolute',
38791			    right: '0px'
38792                        }).bind('click', hide);
38793
38794
38795                    }
38796
38797                    function do_list_scroll(event, ui) {
38798                        _close_button.css({
38799                            top:(_inner_container.scrollTop() )+'px',
38800			    left:(_inner_container.scrollLeft() + _inner_container.width() - _close_button.width() - 21)+'px'
38801                        });
38802                    }
38803
38804                    function do_self_click() {
38805                        _self_click = true;
38806                    }
38807
38808                    function do_blur(evt) {
38809
38810                        if(!_self_click)
38811                            hide();
38812
38813                        _self_click = false;
38814                    }
38815
38816                    function set_position() {
38817
38818                        var container = _tag_container ? _tag_container.get_container() : $this;
38819                        var pos;
38820                        try {
38821                            //pos = container.position();
38822                            pos = container.offset();
38823                        } catch(e) {
38824                            // This is IE8 sauce.
38825                            pos = {left:0, right:0, top:0, left:0};
38826                        }
38827
38828                        var left = 0;
38829                        var width = settings.width ? settings.width : (container.outerWidth() + 100);
38830                        if (settings.horizontal_align == 'left')
38831                            left = pos.left;
38832                        else
38833                            left = (pos.left + container.outerWidth()) - width;
38834
38835                        var viewport_height = $j(window).height();
38836                        //var computed_top_y = (pos.top > viewport_height / 2) ? pos.top - settings.height : pos.top + container.outerHeight();
38837                        var computed_top_y = pos.top + container.outerHeight();
38838
38839                        var style = {
38840                            'position':'absolute',
38841                            'background-color':'#fff',
38842                            'border':'1px solid #ddd',
38843                            'top':computed_top_y,
38844                            'left':left,
38845                            'width':width + 'px',
38846                            'height':settings.height,
38847                            'overflow':'hidden',
38848                            'z-index':settings.zIndex
38849                        }
38850                        _list_container.css(style);
38851
38852                        var style = {
38853                            'position':'relative',
38854                            'background-color':'#fff',
38855                            'border-top':'1px solid #ddd',
38856                            'top':'0em',
38857                            'left':'0px',
38858                            'width':'100%',
38859                            'height':(settings.height),
38860                            'overflow':'auto'
38861                        }
38862                        _inner_container.css(style);
38863                    }
38864
38865                    function clear_selections(event, ui) {
38866                        _ontology_view.select_none();
38867                    }
38868
38869                    function do_node_select(node_id, add) {
38870                        // when a user selects a tag from autocomplete it is added,
38871                        // which fires the select event.  We want to avoid that loop.
38872                        if(_adding_tag) {
38873                            _adding_tag = false;
38874                            return;
38875                        }
38876
38877                        var node = _model.node_by_id(node_id);
38878                        if(add) {
38879
38880                            if(settings.limit_single_selection && !settings.load)
38881                                _tag_container.clear();
38882
38883                            settings.load = false;  // reset load flag
38884                            _tag_container.add_tag(node.acronym);
38885                        }
38886                        else
38887                            _tag_container.remove_tag(node.acronym);
38888                    }
38889
38890		    function do_tree_load(data) {
38891			if (settings.on_ready) {
38892			    settings.on_ready(data);
38893			}
38894		    }
38895
38896                    function set_ids(structure_ids) {
38897
38898                        _tag_container.clear();
38899                        _ontology_view.select_none();
38900
38901                        var len = structure_ids.length;
38902                        for (var i = 0; i < len; i++) {
38903                            var node = _model.node_by_id(structure_ids[i]);
38904                            if(node)
38905                                do_add_tag(node.acronym);
38906                        }
38907                    }
38908
38909                    function set_tags(tags) {
38910
38911                        var tag_array = tags.split(';');
38912                        var len = tag_array.length;
38913
38914                        _tag_container.clear();
38915                        _ontology_view.select_none();
38916                        for (var i = 0; i < len; i++) {
38917                            var node = _model.node_by_acronym(tag_array[i]);
38918                            if(node)
38919                                do_add_tag(node.acronym);
38920                        }
38921                    }
38922
38923		    function get_structure_by_tag(tag) {
38924
38925			if (settings.tag_key == 'acronym')
38926			    return _model.node_by_acronym(tag);
38927			else if (settings.tag_key == 'id')
38928			    return _model.node_by_id(tag);
38929			else {
38930			    var all_nodes = _model.get_all();
38931			    for (var j = 0; j < all_nodes.length; j++) {
38932				var n = all_nodes[j];
38933				if (n[settings.tag_key] == tag) {
38934				    return n;
38935				}
38936			    }
38937			}
38938			return null;
38939		    }
38940		    
38941                    function get_tag_set() {
38942
38943                        var tag_set = [];
38944                        var tags = _tag_container.get_tags();
38945
38946                        for(var i = 0; i < tags.length; i++) {
38947			    var struct = get_structure_by_tag(tags[i]);
38948
38949                            if(!struct)
38950                                trace("no record for struct '" + tags[i] + "'");
38951                            else
38952                                tag_set.push({
38953                                    label:tags[i],
38954                                    value:struct.structure_id
38955                                });
38956                        }
38957
38958                        return(tag_set);
38959                    }
38960
38961
38962                    function structure_selection_change(structure, add) {
38963
38964                        if(_first_open)
38965                            return;
38966
38967                        if(add && settings.on_add_structure)
38968                            settings.on_add_structure(structure);
38969                        else if(!add && settings.on_remove_structure)
38970                            settings.on_remove_structure(structure);
38971                    }
38972
38973                    /**
38974                     * Checks to make sure all of the text in the input really represents
38975                     * valid structures.  Notifies the user if some tokens don't,
38976                     * and removes them.
38977                     */
38978                    function check_tags(tag_string) {
38979
38980                        if (!tag_string.length)
38981                            return;
38982
38983                        // get the array of tokens in the input;
38984                        // if there are multiple delimiters allowed, convert them all into
38985                        // the first before splitting
38986                        var first_delim = settings.delimiters[0];
38987                        for (var i = 1; i < settings.delimiters.length; i++)
38988                            tag_string.replace(settings.delimiters[i], first_delim);
38989                        var acronyms = tag_string.split(first_delim);
38990
38991                        // check to make sure each token is an acronym
38992                        var good_tokens = [];
38993                        var bad_tokens = [];
38994                        var len = acronyms.length;
38995                        for (i = 0; i < len; i++) {
38996                            var acronym = $.trim(acronyms[i]);
38997                            var node = _model.node_by_acronym(acronym);
38998                            if (!node)
38999                                bad_tokens.push(acronym);
39000                            else
39001                                good_tokens.push(node);
39002                        }
39003
39004                        for (i = 0; i < bad_tokens.length; i++) {
39005                            trace("bad token: " + inspect('', bad_tokens[i]));
39006                            _tag_container.remove_tag(bad_tokens[i]);
39007                        }
39008
39009                        return(bad_tokens.length == 0);
39010                    }
39011
39012                    function show() {
39013
39014                        if (_visible)
39015                            return;
39016
39017                        var tags = _tag_container.get_tags();
39018                        _ontology_view.select_none();
39019
39020                        _adding_tag = true;
39021
39022                        if(_first_open)
39023                            on_first_open(tags);
39024
39025			// davidf: the container has to be visible for jstree's auto-scroll-into-view to work
39026                        _list_container.show();
39027
39028                        for(var i = 0; i < tags.length; i++) {
39029                            var node = get_structure_by_tag(tags[i]);
39030
39031                            if(node)
39032                                _ontology_view.open_structure_path(node.structure_id_path, node.structure_id);
39033                        }
39034                        _adding_tag = false;
39035
39036
39037                        set_position();
39038
39039                        _visible = true;
39040                        if(settings.on_open)
39041                            settings.on_open();
39042                    }
39043
39044                    function on_first_open(tags) {
39045
39046                        if(settings.default_open_nodes) {
39047
39048                            for(var acronym in settings.default_open_nodes) {
39049
39050                                // the tags arg is the list of tags that will be shown once the
39051                                // ontology is opened.  We don't want to open them here if we're going
39052                                // to do it again anyway.
39053                                if($.inArray(acronym,tags) < 0) {
39054                                    var node = _model.node_by_acronym(acronym);
39055                                    if(node) {
39056					_ontology_view.open_structure_path(node.structure_id_path, node.structure_id, false);
39057					_ontology_view.deselect_structure(node.structure_id);
39058                                    }
39059                                }
39060                            }
39061                        }
39062                        _first_open = false;
39063                    }
39064
39065                    function hide() {
39066
39067                        if (!_visible)
39068                            return;
39069
39070                        _list_container.hide();
39071                        _visible = false;
39072
39073                        _scrolling = true;
39074                        _replacement_container.scrollTop(0);
39075                        //_scroll_count = 0;
39076                        _scrolling = false;
39077
39078                        if(settings.on_close)
39079                            settings.on_close();
39080                    }
39081
39082                    function set_data() {
39083
39084                        $this.data('ontology_autocomplete', {
39085                            list_class:_list_class,
39086                            tag_container:_tag_container,
39087                            check_tags:check_tags,
39088                            get_tag_set:get_tag_set,
39089                            set_ids:set_ids,
39090                            set_tags:set_tags,
39091                            destroy_all:destroy,
39092                            get_control_id:get_control_id
39093                        });
39094                    }
39095
39096                    function init() {
39097
39098                        build_scaffold();
39099                        set_position();
39100                        setup_view();
39101                        setup_events();
39102                        check_tags(settings.initial_acronyms);
39103                        set_data();
39104                    }
39105
39106                    init();
39107
39108                } // !_data
39109
39110            });
39111        },
39112
39113        set_structure_string:function (method, structs) {
39114
39115            return this.each(function () {
39116                var $this = $(this);
39117                var _data = $this.data('ontology_autocomplete');
39118                _data.set_tags(structs);
39119            });
39120        },
39121
39122        set_structure_ids:function (method, struct_ids) {
39123
39124            return this.each(function () {
39125                var $this = $(this);
39126                var _data = $this.data('ontology_autocomplete');
39127                _data.set_ids(struct_ids);
39128            });
39129        },
39130
39131        get_structure_string:function () {
39132
39133            var $this = $(this);
39134            var _data = $this.data('ontology_autocomplete');
39135            return(_data.tag_container.get_displayed_text());
39136        },
39137
39138        get_tags:function () {
39139
39140            var $this = $(this);
39141            var _data = $this.data('ontology_autocomplete');
39142            return(_data.get_tag_set());
39143        },
39144
39145        destroy : function( ) {
39146
39147            return this.each(function(){
39148
39149                var $this = $(this);
39150                var _data = $this.data('ontology_autocomplete');
39151
39152                // remove the DOM nodes for this instance
39153                if (_data) {
39154                    jQuery("#" + _data.get_control_id()).remove();
39155                    $this.remove();
39156                    $this.removeData();
39157                }
39158            })
39159
39160        }
39161    };
39162
39163
39164    $.fn.ontology_autocomplete = function (method) {
39165        if (methods[method]) {
39166            return methods[ method ].apply(this, Array.prototype.slice.call(arguments));
39167        } else if (typeof method === 'object' || !method) {
39168            return methods.init.apply(this, arguments);
39169        } else {
39170            $.error('Method ' + method + ' does not exist on jQuery.ontology_autocomplete');
39171        }
39172    }
39173
39174})(jQuery);
39175/**
39176 * Encapsulates an ontology graph and provides simple searches.  By default the entire graph is loaded
39177 * when this object is instantiated.
39178 * @param dao       {object}    Not used, can be null
39179 * @param graph_id  {integer}   The is of the structure graph to model
39180 * @param options   {object}    options include:
39181 *
39182 * <br/>ontology_filename: Not actually optional.  For backward compatibility this lives in the options hash.
39183 *                          The path to the ontology file to be loaded and displayed.
39184 * <br/>async:          Default = true.  Set to false to load the ontology data synchronously.
39185 * <br/>on_error:       Assign a callback function to be called when the request for
39186 *                      the given ontology graph fails. fn(this, error).  Default throws.
39187 * <br/>on_ready:       Default = null.  Assign a callback function to be called when the model has fetched
39188 *                      the ontology graph.  fn(this).
39189 * <br/>defer_init:     Default = false.  If defer_init is set to true, the caller must explicitly call ontology_model.init();
39190 */
39191
39192function ontology_model(dao, graph_id, options) {
39193
39194    this.the_dao = dao;
39195    this.graph_id = graph_id;
39196    this.options = options;
39197
39198    this.settings = {
39199        ontology_filename:'ontology.json',
39200        defer_init:false,
39201        search_limit:20,
39202        async:true, // default value used for ajax call, can be overriden by optiohs
39203        on_error:do_error,
39204        on_ready:null,
39205	snippet_length: 0
39206    };
39207    $j.extend(true, this.settings, this.options);
39208
39209    /**
39210     * Returns a single node.  The first that matches the given search term. Wraps around if none are found
39211     * 'below' the previous find position.
39212     * @param term {string} The term for which to search.
39213     * @param contains {boolean} If true, does a 'contains' search.  If false, does a 'starts with' search.
39214     */
39215    this.find_next = function(term, contains) {
39216
39217       return(do_find_next(term, contains));
39218    }
39219
39220    /**
39221     * resets the index of the last successful find_first() to 0 so subsequent searches start at the beginning.
39222     */
39223    this.reset_find_index = function() {
39224        _last_find = 0;
39225    }
39226
39227    /**
39228     * Returns an array of all the nodes that match the given term
39229     * @param term
39230     * @param contains
39231     */
39232    this.find_all = function(term, contains) {
39233
39234       return(do_find_all(term, contains));
39235    }
39236
39237
39238    /**
39239     * Returns the list of ontology nodes
39240     */
39241    this.get_all = function() {
39242        return(_the_nodes);
39243    }
39244
39245
39246    /**
39247     * returns the ontology node matching the given acronym, if it exists.  Otherwise returns null;
39248     * @param {string} acronym
39249     */
39250    this.node_by_acronym = function(acronym) {
39251
39252        return(_acronym_index[acronym]);
39253    }
39254
39255    /**
39256     * returns the ontology node matching the given ID, if it exists.  Otherwise returns null;
39257     * @param {integer} structure ID
39258     */
39259    this.node_by_id = function(id) {
39260
39261        return(_id_index[id]);
39262    }
39263
39264    this.size = function() {
39265        return(_size);
39266    }
39267
39268    /**
39269     * If the defer_init option is set to true, call this method to have the ontology_model request and load
39270     * the ontology_graph data from the AIBS warehouse service.
39271     */
39272    this.init = function() {
39273        do_init();
39274    }
39275
39276
39277    this.get_structure_children = function(structure_id) {
39278
39279        if(!structure_id)
39280            structure_id = _the_nodes[0].parent_structure_id;
39281
39282        var kids = _structure_parent_index[structure_id];
39283
39284        //see if these have any children of their own
39285        if(kids)
39286            for(var i = 0; i < kids.length; i++) {
39287               // kids[i].annotation_state = get_structure_annotation_state(kids[i].id);
39288                kids[i].child_structures = _structure_parent_index[kids[i].structure_id];
39289            }
39290
39291        return(kids);
39292    };
39293
39294    this.structure_autocomplete = function(term, callback) {
39295
39296
39297        structure_search(term, callback);
39298    }
39299
39300
39301    /**
39302     * Returns an array of nodes descended from the given node.
39303     * @param acronym
39304     * @return {Array}
39305     */
39306    this.get_descendants = function(structure_id) {
39307
39308        var node = this.node_by_id(structure_id);
39309        if(!node)
39310            return([]);
39311
39312        var d = [];
39313        for(var i = 0; i < _size; i++) {
39314
39315            // if we find a node where the path starts with the path of our starting node,
39316            // we have found a descendant.
39317            if(_the_nodes[i].structure_id_path.lastIndexOf(node.structure_id_path, 0) === 0)
39318                d.push(_the_nodes[i]);
39319        }
39320        return(d);
39321    }
39322
39323
39324    /**
39325     * Given a list of structure IDs, remove all but the parent nodes.
39326     *
39327     * @param ids.  AN array of structure IDs
39328     * @return {Array}
39329     */
39330    this.prune = function(ids) {
39331
39332        var parent_path = "";
39333        var stump = [];
39334        var nodes = [];
39335        var len = ids.length;
39336
39337        // get all the nodes for the given IDs
39338        for(var i = 0; i < len; i++)
39339            nodes.push( this.node_by_id(ids[i]));
39340
39341        // sort by 'path'
39342        nodes.sort(function(a, b) {return(a.structure_id_path.localeCompare(b.structure_id_path));});
39343        if(!nodes || !nodes.length)
39344            return(stump);
39345
39346        // the first entry must be a parent
39347        stump.push(nodes[0].structure_id);
39348        parent_path = nodes[0].structure_id_path;
39349
39350        // descend the sorted list
39351        for(var j = 1; j < len; j++) {
39352            // if the current node path does not start with the previous path,
39353            if(nodes[j].structure_id_path.lastIndexOf(parent_path, 0) != 0) {
39354                // it is a parent
39355                stump.push(nodes[j].structure_id);
39356                parent_path = nodes[j].structure_id_path;
39357            }
39358        }
39359
39360        return(stump);
39361    }
39362
39363
39364    /**
39365     * @private
39366     * @scope AIBS.DATA.ontology_model
39367     */
39368    var _self = this;
39369    var _the_nodes = [];
39370    var _last_find = 0;
39371    var _size = 0;
39372    var _acronym_index = null;
39373    var _id_index = null;
39374    var _structure_parent_index = null;
39375    
39376    /**
39377     * Convert a string into a snippet with _self.settings.snippet_length words.
39378     * 
39379     * @param index    string index around which the snippet will be made
39380     * @param str      the string being snippeted
39381     */
39382    function make_snippet(index, str) {
39383	// no snippet? no-op
39384	if (_self.settings.snippet_length <= 0)
39385	    return str;
39386
39387	// break the snippet into words.
39388	var words = str.split(" ");
39389
39390	// if the match index is 0, just return the first N words
39391	if (index == 0) {
39392	    var snippet_words = words.slice(0, _self.settings.snippet_length);
39393	    return snippet_words.join(" ") + (words.length == snippet_words.length ? "" : "...");
39394	} else {
39395	    
39396	    // figure out which word the term appears in
39397	    var word_index;
39398	    var num_chars = 0;
39399	    for (word_index = 0; word_index < words.length; word_index++) {
39400		num_chars += words[word_index].length + 1;
39401		if (num_chars > index)
39402		    break;
39403	    }
39404	    
39405	    // the snippet will always have the term in it
39406	    var snippet_words = [ words[word_index] ];
39407	    var before_index = word_index - 1;
39408	    var after_index = word_index + 1;
39409
39410	    // add words before and after the term word until we have enough words
39411	    while (snippet_words.length < _self.settings.snippet_length) {
39412		if (before_index < 0 && after_index >= words.length)  
39413		    break;
39414	    
39415		if (before_index >= 0) {
39416		    snippet_words.unshift(words[before_index]);
39417		}		    
39418	    
39419		if (after_index < words.length) {
39420		    snippet_words.push(words[after_index]);
39421		}
39422		
39423		before_index--;
39424		after_index++;
39425	    }
39426	    
39427	    return (before_index < 0 ? "" : "..." ) + snippet_words.join(' ') + (after_index >= words.length ? "" : "...");
39428	}
39429    }
39430       
39431    /**
39432     *
39433     * @param term
39434     * @param complete     If true, first does a 'starts with' search and then
39435     *                      if the _self.settings.search_limit number of matches
39436     *                      is not found, also does a 'contains search'.
39437     *                      If false or undefined, only a 'starts with' search is done.
39438     */
39439    function structure_search(term, callback) {
39440
39441        var matches = [];
39442        var match_count = 0;
39443	var matched_ids = {};
39444        // first do the starts-with search
39445	var m = null;
39446        var start_patt = new RegExp(("^" + term.term), 'i');
39447        for(var i = 0; i < _size; i++) {
39448
39449            if((m = _the_nodes[i].acronym.match(start_patt)) || (m = _the_nodes[i].name.match(start_patt))) {
39450
39451		var name = (_self.settings.snippet_length > 0) ? make_snippet(0, _the_nodes[i].name) : _the_nodes[i].name;
39452
39453		matched_ids[i] = true;
39454                matches[match_count++] = {
39455                    label:_the_nodes[i].acronym + ' - ' + name,
39456                    value:_the_nodes[i].acronym,
39457                    id:_the_nodes[i].structure_id
39458                };
39459                if(match_count >= _self.settings.search_limit)
39460                    break;
39461            }
39462        }
39463
39464        // if necessary do the 'includes' search
39465        if(match_count < _self.settings.search_limit) {
39466	    
39467            var patt = new RegExp(term.term, 'i');
39468
39469
39470            for(var i = 0; i < _size; i++) {
39471		if (i in matched_ids)
39472		    continue;
39473
39474                // if it's included in the string, but not starting
39475                // the string (because we already matched that above...)
39476                if((m = _the_nodes[i].acronym.match(patt)) || (m = _the_nodes[i].name.match(patt))
39477                   &&
39478                   (!start_patt.test(_the_nodes[i].acronym) && !start_patt.test(_the_nodes[i].name))
39479                  ) {
39480		    matched_ids[i] = true;
39481		    var name = (_self.settings.snippet_length > 0) ? make_snippet(m.index, _the_nodes[i].name) : _the_nodes[i].name;
39482
39483                    matches[match_count++] = {
39484                        label:_the_nodes[i].acronym + ' - ' + name,
39485                        value:_the_nodes[i].acronym,
39486                        id:_the_nodes[i].structure_id
39487                    };
39488                    if(match_count >= _self.settings.search_limit)
39489                        break;
39490                }
39491            }
39492        }
39493
39494        callback(matches);
39495    }
39496
39497    // create three different indices into the data.
39498    function index_data() {
39499
39500       _acronym_index = {};
39501       _id_index = {};
39502       _structure_parent_index = {};
39503
39504       var len = _the_nodes.length;
39505       for(var i = 0; i < len; i++) {
39506
39507            // straight-forward indices by id and acronym
39508            _acronym_index[_the_nodes[i].acronym] = _the_nodes[i];
39509            _id_index[_the_nodes[i].structure_id] = _the_nodes[i];
39510
39511            // an index of children by parent.  For each parent_structure_id we want to list all children
39512
39513            // if this is the first instance of this parent_structure_id, create a new array for its members
39514            if(!_structure_parent_index[_the_nodes[i].parent_structure_id])
39515                _structure_parent_index[_the_nodes[i].parent_structure_id] = [];
39516
39517            _structure_parent_index[_the_nodes[i].parent_structure_id].push(_the_nodes[i]);
39518
39519       }
39520    }
39521
39522
39523    function load_graph() {
39524
39525        // note: jQuery.getJSON does not call error handlers, use jQuery.ajax instead. See http://stackoverflow.com/questions/5388934/jquery-handle-errors-in-getjson
39526        $j.ajax({
39527            url: '/' + _self.settings.ontology_filename + '?' + (Date.parse(new Date())),
39528            dataType: 'json',
39529            async: _self.settings.async,
39530            success: function(data) {
39531                _the_nodes = data;
39532                _size = data.length;
39533                index_data();
39534                if(_self.settings.on_ready)
39535                    _self.settings.on_ready(_self);
39536            },
39537            error: _self.settings.on_error
39538        });
39539    }
39540
39541    function do_error(xhdr, text_status, error) {
39542        var err = xhdr.msg == undefined ? error : xhdr.msg;
39543
39544        if(_self.settings.on_error)
39545            return(_self.settings.on_error(_self, err));
39546
39547        throw "Error fetching ontology graph: " + err;
39548    }
39549
39550    function do_init() {
39551
39552        if(!_self.settings.ontology_filename || !_self.settings.ontology_filename.length)
39553            throw "Error loading ontology model. no ontology file name provided.";
39554
39555        load_graph();
39556    }
39557
39558    if(!_self.settings.defer_init)
39559        do_init();
39560
39561}
39562;
39563
39564
39565var __ONTOLOGY_VIEW_COUNT__ = 0;
39566
39567
39568/**
39569 * This is the UI portion of displaying all or part of an ontology tree.  It's assumed that the
39570 * ontology adheres to the AIBS warehouse API OntologyNode schema, or that node objects contain at least
39571 * these fields:
39572 * {
39573 *   depth:integer,
39574 *   st_order:integer,
39575 *   color:string,
39576 *   acronym:string,
39577 *   name:string,
39578 *   structure_id:integer
39579 * }
39580 * @param container
39581 * @param node_array
39582 * @param options
39583 */
39584function ontology_view(container, node_array, options) {
39585
39586    this.container = $j(container);
39587    this.node_array = node_array;
39588    this.options = options;
39589
39590
39591    this.on_node_over = options ? options.on_node_over ? options.on_node_over : null : null;
39592    this.on_node_out = options ? options.on_node_out ? options.on_node_out : null : null;
39593    this.on_node_select = options ? options.on_node_select ? options.on_node_select : null : null;
39594    this.render_node = options ? options.render_node ? options.render_node : null : null;
39595    this.scroll_speed = options ? options.scroll_speed ? options.scroll_speed : 0 : 0;
39596
39597
39598    this.scroll_to_structure = function(structure_id, highlight) {
39599        var $node = node_for_structure(structure_id);
39600        if($node && $node.length > 0)
39601            $node.scrollintoview();
39602
39603        if(highlight)
39604            this.highlight_structure(structure_id);
39605    }
39606
39607    this.scroll_to_top = function() {
39608        first_item().scrollintoview();
39609    }
39610
39611    this.highlight_structure = function(structure_id) {
39612        var $node = item_for_structure_id(structure_id);
39613        if($node && $node.length > 0) {
39614            if(_current_highlight_node)
39615                _current_highlight_node.removeClass('ui-state-hover');
39616
39617            $node.addClass('ui-state-hover');
39618            _current_highlight_node = $node;
39619        }
39620    }
39621
39622    this.highlight_prev = function() {
39623
39624        if(_current_highlight_node) {
39625            var prev = _current_highlight_node.prev();
39626            this.highlight_none();
39627            if(prev && prev.length) {
39628                prev.scrollintoview();
39629                highlight_node(prev);
39630            }
39631        }
39632        else
39633            this.highlight_none();
39634
39635    }
39636
39637    this.highlight_next = function() {
39638
39639        var next = null;
39640        if(_current_highlight_node) {
39641            next = _current_highlight_node.next();
39642            this.highlight_none();
39643        }
39644        else
39645            next = first_item();
39646
39647        if(next && next.length) {
39648            next.scrollintoview();
39649            highlight_node(next);
39650        }
39651    }
39652
39653    this.highlight_none = function() {
39654        if(_current_highlight_node)
39655           _current_highlight_node.removeClass('ui-state-hover');
39656
39657        _current_highlight_node = null;
39658    }
39659
39660    this.select_at_highlight = function() {
39661        if(!_current_highlight_node)
39662            return;
39663
39664        var item = _current_highlight_node.find('._ov_chk_chk');
39665        toggle_selected(item);
39666    }
39667
39668    this.select_at_structure = function(structure_id) {
39669        var item = node_for_structure(structure_id);
39670        toggle_selected(item);
39671    }
39672
39673    this.select_none = function() {
39674        var $selections = $j('#'+_id_prefix + '_ov_wrapper ._ov_checked');
39675        $selections.each( function(index) {select($j(this), false);} )
39676    }
39677
39678    // remove selection highlight from an ontology row
39679    this.remove_highlight = function(structure_id) {
39680        var $node = node_for_structure(structure_id);
39681        var item = item_for_structure_id(structure_id);
39682
39683        $node.removeClass('_ov_checked');
39684        item.removeClass('_ov_selected');
39685    }
39686
39687
39688    var _self = this;
39689    var _view_class = options ? options.view_class ? options.view_class : '' : '';
39690
39691    var _indent_multiplier = options ? options._indent_multiplier ? options._indent_multiplier : 12 : 12;
39692    var _id_prefix = '_ov_' + __ONTOLOGY_VIEW_COUNT__++;
39693
39694    var _wrapper = null;
39695    var _ulist = null;
39696
39697    var _current_highlight_node = null;
39698
39699
39700    function node_for_structure(structure_id) {
39701       return( $j('#' + _id_prefix + '_ov_wrapper [struct_id="' + structure_id + '"]') );
39702    }
39703
39704    function item_for_structure_id(structure_id) {
39705        return( $j('#' + _id_prefix + '_' +structure_id) );
39706    }
39707
39708    function highlight_node(node) {
39709        if(!node)
39710            return;
39711
39712        node.addClass('ui-state-hover');
39713        _current_highlight_node = node;
39714    }
39715
39716    function first_item() {
39717         return( _self.container.find('li').first());
39718    }
39719
39720    function build_scaffold() {
39721
39722        var id = _id_prefix + '_ov_wrapper';
39723        var class_names = '_ov_wrapper ' + _view_class;
39724
39725        var html = "<div id='" + id + "' class='" + class_names + "'>";
39726        html += "<ul id='" + (id+'_ul') + "' class='_ov_wrapper_ul'></ul>"
39727        html += "</div>";
39728
39729        $j(html).appendTo(_self.container);
39730
39731        //objectify
39732        _wrapper = $j('#'+id);
39733        _ulist = $j('#'+id+'_ul');
39734    }
39735
39736    function render_node(node) {
39737
39738        if(_self.render_node)
39739            return(_self.render_node(node));
39740
39741
39742        var id = _id_prefix + '_' + node.structure_id
39743        var html = "<li id='" + id + "' class='_ov_li'>";
39744        html += "<div class='_ov_li_spacer' style='width:" + (node.depth*_indent_multiplier) + "px'></div>";
39745        html += "<div class='_ov_chk_cont' >";
39746        html += "<div struct_id='" + node.structure_id + "' acronym='" + node.acronym + "' struct_name='" + node.name + "' class='_ov_chk_chk _ov_unchecked' style='background-color:#"+ node.color + "'></div>";
39747        html += "</div>";
39748        html += "<div class='_ov_li_content' title='" + node.name + "'><b>
39748" + node.acronym + "</b> " + node.name + "</div>";
39749        html += "</li>";
39750
39751        return(html);
39752    }
39753
39754    function populate_view() {
39755        var len = _self.node_array.length;
39756        var html = "";
39757
39758        for(var i = 0; i < len; i++)
39759            html += render_node(_self.node_array[i]);
39760
39761        _ulist[0].innerHTML = html;
39762        $j('#'+_id_prefix + '_ov_wrapper ._ov_li').bind('click', selector);
39763    }
39764
39765    function selector(event, ui) {
39766        var node = $j(this).find('._ov_chk_chk');
39767        toggle_selected(node);
39768    }
39769
39770    function checker(event, ui) {
39771
39772        var node = $j(this);
39773        toggle_selected(node)
39774        _self.highlight_structure(node.attr('struct_id'));
39775        event.stopPropagation();
39776
39777        return(false);
39778    }
39779
39780    function toggle_selected($node) {
39781        var selected = $node.hasClass('_ov_checked');
39782        select($node, !selected);
39783    }
39784
39785    function select($node, select) {
39786
39787        var item = item_for_structure_id($node.attr('struct_id'));
39788        if(select) {
39789            $node.addClass('_ov_checked');
39790            item.addClass('_ov_selected');
39791            item.removeClass('_ov_highlight');
39792            item.removeClass('ui-state-hover');
39793        }
39794        else {
39795            $node.removeClass('_ov_checked');
39796            item.removeClass('_ov_selected');
39797        }
39798
39799        var info = {
39800           id:$node.attr('struct_id'),
39801           acronym:$node.attr('acronym'),
39802           name:$node.attr('struct_name')
39803        };
39804
39805        if(_self.on_node_select)
39806            _self.on_node_select(_self, select, info);
39807    }
39808
39809    function init() {
39810        build_scaffold();
39811        populate_view();
39812
39813    }
39814
39815    init();
39816}
39817;
39818
39819
39820
39821
39822
39823
39824
39825
39826
39827/*!
39828 * jQuery scrollintoview() plugin and :scrollable selector filter
39829 *
39830 * Version 1.8 (14 Jul 2011)
39831 * Requires jQuery 1.4 or newer
39832 *
39833 * Copyright (c) 2011 Robert Koritnik
39834 * Licensed under the terms of the MIT license
39835 * http://www.opensource.org/licenses/mit-license.php
39836 */
39837
39838
39839(function ($) {
39840	var converter = {
39841		vertical: { x: false, y: true },
39842		horizontal: { x: true, y: false },
39843		both: { x: true, y: true },
39844		x: { x: true, y: false },
39845		y: { x: false, y: true }
39846	};
39847
39848	var settings = {
39849		duration: "fast",
39850		direction: "both"
39851	};
39852
39853	var rootrx = /^(?:html)$/i;
39854
39855	// gets border dimensions
39856	var borders = function (domElement, styles) {
39857		styles = styles || (document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(domElement, null) : domElement.currentStyle);
39858		var px = document.defaultView && document.defaultView.getComputedStyle ? true : false;
39859		var b = {
39860			top: (parseFloat(px ? styles.borderTopWidth : $.css(domElement, "borderTopWidth")) || 0),
39861			left: (parseFloat(px ? styles.borderLeftWidth : $.css(domElement, "borderLeftWidth")) || 0),
39862			bottom: (parseFloat(px ? styles.borderBottomWidth : $.css(domElement, "borderBottomWidth")) || 0),
39863			right: (parseFloat(px ? styles.borderRightWidth : $.css(domElement, "borderRightWidth")) || 0)
39864		};
39865		return {
39866			top: b.top,
39867			left: b.left,
39868			bottom: b.bottom,
39869			right: b.right,
39870			vertical: b.top + b.bottom,
39871			horizontal: b.left + b.right
39872		};
39873	};
39874
39875	var dimensions = function ($element) {
39876		var win = $(window);
39877		var isRoot = rootrx.test($element[0].nodeName);
39878		return {
39879			border: isRoot ? { top: 0, left: 0, bottom: 0, right: 0} : borders($element[0]),
39880			scroll: {
39881				top: (isRoot ? win : $element).scrollTop(),
39882				left: (isRoot ? win : $element).scrollLeft()
39883			},
39884			scrollbar: {
39885				right: isRoot ? 0 : $element.innerWidth() - $element[0].clientWidth,
39886				bottom: isRoot ? 0 : $element.innerHeight() - $element[0].clientHeight
39887			},
39888			rect: (function () {
39889				var r = $element[0].getBoundingClientRect();
39890				return {
39891					top: isRoot ? 0 : r.top,
39892					left: isRoot ? 0 : r.left,
39893					bottom: isRoot ? $element[0].clientHeight : r.bottom,
39894					right: isRoot ? $element[0].clientWidth : r.right
39895				};
39896			})()
39897		};
39898	};
39899
39900	$.fn.extend({
39901		scrollintoview: function (options) {
39902			/// <summary>Scrolls the first element in the set into view by scrolling its closest scrollable parent.</summary>
39903			/// <param name="options" type="Object">Additional options that can configure scrolling:
39904			///        duration (default: "fast") - jQuery animation speed (can be a duration string or number of milliseconds)
39905			///        direction (default: "both") - select possible scrollings ("vertical" or "y", "horizontal" or "x", "both")
39906			///        complete (default: none) - a function to call when scrolling completes (called in context of the DOM element being scrolled)
39907			/// </param>
39908			/// <return type="jQuery">Returns the same jQuery set that this function was run on.</return>
39909
39910			options = $.extend({}, settings, options);
39911			options.direction = converter[typeof (options.direction) === "string" && options.direction.toLowerCase()] || converter.both;
39912
39913			var dirStr = "";
39914			if (options.direction.x === true) dirStr = "horizontal";
39915			if (options.direction.y === true) dirStr = dirStr ? "both" : "vertical";
39916
39917			var el = this.eq(0);
39918			var scroller = el.closest(":scrollable(" + dirStr + ")");
39919
39920			// check if there's anything to scroll in the first place
39921			if (scroller.length > 0)
39922			{
39923				scroller = scroller.eq(0);
39924
39925				var dim = {
39926					e: dimensions(el),
39927					s: dimensions(scroller)
39928				};
39929
39930				var rel = {
39931					top: dim.e.rect.top - (dim.s.rect.top + dim.s.border.top),
39932					bottom: dim.s.rect.bottom - dim.s.border.bottom - dim.s.scrollbar.bottom - dim.e.rect.bottom,
39933					left: dim.e.rect.left - (dim.s.rect.left + dim.s.border.left),
39934					right: dim.s.rect.right - dim.s.border.right - dim.s.scrollbar.right - dim.e.rect.right
39935				};
39936
39937				var animOptions = {};
39938
39939				// vertical scroll
39940				if (options.direction.y === true)
39941				{
39942					if (rel.top < 0)
39943					{
39944						animOptions.scrollTop = dim.s.scroll.top + rel.top;
39945					}
39946					else if (rel.top > 0 && rel.bottom < 0)
39947					{
39948						animOptions.scrollTop = dim.s.scroll.top + Math.min(rel.top, -rel.bottom);
39949					}
39950				}
39951
39952				// horizontal scroll
39953				if (options.direction.x === true)
39954				{
39955					if (rel.left < 0)
39956					{
39957						animOptions.scrollLeft = dim.s.scroll.left + rel.left;
39958					}
39959					else if (rel.left > 0 && rel.right < 0)
39960					{
39961						animOptions.scrollLeft = dim.s.scroll.left + Math.min(rel.left, -rel.right);
39962					}
39963				}
39964
39965				// scroll if needed
39966				if (!$.isEmptyObject(animOptions))
39967				{
39968					if (rootrx.test(scroller[0].nodeName))
39969					{
39970						scroller = $("html,body");
39971					}
39972					scroller
39973						.animate(animOptions, options.duration)
39974						.eq(0) // we want function to be called just once (ref. "html,body")
39975						.queue(function (next) {
39976							$.isFunction(options.complete) && options.complete.call(scroller[0]);
39977							next();
39978						});
39979				}
39980				else
39981				{
39982					// when there's nothing to scroll, just call the "complete" function
39983					$.isFunction(options.complete) && options.complete.call(scroller[0]);
39984				}
39985			}
39986
39987			// return set back
39988			return this;
39989		}
39990	});
39991
39992	var scrollValue = {
39993		auto: true,
39994		scroll: true,
39995		visible: false,
39996		hidden: false
39997	};
39998
39999	$.extend($.expr[":"], {
40000		scrollable: function (element, index, meta, stack) {
40001			var direction = converter[typeof (meta[3]) === "string" && meta[3].toLowerCase()] || converter.both;
40002			var styles = (document.defaultView && document.defaultView.getComputedStyle ? document.defaultView.getComputedStyle(element, null) : element.currentStyle);
40003			var overflow = {
40004				x: scrollValue[styles.overflowX.toLowerCase()] || false,
40005				y: scrollValue[styles.overflowY.toLowerCase()] || false,
40006				isRoot: rootrx.test(element.nodeName)
40007			};
40008
40009			// check if completely unscrollable (exclude HTML element because it's special)
40010			if (!overflow.x && !overflow.y && !overflow.isRoot)
40011			{
40012				return false;
40013			}
40014
40015			var size = {
40016				height: {
40017					scroll: element.scrollHeight,
40018					client: element.clientHeight
40019				},
40020				width: {
40021					scroll: element.scrollWidth,
40022					client: element.clientWidth
40023				},
40024				// check overflow.x/y because iPad (and possibly other tablets) don't dislay scrollbars
40025				scrollableX: function () {
40026					return (overflow.x || overflow.isRoot) && this.width.scroll > this.width.client;
40027				},
40028				scrollableY: function () {
40029					return (overflow.y || overflow.isRoot) && this.height.scroll > this.height.client;
40030				}
40031			};
40032			return direction.y && size.scrollableY() || direction.x && size.scrollableX();
40033		}
40034	});
40035})(jQuery);
40036(function(a){a.widget("ui.dropdownchecklist",{version:function(){alert("DropDownCheckList v1.4")},_appendDropContainer:function(b){var d=a("<div/>");d.addClass("ui-dropdownchecklist ui-dropdownchecklist-dropcontainer-wrapper");d.addClass("ui-widget");d.attr("id",b.attr("id")+"-ddw");d.css({position:"absolute",left:"-33000px",top:"-33000px"});var c=a("<div/>");c.addClass("ui-dropdownchecklist-dropcontainer ui-widget-content");c.css("overflow-y","auto");d.append(c);d.insertAfter(b);d.isOpen=false;return d},_isDropDownKeyShortcut:function(c,b){return c.altKey&&(a.ui.keyCode.DOWN==b)},_isDropDownCloseKey:function(c,b){return(a.ui.keyCode.ESCAPE==b)||(a.ui.keyCode.ENTER==b)},_keyFocusChange:function(f,i,c){var g=a(":focusable");var d=g.index(f);if(d>=0){d+=i;if(c){var e=this.dropWrapper.find("input:not([disabled])");var b=g.index(e.get(0));var h=g.index(e.get(e.length-1));if(d<b){d=h}else{if(d>h){d=b}}}g.get(d).focus()}},_handleKeyboard:function(d){var b=this;var c=(d.keyCode||d.which);if(!b.dropWrapper.isOpen&&b._isDropDownKeyShortcut(d,c)){d.stopImmediatePropagation();b._toggleDropContainer(true)}else{if(b.dropWrapper.isOpen&&b._isDropDownCloseKey(d,c)){d.stopImmediatePropagation();b._toggleDropContainer(false);b.controlSelector.focus()}else{if(b.dropWrapper.isOpen&&(d.target.type=="checkbox")&&((c==a.ui.keyCode.DOWN)||(c==a.ui.keyCode.UP))){d.stopImmediatePropagation();b._keyFocusChange(d.target,(c==a.ui.keyCode.DOWN)?1:-1,true)}else{if(b.dropWrapper.isOpen&&(c==a.ui.keyCode.TAB)){}}}}},_handleFocus:function(f,d,b){var c=this;if(b&&!c.dropWrapper.isOpen){f.stopImmediatePropagation();if(d){c.controlSelector.addClass("ui-state-hover");if(a.ui.dropdownchecklist.gLastOpened!=null){a.ui.dropdownchecklist.gLastOpened._toggleDropContainer(false)}}else{c.controlSelector.removeClass("ui-state-hover")}}else{if(!b&&!d){if(f!=null){f.stopImmediatePropagation()}c.controlSelector.removeClass("ui-state-hover");c._toggleDropContainer(false)}}},_cancelBlur:function(c){var b=this;if(b.blurringItem!=null){clearTimeout(b.blurringItem);b.blurringItem=null}},_appendControl:function(){var j=this,c=this.sourceSelect,k=this.options;var b=a("<span/>");b.addClass("ui-dropdownchecklist ui-dropdownchecklist-selector-wrapper ui-widget");b.css({display:"inline-block",cursor:"default",overflow:"hidden"});var f=c.attr("id");
40036if((f==null)||(f=="")){f="ddcl-"+a.ui.dropdownchecklist.gIDCounter++}else{f="ddcl-"+f}b.attr("id",f);var h=a("<span/>");h.addClass("ui-dropdownchecklist-selector ui-state-default");h.css({display:"inline-block",overflow:"hidden","white-space":"nowrap"});var d=c.attr("tabIndex");if(d==null){d=0}else{d=parseInt(d);if(d<0){d=0}}h.attr("tabIndex",d);h.keyup(function(l){j._handleKeyboard(l)});h.focus(function(l){j._handleFocus(l,true,true)});h.blur(function(l){j._handleFocus(l,false,true)});b.append(h);if(k.icon!=null){var i=(k.icon.placement==null)?"left":k.icon.placement;var g=a("<div/>");g.addClass("ui-icon");g.addClass((k.icon.toOpen!=null)?k.icon.toOpen:"ui-icon-triangle-1-e");g.css({"float":i});h.append(g)}var e=a("<span/>");e.addClass("ui-dropdownchecklist-text");e.css({display:"inline-block","white-space":"nowrap",overflow:"hidden"});h.append(e);b.hover(function(){if(!j.disabled){h.addClass("ui-state-hover")}},function(){if(!j.disabled){h.removeClass("ui-state-hover")}});b.click(function(l){if(!j.disabled){l.stopImmediatePropagation();j._toggleDropContainer(!j.dropWrapper.isOpen)}});b.insertAfter(c);a(window).resize(function(){if(!j.disabled&&j.dropWrapper.isOpen){j._toggleDropContainer(true)}});return b},_createDropItem:function(g,f,o,l,q,h,e,k){var m=this,c=this.options,d=this.sourceSelect,p=this.controlWrapper;var t=a("<div/>");t.addClass("ui-dropdownchecklist-item");t.css({"white-space":"nowrap"});var r=h?' checked="checked"':"";var j=e?' class="inactive"':' class="active"';var b=p.attr("id");var n=b+"-i"+g;var s;if(m.isMultiple){s=a('<input disabled type="checkbox" id="'+n+'"'+r+j+' tabindex="'+f+'" />')}else{s=a('<input disabled type="radio" id="'+n+'" name="'+b+'"'+r+j+' tabindex="'+f+'" />')}s=s.attr("index",g).val(o);t.append(s);var i=a("<label for="+n+"/>");i.addClass("ui-dropdownchecklist-text");if(q!=null){i.attr("style",q)}i.css({cursor:"default"});i.html(l);if(k){t.addClass("ui-dropdownchecklist-indent")}t.addClass("ui-state-default");if(e){t.addClass("ui-state-disabled")}i.click(function(u){u.stopImmediatePropagation()});t.append(i);t.hover(function(v){var u=a(this);if(!u.hasClass("ui-state-disabled")){u.addClass("ui-state-hover")}},function(v){var u=a(this);u.removeClass("ui-state-hover")});s.click(function(w){var v=a(this);w.stopImmediatePropagation();if(v.hasClass("active")){var x=m.options.onItemClick;if(a.isFunction(x)){try{x.call(m,v,d.get(0))}catch(u){v.prop("checked",!v.prop("checked"));m._syncSelected(v);return}}m._syncSelected(v);m.sourceSelect.trigger("change","ddcl_internal");if(!m.isMultiple&&c.closeRadioOnClick){m._toggleDropContainer(false)}}});t.click(function(y){var x=a(this);y.stopImmediatePropagation();if(!x.hasClass("ui-state-disabled")){var v=x.find("input");var w=v.prop("checked");v.prop("checked",!w);var z=m.options.onItemClick;if(a.isFunction(z)){try{z.call(m,v,d.get(0))}catch(u){v.prop("checked",w);m._syncSelected(v);return}}m._syncSelected(v);m.sourceSelect.trigger("change","ddcl_internal");if(!w&&!m.isMultiple&&c.closeRadioOnClick){m._toggleDropContainer(false)}}else{x.focus();m._cancelBlur()}});t.focus(function(v){var u=a(this);v.stopImmediatePropagation()});t.keyup(function(u){m._handleKeyboard(u)});return t},_createGroupItem:function(f,d){var b=this;var e=a("<div />");e.addClass("ui-dropdownchecklist-group ui-widget-header");if(d){e.addClass("ui-state-disabled")}e.css({"white-space":"nowrap"});var c=a("<span/>");c.addClass("ui-dropdownchecklist-text");c.css({cursor:"default"});c.text(f);e.append(c);e.click(function(h){var g=a(this);h.stopImmediatePropagation();g.focus();b._cancelBlur()});e.focus(function(h){var g=a(this);h.stopImmediatePropagation()});return e},_createCloseItem:function(e){var b=this;var d=a("<div />");d.addClass("ui-state-default ui-dropdownchecklist-close ui-dropdownchecklist-item");d.css({"white-space":"nowrap","text-align":"right"});var c=a("<span/>");c.addClass("ui-dropdownchecklist-text");c.css({cursor:"default"});c.html(e);d.append(c);d.click(function(g){var f=a(this);g.stopImmediatePropagation();f.focus();b._toggleDropContainer(false)});d.hover(function(f){a(this).addClass("ui-state-hover")},function(f){a(this).removeClass("ui-state-hover")});d.focus(function(g){var f=a(this);g.stopImmediatePropagation()});return d},_appendItems:function(){var d=this,f=this.options,h=this.sourceSelect,g=this.dropWrapper;var b=g.find(".ui-dropdownchecklist-dropcontainer");h.children().each(function(j){var k=a(this);if(k.is("option")){d._appendOption(k,b,j,false,false)}else{if(k.is("optgroup")){var l=k.prop("disabled");var n=k.attr("label");if(n!=""){var m=d._createGroupItem(n,l);b.append(m)}d._appendOptions(k,b,j,true,l)}}});if(f.explicitClose!=null){var i=d._createCloseItem(f.explicitClose);b.append(i)}var c=b.outerWidth();var e=b.outerHeight();return{width:c,height:e}},_appendOptions:function(g,d,f,c,b){var e=this;g.children("option").each(function(h){var i=a(this);var j=(f+"."+h);e._appendOption(i,d,j,c,b)})},_appendOption:function(g,b,h,d,n){var m=this;var k=g.html();if((k!=null)&&(k!="")){var j=g.val();var i=g.attr("style");var f=g.prop("selected");var e=(n||g.prop("disabled"));var c=m.controlSelector.attr("tabindex");var l=m._createDropItem(h,c,j,k,i,f,e,d);b.append(l)}},_syncSelected:function(h){var i=this,l=this.options,b=this.sourceSelect,d=this.dropWrapper;var c=b.get(0).options;var g=d.find("input.active");if(l.firstItemChecksAll=="exclusive"){if((h==null)&&a(c[0]).prop("selected")){g.prop("checked",false);a(g[0]).prop("checked",true)}
40036else{if((h!=null)&&(h.attr("index")==0)){var e=h.prop("checked");g.prop("checked",false);a(g[0]).prop("checked",e)}else{var f=true;var k=null;g.each(function(m){if(m>0){var n=a(this).prop("checked");if(!n){f=false}}else{k=a(this)}});if(k!=null){if(f){g.prop("checked",false)}k.prop("checked",f)}}}}else{if(l.firstItemChecksAll){if((h==null)&&a(c[0]).prop("selected")){g.prop("checked",true)}else{if((h!=null)&&(h.attr("index")==0)){g.prop("checked",h.prop("checked"))}else{var f=true;var k=null;g.each(function(m){if(m>0){var n=a(this).prop("checked");if(!n){f=false}}else{k=a(this)}});if(k!=null){k.prop("checked",f)}}}}}var j=0;g=d.find("input");g.each(function(n){var m=a(c[n+j]);var o=m.html();if((o==null)||(o=="")){j+=1;m=a(c[n+j])}m.prop("selected",a(this).prop("checked"))});i._updateControlText();if(h!=null){h.focus()}},_sourceSelectChangeHandler:function(c){var b=this,d=this.dropWrapper;d.find("input").val(b.sourceSelect.val());b._updateControlText()},_updateControlText:function(){var c=this,g=this.sourceSelect,d=this.options,f=this.controlWrapper;var h=g.find("option:first");var b=g.find("option");var i=c._formatText(b,d.firstItemChecksAll,h);var e=f.find(".ui-dropdownchecklist-text");e.html(i);e.attr("title",e.text())},_formatText:function(b,d,e){var f;if(a.isFunction(this.options.textFormatFunction)){try{f=this.options.textFormatFunction(b)}catch(c){alert("textFormatFunction failed: "+c)}}else{if(d&&(e!=null)&&e.prop("selected")){f=e.html()}else{f="";b.each(function(){if(a(this).prop("selected")){if(f!=""){f+=", "}var g=a(this).attr("style");var h=a("<span/>");h.html(a(this).html());if(g==null){f+=h.html()}else{h.attr("style",g);f+=a("<span/>").append(h).html()}}});if(f==""){f=(this.options.emptyText!=null)?this.options.emptyText:"&nbsp;"}}}return f},_toggleDropContainer:function(e){var c=this;var d=function(f){if((f!=null)&&f.dropWrapper.isOpen){f.dropWrapper.isOpen=false;a.ui.dropdownchecklist.gLastOpened=null;var h=f.options;f.dropWrapper.css({top:"-33000px",left:"-33000px"});var g=f.controlSelector;g.removeClass("ui-state-active");g.removeClass("ui-state-hover");var j=f.controlWrapper.find(".ui-icon");if(j.length>0){j.removeClass((h.icon.toClose!=null)?h.icon.toClose:"ui-icon-triangle-1-s");j.addClass((h.icon.toOpen!=null)?h.icon.toOpen:"ui-icon-triangle-1-e")}a(document).unbind("click",d);f.dropWrapper.find("input.active").prop("disabled",true);if(a.isFunction(h.onComplete)){try{h.onComplete.call(f,f.sourceSelect.get(0))}catch(i){alert("callback failed: "+i)}}}};var b=function(n){if(!n.dropWrapper.isOpen){n.dropWrapper.isOpen=true;a.ui.dropdownchecklist.gLastOpened=n;var g=n.options;if((g.positionHow==null)||(g.positionHow=="absolute")){n.dropWrapper.css({position:"absolute",top:n.controlWrapper.position().top+n.controlWrapper.outerHeight()+"px",left:n.controlWrapper.position().left+"px"})}else{if(g.positionHow=="relative"){n.dropWrapper.css({position:"relative",top:"0px",left:"0px"})}}var m=0;if(g.zIndex==null){var l=n.controlWrapper.parents().map(function(){var o=a(this).css("z-index");return isNaN(o)?0:o}).get();var i=Math.max.apply(Math,l);if(i>=0){m=i+1}}else{m=parseInt(g.zIndex)}if(m>0){n.dropWrapper.css({"z-index":m})}var j=n.controlSelector;j.addClass("ui-state-active");j.removeClass("ui-state-hover");var h=n.controlWrapper.find(".ui-icon");if(h.length>0){h.removeClass((g.icon.toOpen!=null)?g.icon.toOpen:"ui-icon-triangle-1-e");h.addClass((g.icon.toClose!=null)?g.icon.toClose:"ui-icon-triangle-1-s")}a(document).bind("click",function(o){d(n)});var f=n.dropWrapper.find("input.active");f.prop("disabled",false);var k=f.get(0);if(k!=null){k.focus()}}};if(e){d(a.ui.dropdownchecklist.gLastOpened);b(c)}else{d(c)}},_setSize:function(b){var m=this.options,f=this.dropWrapper,l=this.controlWrapper;var k=b.width;if(m.width!=null){k=parseInt(m.width)}else{if(m.minWidth!=null){var c=parseInt(m.minWidth);if(k<c){k=c}}}var i=this.controlSelector;i.css({width:k+"px"});var g=i.find(".ui-dropdownchecklist-text");var d=i.find(".ui-icon");if(d!=null){k-=(d.outerWidth()+4);g.css({width:k+"px"})}k=l.outerWidth();var j=(m.maxDropHeight!=null)?parseInt(m.maxDropHeight):-1;var h=((j>0)&&(b.height>j))?j:b.height;var e=b.width<k?k:b.width;a(f).css({height:h+"px",width:e+"px"});f.find(".ui-dropdownchecklist-dropcontainer").css({height:h+"px"})},_init:function(){var c=this,d=this.options;if(a.ui.dropdownchecklist.gIDCounter==null){a.ui.dropdownchecklist.gIDCounter=1}c.blurringItem=null;var g=c.element;c.initialDisplay=g.css("display");g.css("display","none");c.initialMultiple=g.prop("multiple");c.isMultiple=c.initialMultiple;if(d.forceMultiple!=null){c.isMultiple=d.forceMultiple}g.prop("multiple",true);c.sourceSelect=g;var e=c._appendControl();c.controlWrapper=e;c.controlSelector=e.find(".ui-dropdownchecklist-selector");var f=c._appendDropContainer(e);c.dropWrapper=f;var b=c._appendItems();c._updateControlText(e,f,g);c._setSize(b);if(d.firstItemChecksAll){c._syncSelected(null)}if(d.bgiframe&&typeof c.dropWrapper.bgiframe=="function"){c.dropWrapper.bgiframe()}c.sourceSelect.change(function(i,h){if(h!="ddcl_internal"){c._sourceSelectChangeHandler(i)}})},_refreshOption:function(e,d,c){var b=e.parent();if(d){e.prop("disabled",true);e.removeClass("active");e.addClass("inactive");b.addClass("ui-state-disabled")}
40036else{e.prop("disabled",false);e.removeClass("inactive");e.addClass("active");b.removeClass("ui-state-disabled")}e.prop("checked",c)},_refreshGroup:function(c,b){if(b){c.addClass("ui-state-disabled")}else{c.removeClass("ui-state-disabled")}},close:function(){this._toggleDropContainer(false)},refresh:function(){var b=this,e=this.sourceSelect,d=this.dropWrapper;var c=d.find("input");var g=d.find(".ui-dropdownchecklist-group");var h=0;var f=0;e.children().each(function(i){var j=a(this);var l=j.prop("disabled");if(j.is("option")){var k=j.prop("selected");var n=a(c[f]);b._refreshOption(n,l,k);f+=1}else{if(j.is("optgroup")){var o=j.attr("label");if(o!=""){var m=a(g[h]);b._refreshGroup(m,l);h+=1}j.children("option").each(function(){var p=a(this);var r=(l||p.prop("disabled"));var q=p.prop("selected");var s=a(c[f]);b._refreshOption(s,r,q);f+=1})}}});b._syncSelected(null)},enable:function(){this.controlSelector.removeClass("ui-state-disabled");this.disabled=false},disable:function(){this.controlSelector.addClass("ui-state-disabled");this.disabled=true},destroy:function(){a.Widget.prototype.destroy.apply(this,arguments);this.sourceSelect.css("display",this.initialDisplay);this.sourceSelect.prop("multiple",this.initialMultiple);this.controlWrapper.unbind().remove();this.dropWrapper.remove()}});a.extend(a.ui.dropdownchecklist,{defaults:{width:null,maxDropHeight:null,firstItemChecksAll:false,closeRadioOnClick:false,minWidth:50,positionHow:"absolute",bgiframe:false,explicitClose:null}})})(jQuery);
40037/*!
40038 *jQuery UI Spinner 1.25
40039 *
40040 *Copyright (c) 2009-2010 Brant Burnett
40041 *Copyright (c) 2010 Johannes Geppert http://www.jgeppert.com
40042 *Dual licensed under the MIT or GPL Version 2 licenses.
40043 *
40044 *  Depends:
40045 *	jquery.ui.core.js
40046 *	jquery.ui.widget.js
40047 *	jquery.ui.mouse.js
40048 *	jquery.ui.position.js
40049 */
40050
40051(function(g,h){var i="ui-state-active",s="ui-state-hover",e="ui-state-disabled",k=g.ui.keyCode,m=k.UP,n=k.DOWN,r=k.RIGHT,f=k.LEFT,j=k.PAGE_UP,p=k.PAGE_DOWN,q=k.HOME,d=k.END,l=g.browser.msie,c=g.browser.mozilla?"DOMMouseScroll":"mousewheel",o=".uispinner",a=[m,n,r,f,j,p,q,d,k.BACKSPACE,k.DELETE,k.TAB],b;g.widget("ui.spinner",{options:{min:null,max:null,allowNull:false,group:"",point:".",prefix:"",suffix:"",places:null,defaultStep:1,largeStep:10,mouseWheel:true,increment:"slow",className:null,showOn:"always",width:16,upIconClass:"ui-icon-triangle-1-n",downIconClass:"ui-icon-triangle-1-s",format:function(w,v){var u=this,x=/(\d+)(\d{3})/,t=((isNaN(w)?0:Math.abs(w)).toFixed(v))+"";for(t=t.replace(".",u.point);x.test(t)&&u.group;t=t.replace(x,"$1"+u.group+"$2")){}return(w<0?"-":"")+u.prefix+t+u.suffix},parse:function(u){var t=this;if(t.group=="."){u=u.replace(".","")}if(t.point!="."){u=u.replace(t.point,".")}return parseFloat(u.replace(/[^0-9\-\.]/g,""))}},_create:function(){var u=this,t=u.element,v=t.attr("type");if(!t.is("input")||((v!="text")&&(v!="number"))){console.error("Invalid target for ui.spinner");return}u._procOptions(true);u._createButtons(t);if(!t.is(":enabled")){u.disable()}},_createButtons:function(E){function D(W){return W=="auto"?0:parseInt(W,10)}var H=this,x=H.options,v=x.className,N=x.width,y=x.showOn,C=g.support.boxModel,I=E.outerHeight(),R=H.oMargin=D(E.css("margin-right")),A=H.wrapper=E.css({width:(H.oWidth=(C?E.width():E.outerWidth()))-N,marginRight:R+N,textAlign:"right"}).after("<span class='ui-spinner ui-widget' style='top:0;left:0;'></span>").next(),L=H.btnContainer=g("<div class='ui-spinner-buttons'><div class='ui-spinner-up ui-spinner-button ui-state-default ui-corner-tr'><span class='ui-icon "+x.upIconClass+"'>&nbsp;</span></div><div class='ui-spinner-down ui-spinner-button ui-state-default ui-corner-br'><span class='ui-icon "+x.downIconClass+"'>&nbsp;</span></div></div>"),w,u,Q,V,F,B,G,P,J,O,z=E[0].dir=="rtl";if(v){A.addClass(v)}A.append(L.css({height:I,left:-N-R,top:(E.offset().top-A.offset().top)+"px"}));Q=H.buttons=L.find(".ui-spinner-button");Q.css({width:N-(C?Q.outerWidth()-Q.width():0),height:I/2-(C?Q.outerHeight()-Q.height():0)});w=Q[0];u=Q[1];V=Q.find(".ui-icon");V.css({marginLeft:(Q.innerWidth()-V.width())/2,marginTop:(Q.innerHeight()-V.height())/2});L.width(Q.outerWidth());if(y!="always"){L.css("opacity",0)}E.addClass("ui-spinner-input").attr({role:"textbox"});A.position({of:this.element,my:"left center",at:"right center",offset:"0 0",collision:"flip flip"});if(y=="hover"||y=="both"){Q.add(E).bind("mouseenter"+o,function(){T(function(){G=true;if(!H.focused||(y=="hover")){H.showButtons()}})}).bind("mouseleave"+o,function K(){T(function(){G=false;if(!H.focused||(y=="hover")){H.hideButtons()}})})}function U(){if(!x.disabled){var W=H.element[0],X=(this===w?1:-1);W.focus();W.select();g(this).addClass(i);O=true;H._startSpin(X)}return false}function S(){if(O){g(this).removeClass(i);H._stopSpin();O=false}return false}Q.hover(function(){H.buttons.removeClass(s);if(!x.disabled){g(this).addClass(s)}},function(){g(this).removeClass(s)}).mousedown(U).mouseup(S).mouseout(S);if(l){Q.dblclick(function(){if(!x.disabled){H._change();H._doSpin((this===w?1:-1)*x.step)}return false}).bind("selectstart",function(){return false})}E.bind("keydown"+o,function(aa){var X,Y,W,Z=aa.keyCode;if(aa.ctrl||aa.alt){return true}if(t(Z)){J=true}if(P){return false}switch(Z){case m:case j:X=1;Y=Z==j;break;case n:case p:X=-1;Y=Z==p;break;case r:case f:X=(Z==r)^z?1:-1;break;case q:W=H.options.min;if(W!==null){H._setValue(W)}return false;case d:W=H.options.max;W=H.options.max;if(W!==null){H._setValue(W)}return false}
40051if(X){if(!P&&!x.disabled){keyDir=X;g(X>0?w:u).addClass(i);P=true;H._startSpin(X,Y)}return false}}).bind("keyup"+o,function(W){if(W.ctrl||W.alt){return true}if(t(k)){J=false}switch(W.keyCode){case m:case r:case j:case n:case f:case p:Q.removeClass(i);H._stopSpin();P=false;return false}}).bind("change"+o,function(){H._change()}).bind("focus"+o,function(){function W(){H.element.select()}if(l){W()}else{setTimeout(W,0)}H.focused=true;b=H;if(!G&&(y=="focus"||y=="both")){H.showButtons()}}).bind("blur"+o,function(){H.focused=false;if(!G&&(y=="focus"||y=="both")){H.hideButtons()}});function t(X){for(var W=0;W<a.length;W++){if(a[W]==X){return true}}return false}function M(Z,W){if(J){return false}var Y=String.fromCharCode(W||Z),X=H.options;if((Y>="0")&&(Y<="9")||(Y=="-")){return false}if(((H.places>0)&&(Y==X.point))||(Y==X.group)){return false}return true}function T(X){if(F){if(X===B){return}clearTimeout(F)}function W(){F=0;X()}B=X;F=setTimeout(W,100)}},_procOptions:function(A){var B=this,y=B.element,C=B.options,w=C.min,x=C.max,v=C.step,t=C.places,u=-1,z;if(C.increment=="slow"){C.increment=[{count:1,mult:1,delay:250},{count:3,mult:1,delay:100},{count:0,mult:1,delay:50}]}else{if(C.increment=="fast"){C.increment=[{count:1,mult:1,delay:250},{count:19,mult:1,delay:100},{count:80,mult:1,delay:20},{count:100,mult:10,delay:20},{count:0,mult:100,delay:20}]}}if((w===null)&&((z=y.attr("min"))!==null)){w=parseFloat(z)}if((x===null)&&((z=y.attr("max"))!==null)){x=parseFloat(z)}if(!v&&((z=y.attr("step"))!==null)){if(z!="any"){v=parseFloat(z);C.largeStep*=v}}C.step=v=v||C.defaultStep;if((t===null)&&((z=v+"").indexOf(".")!=-1)){t=z.length-z.indexOf(".")-1}B.places=t;if((x!==null)&&(w!==null)){if(w>x){w=x}u=Math.max(Math.max(u,C.format(x,t,y).length),C.format(w,t,y).length)}if(A){B.inputMaxLength=y[0].maxLength}z=B.inputMaxLength;if(z>0){u=u>0?Math.min(z,u):z;z=Math.pow(10,u)-1;if((x===null)||(x>z)){x=z}z=-(z+1)/10+1;if((w===null)||(w<z)){w=z}}if(u>0){y.attr("maxlength",u)}C.min=w;C.max=x;B._change();y.unbind(c+o);if(C.mouseWheel){y.bind(c+o,B._mouseWheel)}},_mouseWheel:function(u){var t=g.data(this,"spinner");if(!t.options.disabled&&t.focused&&(b===t)){t._change();t._doSpin(((u.wheelDelta||-u.detail)>0?1:-1)*t.options.step);return false}},_setTimer:function(v,u,x){function w(){t._spin(u,x)}var t=this;t._stopSpin();t.timer=setInterval(w,v)},_stopSpin:function(){if(this.timer){clearInterval(this.timer);this.timer=0}},_startSpin:function(w,x){var u=this,v=u.options,t=v.increment;u._change();u._doSpin(w*(x?u.options.largeStep:u.options.step));if(t&&t.length>0){u.counter=0;u.incCounter=0;u._setTimer(t[0].delay,w,x)}},_spin:function(v,w){var u=this,t=u.options.increment,x=t[u.incCounter];u._doSpin(v*x.mult*(w?u.options.largeStep:u.options.step));u.counter++;if((u.counter>x.count)&&(u.incCounter<t.length-1)){u.counter=0;x=t[++u.incCounter];u._setTimer(x.delay,v,w)}},_doSpin:function(u){var t=this,v=t.curvalue;if(v===null){v=(u>0?t.options.min:t.options.max)||0}t._setValue(v+u)},_parseValue:function(){var t=this.element.val();return t?this.options.parse(t,this.element):null},_validate:function(w){var u=this.options,v=u.min,t=u.max;if((w===null)&&!u.allowNull){w=this.curvalue!==null?this.curvalue:v||t||0}if((t!==null)&&(w>t)){return t}else{if((v!==null)&&(w<v)){return v}else{return w}}},_change:function(){var u=this,w=u._parseValue(),v=u.options.min,t=u.options.max;if(!u.selfChange){if(isNaN(w)){w=u.curvalue}u._setValue(w,true)}},_setOption:function(t,u){g.Widget.prototype._setOption.call(this,t,u);this._procOptions()},increment:function(){this._doSpin(this.options.step)},decrement:function(){this._doSpin(-this.options.step)},showButtons:function(u){var t=this.btnContainer.stop();if(u){t.css("opacity",1)}else{t.fadeTo("fast",1)}},hideButtons:function(u){var t=this.btnContainer.stop();if(u){t.css("opacity",0)}else{t.fadeTo("fast",0)}this.buttons.removeClass(s)},_setValue:function(v,t){var u=this;u.curvalue=v=u._validate(v);u.element.val(v!==null?u.options.format(v,u.places,u.element):"");if(!t){u.selfChange=true;u.element.change();u.selfChange=false}},value:function(t){if(arguments.length){this._setValue(t);return this.element}return this.curvalue},enable:function(){this.buttons.removeClass(e);this.element[0].disabled=false;g.Widget.prototype.enable.call(this)},disable:function(){this.buttons.addClass(e).removeClass(s);this.element[0].disabled=true;g.Widget.prototype.disable.call(this)},destroy:function(t){this.wrapper.remove();this.element.unbind(o).css({width:this.oWidth,marginRight:this.oMargin});g.Widget.prototype.destroy.call(this)}})})(jQuery);
40052/**
40053 * This jQuery plugin displays pagination links inside the selected elements.
40054 * 
40055 * This plugin needs at least jQuery 1.4.2
40056 *
40057 * @author Gabriel Birke (birke *at* d-scribe *dot* de)
40058 * @version 2.2
40059 * @param {int} maxentries Number of entries to paginate
40060 * @param {Object} opts Several options (see README for documentation)
40061 * @return {Object} jQuery Object
40062 */
40063
40064 (function($){
40065	/**
40066	 * @class Class for calculating pagination values
40067	 */
40068	$.PaginationCalculator = function(maxentries, opts) {
40069		this.maxentries = maxentries;
40070		this.opts = opts;
40071	}
40072	
40073	$.extend($.PaginationCalculator.prototype, {
40074		/**
40075		 * Calculate the maximum number of pages
40076		 * @method
40077		 * @returns {Number}
40078		 */
40079		numPages:function() {
40080			return Math.ceil(this.maxentries/this.opts.items_per_page);
40081		},
40082		/**
40083		 * Calculate start and end point of pagination links depending on 
40084		 * current_page and num_display_entries.
40085		 * @returns {Array}
40086		 */
40087		getInterval:function(current_page)  {
40088			var ne_half = Math.floor(this.opts.num_display_entries/2);
40089			var np = this.numPages();
40090			var upper_limit = np - this.opts.num_display_entries;
40091			var start = current_page > ne_half ? Math.max( Math.min(current_page - ne_half, upper_limit), 0 ) : 0;
40092			var end = current_page > ne_half?Math.min(current_page+ne_half + (this.opts.num_display_entries % 2), np):Math.min(this.opts.num_display_entries, np);
40093			return {start:start, end:end};
40094		}
40095	});
40096	
40097	// Initialize jQuery object container for pagination renderers
40098	$.PaginationRenderers = {}
40099	
40100	/**
40101	 * @class Default renderer for rendering pagination links
40102	 */
40103	$.PaginationRenderers.defaultRenderer = function(maxentries, opts) {
40104		this.maxentries = maxentries;
40105		this.opts = opts;
40106		this.pc = new $.PaginationCalculator(maxentries, opts);
40107	}
40108	$.extend($.PaginationRenderers.defaultRenderer.prototype, {
40109		/**
40110		 * Helper function for generating a single link (or a span tag if it's the current page)
40111		 * @param {Number} page_id The page id for the new item
40112		 * @param {Number} current_page 
40113		 * @param {Object} appendopts Options for the new item: text and classes
40114		 * @returns {jQuery} jQuery object containing the link
40115		 */
40116		createLink:function(page_id, current_page, appendopts){
40117			var lnk, np = this.pc.numPages();
40118			page_id = page_id<0?0:(page_id<np?page_id:np-1); // Normalize page id to sane value
40119			appendopts = $.extend({text:page_id+1, classes:""}, appendopts||{});
40120			if(page_id == current_page){
40121				lnk = $("<span class='current'>" + appendopts.text + "</span>");
40122			}
40123			else
40124			{
40125				lnk = $("<a>" + appendopts.text + "</a>")
40126					.attr('href', this.opts.link_to.replace(/__id__/,page_id));
40127			}
40128			if(appendopts.classes){ lnk.addClass(appendopts.classes); }
40129			lnk.data('page_id', page_id);
40130			return lnk;
40131		},
40132		// Generate a range of numeric links 
40133		appendRange:function(container, current_page, start, end, opts) {
40134			var i;
40135			for(i=start; i<end; i++) {
40136				this.createLink(i, current_page, opts).appendTo(container);
40137			}
40138		},
40139		getLinks:function(current_page, eventHandler) {
40140			var begin, end,
40141				interval = this.pc.getInterval(current_page),
40142				np = this.pc.numPages(),
40143				fragment = $("<div class='pagination'></div>");
40144			
40145			// Generate "Previous"-Link
40146			if(this.opts.prev_text && (current_page > 0 || this.opts.prev_show_always)){
40147				fragment.append(this.createLink(current_page-1, current_page, {text:this.opts.prev_text, classes:"prev"}));
40148			}
40149			// Generate starting points
40150			if (interval.start > 0 && this.opts.num_edge_entries > 0)
40151			{
40152				end = Math.min(this.opts.num_edge_entries, interval.start);
40153				this.appendRange(fragment, current_page, 0, end, {classes:'sp'});
40154				if(this.opts.num_edge_entries < interval.start && this.opts.ellipse_text)
40155				{
40156					jQuery("<span>"+this.opts.ellipse_text+"</span>").appendTo(fragment);
40157				}
40158			}
40159			// Generate interval links
40160			this.appendRange(fragment, current_page, interval.start, interval.end);
40161			// Generate ending points
40162			if (interval.end < np && this.opts.num_edge_entries > 0)
40163			{
40164				if(np-this.opts.num_edge_entries > interval.end && this.opts.ellipse_text)
40165				{
40166					jQuery("<span>"+this.opts.ellipse_text+"</span>").appendTo(fragment);
40167				}
40168				begin = Math.max(np-this.opts.num_edge_entries, interval.end);
40169				this.appendRange(fragment, current_page, begin, np, {classes:'ep'});
40170				
40171			}
40172			// Generate "Next"-Link
40173			if(this.opts.next_text && (current_page < np-1 || this.opts.next_show_always)){
40174				fragment.append(this.createLink(current_page+1, current_page, {text:this.opts.next_text, classes:"next"}));
40175			}
40176			$('a', fragment).click(eventHandler);
40177			return fragment;
40178		}
40179	});
40180	
40181	// Extend jQuery
40182	$.fn.pagination = function(maxentries, opts){
40183		
40184		// Initialize options with default values
40185		opts = jQuery.extend({
40186			items_per_page:10,
40187			num_display_entries:11,
40188			current_page:0,
40189			num_edge_entries:0,
40190			link_to:"#",
40191			prev_text:"Prev",
40192			next_text:"Next",
40193			ellipse_text:"...",
40194			prev_show_always:true,
40195			next_show_always:true,
40196			renderer:"defaultRenderer",
40197			load_first_page:false,
40198			callback:function(){return false;}
40199		},opts||{});
40200		
40201		var containers = this,
40202			renderer, links, current_page;
40203		
40204		/**
40205		 * This is the event handling function for the pagination links.
40206         * NOTE: added evt.preventDefault() to the original code to prevent loading the link
40207		 * @param {int} page_id The new page number
40208		 */
40209		function paginationClickHandler(evt){
40210
40211            // the default is for browser to load the URL at the link
40212            // However even the link generated here such as "http://original_url#" will cause jquery history to
40213            // think there is a state change in IE8/9. This will cause the app to load state.
40214            evt.preventDefault();
40215
40216			var links, 
40217				new_current_page = $(evt.target).data('page_id'),
40218				continuePropagation = selectPage(new_current_page);
40219			if (!continuePropagation) {
40220				evt.stopPropagation();
40221			}
40222			return continuePropagation;
40223		}
40224		
40225		/**
40226		 * This is a utility function for the internal event handlers. 
40227		 * It sets the new current page on the pagination container objects, 
40228		 * generates a new HTMl fragment for the pagination links and calls
40229		 * the callback function.
40230		 */
40231		function selectPage(new_current_page) {
40232			// update the link display of a all containers
40233			containers.data('current_page', new_current_page);
40234			links = renderer.getLinks(new_current_page, paginationClickHandler);
40235			containers.empty();
40236			links.appendTo(containers);
40237			// call the callback and propagate the event if it does not return false
40238			var continuePropagation = opts.callback(new_current_page, containers);
40239			return continuePropagation;
40240		}
40241		
40242		// -----------------------------------
40243		// Initialize containers
40244		// -----------------------------------
40245		current_page = opts.current_page;
40246		containers.data('current_page', current_page);
40247		// Create a sane value for maxentries and items_per_page
40248		maxentries = (!maxentries || maxentries < 0)?1:maxentries;
40249		opts.items_per_page = (!opts.items_per_page || opts.items_per_page < 0)?1:opts.items_per_page;
40250		
40251		if(!$.PaginationRenderers[opts.renderer])
40252		{
40253			throw new ReferenceError("Pagination renderer '" + opts.renderer + "' was not found in jQuery.PaginationRenderers object.");
40254		}
40255		renderer = new $.PaginationRenderers[opts.renderer](maxentries, opts);
40256		
40257		// Attach control events to the DOM elements
40258		var pc = new $.PaginationCalculator(maxentries, opts);
40259		var np = pc.numPages();
40260		containers.bind('setPage', {numPages:np}, function(evt, page_id) { 
40261				if(page_id >= 0 && page_id < evt.data.numPages) {
40262					selectPage(page_id); return false;
40263				}
40264		});
40265		containers.bind('prevPage', function(evt){
40266				var current_page = $(this).data('current_page');
40267				if (current_page > 0) {
40268					selectPage(current_page - 1);
40269				}
40270				return false;
40271		});
40272		containers.bind('nextPage', {numPages:np}, function(evt){
40273				var current_page = $(this).data('current_page');
40274				if(current_page < evt.data.numPages - 1) {
40275					selectPage(current_page + 1);
40276				}
40277				return false;
40278		});
40279		
40280		// When all initialisation is done, draw the links
40281		links = renderer.getLinks(current_page, paginationClickHandler);
40282		containers.empty();
40283		links.appendTo(containers);
40284		// call callback function
40285		if(opts.load_first_page) {
40286			opts.callback(current_page, containers);
40287		}
40288	} // End of $.fn.pagination block
40289	
40290})(jQuery);
40291(function($){
40292  $.fn.clonePosition = function(element, options){
40293    var options = $.extend({
40294        setLeft : true,
40295        setTop  : true,
40296        setWidth: false,
40297        setHeight: false,
40298        offsetLeft: 0,
40299        offsetTop: 0
40300    }, (options || {}));
40301 
40302    var el = $(this); //what to position
40303    var src = $(element); //where to position
40304    var offsets = el.offset();
40305    var p = [src.offset().left, src.offset().top];
40306    var delta = [0, 0];
40307    //difference in size between element and source (to align centers, not top-left corners)
40308    var delta_size =[0, 0];
40309    var parent = null;
40310 
40311    if(el.css('position') == 'absolute') {
40312        d = el.offset();//relative to the parent
40313        delta[0]=d.left;
40314        delta[1]=d.top;
40315    }
40316    //use outer width to account for borders and padding
40317    delta_size[0] = (el.outerWidth()-src.outerWidth())/2;
40318    delta_size[1] = (el.outerHeight()-src.outerHeight())/2;
40319 
40320    if(parent == document.body){
40321        delta[0] -= document.body.offsetLeft;
40322        delta[1] -= document.body.offsetTop;
40323    }
40324 
40325    if(options.setLeft)   el.css('left',(p[0] - delta[0] + options.offsetLeft - delta_size[0]) + 'px');
40326    if(options.setTop)    el.css('top', (p[1] - delta[1] + options.offsetTop - delta_size[1]) + 'px');
40327    if(options.setWidth)  el.width(src.width() + 'px');
40328    if(options.setHeight) el.height(src.height() + 'px');
40329 
40330    return el;
40331  }
40332})(jQuery);
40333/*  Prototype JavaScript framework, version 1.6.1
40334 *  (c) 2005-2009 Sam Stephenson
40335 *
40336 *  Prototype is freely distributable under the terms of an MIT-style license.
40337 *  For details, see the Prototype web site: http://www.prototypejs.org/
40338 *
40339 *--------------------------------------------------------------------------*/
40340
40341
40342var Prototype = {
40343  Version: '1.6.1',
40344
40345  Browser: (function(){
40346    var ua = navigator.userAgent;
40347    var isOpera = Object.prototype.toString.call(window.opera) == '[object Opera]';
40348    return {
40349      IE:             !!window.attachEvent && !isOpera,
40350      Opera:          isOpera,
40351      WebKit:         ua.indexOf('AppleWebKit/') > -1,
40352      Gecko:          ua.indexOf('Gecko') > -1 && ua.indexOf('KHTML') === -1,
40353      MobileSafari:   /Apple.*Mobile.*Safari/.test(ua)
40354    }
40355  })(),
40356
40357  BrowserFeatures: {
40358    XPath: !!document.evaluate,
40359    SelectorsAPI: !!document.querySelector,
40360    ElementExtensions: (function() {
40361      var constructor = window.Element || window.HTMLElement;
40362      return !!(constructor && constructor.prototype);
40363    })(),
40364    SpecificElementExtensions: (function() {
40365      if (typeof window.HTMLDivElement !== 'undefined')
40366        return true;
40367
40368      var div = document.createElement('div');
40369      var form = document.createElement('form');
40370      var isSupported = false;
40371
40372      if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) {
40373        isSupported = true;
40374      }
40375
40376      div = form = null;
40377
40378      return isSupported;
40379    })()
40380  },
40381
40382  ScriptFragment: '<script[^>]*>([\\S\\s]*?)<\/script>',
40383  JSONFilter: /^\/\*-secure-([\s\S]*)\*\/\s*$/,
40384
40385  emptyFunction: function() { },
40386  K: function(x) { return x }
40387};
40388
40389if (Prototype.Browser.MobileSafari)
40390  Prototype.BrowserFeatures.SpecificElementExtensions = false;
40391
40392
40393var Abstract = { };
40394
40395
40396var Try = {
40397  these: function() {
40398    var returnValue;
40399
40400    for (var i = 0, length = arguments.length; i < length; i++) {
40401      var lambda = arguments[i];
40402      try {
40403        returnValue = lambda();
40404        break;
40405      } catch (e) { }
40406    }
40407
40408    return returnValue;
40409  }
40410};
40411
40412/* Based on Alex Arnell's inheritance implementation. */
40413
40414var Class = (function() {
40415  function subclass() {};
40416  function create() {
40417    var parent = null, properties = $A(arguments);
40418    if (Object.isFunction(properties[0]))
40419      parent = properties.shift();
40420
40421    function klass() {
40422      this.initialize.apply(this, arguments);
40423    }
40424
40425    Object.extend(klass, Class.Methods);
40426    klass.superclass = parent;
40427    klass.subclasses = [];
40428
40429    if (parent) {
40430      subclass.prototype = parent.prototype;
40431      klass.prototype = new subclass;
40432      parent.subclasses.push(klass);
40433    }
40434
40435    for (var i = 0; i < properties.length; i++)
40436      klass.addMethods(properties[i]);
40437
40438    if (!klass.prototype.initialize)
40439      klass.prototype.initialize = Prototype.emptyFunction;
40440
40441    klass.prototype.constructor = klass;
40442    return klass;
40443  }
40444
40445  function addMethods(source) {
40446    var ancestor   = this.superclass && this.superclass.prototype;
40447    var properties = Object.keys(source);
40448
40449    if (!Object.keys({ toString: true }).length) {
40450      if (source.toString != Object.prototype.toString)
40451        properties.push("toString");
40452      if (source.valueOf != Object.prototype.valueOf)
40453        properties.push("valueOf");
40454    }
40455
40456    for (var i = 0, length = properties.length; i < length; i++) {
40457      var property = properties[i], value = source[property];
40458      if (ancestor && Object.isFunction(value) &&
40459          value.argumentNames().first() == "$super") {
40460        var method = value;
40461        value = (function(m) {
40462          return function() { return ancestor[m].apply(this, arguments); };
40463        })(property).wrap(method);
40464
40465        value.valueOf = method.valueOf.bind(method);
40466        value.toString = method.toString.bind(method);
40467      }
40468      this.prototype[property] = value;
40469    }
40470
40471    return this;
40472  }
40473
40474  return {
40475    create: create,
40476    Methods: {
40477      addMethods: addMethods
40478    }
40479  };
40480})();
40481(function() {
40482
40483  var _toString = Object.prototype.toString;
40484
40485  function extend(destination, source) {
40486    for (var property in source)
40487      destination[property] = source[property];
40488    return destination;
40489  }
40490
40491  function inspect(object) {
40492    try {
40493      if (isUndefined(object)) return 'undefined';
40494      if (object === null) return 'null';
40495      return object.inspect ? object.inspect() : String(object);
40496    } catch (e) {
40497      if (e instanceof RangeError) return '...';
40498      throw e;
40499    }
40500  }
40501
40502  function toJSON(object) {
40503    var type = typeof object;
40504    switch (type) {
40505      case 'undefined':
40506      case 'function':
40507      case 'unknown': return;
40508      case 'boolean': return object.toString();
40509    }
40510
40511    if (object === null) return 'null';
40512    if (object.toJSON) return object.toJSON();
40513    if (isElement(object)) return;
40514
40515    var results = [];
40516    for (var property in object) {
40517      var value = toJSON(object[property]);
40518      if (!isUndefined(value))
40519        results.push(property.toJSON() + ': ' + value);
40520    }
40521
40522    return '{' + results.join(', ') + '}';
40523  }
40524
40525  function toQueryString(object) {
40526    return $H(object).toQueryString();
40527  }
40528
40529  function toHTML(object) {
40530    return object && object.toHTML ? object.toHTML() : String.interpret(object);
40531  }
40532
40533  function keys(object) {
40534    var results = [];
40535    for (var property in object)
40536      results.push(property);
40537    return results;
40538  }
40539
40540  function values(object) {
40541    var results = [];
40542    for (var property in object)
40543      results.push(object[property]);
40544    return results;
40545  }
40546
40547  function clone(object) {
40548    return extend({ }, object);
40549  }
40550
40551  function isElement(object) {
40552    return !!(object && object.nodeType == 1);
40553  }
40554
40555  function isArray(object) {
40556    return _toString.call(object) == "[object Array]";
40557  }
40558
40559
40560  function isHash(object) {
40561    return object instanceof Hash;
40562  }
40563
40564  function isFunction(object) {
40565    return typeof object === "function";
40566  }
40567
40568  function isString(object) {
40569    return _toString.call(object) == "[object String]";
40570  }
40571
40572  function isNumber(object) {
40573    return _toString.call(object) == "[object Number]";
40574  }
40575
40576  function isUndefined(object) {
40577    return typeof object === "undefined";
40578  }
40579
40580  extend(Object, {
40581    extend:        extend,
40582    inspect:       inspect,
40583    toJSON:        toJSON,
40584    toQueryString: toQueryString,
40585    toHTML:        toHTML,
40586    keys:          keys,
40587    values:        values,
40588    clone:         clone,
40589    isElement:     isElement,
40590    isArray:       isArray,
40591    isHash:        isHash,
40592    isFunction:    isFunction,
40593    isString:      isString,
40594    isNumber:      isNumber,
40595    isUndefined:   isUndefined
40596  });
40597})();
40598Object.extend(Function.prototype, (function() {
40599  var slice = Array.prototype.slice;
40600
40601  function update(array, args) {
40602    var arrayLength = array.length, length = args.length;
40603    while (length--) array[arrayLength + length] = args[length];
40604    return array;
40605  }
40606
40607  function merge(array, args) {
40608    array = slice.call(array, 0);
40609    return update(array, args);
40610  }
40611
40612  function argumentNames() {
40613    var names = this.toString().match(/^[\s\(]*function[^(]*\(([^)]*)\)/)[1]
40614      .replace(/\/\/.*?[\r\n]|\/\*(?:.|[\r\n])*?\*\//g, '')
40615      .replace(/\s+/g, '').split(',');
40616    return names.length == 1 && !names[0] ? [] : names;
40617  }
40618
40619  function bind(context) {
40620    if (arguments.length < 2 && Object.isUndefined(arguments[0])) return this;
40621    var __method = this, args = slice.call(arguments, 1);
40622    return function() {
40623      var a = merge(args, arguments);
40624      return __method.apply(context, a);
40625    }
40626  }
40627
40628  function bindAsEventListener(context) {
40629    var __method = this, args = slice.call(arguments, 1);
40630    return function(event) {
40631      var a = update([event || window.event], args);
40632      return __method.apply(context, a);
40633    }
40634  }
40635
40636  function curry() {
40637    if (!arguments.length) return this;
40638    var __method = this, args = slice.call(arguments, 0);
40639    return function() {
40640      var a = merge(args, arguments);
40641      return __method.apply(this, a);
40642    }
40643  }
40644
40645  function delay(timeout) {
40646    var __method = this, args = slice.call(arguments, 1);
40647    timeout = timeout * 1000
40648    return window.setTimeout(function() {
40649      return __method.apply(__method, args);
40650    }, timeout);
40651  }
40652
40653  function defer() {
40654    var args = update([0.01], arguments);
40655    return this.delay.apply(this, args);
40656  }
40657
40658  function wrap(wrapper) {
40659    var __method = this;
40660    return function() {
40661      var a = update([__method.bind(this)], arguments);
40662      return wrapper.apply(this, a);
40663    }
40664  }
40665
40666  function methodize() {
40667    if (this._methodized) return this._methodized;
40668    var __method = this;
40669    return this._methodized = function() {
40670      var a = update([this], arguments);
40671      return __method.apply(null, a);
40672    };
40673  }
40674
40675  return {
40676    argumentNames:       argumentNames,
40677    bind:                bind,
40678    bindAsEventListener: bindAsEventListener,
40679    curry:               curry,
40680    delay:               delay,
40681    defer:               defer,
40682    wrap:                wrap,
40683    methodize:           methodize
40684  }
40685})());
40686
40687
40688Date.prototype.toJSON = function() {
40689  return '"' + this.getUTCFullYear() + '-' +
40690    (this.getUTCMonth() + 1).toPaddedString(2) + '-' +
40691    this.getUTCDate().toPaddedString(2) + 'T' +
40692    this.getUTCHours().toPaddedString(2) + ':' +
40693    this.getUTCMinutes().toPaddedString(2) + ':' +
40694    this.getUTCSeconds().toPaddedString(2) + 'Z"';
40695};
40696
40697
40698RegExp.prototype.match = RegExp.prototype.test;
40699
40700RegExp.escape = function(str) {
40701  return String(str).replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1');
40702};
40703var PeriodicalExecuter = Class.create({
40704  initialize: function(callback, frequency) {
40705    this.callback = callback;
40706    this.frequency = frequency;
40707    this.currentlyExecuting = false;
40708
40709    this.registerCallback();
40710  },
40711
40712  registerCallback: function() {
40713    this.timer = setInterval(this.onTimerEvent.bind(this), this.frequency * 1000);
40714  },
40715
40716  execute: function() {
40717    this.callback(this);
40718  },
40719
40720  stop: function() {
40721    if (!this.timer) return;
40722    clearInterval(this.timer);
40723    this.timer = null;
40724  },
40725
40726  onTimerEvent: function() {
40727    if (!this.currentlyExecuting) {
40728      try {
40729        this.currentlyExecuting = true;
40730        this.execute();
40731        this.currentlyExecuting = false;
40732      } catch(e) {
40733        this.currentlyExecuting = false;
40734        throw e;
40735      }
40736    }
40737  }
40738});
40739Object.extend(String, {
40740  interpret: function(value) {
40741    return value == null ? '' : String(value);
40742  },
40743  specialChar: {
40744    '\b': '\\b',
40745    '\t': '\\t',
40746    '\n': '\\n',
40747    '\f': '\\f',
40748    '\r': '\\r',
40749    '\\': '\\\\'
40750  }
40751});
40752
40753Object.extend(String.prototype, (function() {
40754
40755  function prepareReplacement(replacement) {
40756    if (Object.isFunction(replacement)) return replacement;
40757    var template = new Template(replacement);
40758    return function(match) { return template.evaluate(match) };
40759  }
40760
40761  function gsub(pattern, replacement) {
40762    var result = '', source = this, match;
40763    replacement = prepareReplacement(replacement);
40764
40765    if (Object.isString(pattern))
40766      pattern = RegExp.escape(pattern);
40767
40768    if (!(pattern.length || pattern.source)) {
40769      replacement = replacement('');
40770      return replacement + source.split('').join(replacement) + replacement;
40771    }
40772
40773    while (source.length > 0) {
40774      if (match = source.match(pattern)) {
40775        result += source.slice(0, match.index);
40776        result += String.interpret(replacement(match));
40777        source  = source.slice(match.index + match[0].length);
40778      } else {
40779        result += source, source = '';
40780      }
40781    }
40782    return result;
40783  }
40784
40785  function sub(pattern, replacement, count) {
40786    replacement = prepareReplacement(replacement);
40787    count = Object.isUndefined(count) ? 1 : count;
40788
40789    return this.gsub(pattern, function(match) {
40790      if (--count < 0) return match[0];
40791      return replacement(match);
40792    });
40793  }
40794
40795  function scan(pattern, iterator) {
40796    this.gsub(pattern, iterator);
40797    return String(this);
40798  }
40799
40800  function truncate(length, truncation) {
40801    length = length || 30;
40802    truncation = Object.isUndefined(truncation) ? '...' : truncation;
40803    return this.length > length ?
40804      this.slice(0, length - truncation.length) + truncation : String(this);
40805  }
40806
40807  function strip() {
40808    return this.replace(/^\s+/, '').replace(/\s+$/, '');
40809  }
40810
40811  function stripTags() {
40812    return this.replace(/<\w+(\s+("[^"]*"|'[^']*'|[^>])+)?>|<\/\w+>/gi, '');
40813  }
40814
40815  function stripScripts() {
40816    return this.replace(new RegExp(Prototype.ScriptFragment, 'img'), '');
40817  }
40818
40819  function extractScripts() {
40820    var matchAll = new RegExp(Prototype.ScriptFragment, 'img');
40821    var matchOne = new RegExp(Prototype.ScriptFragment, 'im');
40822    return (this.match(matchAll) || []).map(function(scriptTag) {
40823      return (scriptTag.match(matchOne) || ['', ''])[1];
40824    });
40825  }
40826
40827  function evalScripts() {
40828    return this.extractScripts().map(function(script) { return eval(script) });
40829  }
40830
40831  function escapeHTML() {
40832    return this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
40833  }
40834
40835  function unescapeHTML() {
40836    return this.stripTags().replace(/&lt;/g,'<').replace(/&gt;/g,'>').replace(/&amp;/g,'&');
40837  }
40838
40839
40840  function toQueryParams(separator) {
40841    var match = this.strip().match(/([^?#]*)(#.*)?$/);
40842    if (!match) return { };
40843
40844    return match[1].split(separator || '&').inject({ }, function(hash, pair) {
40845      if ((pair = pair.split('='))[0]) {
40846        var key = decodeURIComponent(pair.shift());
40847        var value = pair.length > 1 ? pair.join('=') : pair[0];
40848        if (value != undefined) value = decodeURIComponent(value);
40849
40850        if (key in hash) {
40851          if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
40852          hash[key].push(value);
40853        }
40854        else hash[key] = value;
40855      }
40856      return hash;
40857    });
40858  }
40859
40860  function toArray() {
40861    return this.split('');
40862  }
40863
40864  function succ() {
40865    return this.slice(0, this.length - 1) +
40866      String.fromCharCode(this.charCodeAt(this.length - 1) + 1);
40867  }
40868
40869  function times(count) {
40870    return count < 1 ? '' : new Array(count + 1).join(this);
40871  }
40872
40873  function camelize() {
40874    var parts = this.split('-'), len = parts.length;
40875    if (len == 1) return parts[0];
40876
40877    var camelized = this.charAt(0) == '-'
40878      ? parts[0].charAt(0).toUpperCase() + parts[0].substring(1)
40879      : parts[0];
40880
40881    for (var i = 1; i < len; i++)
40882      camelized += parts[i].charAt(0).toUpperCase() + parts[i].substring(1);
40883
40884    return camelized;
40885  }
40886
40887  function capitalize() {
40888    return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase();
40889  }
40890
40891  function underscore() {
40892    return this.replace(/::/g, '/')
40893               .replace(/([A-Z]+)([A-Z][a-z])/g, '$1_$2')
40894               .replace(/([a-z\d])([A-Z])/g, '$1_$2')
40895               .replace(/-/g, '_')
40896               .toLowerCase();
40897  }
40898
40899  function dasherize() {
40900    return this.replace(/_/g, '-');
40901  }
40902
40903  function inspect(useDoubleQuotes) {
40904    var escapedString = this.replace(/[\x00-\x1f\\]/g, function(ch
40904aracter) {
40905      if (character in String.specialChar) {
40906        return String.specialChar[character];
40907      }
40908      return '\\u00' + character.charCodeAt().toPaddedString(2, 16);
40909    });
40910    if (useDoubleQuotes) return '"' + escapedString.replace(/"/g, '\\"') + '"';
40911    return "'" + escapedString.replace(/'/g, '\\\'') + "'";
40912  }
40913
40914  function toJSON() {
40915    return this.inspect(true);
40916  }
40917
40918  function unfilterJSON(filter) {
40919    return this.replace(filter || Prototype.JSONFilter, '$1');
40920  }
40921
40922  function isJSON() {
40923    var str = this;
40924    if (str.blank()) return false;
40925    str = this.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, '');
40926    return (/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(str);
40927  }
40928
40929  function evalJSON(sanitize) {
40930    var json = this.unfilterJSON();
40931    try {
40932      if (!sanitize || json.isJSON()) return eval('(' + json + ')');
40933    } catch (e) { }
40934    throw new SyntaxError('Badly formed JSON string: ' + this.inspect());
40935  }
40936
40937  function include(pattern) {
40938    return this.indexOf(pattern) > -1;
40939  }
40940
40941  function startsWith(pattern) {
40942    return this.indexOf(pattern) === 0;
40943  }
40944
40945  function endsWith(pattern) {
40946    var d = this.length - pattern.length;
40947    return d >= 0 && this.lastIndexOf(pattern) === d;
40948  }
40949
40950  function empty() {
40951    return this == '';
40952  }
40953
40954  function blank() {
40955    return /^\s*$/.test(this);
40956  }
40957
40958  function interpolate(object, pattern) {
40959    return new Template(this, pattern).evaluate(object);
40960  }
40961
40962  return {
40963    gsub:           gsub,
40964    sub:            sub,
40965    scan:           scan,
40966    truncate:       truncate,
40967    strip:          String.prototype.trim ? String.prototype.trim : strip,
40968    stripTags:      stripTags,
40969    stripScripts:   stripScripts,
40970    extractScripts: extractScripts,
40971    evalScripts:    evalScripts,
40972    escapeHTML:     escapeHTML,
40973    unescapeHTML:   unescapeHTML,
40974    toQueryParams:  toQueryParams,
40975    parseQuery:     toQueryParams,
40976    toArray:        toArray,
40977    succ:           succ,
40978    times:          times,
40979    camelize:       camelize,
40980    capitalize:     capitalize,
40981    underscore:     underscore,
40982    dasherize:      dasherize,
40983    inspect:        inspect,
40984    toJSON:         toJSON,
40985    unfilterJSON:   unfilterJSON,
40986    isJSON:         isJSON,
40987    evalJSON:       evalJSON,
40988    include:        include,
40989    startsWith:     startsWith,
40990    endsWith:       endsWith,
40991    empty:          empty,
40992    blank:          blank,
40993    interpolate:    interpolate
40994  };
40995})());
40996
40997var Template = Class.create({
40998  initialize: function(template, pattern) {
40999    this.template = template.toString();
41000    this.pattern = pattern || Template.Pattern;
41001  },
41002
41003  evaluate: function(object) {
41004    if (object && Object.isFunction(object.toTemplateReplacements))
41005      object = object.toTemplateReplacements();
41006
41007    return this.template.gsub(this.pattern, function(match) {
41008      if (object == null) return (match[1] + '');
41009
41010      var before = match[1] || '';
41011      if (before == '\\') return match[2];
41012
41013      var ctx = object, expr = match[3];
41014      var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/;
41015      match = pattern.exec(expr);
41016      if (match == null) return before;
41017
41018      while (match != null) {
41019        var comp = match[1].startsWith('[') ? match[2].replace(/\\\\]/g, ']') : match[1];
41020        ctx = ctx[comp];
41021        if (null == ctx || '' == match[3]) break;
41022        expr = expr.substring('[' == match[3] ? match[1].length : match[0].length);
41023        match = pattern.exec(expr);
41024      }
41025
41026      return before + String.interpret(ctx);
41027    });
41028  }
41029});
41030Template.Pattern = /(^|.|\r|\n)(#\{(.*?)\})/;
41031
41032var $break = { };
41033
41034var Enumerable = (function() {
41035  function each(iterator, context) {
41036    var index = 0;
41037    try {
41038      this._each(function(value) {
41039        iterator.call(context, value, index++);
41040      });
41041    } catch (e) {
41042      if (e != $break) throw e;
41043    }
41044    return this;
41045  }
41046
41047  function eachSlice(number, iterator, context) {
41048    var index = -number, slices = [], array = this.toArray();
41049    if (number < 1) return array;
41050    while ((index += number) < array.length)
41051      slices.push(array.slice(index, index+number));
41052    return slices.collect(iterator, context);
41053  }
41054
41055  function all(iterator, context) {
41056    iterator = iterator || Prototype.K;
41057    var result = true;
41058    this.each(function(value, index) {
41059      result = result && !!iterator.call(context, value, index);
41060      if (!result) throw $break;
41061    });
41062    return result;
41063  }
41064
41065  function any(iterator, context) {
41066    iterator = iterator || Prototype.K;
41067    var result = false;
41068    this.each(function(value, index) {
41069      if (result = !!iterator.call(context, value, index))
41070        throw $break;
41071    });
41072    return result;
41073  }
41074
41075  function collect(iterator, context) {
41076    iterator = iterator || Prototype.K;
41077    var results = [];
41078    this.each(function(value, index) {
41079      results.push(iterator.call(context, value, index));
41080    });
41081    return results;
41082  }
41083
41084  function detect(iterator, context) {
41085    var result;
41086    this.each(function(value, index) {
41087      if (iterator.call(context, value, index)) {
41088        result = value;
41089        throw $break;
41090      }
41091    });
41092    return result;
41093  }
41094
41095  function findAll(iterator, context) {
41096    var results = [];
41097    this.each(function(value, index) {
41098      if (iterator.call(context, value, index))
41099        results.push(value);
41100    });
41101    return results;
41102  }
41103
41104  function grep(filter, iterator, context) {
41105    iterator = iterator || Prototype.K;
41106    var results = [];
41107
41108    if (Object.isString(filter))
41109      filter = new RegExp(RegExp.escape(filter));
41110
41111    this.each(function(value, index) {
41112      if (filter.match(value))
41113        results.push(iterator.call(context, value, index));
41114    });
41115    return results;
41116  }
41117
41118  function include(object) {
41119    if (Object.isFunction(this.indexOf))
41120      if (this.indexOf(object) != -1) return true;
41121
41122    var found = false;
41123    this.each(function(value) {
41124      if (value == object) {
41125        found = true;
41126        throw $break;
41127      }
41128    });
41129    return found;
41130  }
41131
41132  function inGroupsOf(number, fillWith) {
41133    fillWith = Object.isUndefined(fillWith) ? null : fillWith;
41134    return this.eachSlice(number, function(slice) {
41135      while(slice.length < number) slice.push(fillWith);
41136      return slice;
41137    });
41138  }
41139
41140  function inject(memo, iterator, context) {
41141    this.each(function(value, index) {
41142      memo = iterator.call(context, memo, value, index);
41143    });
41144    return memo;
41145  }
41146
41147  function invoke(method) {
41148    var args = $A(arguments).slice(1);
41149    return this.map(function(value) {
41150      return value[method].apply(value, args);
41151    });
41152  }
41153
41154  function max(iterator, context) {
41155    iterator = iterator || Prototype.K;
41156    var result;
41157    this.each(function(value, index) {
41158      value = iterator.call(context, value, index);
41159      if (result == null || value >= result)
41160        result = value;
41161    });
41162    return result;
41163  }
41164
41165  function min(iterator, context) {
41166    iterator = iterator || Prototype.K;
41167    var result;
41168    this.each(function(value, index) {
41169      value = iterator.call(context, value, index);
41170      if (result == null || value < result)
41171        result = value;
41172    });
41173    return result;
41174  }
41175
41176  function partition(iterator, context) {
41177    iterator = iterator || Prototype.K;
41178    var trues = [], falses = [];
41179    this.each(function(value, index) {
41180      (iterator.call(context, value, index) ?
41181        trues : falses).push(value);
41182    });
41183    return [trues, falses];
41184  }
41185
41186  function pluck(property) {
41187    var results = [];
41188    this.each(function(value) {
41189      results.push(value[property]);
41190    });
41191    return results;
41192  }
41193
41194  function reject(iterator, context) {
41195    var results = [];
41196    this.each(function(value, index) {
41197      if (!iterator.call(context, value, index))
41198        results.push(value);
41199    });
41200    return results;
41201  }
41202
41203  function sortBy(iterator, context) {
41204    return this.map(function(value, index) {
41205      return {
41206        value: value,
41207        criteria: iterator.call(context, value, index)
41208      };
41209    }).sort(function(left, right) {
41210      var a = left.criteria, b = right.criteria;
41211      return a < b ? -1 : a > b ? 1 : 0;
41212    }).pluck('value');
41213  }
41214
41215  function toArray() {
41216    return this.map();
41217  }
41218
41219  function zip() {
41220    var iterator = Prototype.K, args = $A(arguments);
41221    if (Object.isFunction(args.last()))
41222      iterator = args.pop();
41223
41224    var collections = [this].concat(args).map($A);
41225    return this.map(function(value, index) {
41226      return iterator(collections.pluck(index));
41227    });
41228  }
41229
41230  function size() {
41231    return this.toArray().length;
41232  }
41233
41234  function inspect() {
41235    return '#<Enumerable:' + this.toArray().inspect() + '>';
41236  }
41237
41238
41239
41240
41241
41242
41243
41244
41245
41246  return {
41247    each:       each,
41248    eachSlice:  eachSlice,
41249    all:        all,
41250    every:      all,
41251    any:        any,
41252    some:       any,
41253    collect:    collect,
41254    map:        collect,
41255    detect:     detect,
41256    findAll:    findAll,
41257    select:     findAll,
41258    filter:     findAll,
41259    grep:       grep,
41260    include:    include,
41261    member:     include,
41262    inGroupsOf: inGroupsOf,
41263    inject:     inject,
41264    invoke:     invoke,
41265    max:        max,
41266    min:        min,
41267    partition:  partition,
41268    pluck:      pluck,
41269    reject:     reject,
41270    sortBy:     sortBy,
41271    toArray:    toArray,
41272    entries:    toArray,
41273    zip:        zip,
41274    size:       size,
41275    inspect:    inspect,
41276    find:       detect
41277  };
41278})();
41279function $A(iterable) {
41280  if (!iterable) return [];
41281  if ('toArray' in Object(iterable)) return iterable.toArray();
41282  var length = iterable.length || 0, results = new Array(length);
41283  while (length--) results[length] = iterable[length];
41284  return results;
41285}
41286
41287function $w(string) {
41288  if (!Object.isString(string)) return [];
41289  string = string.strip();
41290  return string ? string.split(/\s+/) : [];
41291}
41292
41293Array.from = $A;
41294
41295
41296(function() {
41297  var arrayProto = Array.prototype,
41298      slice = arrayProto.slice,
41299      _each = arrayProto.forEach; // use native browser JS 1.6 implementation if available
41300
41301  function each(iterator) {
41302    for (var i = 0, length = this.length; i < length; i++)
41303      iterator(this[i]);
41304  }
41305  if (!_each) _each = each;
41306
41307  function clear() {
41308    this.length = 0;
41309    return this;
41310  }
41311
41312  function first() {
41313    return this[0];
41314  }
41315
41316  function last() {
41317    return this[this.length - 1];
41318  }
41319
41320  function compact() {
41321    return this.select(function(value) {
41322      return value != null;
41323    });
41324  }
41325
41326  function flatten() {
41327    return this.inject([], function(array, value) {
41328      if (Object.isArray(value))
41329        return array.concat(value.flatten());
41330      array.push(value);
41331      return array;
41332    });
41333  }
41334
41335  function without() {
41336    var values = slice.call(arguments, 0);
41337    return this.select(function(value) {
41338      return !values.include(value);
41339    });
41340  }
41341
41342  function reverse(inline) {
41343    return (inline !== false ? this : this.toArray())._reverse();
41344  }
41345
41346  function uniq(sorted) {
41347    return this.inject([], function(array, value, index) {
41348      if (0 == index || (sorted ? array.last() != value : !array.include(value)))
41349        array.push(value);
41350      return array;
41351    });
41352  }
41353
41354  function intersect(array) {
41355    return this.uniq().findAll(function(item) {
41356      return array.detect(function(value) { return item === value });
41357    });
41358  }
41359
41360
41361  function clone() {
41362    return slice.call(this, 0);
41363  }
41364
41365  function size() {
41366    return this.length;
41367  }
41368
41369  function inspect() {
41370    return '[' + this.map(Object.inspect).join(', ') + ']';
41371  }
41372
41373  function toJSON() {
41374    var results = [];
41375    this.each(function(object) {
41376      var value = Object.toJSON(object);
41377      if (!Object.isUndefined(value)) results.push(value);
41378    });
41379    return '[' + results.join(', ') + ']';
41380  }
41381
41382  function indexOf(item, i) {
41383    i || (i = 0);
41384    var length = this.length;
41385    if (i < 0) i = length + i;
41386    for (; i < length; i++)
41387      if (this[i] === item) return i;
41388    return -1;
41389  }
41390
41391  function lastIndexOf(item, i) {
41392    i = isNaN(i) ? this.length : (i < 0 ? this.length + i : i) + 1;
41393    var n = this.slice(0, i).reverse().indexOf(item);
41394    return (n < 0) ? n : i - n - 1;
41395  }
41396
41397  function concat() {
41398    var array = slice.call(this, 0), item;
41399    for (var i = 0, length = arguments.length; i < length; i++) {
41400      item = arguments[i];
41401      if (Object.isArray(item) && !('callee' in item)) {
41402        for (var j = 0, arrayLength = item.length; j < arrayLength; j++)
41403          array.push(item[j]);
41404      } else {
41405        array.push(item);
41406      }
41407    }
41408    return array;
41409  }
41410
41411  Object.extend(arrayProto, Enumerable);
41412
41413  if (!arrayProto._reverse)
41414    arrayProto._reverse = arrayProto.reverse;
41415
41416  Object.extend(arrayProto, {
41417    _each:     _each,
41418    clear:     clear,
41419    first:     first,
41420    last:      last,
41421    compact:   compact,
41422    flatten:   flatten,
41423    without:   without,
41424    reverse:   reverse,
41425    uniq:      uniq,
41426    intersect: intersect,
41427    clone:     clone,
41428    toArray:   clone,
41429    size:      size,
41430    inspect:   inspect,
41431    toJSON:    toJSON
41432  });
41433
41434  var CONCAT_ARGUMENTS_BUGGY = (function() {
41435    return [].concat(arguments)[0][0] !== 1;
41436  })(1,2)
41437
41438  if (CONCAT_ARGUMENTS_BUGGY) arrayProto.concat = concat;
41439
41440  if (!arrayProto.indexOf) arrayProto.indexOf = indexOf;
41441  if (!arrayProto.lastIndexOf) arrayProto.lastIndexOf = lastIndexOf;
41442})();
41443function $H(object) {
41444  return new Hash(object);
41445};
41446
41447var Hash = Class.create(Enumerable, (function() {
41448  function initialize(object) {
41449    this._object = Object.isHash(object) ? object.toObject() : Object.clone(object);
41450  }
41451
41452  function _each(iterator) {
41453    for (var key in this._object) {
41454      var value = this._object[key], pair = [key, value];
41455      pair.key = key;
41456      pair.value = value;
41457      iterator(pair);
41458    }
41459  }
41460
41461  function set(key, value) {
41462    return this._object[key] = value;
41463  }
41464
41465  function get(key) {
41466    if (this._object[key] !== Object.prototype[key])
41467      return this._object[key];
41468  }
41469
41470  function unset(key) {
41471    var value = this._object[key];
41472    delete this._object[key];
41473    return value;
41474  }
41475
41476  function toObject() {
41477    return Object.clone(this._object);
41478  }
41479
41480  function keys() {
41481    return this.pluck('key');
41482  }
41483
41484  function values() {
41485    return this.pluck('value');
41486  }
41487
41488  function index(value) {
41489    var match = this.detect(function(pair) {
41490      return pair.value === value;
41491    });
41492    return match && match.key;
41493  }
41494
41495  function merge(object) {
41496    return this.clone().update(object);
41497  }
41498
41499  function update(object) {
41500    return new Hash(object).inject(this, function(result, pair) {
41501      result.set(pair.key, pair.value);
41502      return result;
41503    });
41504  }
41505
41506  function toQueryPair(key, value) {
41507    if (Object.isUndefined(value)) return key;
41508    return key + '=' + encodeURIComponent(String.interpret(value));
41509  }
41510
41511  function toQueryString() {
41512    return this.inject([], function(results, pair) {
41513      var key = encodeURIComponent(pair.key), values = pair.value;
41514
41515      if (values && typeof values == 'object') {
41516        if (Object.isArray(values))
41517          return results.concat(values.map(toQueryPair.curry(key)));
41518      } else results.push(toQueryPair(key, values));
41519      return results;
41520    }).join('&');
41521  }
41522
41523  function inspect() {
41524    return '#<Hash:{' + this.map(function(pair) {
41525      return pair.map(Object.inspect).join(': ');
41526    }).join(', ') + '}>';
41527  }
41528
41529  function toJSON() {
41530    return Object.toJSON(this.toObject());
41531  }
41532
41533  function clone() {
41534    return new Hash(this);
41535  }
41536
41537  return {
41538    initialize:             initialize,
41539    _each:                  _each,
41540    set:                    set,
41541    get:                    get,
41542    unset:                  unset,
41543    toObject:               toObject,
41544    toTemplateReplacements: toObject,
41545    keys:                   keys,
41546    values:                 values,
41547    index:                  index,
41548    merge:                  merge,
41549    update:                 update,
41550    toQueryString:          toQueryString,
41551    inspect:                inspect,
41552    toJSON:                 toJSON,
41553    clone:                  clone
41554  };
41555})());
41556
41557Hash.from = $H;
41558Object.extend(Number.prototype, (function() {
41559  function toColorPart() {
41560    return this.toPaddedString(2, 16);
41561  }
41562
41563  function succ() {
41564    return this + 1;
41565  }
41566
41567  function times(iterator, context) {
41568    $R(0, this, true).each(iterator, context);
41569    return this;
41570  }
41571
41572  function toPaddedString(length, radix) {
41573    var string = this.toString(radix || 10);
41574    return '0'.times(length - string.length) + string;
41575  }
41576
41577  function toJSON() {
41578    return isFinite(this) ? this.toString() : 'null';
41579  }
41580
41581  function abs() {
41582    return Math.abs(this);
41583  }
41584
41585  function round() {
41586    return Math.round(this);
41587  }
41588
41589  function ceil() {
41590    return Math.ceil(this);
41591  }
41592
41593  function floor() {
41594    return Math.floor(this);
41595  }
41596
41597  return {
41598    toColorPart:    toColorPart,
41599    succ:           succ,
41600    times:          times,
41601    toPaddedString: toPaddedString,
41602    toJSON:         toJSON,
41603    abs:            abs,
41604    round:          round,
41605    ceil:           ceil,
41606    floor:          floor
41607  };
41608})());
41609
41610function $R(start, end, exclusive) {
41611  return new ObjectRange(start, end, exclusive);
41612}
41613
41614var ObjectRange = Class.create(Enumerable, (function() {
41615  function initialize(start, end, exclusive) {
41616    this.start = start;
41617    this.end = end;
41618    this.exclusive = exclusive;
41619  }
41620
41621  function _each(iterator) {
41622    var value = this.start;
41623    while (this.include(value)) {
41624      iterator(value);
41625      value = value.succ();
41626    }
41627  }
41628
41629  function include(value) {
41630    if (value < this.start)
41631      return false;
41632    if (this.exclusive)
41633      return value < this.end;
41634    return value <= this.end;
41635  }
41636
41637  return {
41638    initialize: initialize,
41639    _each:      _each,
41640    include:    include
41641  };
41642})());
41643
41644
41645
41646var Ajax = {
41647  getTransport: function() {
41648    return Try.these(
41649      function() {return new XMLHttpRequest()},
41650      function() {return new ActiveXObject('Msxml2.XMLHTTP')},
41651      function() {return new ActiveXObject('Microsoft.XMLHTTP')}
41652    ) || false;
41653  },
41654
41655  activeRequestCount: 0
41656};
41657
41658Ajax.Responders = {
41659  responders: [],
41660
41661  _each: function(iterator) {
41662    this.responders._each(iterator);
41663  },
41664
41665  register: function(responder) {
41666    if (!this.include(responder))
41667      this.responders.push(responder);
41668  },
41669
41670  unregister: function(responder) {
41671    this.responders = this.responders.without(responder);
41672  },
41673
41674  dispatch: function(callback, request, transport, json) {
41675    this.each(function(responder) {
41676      if (Object.isFunction(responder[callback])) {
41677        try {
41678          responder[callback].apply(responder, [request, transport, json]);
41679        } catch (e) { }
41680      }
41681    });
41682  }
41683};
41684
41685Object.extend(Ajax.Responders, Enumerable);
41686
41687Ajax.Responders.register({
41688  onCreate:   function() { Ajax.activeRequestCount++ },
41689  onComplete: function() { Ajax.activeRequestCount-- }
41690});
41691Ajax.Base = Class.create({
41692  initialize: function(options) {
41693    this.options = {
41694      method:       'post',
41695      asynchronous: true,
41696      contentType:  'application/x-www-form-urlencoded',
41697      encoding:     'UTF-8',
41698      parameters:   '',
41699      evalJSON:     true,
41700      evalJS:       true
41701    };
41702    Object.extend(this.options, options || { });
41703
41704    this.options.method = this.options.method.toLowerCase();
41705
41706    if (Object.isString(this.options.parameters))
41707      this.options.parameters = this.options.parameters.toQueryParams();
41708    else if (Object.isHash(this.options.parameters))
41709      this.options.parameters = this.options.parameters.toObject();
41710  }
41711});
41712Ajax.Request = Class.create(Ajax.Base, {
41713  _complete: false,
41714
41715  initialize: function($super, url, options) {
41716    $super(options);
41717    this.transport = Ajax.getTransport();
41718    this.request(url);
41719  },
41720
41721  request: function(url) {
41722    this.url = url;
41723    this.method = this.options.method;
41724    var params = Object.clone(this.options.parameters);
41725
41726    if (!['get', 'post'].include(this.method)) {
41727      params['_method'] = this.method;
41728      this.method = 'post';
41729    }
41730
41731    this.parameters = params;
41732
41733    if (params = Object.toQueryString(params)) {
41734      if (this.method == 'get')
41735        this.url += (this.url.include('?') ? '&' : '?') + params;
41736      else if (/Konqueror|Safari|KHTML/.test(navigator.userAgent))
41737        params += '&_=';
41738    }
41739
41740    try {
41741      var response = new Ajax.Response(this);
41742      if (this.options.onCreate) this.options.onCreate(response);
41743      Ajax.Responders.dispatch('onCreate', this, response);
41744
41745      this.transport.open(this.method.toUpperCase(), this.url,
41746        this.options.asynchronous);
41747
41748      if (this.options.asynchronous) this.respondToReadyState.bind(this).defer(1);
41749
41750      this.transport.onreadystatechange = this.onStateChange.bind(this);
41751      this.setRequestHeaders();
41752
41753      this.body = this.method == 'post' ? (this.options.postBody || params) : null;
41754      this.transport.send(this.body);
41755
41756      /* Force Firefox to handle ready state 4 for synchronous requests */
41757      if (!this.options.asynchronous && this.transport.overrideMimeType)
41758        this.onStateChange();
41759
41760    }
41761    catch (e) {
41762      this.dispatchException(e);
41763    }
41764  },
41765
41766  onStateChange: function() {
41767    var readyState = this.transport.readyState;
41768    if (readyState > 1 && !((readyState == 4) && this._complete))
41769      this.respondToReadyState(this.transport.readyState);
41770  },
41771
41772  setRequestHeaders: function() {
41773    var headers = {
41774    //  'X-Requested-With': 'XMLHttpRequest',
41775    //  'X-Prototype-Version': Prototype.Version,
41776      'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
41777    };
41778
41779    if (this.method == 'post') {
41780      headers['Content-type'] = this.options.contentType +
41781        (this.options.encoding ? '; charset=' + this.options.encoding : '');
41782
41783      /* Force "Connection: close" for older Mozilla browsers to work
41784       * around a bug where XMLHttpRequest sends an incorrect
41785       * Content-length header. See Mozilla Bugzilla #246651.
41786       */
41787      if (this.transport.overrideMimeType &&
41788          (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)
41789            headers['Connection'] = 'close';
41790    }
41791
41792    if (typeof this.options.requestHeaders == 'object') {
41793      var extras = this.options.requestHeaders;
41794
41795      if (Object.isFunction(extras.push))
41796        for (var i = 0, length = extras.length; i < length; i += 2)
41797          headers[extras[i]] = extras[i+1];
41798      else
41799        $H(extras).each(function(pair) { headers[pair.key] = pair.value });
41800    }
41801
41802    for (var name in headers)
41803      this.transport.setRequestHeader(name, headers[name]);
41804  },
41805
41806  success: function() {
41807    var status = this.getStatus();
41808    return !status || (status >= 200 && status < 300);
41809  },
41810
41811  getStatus: function() {
41812    try {
41813      return this.transport.status || 0;
41814    } catch (e) { return 0 }
41815  },
41816
41817  respondToReadyState: function(readyState) {
41818    var state = Ajax.Request.Events[readyState], response = new Ajax.Response(this);
41819
41820    if (state == 'Complete') {
41821      try {
41822        this._complete = true;
41823        (this.options['on' + response.status]
41824         || this.options['on' + (this.success() ? 'Success' : 'Failure')]
41825         || Prototype.emptyFunction)(response, response.headerJSON);
41826      } catch (e) {
41827        this.dispatchException(e);
41828      }
41829
41830      var contentType = response.getHeader('Content-type');
41831      if (this.options.evalJS == 'force'
41832          || (this.options.evalJS && this.isSameOrigin() && contentType
41833          && contentType.match(/^\s*(text|application)\/(x-)?(java|ecma)script(;.*)?\s*$/i)))
41834        this.evalResponse();
41835    }
41836
41837    try {
41838      (this.options['on' + state] || Prototype.emptyFunction)(response, response.headerJSON);
41839      Ajax.Responders.dispatch('on' + state, this, response, response.headerJSON);
41840    } catch (e) {
41841      this.dispatchException(e);
41842    }
41843
41844    if (state == 'Complete') {
41845      this.transport.onreadystatechange = Prototype.emptyFunction;
41846    }
41847  },
41848
41849  isSameOrigin: function() {
41850    var m = this.url.match(/^\s*https?:\/\/[^\/]*/);
41851    return !m || (m[0] == '#{protocol}//#{domain}#{port}'.interpolate({
41852      protocol: location.protocol,
41853      domain: document.domain,
41854      port: location.port ? ':' + location.port : ''
41855    }));
41856  },
41857
41858  getHeader: function(name) {
41859    try {
41860      return this.transport.getResponseHeader(name) || null;
41861    } catch (e) { return null; }
41862  },
41863
41864  evalResponse: function() {
41865    try {
41866      return eval((this.transport.responseText || '').unfilterJSON());
41867    } catch (e) {
41868      this.dispatchException(e);
41869    }
41870  },
41871
41872  dispatchException: function(exception) {
41873    (this.options.onException || Prototype.emptyFunction)(this, exception);
41874    Ajax.Responders.dispatch('onException', this, exception);
41875  }
41876});
41877
41878Ajax.Request.Events =
41879  ['Uninitialized', 'Loading', 'Loaded', 'Interactive', 'Complete'];
41880
41881
41882
41883
41884
41885
41886
41887
41888Ajax.Response = Class.create({
41889  initialize: function(request){
41890    this.request = request;
41891    var transport  = this.transport  = request.transport,
41892        readyState = this.readyState = transport.readyState;
41893
41894    if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) {
41895      this.status       = this.getStatus();
41896      this.statusText   = this.getStatusText();
41897      this.responseText = String.interpret(transport.responseText);
41898      this.headerJSON   = this._getHeaderJSON();
41899    }
41900
41901    if(readyState == 4) {
41902      var xml = transport.responseXML;
41903      this.responseXML  = Object.isUndefined(xml) ? null : xml;
41904      this.responseJSON = this._getResponseJSON();
41905    }
41906  },
41907
41908  status:      0,
41909
41910  statusText: '',
41911
41912  getStatus: Ajax.Request.prototype.getStatus,
41913
41914  getStatusText: function() {
41915    try {
41916      return this.transport.statusText || '';
41917    } catch (e) { return '' }
41918  },
41919
41920  getHeader: Ajax.Request.prototype.getHeader,
41921
41922  getAllHeaders: function() {
41923    try {
41924      return this.getAllResponseHeaders();
41925    } catch (e) { return null }
41926  },
41927
41928  getResponseHeader: function(name) {
41929    return this.transport.getResponseHeader(name);
41930  },
41931
41932  getAllResponseHeaders: function() {
41933    return this.transport.getAllResponseHeaders();
41934  },
41935
41936  _getHeaderJSON: function() {
41937    var json = this.getHeader('X-JSON');
41938    if (!json) return null;
41939    json = decodeURIComponent(escape(json));
41940    try {
41941      return json.evalJSON(this.request.options.sanitizeJSON ||
41942        !this.request.isSameOrigin());
41943    } catch (e) {
41944      this.request.dispatchException(e);
41945    }
41946  },
41947
41948  _getResponseJSON: function() {
41949    var options = this.request.options;
41950    if (!options.evalJSON || (options.evalJSON != 'force' &&
41951      !(this.getHeader('Content-type') || '').include('application/json')) ||
41952        this.responseText.blank())
41953          return null;
41954    try {
41955      return this.responseText.evalJSON(options.sanitizeJSON ||
41956        !this.request.isSameOrigin());
41957    } catch (e) {
41958      this.request.dispatchException(e);
41959    }
41960  }
41961});
41962
41963Ajax.Updater = Class.create(Ajax.Request, {
41964  initialize: function($super, container, url, options) {
41965    this.container = {
41966      success: (container.success || container),
41967      failure: (container.failure || (container.success ? null : container))
41968    };
41969
41970    options = Object.clone(options);
41971    var onComplete = options.onComplete;
41972    options.onComplete = (function(response, json) {
41973      this.updateContent(response.responseText);
41974      if (Object.isFunction(onComplete)) onComplete(response, json);
41975    }).bind(this);
41976
41977    $super(url, options);
41978  },
41979
41980  updateContent: function(responseText) {
41981    var receiver = this.container[this.success() ? 'success' : 'failure'],
41982        options = this.options;
41983
41984    if (!options.evalScripts) responseText = responseText.stripScripts();
41985
41986    if (receiver = $(receiver)) {
41987      if (options.insertion) {
41988        if (Object.isString(options.insertion)) {
41989          var insertion = { }; insertion[options.insertion] = responseText;
41990          receiver.insert(insertion);
41991        }
41992        else options.insertion(receiver, responseText);
41993      }
41994      else receiver.update(responseText);
41995    }
41996  }
41997});
41998
41999Ajax.PeriodicalUpdater = Class.create(Ajax.Base, {
42000  initialize: function($super, container, url, options) {
42001    $super(options);
42002    this.onComplete = this.options.onComplete;
42003
42004    this.frequency = (this.options.frequency || 2);
42005    this.decay = (this.options.decay || 1);
42006
42007    this.updater = { };
42008    this.container = container;
42009    this.url = url;
42010
42011    this.start();
42012  },
42013
42014  start: function() {
42015    this.options.onComplete = this.updateComplete.bind(this);
42016    this.onTimerEvent();
42017  },
42018
42019  stop: function() {
42020    this.updater.options.onComplete = undefined;
42021    clearTimeout(this.timer);
42022    (this.onComplete || Prototype.emptyFunction).apply(this, arguments);
42023  },
42024
42025  updateComplete: function(response) {
42026    if (this.options.decay) {
42027      this.decay = (response.responseText == this.lastText ?
42028        this.decay * this.options.decay : 1);
42029
42030      this.lastText = response.responseText;
42031    }
42032    this.timer = this.onTimerEvent.bind(this).delay(this.decay * this.frequency);
42033  },
42034
42035  onTimerEvent: function() {
42036    this.updater = new Ajax.Updater(this.container, this.url, this.options);
42037  }
42038});
42039
42040
42041
42042function $(element) {
42043  if (arguments.length > 1) {
42044    for (var i = 0, elements = [], length = arguments.length; i < length; i++)
42045      elements.push($(arguments[i]));
42046    return elements;
42047  }
42048  if (Object.isString(element))
42049    element = document.getElementById(element);
42050  return Element.extend(element);
42051}
42052
42053if (Prototype.BrowserFeatures.XPath) {
42054  document._getElementsByXPath = function(expression, parentElement) {
42055    var results = [];
42056    var query = document.evaluate(expression, $(parentElement) || document,
42057      null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
42058    for (var i = 0, length = query.snapshotLength; i < length; i++)
42059      results.push(Element.extend(query.snapshotItem(i)));
42060    return results;
42061  };
42062}
42063
42064/*--------------------------------------------------------------------------*/
42065
42066if (!window.Node) var Node = { };
42067
42068if (!Node.ELEMENT_NODE) {
42069  Object.extend(Node, {
42070    ELEMENT_NODE: 1,
42071    ATTRIBUTE_NODE: 2,
42072    TEXT_NODE: 3,
42073    CDATA_SECTION_NODE: 4,
42074    ENTITY_REFERENCE_NODE: 5,
42075    ENTITY_NODE: 6,
42076    PROCESSING_INSTRUCTION_NODE: 7,
42077    COMMENT_NODE: 8,
42078    DOCUMENT_NODE: 9,
42079    DOCUMENT_TYPE_NODE: 10,
42080    DOCUMENT_FRAGMENT_NODE: 11,
42081    NOTATION_NODE: 12
42082  });
42083}
42084
42085
42086(function(global) {
42087
42088  var SETATTRIBUTE_IGNORES_NAME = (function(){
42089    var elForm = document.createElement("form");
42090    var elInput = document.createElement("input");
42091    var root = document.documentElement;
42092    elInput.setAttribute("name", "test");
42093    elForm.appendChild(elInput);
42094    root.appendChild(elForm);
42095    var isBuggy = elForm.elements
42096      ? (typeof elForm.elements.test == "undefined")
42097      : null;
42098    root.removeChild(elForm);
42099    elForm = elInput = null;
42100    return isBuggy;
42101  })();
42102
42103  var element = global.Element;
42104  global.Element = function(tagName, attributes) {
42105    attributes = attributes || { };
42106    tagName = tagName.toLowerCase();
42107    var cache = Element.cache;
42108    if (SETATTRIBUTE_IGNORES_NAME && attributes.name) {
42109      tagName = '<' + tagName + ' name="' + attributes.name + '">';
42110      delete attributes.name;
42111      return Element.writeAttribute(document.createElement(tagName), attributes);
42112    }
42113    if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName));
42114    return Element.writeAttribute(cache[tagName].cloneNode(false), attributes);
42115  };
42116  Object.extend(global.Element, element || { });
42117  if (element) global.Element.prototype = element.prototype;
42118})(this);
42119
42120Element.cache = { };
42121Element.idCounter = 1;
42122
42123Element.Methods = {
42124  visible: function(element) {
42125    return $(element).style.display != 'none';
42126  },
42127
42128  toggle: function(element) {
42129    element = $(element);
42130    Element[Element.visible(element) ? 'hide' : 'show'](element);
42131    return element;
42132  },
42133
42134
42135  hide: function(element) {
42136    element = $(element);
42137    element.style.display = 'none';
42138    return element;
42139  },
42140
42141  show: function(element) {
42142    element = $(element);
42143    element.style.display = '';
42144    return element;
42145  },
42146
42147  remove: function(element) {
42148    element = $(element);
42149    element.parentNode.removeChild(element);
42150    return element;
42151  },
42152
42153  update: (function(){
42154
42155    var SELECT_ELEMENT_INNERHTML_BUGGY = (function(){
42156      var el = document.createElement("select"),
42157          isBuggy = true;
42158      el.innerHTML = "<option value=\"test\">test</option>";
42159      if (el.options && el.options[0]) {
42160        isBuggy = el.options[0].nodeName.toUpperCase() !== "OPTION";
42161      }
42162      el = null;
42163      return isBuggy;
42164    })();
42165
42166    var TABLE_ELEMENT_INNERHTML_BUGGY = (function(){
42167      try {
42168        var el = document.createElement("table");
42169        if (el && el.tBodies) {
42170          el.innerHTML = "<tbody><tr><td>test</td></tr></tbody>";
42171          var isBuggy = typeof el.tBodies[0] == "undefined";
42172          el = null;
42173          return isBuggy;
42174        }
42175      } catch (e) {
42176        return true;
42177      }
42178    })();
42179
42180    var SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING = (function () {
42181      var s = document.createElement("script"),
42182          isBuggy = false;
42183      try {
42184        s.appendChild(document.createTextNode(""));
42185        isBuggy = !s.firstChild ||
42186          s.firstChild && s.firstChild.nodeType !== 3;
42187      } catch (e) {
42188        isBuggy = true;
42189      }
42190      s = null;
42191      return isBuggy;
42192    })();
42193
42194    function update(element, content) {
42195      element = $(element);
42196
42197      if (content && content.toElement)
42198        content = content.toElement();
42199
42200      if (Object.isElement(content))
42201        return element.update().insert(content);
42202
42203      content = Object.toHTML(content);
42204
42205      var tagName = element.tagName.toUpperCase();
42206
42207      if (tagName === 'SCRIPT' && SCRIPT_ELEMENT_REJECTS_TEXTNODE_APPENDING) {
42208        element.text = content;
42209        return element;
42210      }
42211
42212      if (SELECT_ELEMENT_INNERHTML_BUGGY || TABLE_ELEMENT_INNERHTML_BUGGY) {
42213        if (tagName in Element._insertionTranslations.tags) {
42214          while (element.firstChild) {
42215            element.removeChild(element.firstChild);
42216          }
42217          Element._getContentFromAnonymousElement(tagName, content.stripScripts())
42218            .each(function(node) {
42219              element.appendChild(node)
42220            });
42221        }
42222        else {
42223          element.innerHTML = content.stripScripts();
42224        }
42225      }
42226      else {
42227        element.innerHTML = content.stripScripts();
42228      }
42229
42230      content.evalScripts.bind(content).defer();
42231      return element;
42232    }
42233
42234    return update;
42235  })(),
42236
42237  replace: function(element, content) {
42238    element = $(element);
42239    if (content && content.toElement) content = content.toElement();
42240    else if (!Object.isElement(content)) {
42241      content = Object.toHTML(content);
42242      var range = element.ownerDocument.createRange();
42243      range.selectNode(element);
42244      content.evalScripts.bind(content).defer();
42245      content = range.createContextualFragment(content.stripScripts());
42246    }
42247    element.parentNode.replaceChild(content, element);
42248    return element;
42249  },
42250
42251  insert: function(element, insertions) {
42252    element = $(element);
42253
42254    if (Object.isString(insertions) || Object.isNumber(insertions) ||
42255        Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
42256          insertions = {bottom:insertions};
42257
42258    var content, insert, tagName, childNodes;
42259
42260    for (var position in insertions) {
42261      content  = insertions[position];
42262      position = position.toLowerCase();
42263      insert = Element._insertionTranslations[position];
42264
42265      if (content && content.toElement) content = content.toElement();
42266      if (Object.isElement(content)) {
42267        insert(element, content);
42268        continue;
42269      }
42270
42271      content = Object.toHTML(content);
42272
42273      tagName = ((position == 'before' || position == 'after')
42274        ? element.parentNode : element).tagName.toUpperCase();
42275
42276      childNodes = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
42277
42278      if (position == 'top' || position == 'after') childNodes.reverse();
42279      childNodes.each(insert.curry(element));
42280
42281      content.evalScripts.bind(content).defer();
42282    }
42283
42284    return element;
42285  },
42286
42287  wrap: function(element, wrapper, attributes) {
42288    element = $(element);
42289    if (Object.isElement(wrapper))
42290      $(wrapper).writeAttribute(attributes || { });
42291    else if (Object.isString(wrapper)) wrapper = new Element(wrapper, attributes);
42292    else wrapper = new Element('div', wrapper);
42293    if (element.parentNode)
42294      element.parentNode.replaceChild(wrapper, element);
42295    wrapper.appendChild(element);
42296    return wrapper;
42297  },
42298
42299  inspect: function(element) {
42300    element = $(element);
42301    var result = '<' + element.tagName.toLowerCase();
42302    $H({'id': 'id', 'className': 'class'}).each(function(pair) {
42303      var property = pair.first(), attribute = pair.last();
42304      var value = (element[property] || '').toString();
42305      if (value) result += ' ' + attribute + '=' + value.inspect(true);
42306    });
42307    return result + '>';
42308  },
42309
42310  recursivelyCollect: function(element, property) {
42311    element = $(element);
42312    var elements = [];
42313    while (element = element[property])
42314      if (element.nodeType == 1)
42315        elements.push(Element.extend(element));
42316    return elements;
42317  },
42318
42319  ancestors: function(element) {
42320    return Element.recursivelyCollect(element, 'parentNode');
42321  },
42322
42323  descendants: function(element) {
42324    return Element.select(element, "*");
42325  },
42326
42327  firstDescendant: function(element) {
42328    element = $(element).firstChild;
42329    while (element && element.nodeType != 1) element = element.nextSibling;
42330    return $(element);
42331  },
42332
42333  immediateDescendants: function(element) {
42334    if (!(element = $(element).firstChild)) return [];
42335    while (element && element.nodeType != 1) element = element.nextSibling;
42336    if (element) return [element].concat($(element).nextSiblings());
42337    return [];
42338  },
42339
42340  previousSiblings: function(element) {
42341    return Element.recursivelyCollect(element, 'previousSibling');
42342  },
42343
42344  nextSiblings: function(element) {
42345    return Element.recursivelyCollect(element, 'nextSibling');
42346  },
42347
42348  siblings: function(element) {
42349    element = $(element);
42350    return Element.previousSiblings(element).reverse()
42351      .concat(Element.nextSiblings(element));
42352  },
42353
42354  match: function(element, selector) {
42355    if (Object.isString(selector))
42356      selector = new Selector(selector);
42357    return selector.match($(element));
42358  },
42359
42360  up: function(element, expression, index) {
42361    element = $(element);
42362    if (arguments.length == 1) return $(element.parentNode);
42363    var ancestors = Element.ancestors(element);
42364    return Object.isNumber(expression) ? ancestors[expression] :
42365      Selector.findElement(ancestors, expression, index);
42366  },
42367
42368  down: function(element, expression, index) {
42369    element = $(element);
42370    if (arguments.length == 1) return Element.firstDescendant(element);
42371    return Object.isNumber(expression) ? Element.descendants(element)[expression] :
42372      Element.select(element, expression)[index || 0];
42373  },
42374
42375  previous: function(element, expression, index) {
42376    element = $(element);
42377    if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element));
42378    var previousSiblings = Element.previousSiblings(element);
42379    return Object.isNumber(expression) ? previousSiblings[expression] :
42380      Selector.findElement(previousSiblings, expression, index);
42381  },
42382
42383  next: function(element, expression, index) {
42384    element = $(element);
42385    if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element));
42386    var nextSiblings = Element.nextSiblings(element);
42387    return Object.isNumber(expression) ? nextSiblings[expression] :
42388      Selector.findElement(nextSiblings, expression, index);
42389  },
42390
42391
42392  select: function(element) {
42393    var args = Array.prototype.slice.call(arguments, 1);
42394    return Selector.findChildElements(element, args);
42395  },
42396
42397  adjacent: function(element) {
42398    var args = Array.prototype.slice.call(arguments, 1);
42399    return Selector.findChildElements(element.parentNode, args).without(element);
42400  },
42401
42402  identify: function(element) {
42403    element = $(element);
42404    var id = Element.readAttribute(element, 'id');
42405    if (id) return id;
42406    do { id = 'anonymous_element_' + Element.idCounter++ } while ($(id));
42407    Element.writeAttribute(element, 'id', id);
42408    return id;
42409  },
42410
42411  readAttribute: function(element, name) {
42412    element = $(element);
42413    if (Prototype.Browser.IE) {
42414      var t = Element._attributeTranslations.read;
42415      if (t.values[name]) return t.values[name](element, name);
42416      if (t.names[name]) name = t.names[name];
42417      if (name.include(':')) {
42418        return (!element.attributes || !element.attributes[name]) ? null :
42419         element.attributes[name].value;
42420      }
42421    }
42422    return element.getAttribute(name);
42423  },
42424
42425  writeAttribute: function(element, name, value) {
42426    element = $(element);
42427    var attributes = { }, t = Element._attributeTranslations.write;
42428
42429    if (typeof name == 'object') attributes = name;
42430    else attributes[name] = Object.isUndefined(value) ? true : value;
42431
42432    for (var attr in attributes) {
42433      name = t.names[attr] || attr;
42434      value = attributes[attr];
42435      if (t.values[attr]) name = t.values[attr](element, value);
42436      if (value === false || value === null)
42437        element.removeAttribute(name);
42438      else if (value === true)
42439        element.setAttribute(name, name);
42440      else element.setAttribute(name, value);
42441    }
42442    return element;
42443  },
42444
42445  getHeight: function(element) {
42446    return Element.getDimensions(element).height;
42447  },
42448
42449  getWidth: function(element) {
42450    return Element.getDimensions(element).width;
42451  },
42452
42453  classNames: function(element) {
42454    return new Element.ClassNames(element);
42455  },
42456
42457  hasClassName: function(element, className) {
42458    if (!(element = $(element))) return;
42459    var elementClassName = element.className;
42460    return (elementClassName.length > 0 && (elementClassName == className ||
42461      new RegExp("(^|\\s)" + className + "(\\s|$)").test(elementClassName)));
42462  },
42463
42464  addClassName: function(element, className) {
42465    if (!(element = $(element))) return;
42466    if (!Element.hasClassName(element, className))
42467      element.className += (element.className ? ' ' : '') + className;
42468    return element;
42469  },
42470
42471  removeClassName: function(element, className) {
42472    if (!(element = $(element))) return;
42473    element.className = element.className.replace(
42474      new RegExp("(^|\\s+)" + className + "(\\s+|$)"), ' ').strip();
42475    return element;
42476  },
42477
42478  toggleClassName: function(element, className) {
42479    if (!(element = $(element))) return;
42480    return Element[Element.hasClassName(element, className) ?
42481      'removeClassName' : 'addClassName'](element, className);
42482  },
42483
42484  cleanWhitespace: function(element) {
42485    element = $(element);
42486    var node = element.firstChild;
42487    while (node) {
42488      var nextNode = node.nextSibling;
42489      if (node.nodeType == 3 && !/\S/.test(node.nodeValue))
42490        element.removeChild(node);
42491      node = nextNode;
42492    }
42493    return element;
42494  },
42495
42496  empty: function(element) {
42497    return $(element).innerHTML.blank();
42498  },
42499
42500  descendantOf: function(element, ancestor) {
42501    element = $(element), ancestor = $(ancestor);
42502
42503    if (element.compareDocumentPosition)
42504      return (element.compareDocumentPosition(ancestor) & 8) === 8;
42505
42506    if (ancestor.contains)
42507      return ancestor.contains(element) && ancestor !== element;
42508
42509    while (element = element.parentNode)
42510      if (element == ancestor) return true;
42511
42512    return false;
42513  },
42514
42515  scrollTo: function(element) {
42516    element = $(element);
42517    var pos = Element.cumulativeOffset(element);
42518    window.scrollTo(pos[0], pos[1]);
42519    return element;
42520  },
42521
42522  getStyle: function(element, style) {
42523    element = $(element);
42524    style = style == 'float' ? 'cssFloat' : style.camelize();
42525    var value = element.style[style];
42526    if (!value || value == 'auto') {
42527      var css = document.defaultView.getComputedStyle(element, null);
42528      value = css ? css[style] : null;
42529    }
42530    if (style == 'opacity') return value ? parseFloat(value) : 1.0;
42531    return value == 'auto' ? null : value;
42532  },
42533
42534  getOpacity: function(element) {
42535    return $(element).getStyle('opacity');
42536  },
42537
42538  setStyle: function(element, styles) {
42539    element = $(element);
42540    var elementStyle = element.style, match;
42541    if (Object.isString(styles)) {
42542      element.style.cssText += ';' + styles;
42543      return styles.include('opacity') ?
42544        element.setOpacity(styles.match(/opacity:\s*(\d?\.?\d*)/)[1]) : element;
42545    }
42546    for (var property in styles)
42547      if (property == 'opacity') element.setOpacity(styles[property]);
42548      else
42549        elementStyle[(property == 'float' || property == 'cssFloat') ?
42550          (Object.isUndefined(elementStyle.styleFloat) ? 'cssFloat' : 'styleFloat') :
42551            property] = styles[property];
42552
42553    return element;
42554  },
42555
42556  setOpacity: function(element, value) {
42557    element = $(element);
42558    element.style.opacity = (value == 1 || value === '') ? '' :
42559      (value < 0.00001) ? 0 : value;
42560    return element;
42561  },
42562
42563  getDimensions: function(element) {
42564    element = $(element);
42565    var display = Element.getStyle(element, 'display');
42566    if (display != 'none' && display != null) // Safari bug
42567      return {width: element.offsetWidth, height: element.offsetHeight};
42568
42569    var els = element.style;
42570    var originalVisibility = els.visibility;
42571    var originalPosition = els.position;
42572    var originalDisplay = els.display;
42573    els.visibility = 'hidden';
42574    if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari
42575      els.position = 'absolute';
42576    els.display = 'block';
42577    var originalWidth = element.clientWidth;
42578    var originalHeight = element.clientHeight;
42579    els.display = originalDisplay;
42580    els.position = originalPosition;
42581    els.visibility = originalVisibility;
42582    return {width: originalWidth, height: originalHeight};
42583  },
42584
42585  makePositioned: function(element) {
42586    element = $(element);
42587    var pos = Element.getStyle(element, 'position');
42588    if (pos == 'static' || !pos) {
42589      element._madePositioned = true;
42590      element.style.position = 'relative';
42591      if (Prototype.Browser.Opera) {
42592        element.style.top = 0;
42593        element.style.left = 0;
42594      }
42595    }
42596    return element;
42597  },
42598
42599  undoPositioned: function(element) {
42600    element = $(element);
42601    if (element._madePositioned) {
42602      element._madePositioned = undefined;
42603      element.style.position =
42604        element.style.top =
42605        element.style.left =
42606        element.style.bottom =
42607        element.style.right = '';
42608    }
42609    return element;
42610  },
42611
42612  makeClipping: function(element) {
42613    element = $(element);
42614    if (element._overflow) return element;
42615    element._overflow = Element.getStyle(element, 'overflow') || 'auto';
42616    if (element._overflow !== 'hidden')
42617      element.style.overflow = 'hidden';
42618    return element;
42619  },
42620
42621  undoClipping: function(element) {
42622    element = $(element);
42623    if (!element._overflow) return element;
42624    element.style.overflow = element._overflow == 'auto' ? '' : element._overflow;
42625    element._overflow = null;
42626    return element;
42627  },
42628
42629  cumulativeOffset: function(element) {
42630    var valueT = 0, valueL = 0;
42631    do {
42632      valueT += element.offsetTop  || 0;
42633      valueL += element.offsetLeft || 0;
42634      element = element.offsetParent;
42635    } while (element);
42636    return Element._returnOffset(valueL, valueT);
42637  },
42638
42639  positionedOffset: function(element) {
42640    var valueT = 0, valueL = 0;
42641    do {
42642      valueT += element.offsetTop  || 0;
42643      valueL += element.offsetLeft || 0;
42644      element = element.offsetParent;
42645      if (element) {
42646        if (element.tagName.toUpperCase() == 'BODY') break;
42647        var p = Element.getStyle(element, 'position');
42648        if (p !== 'static') break;
42649      }
42650    } while (element);
42651    return Element._returnOffset(valueL, valueT);
42652  },
42653
42654  absolutize: function(element) {
42655    element = $(element);
42656    if (Element.getStyle(element, 'position') == 'absolute') return element;
42657
42658    var offsets = Element.positionedOffset(element);
42659    var top     = offsets[1];
42660    var left    = offsets[0];
42661    var width   = element.clientWidth;
42662    var height  = element.clientHeight;
42663
42664    element._originalLeft   = left - parseFloat(element.style.left  || 0);
42665    element._originalTop    = top  - parseFloat(element.style.top || 0);
42666    element._originalWidth  = element.style.width;
42667    element._originalHeight = element.style.height;
42668
42669    element.style.position = 'absolute';
42670    element.style.top    = top + 'px';
42671    element.style.left   = left + 'px';
42672    element.style.width  = width + 'px';
42673    element.style.height = height + 'px';
42674    return element;
42675  },
42676
42677  relativize: function(element) {
42678    element = $(element);
42679    if (Element.getStyle(element, 'position') == 'relative') return element;
42680
42681    element.style.position = 'relative';
42682    var top  = parseFloat(element.style.top  || 0) - (element._originalTop || 0);
42683    var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0);
42684
42685    element.style.top    = top + 'px';
42686    element.style.left   = left + 'px';
42687    element.style.height = element._originalHeight;
42688    element.style.width  = element._originalWidth;
42689    return element;
42690  },
42691
42692  cumulativeScrollOffset: function(element) {
42693    var valueT = 0, valueL = 0;
42694    do {
42695      valueT += element.scrollTop  || 0;
42696      valueL += element.scrollLeft || 0;
42697      element = element.parentNode;
42698    } while (element);
42699    return Element._returnOffset(valueL, valueT);
42700  },
42701
42702  getOffsetParent: function(element) {
42703    if (element.offsetParent) return $(element.offsetParent);
42704    if (element == document.body) return $(element);
42705
42706    while ((element = element.parentNode) && element != document.body)
42707      if (Element.getStyle(element, 'position') != 'static')
42708        return $(element);
42709
42710    return $(document.body);
42711  },
42712
42713  viewportOffset: function(forElement) {
42714    var valueT = 0, valueL = 0;
42715
42716    var element = forElement;
42717    do {
42718      valueT += element.offsetTop  || 0;
42719      valueL += element.offsetLeft || 0;
42720
42721      if (element.offsetParent == document.body &&
42722        Element.getStyle(element, 'position') == 'absolute') break;
42723
42724    } while (element = element.offsetParent);
42725
42726    element = forElement;
42727    do {
42728      if (!Prototype.Browser.Opera || (element.tagName && (element.tagName.toUpperCase() == 'BODY'))) {
42729        valueT -= element.scrollTop  || 0;
42730        valueL -= element.scrollLeft || 0;
42731      }
42732    } while (element = element.parentNode);
42733
42734    return Element._returnOffset(valueL, valueT);
42735  },
42736
42737  clonePosition: function(element, source) {
42738    var options = Object.extend({
42739      setLeft:    true,
42740      setTop:     true,
42741      setWidth:   true,
42742      setHeight:  true,
42743      offsetTop:  0,
42744      offsetLeft: 0
42745    }, arguments[2] || { });
42746
42747    source = $(source);
42748    var p = Element.viewportOffset(source);
42749
42750    element = $(element);
42751    var delta = [0, 0];
42752    var parent = null;
42753    if (Element.getStyle(element, 'position') == 'absolute') {
42754      parent = Element.getOffsetParent(element);
42755      delta = Element.viewportOffset(parent);
42756    }
42757
42758    if (parent == document.body) {
42759      delta[0] -= document.body.offsetLeft;
42760      delta[1] -= document.body.offsetTop;
42761    }
42762
42763    if (options.setLeft)   element.style.left  = (p[0] - delta[0] + options.offsetLeft) + 'px';
42764    if (options.setTop)    element.style.top   = (p[1] - delta[1] + options.offsetTop) + 'px';
42765    if (options.setWidth)  element.style.width = source.offsetWidth + 'px';
42766    if (options.setHeight) element.style.height = source.offsetHeight + 'px';
42767    return element;
42768  }
42769};
42770
42771Object.extend(Element.Methods, {
42772  getElementsBySelector: Element.Methods.select,
42773
42774  childElements: Element.Methods.immediateDescendants
42775});
42776
42777Element._attributeTranslations = {
42778  write: {
42779    names: {
42780      className: 'class',
42781      htmlFor:   'for'
42782    },
42783    values: { }
42784  }
42785};
42786
42787if (Prototype.Browser.Opera) {
42788  Element.Methods.getStyle = Element.Methods.getStyle.wrap(
42789    function(proceed, element, style) {
42790      switch (style) {
42791        case 'left': case 'top': case 'right': case 'bottom':
42792          if (proceed(element, 'position') === 'static') return null;
42793        case 'height': case 'width':
42794          if (!Element.visible(element)) return null;
42795
42796          var dim = parseInt(proceed(element, style), 10);
42797
42798          if (dim !== element['offset' + style.capitalize()])
42799            return dim + 'px';
42800
42801          var properties;
42802          if (style === 'height') {
42803            properties = ['border-top-width', 'padding-top',
42804             'padding-bottom', 'border-bottom-width'];
42805          }
42806          else {
42807            properties = ['border-left-width', 'padding-left',
42808             'padding-right', 'border-right-width'];
42809          }
42810          return properties.inject(dim, function(memo, property) {
42811            var val = proceed(element, property);
42812            return val === null ? memo : memo - parseInt(val, 10);
42813          }) + 'px';
42814        default: return proceed(element, style);
42815      }
42816    }
42817  );
42818
42819  Element.Methods.readAttribute = Element.Methods.readAttribute.wrap(
42820    function(proceed, element, attribute) {
42821      if (attribute === 'title') return element.title;
42822      return proceed(element, attribute);
42823    }
42824  );
42825}
42826
42827else if (Prototype.Browser.IE) {
42828  Element.Methods.getOffsetParent = Element.Methods.getOffsetParent.wrap(
42829    function(proceed, element) {
42830      element = $(element);
42831      try { element.offsetParent }
42832      catch(e) { return $(document.body) }
42833      var position = element.getStyle('position');
42834      if (position !== 'static') return proceed(element);
42835      element.setStyle({ position: 'relative' });
42836      var value = proceed(element);
42837      element.setStyle({ position: position });
42838      return value;
42839    }
42840  );
42841
42842  $w('positionedOffset viewportOffset').each(function(method) {
42843    Element.Methods[method] = Element.Methods[method].wrap(
42844      function(proceed, element) {
42845        element = $(element);
42846        try { element.offsetParent }
42847        catch(e) { return Element._returnOffset(0,0) }
42848        var position = element.getStyle('position');
42849        if (position !== 'static') return proceed(element);
42850        var offsetParent = element.getOffsetParent();
42851        if (offsetParent && offsetParent.getStyle('position') === 'fixed')
42852          offsetParent.setStyle({ zoom: 1 });
42853        element.setStyle({ position: 'relative' });
42854        var value = proceed(element);
42855        element.setStyle({ position: position });
42856        return value;
42857      }
42858    );
42859  });
42860
42861  Element.Methods.cumulativeOffset = Element.Methods.cumulativeOffset.wrap(
42862    function(proceed, element) {
42863      try { element.offsetParent }
42864      catch(e) { return Element._returnOffset(0,0) }
42865      return proceed(element);
42866    }
42867  );
42868
42869  Element.Methods.getStyle = function(element, style) {
42870    element = $(element);
42871    style = (style == 'float' || style == 'cssFloat') ? 'styleFloat' : style.camelize();
42872    var value = element.style[style];
42873    if (!value && element.currentStyle) value = element.currentStyle[style];
42874
42875    if (style == 'opacity') {
42876      if (value = (element.getStyle('filter') || '').match(/alpha\(opacity=(.*)\)/))
42877        if (value[1]) return parseFloat(value[1]) / 100;
42878      return 1.0;
42879    }
42880
42881    if (value == 'auto') {
42882      if ((style == 'width' || style == 'height') && (element.getStyle('display') != 'none'))
42883        return element['offset' + style.capitalize()] + 'px';
42884      return null;
42885    }
42886    return value;
42887  };
42888
42889  Element.Methods.setOpacity = function(element, value) {
42890    function stripAlpha(filter){
42891      return filter.replace(/alpha\([^\)]*\)/gi,'');
42892    }
42893    element = $(element);
42894    var currentStyle = element.currentStyle;
42895    if ((currentStyle && !currentStyle.hasLayout) ||
42896      (!currentStyle && element.style.zoom == 'normal'))
42897        element.style.zoom = 1;
42898
42899    var filter = element.getStyle('filter'), style = element.style;
42900    if (value == 1 || value === '') {
42901      (filter = stripAlpha(filter)) ?
42902        style.filter = filter : style.removeAttribute('filter');
42903      return element;
42904    } else if (value < 0.00001) value = 0;
42905    style.filter = stripAlpha(filter) +
42906      'alpha(opacity=' + (value * 100) + ')';
42907    return element;
42908  };
42909
42910  Element._attributeTranslations = (function(){
42911
42912    var classProp = 'className';
42913    var forProp = 'for';
42914
42915    var el = document.createElement('div');
42916
42917    el.setAttribute(classProp, 'x');
42918
42919    if (el.className !== 'x') {
42920      el.setAttribute('class', 'x');
42921      if (el.className === 'x') {
42922        classProp = 'class';
42923      }
42924    }
42925    el = null;
42926
42927    el = document.createElement('label');
42928    el.setAttribute(forProp, 'x');
42929    if (el.htmlFor !== 'x') {
42930      el.setAttribute('htmlFor', 'x');
42931      if (el.htmlFor === 'x') {
42932        forProp = 'htmlFor';
42933      }
42934    }
42935    el = null;
42936
42937    return {
42938      read: {
42939        names: {
42940          'class':      classProp,
42941          'className':  classProp,
42942          'for':        forProp,
42943          'htmlFor':    forProp
42944        },
42945        values: {
42946          _getAttr: function(element, attribute) {
42947            return element.getAttribute(attribute);
42948          },
42949          _getAttr2: function(element, attribute) {
42950            return element.getAttribute(attribute, 2);
42951          },
42952          _getAttrNode: function(element, attribute) {
42953            var node = element.getAttributeNode(attribute);
42954            return node ? node.value : "";
42955          },
42956          _getEv: (function(){
42957
42958            var el = document.createElement('div');
42959            el.onclick = Prototype.emptyFunction;
42960            var value = el.getAttribute('onclick');
42961            var f;
42962
42963            if (String(value).indexOf('{') > -1) {
42964              f = function(element, attribute) {
42965                attribute = element.getAttribute(attribute);
42966                if (!attribute) return null;
42967                attribute = attribute.toString();
42968                attribute = attribute.split('{')[1];
42969                attribute = attribute.split('}')[0];
42970                return attribute.strip();
42971              };
42972            }
42973            else if (value === '') {
42974              f = function(element, attribute) {
42975                attribute = element.getAttribute(attribute);
42976                if (!attribute) return null;
42977                return attribute.strip();
42978              };
42979            }
42980            el = null;
42981            return f;
42982          })(),
42983          _flag: function(element, attribute) {
42984            return $(element).hasAttribute(attribute) ? attribute : null;
42985          },
42986          style: function(element) {
42987            return element.style.cssText.toLowerCase();
42988          },
42989          title: function(element) {
42990            return element.title;
42991          }
42992        }
42993      }
42994    }
42995  })();
42996
42997  Element._attributeTranslations.write = {
42998    names: Object.extend({
42999      cellpadding: 'cellPadding',
43000      cellspacing: 'cellSpacing'
43001    }, Element._attributeTranslations.read.names),
43002    values: {
43003      checked: function(element, value) {
43004        element.checked = !!value;
43005      },
43006
43007      style: function(element, value) {
43008        element.style.cssText = value ? value : '';
43009      }
43010    }
43011  };
43012
43013  Element._attributeTranslations.has = {};
43014
43015  $w('colSpan rowSpan vAlign dateTime accessKey tabIndex ' +
43016      'encType maxLength readOnly longDesc frameBorder').each(function(attr) {
43017    Element._attributeTranslations.write.names[attr.toLowerCase()] = attr;
43018    Element._attributeTranslations.has[attr.toLowerCase()] = attr;
43019  });
43020
43021  (function(v) {
43022    Object.extend(v, {
43023      href:        v._getAttr2,
43024      src:         v._getAttr2,
43025      type:        v._getAttr,
43026      action:      v._getAttrNode,
43027      disabled:    v._flag,
43028      checked:     v._flag,
43029      readonly:    v._flag,
43030      multiple:    v._flag,
43031      onload:      v._getEv,
43032      onunload:    v._getEv,
43033      onclick:     v._getEv,
43034      ondblclick:  v._getEv,
43035      onmousedown: v._getEv,
43036      onmouseup:   v._getEv,
43037      onmouseover: v._getEv,
43038      onmousemove: v._getEv,
43039      onmouseout:  v._getEv,
43040      onfocus:     v._getEv,
43041      onblur:      v._getEv,
43042      onkeypress:  v._getEv,
43043      onkeydown:   v._getEv,
43044      onkeyup:     v._getEv,
43045      onsubmit:    v._getEv,
43046      onreset:     v._getEv,
43047      onselect:    v._getEv,
43048      onchange:    v._getEv
43049    });
43050  })(Element._attributeTranslations.read.values);
43051
43052  if (Prototype.BrowserFeatures.ElementExtensions) {
43053    (function() {
43054      function _descendants(element) {
43055        var nodes = element.getElementsByTagName('*'), results = [];
43056        for (var i = 0, node; node = nodes[i]; i++)
43057          if (node.tagName !== "!") // Filter out comment nodes.
43058            results.push(node);
43059        return results;
43060      }
43061
43062      Element.Methods.down = function(element, expression, index) {
43063        element = $(element);
43064        if (arguments.length == 1) return element.firstDescendant();
43065        return Object.isNumber(expression) ? _descendants(element)[expression] :
43066          Element.select(element, expression)[index || 0];
43067      }
43068    })();
43069  }
43070
43071}
43072
43073else if (Prototype.Browser.Gecko && /rv:1\.8\.0/.test(navigator.userAgent)) {
43074  Element.Methods.setOpacity = function(element, value) {
43075    element = $(element);
43076    element.style.opacity = (value == 1) ? 0.999999 :
43077      (value === '') ? '' : (value < 0.00001) ? 0 : value;
43078    return element;
43079  };
43080}
43081
43082else if (Prototype.Browser.WebKit) {
43083  Element.Methods.setOpacity = function(element, value) {
43084    element = $(element);
43085    element.style.opacity = (value == 1 || value === '') ? '' :
43086      (value < 0.00001) ? 0 : value;
43087
43088    if (value == 1)
43089      if(element.tagName.toUpperCase() == 'IMG' && element.width) {
43090        element.width++; element.width--;
43091      } else try {
43092        var n = document.createTextNode(' ');
43093        element.appendChild(n);
43094        element.removeChild(n);
43095      } catch (e) { }
43096
43097    return element;
43098  };
43099
43100  Element.Methods.cumulativeOffset = function(element) {
43101    var valueT = 0, valueL = 0;
43102    do {
43103      valueT += element.offsetTop  || 0;
43104      valueL += element.offsetLeft || 0;
43105      if (element.offsetParent == document.body)
43106        if (Element.getStyle(element, 'position') == 'absolute') break;
43107
43108      element = element.offsetParent;
43109    } while (element);
43110
43111    return Element._returnOffset(valueL, valueT);
43112  };
43113}
43114
43115if ('outerHTML' in document.documentElement) {
43116  Element.Methods.replace = function(element, content) {
43117    element = $(element);
43118
43119    if (content && content.toElement) content = content.toElement();
43120    if (Object.isElement(content)) {
43121      element.parentNode.replaceChild(content, element);
43122      return element;
43123    }
43124
43125    content = Object.toHTML(content);
43126    var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
43127
43128    if (Element._insertionTranslations.tags[tagName]) {
43129      var nextSibling = element.next();
43130      var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
43131      parent.removeChild(element);
43132      if (nextSibling)
43133        fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
43134      else
43135        fragments.each(function(node) { parent.appendChild(node) });
43136    }
43137    else element.outerHTML = content.stripScripts();
43138
43139    content.evalScripts.bind(content).defer();
43140    return element;
43141  };
43142}
43143
43144Element._returnOffset = function(l, t) {
43145  var result = [l, t];
43146  result.left = l;
43147  result.top = t;
43148  return result;
43149};
43150
43151Element._getContentFromAnonymousElement = function(tagName, html) {
43152  var div = new Element('div'), t = Element._insertionTranslations.tags[tagName];
43153  if (t) {
43154    div.innerHTML = t[0] + html + t[1];
43155    t[2].times(function() { div = div.firstChild });
43156  } else div.innerHTML = html;
43157  return $A(div.childNodes);
43158};
43159
43160Element._insertionTranslations = {
43161  before: function(element, node) {
43162    element.parentNode.insertBefore(node, element);
43163  },
43164  top: function(element, node) {
43165    element.insertBefore(node, element.firstChild);
43166  },
43167  bottom: function(element, node) {
43168    element.appendChild(node);
43169  },
43170  after: function(element, node) {
43171    element.parentNode.insertBefore(node, element.nextSibling);
43172  },
43173  tags: {
43174    TABLE:  ['<table>',                '</table>',                   1],
43175    TBODY:  ['<table><tbody>',         '</tbody></table>',           2],
43176    TR:     ['<table><tbody><tr>',     '</tr></tbody></table>',      3],
43177    TD:     ['<table><tbody><tr><td>', '</td></tr></tbody></table>', 4],
43178    SELECT: ['<select>',               '</select>',                  1]
43179  }
43180};
43181
43182(function() {
43183  var tags = Element._insertionTranslations.tags;
43184  Object.extend(tags, {
43185    THEAD: tags.TBODY,
43186    TFOOT: tags.TBODY,
43187    TH:    tags.TD
43188  });
43189})();
43190
43191Element.Methods.Simulated = {
43192  hasAttribute: function(element, attribute) {
43193    attribute = Element._attributeTranslations.has[attribute] || attribute;
43194    var node = $(element).getAttributeNode(attribute);
43195    return !!(node && node.specified);
43196  }
43197};
43198
43199Element.Methods.ByTag = { };
43200
43201Object.extend(Element, Element.Methods);
43202
43203(function(div) {
43204
43205  if (!Prototype.BrowserFeatures.ElementExtensions && div['__proto__']) {
43206    window.HTMLElement = { };
43207    window.HTMLElement.prototype = div['__proto__'];
43208    Prototype.BrowserFeatures.ElementExtensions = true;
43209  }
43210
43211  div = null;
43212
43213})(document.createElement('div'))
43214
43215Element.extend = (function() {
43216
43217  function checkDeficiency(tagName) {
43218    if (typeof window.Element != 'undefined') {
43219      var proto = window.Element.prototype;
43220      if (proto) {
43221        var id = '_' + (Math.random()+'').slice(2);
43222        var el = document.createElement(tagName);
43223        proto[id] = 'x';
43224        var isBuggy = (el[id] !== 'x');
43225        delete proto[id];
43226        el = null;
43227        return isBuggy;
43228      }
43229    }
43230    return false;
43231  }
43232
43233  function extendElementWith(element, methods) {
43234    for (var property in methods) {
43235      var value = methods[property];
43236      if (Object.isFunction(value) && !(property in element))
43237        element[property] = value.methodize();
43238    }
43239  }
43240
43241  var HTMLOBJECTELEMENT_PROTOTYPE_BUGGY = checkDeficiency('object');
43242
43243  if (Prototype.BrowserFeatures.SpecificElementExtensions) {
43244    if (HTMLOBJECTELEMENT_PROTOTYPE_BUGGY) {
43245      return function(element) {
43246        if (element && typeof element._extendedByPrototype == 'undefined') {
43247          var t = element.tagName;
43248          if (t && (/^(?:object|applet|embed)$/i.test(t))) {
43249            extendElementWith(element, Element.Methods);
43250            extendElementWith(element, Element.Methods.Simulated);
43251            extendElementWith(element, Element.Methods.ByTag[t.toUpperCase()]);
43252          }
43253        }
43254        return element;
43255      }
43256    }
43257    return Prototype.K;
43258  }
43259
43260  var Methods = { }, ByTag = Element.Methods.ByTag;
43261
43262  var extend = Object.extend(function(element) {
43263    if (!element || typeof element._extendedByPrototype != 'undefined' ||
43264        element.nodeType != 1 || element == window) return element;
43265
43266    var methods = Object.clone(Methods),
43267        tagName = element.tagName.toUpperCase();
43268
43269    if (ByTag[tagName]) Object.extend(methods, ByTag[tagName]);
43270
43271    extendElementWith(element, methods);
43272
43273    element._extendedByPrototype = Prototype.emptyFunction;
43274    return element;
43275
43276  }, {
43277    refresh: function() {
43278      if (!Prototype.BrowserFeatures.ElementExtensions) {
43279        Object.extend(Methods, Element.Methods);
43280        Object.extend(Methods, Element.Methods.Simulated);
43281      }
43282    }
43283  });
43284
43285  extend.refresh();
43286  return extend;
43287})();
43288
43289Element.hasAttribute = function(element, attribute) {
43290  if (element.hasAttribute) return element.hasAttribute(attribute);
43291  return Element.Methods.Simulated.hasAttribute(element, attribute);
43292};
43293
43294Element.addMethods = function(methods) {
43295  var F = Prototype.BrowserFeatures, T = Element.Methods.ByTag;
43296
43297  if (!methods) {
43298    Object.extend(Form, Form.Methods);
43299    Object.extend(Form.Element, Form.Element.Methods);
43300    Object.extend(Element.Methods.ByTag, {
43301      "FORM":     Object.clone(Form.Methods),
43302      "INPUT":    Object.clone(Form.Element.Methods),
43303      "SELECT":   Object.clone(Form.Element.Methods),
43304      "TEXTAREA": Object.clone(Form.Element.Methods)
43305    });
43306  }
43307
43308  if (arguments.length == 2) {
43309    var tagName = methods;
43310    methods = arguments[1];
43311  }
43312
43313  if (!tagName) Object.extend(Element.Methods, methods || { });
43314  else {
43315    if (Object.isArray(tagName)) tagName.each(extend);
43316    else extend(tagName);
43317  }
43318
43319  function extend(tagName) {
43320    tagName = tagName.toUpperCase();
43321    if (!Element.Methods.ByTag[tagName])
43322      Element.Methods.ByTag[tagName] = { };
43323    Object.extend(Element.Methods.ByTag[tagName], methods);
43324  }
43325
43326  function copy(methods, destination, onlyIfAbsent) {
43327    onlyIfAbsent = onlyIfAbsent || false;
43328    for (var property in methods) {
43329      var value = methods[property];
43330      if (!Object.isFunction(value)) continue;
43331      if (!onlyIfAbsent || !(property in destination))
43332        destination[property] = value.methodize();
43333    }
43334  }
43335
43336  function findDOMClass(tagName) {
43337    var klass;
43338    var trans = {
43339      "OPTGROUP": "OptGroup", "TEXTAREA": "TextArea", "P": "Paragraph",
43340      "FIELDSET": "FieldSet", "UL": "UList", "OL": "OList", "DL": "DList",
43341      "DIR": "Directory", "H1": "Heading", "H2": "Heading", "H3": "Heading",
43342      "H4": "Heading", "H5": "Heading", "H6": "Heading", "Q": "Quote",
43343      "INS": "Mod", "DEL": "Mod", "A": "Anchor", "IMG": "Image", "CAPTION":
43344      "TableCaption", "COL": "TableCol", "COLGROUP": "TableCol", "THEAD":
43345      "TableSection", "TFOOT": "TableSection", "TBODY": "TableSection", "TR":
43346      "TableRow", "TH": "TableCell", "TD": "TableCell", "FRAMESET":
43347      "FrameSet", "IFRAME": "IFrame"
43348    };
43349    if (trans[tagName]) klass = 'HTML' + trans[tagName] + 'Element';
43350    if (window[klass]) return window[klass];
43351    klass = 'HTML' + tagName + 'Element';
43352    if (window[klass]) return window[klass];
43353    klass = 'HTML' + tagName.capitalize() + 'Element';
43354    if (window[klass]) return window[klass];
43355
43356    var element = document.createElement(tagName);
43357    var proto = element['__proto__'] || element.constructor.prototype;
43358    element = null;
43359    return proto;
43360  }
43361
43362  var elementPrototype = window.HTMLElement ? HTMLElement.prototype :
43363   Element.prototype;
43364
43365  if (F.ElementExtensions) {
43366    copy(Element.Methods, elementPrototype);
43367    copy(Element.Methods.Simulated, elementPrototype, true);
43368  }
43369
43370  if (F.SpecificElementExtensions) {
43371    for (var tag in Element.Methods.ByTag) {
43372      var klass = findDOMClass(tag);
43373      if (Object.isUndefined(klass)) continue;
43374      copy(T[tag], klass.prototype);
43375    }
43376  }
43377
43378  Object.extend(Element, Element.Methods);
43379  delete Element.ByTag;
43380
43381  if (Element.extend.refresh) Element.extend.refresh();
43382  Element.cache = { };
43383};
43384
43385
43386document.viewport = {
43387
43388  getDimensions: function() {
43389    return { width: this.getWidth(), height: this.getHeight() };
43390  },
43391
43392  getScrollOffsets: function() {
43393    return Element._returnOffset(
43394      window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
43395      window.pageYOffset || document.documentElement.scrollTop  || document.body.scrollTop);
43396  }
43397};
43398
43399(function(viewport) {
43400  var B = Prototype.Browser, doc = document, element, property = {};
43401
43402  function getRootElement() {
43403    if (B.WebKit && !doc.evaluate)
43404      return document;
43405
43406    if (B.Opera && window.parseFloat(window.opera.version()) < 9.5)
43407      return document.body;
43408
43409    return document.documentElement;
43410  }
43411
43412  function define(D) {
43413    if (!element) element = getRootElement();
43414
43415    property[D] = 'client' + D;
43416
43417    viewport['get' + D] = function() { return element[property[D]] };
43418    return viewport['get' + D]();
43419  }
43420
43421  viewport.getWidth  = define.curry('Width');
43422
43423  viewport.getHeight = define.curry('Height');
43424})(document.viewport);
43425
43426
43427Element.Storage = {
43428  UID: 1
43429};
43430
43431Element.addMethods({
43432  getStorage: function(element) {
43433    if (!(element = $(element))) return;
43434
43435    var uid;
43436    if (element === window) {
43437      uid = 0;
43438    } else {
43439      if (typeof element._prototypeUID === "undefined")
43440        element._prototypeUID = [Element.Storage.UID++];
43441      uid = element._prototypeUID[0];
43442    }
43443
43444    if (!Element.Storage[uid])
43445      Element.Storage[uid] = $H();
43446
43447    return Element.Storage[uid];
43448  },
43449
43450  store: function(element, key, value) {
43451    if (!(element = $(element))) return;
43452
43453    if (arguments.length === 2) {
43454      Element.getStorage(element).update(key);
43455    } else {
43456      Element.getStorage(element).set(key, value);
43457    }
43458
43459    return element;
43460  },
43461
43462  retrieve: function(element, key, defaultValue) {
43463    if (!(element = $(element))) return;
43464    var hash = Element.getStorage(element), value = hash.get(key);
43465
43466    if (Object.isUndefined(value)) {
43467      hash.set(key, defaultValue);
43468      value = defaultValue;
43469    }
43470
43471    return value;
43472  },
43473
43474  clone: function(element, deep) {
43475    if (!(element = $(element))) return;
43476    var clone = element.cloneNode(deep);
43477    clone._prototypeUID = void 0;
43478    if (deep) {
43479      var descendants = Element.select(clone, '*'),
43480          i = descendants.length;
43481      while (i--) {
43482        descendants[i]._prototypeUID = void 0;
43483      }
43484    }
43485    return Element.extend(clone);
43486  }
43487});
43488/* Portions of the Selector class are derived from Jack Slocum's DomQuery,
43489 * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style
43490 * license.  Please see http://www.yui-ext.com/ for more information. */
43491
43492var Selector = Class.create({
43493  initialize: function(expression) {
43494    this.expression = expression.strip();
43495
43496    if (this.shouldUseSelectorsAPI()) {
43497      this.mode = 'selectorsAPI';
43498    } else if (this.shouldUseXPath()) {
43499      this.mode = 'xpath';
43500      this.compileXPathMatcher();
43501    } else {
43502      this.mode = "normal";
43503      this.compileMatcher();
43504    }
43505
43506  },
43507
43508  shouldUseXPath: (function() {
43509
43510    var IS_DESCENDANT_SELECTOR_BUGGY = (function(){
43511      var isBuggy = false;
43512      if (document.evaluate && window.XPathResult) {
43513        var el = document.createElement('div');
43514        el.innerHTML = '<ul><li></li></ul><div><ul><li></li></ul></div>';
43515
43516        var xpath = ".//*[local-name()='ul' or local-name()='UL']" +
43517          "//*[local-name()='li' or local-name()='LI']";
43518
43519        var result = document.evaluate(xpath, el, null,
43520          XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
43521
43522        isBuggy = (result.snapshotLength !== 2);
43523        el = null;
43524      }
43525      return isBuggy;
43526    })();
43527
43528    return function() {
43529      if (!Prototype.BrowserFeatures.XPath) return false;
43530
43531      var e = this.expression;
43532
43533      if (Prototype.Browser.WebKit &&
43534       (e.include("-of-type") || e.include(":empty")))
43535        return false;
43536
43537      if ((/(\[[\w-]*?:|:checked)/).test(e))
43538        return false;
43539
43540      if (IS_DESCENDANT_SELECTOR_BUGGY) return false;
43541
43542      return true;
43543    }
43544
43545  })(),
43546
43547  shouldUseSelectorsAPI: function() {
43548    if (!Prototype.BrowserFeatures.SelectorsAPI) return false;
43549
43550    if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false;
43551
43552    if (!Selector._div) Selector._div = new Element('div');
43553
43554    try {
43555      Selector._div.querySelector(this.expression);
43556    } catch(e) {
43557      return false;
43558    }
43559
43560    return true;
43561  },
43562
43563  compileMatcher: function() {
43564    var e = this.expression, ps = Selector.patterns, h = Selector.handlers,
43565        c = Selector.criteria, le, p, m, len = ps.length, name;
43566
43567    if (Selector._cache[e]) {
43568      this.matcher = Selector._cache[e];
43569      return;
43570    }
43571
43572    this.matcher = ["this.matcher = function(root) {",
43573                    "var r = root, h = Selector.handlers, c = false, n;"];
43574
43575    while (e && le != e && (/\S/).test(e)) {
43576      le = e;
43577      for (var i = 0; i<len; i++) {
43578        p = ps[i].re;
43579        name = ps[i].name;
43580        if (m = e.match(p)) {
43581          this.matcher.push(Object.isFunction(c[name]) ? c[name](m) :
43582            new Template(c[name]).evaluate(m));
43583          e = e.replace(m[0], '');
43584          break;
43585        }
43586      }
43587    }
43588
43589    this.matcher.push("return h.unique(n);\n}");
43590    eval(this.matcher.join('\n'));
43591    Selector._cache[this.expression] = this.matcher;
43592  },
43593
43594  compileXPathMatcher: function() {
43595    var e = this.expression, ps = Selector.patterns,
43596        x = Selector.xpath, le, m, len = ps.length, name;
43597
43598    if (Selector._cache[e]) {
43599      this.xpath = Selector._cache[e]; return;
43600    }
43601
43602    this.matcher = ['.//*'];
43603    while (e && le != e && (/\S/).test(e)) {
43604      le = e;
43605      for (var i = 0; i<len; i++) {
43606        name = ps[i].name;
43607        if (m = e.match(ps[i].re)) {
43608          this.matcher.push(Object.isFunction(x[name]) ? x[name](m) :
43609            new Template(x[name]).evaluate(m));
43610          e = e.replace(m[0], '');
43611          break;
43612        }
43613      }
43614    }
43615
43616    this.xpath = this.matcher.join('');
43617    Selector._cache[this.expression] = this.xpath;
43618  },
43619
43620  findElements: function(root) {
43621    root = root || document;
43622    var e = this.expression, results;
43623
43624    switch (this.mode) {
43625      case 'selectorsAPI':
43626        if (root !== document) {
43627          var oldId = root.id, id = $(root).identify();
43628          id = id.replace(/([\.:])/g, "\\$1");
43629          e = "#" + id + " " + e;
43630        }
43631
43632        results = $A(root.querySelectorAll(e)).map(Element.extend);
43633        root.id = oldId;
43634
43635        return results;
43636      case 'xpath':
43637        return document._getElementsByXPath(this.xpath, root);
43638      default:
43639       return this.matcher(root);
43640    }
43641  },
43642
43643  match: function(element) {
43644    this.tokens = [];
43645
43646    var e = this.expression, ps = Selector.patterns, as = Selector.assertions;
43647    var le, p, m, len = ps.length, name;
43648
43649    while (e && le !== e && (/\S/).test(e)) {
43650      le = e;
43651      for (var i = 0; i<len; i++) {
43652        p = ps[i].re;
43653        name = ps[i].name;
43654        if (m = e.match(p)) {
43655          if (as[name]) {
43656            this.tokens.push([name, Object.clone(m)]);
43657            e = e.replace(m[0], '');
43658          } else {
43659            return this.findElements(document).include(element);
43660          }
43661        }
43662      }
43663    }
43664
43665    var match = true, name, matches;
43666    for (var i = 0, token; token = this.tokens[i]; i++) {
43667      name = token[0], matches = token[1];
43668      if (!Selector.assertions[name](element, matches)) {
43669        match = false; break;
43670      }
43671    }
43672
43673    return match;
43674  },
43675
43676  toString: function() {
43677    return this.expression;
43678  },
43679
43680  inspect: function() {
43681    return "#<Selector:" + this.expression.inspect() + ">";
43682  }
43683});
43684
43685if (Prototype.BrowserFeatures.SelectorsAPI &&
43686 document.compatMode === 'BackCompat') {
43687  Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){
43688    var div = document.createElement('div'),
43689     span = document.createElement('span');
43690
43691    div.id = "prototype_test_id";
43692    span.className = 'Test';
43693    div.appendChild(span);
43694    var isIgnored = (div.querySelector('#prototype_test_id .test') !== null);
43695    div = span = null;
43696    return isIgnored;
43697  })();
43698}
43699
43700Object.extend(Selector, {
43701  _cache: { },
43702
43703  xpath: {
43704    descendant:   "//*",
43705    child:        "/*",
43706    adjacent:     "/following-sibling::*[1]",
43707    laterSibling: '/following-sibling::*',
43708    tagName:      function(m) {
43709      if (m[1] == '*') return '';
43710      return "[local-name()='" + m[1].toLowerCase() +
43711             "' or local-name()='" + m[1].toUpperCase() + "']";
43712    },
43713    className:    "[contains(concat(' ', @class, ' '), ' #{1} ')]",
43714    id:           "[@id='#{1}']",
43715    attrPresence: function(m) {
43716      m[1] = m[1].toLowerCase();
43717      return new Template("[@#{1}]").evaluate(m);
43718    },
43719    attr: function(m) {
43720      m[1] = m[1].toLowerCase();
43721      m[3] = m[5] || m[6];
43722      return new Template(Selector.xpath.operators[m[2]]).evaluate(m);
43723    },
43724    pseudo: function(m) {
43725      var h = Selector.xpath.pseudos[m[1]];
43726      if (!h) return '';
43727      if (Object.isFunction(h)) return h(m);
43728      return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m);
43729    },
43730    operators: {
43731      '=':  "[@#{1}='#{3}']",
43732      '!=': "[@#{1}!='#{3}']",
43733      '^=': "[starts-with(@#{1}, '#{3}')]",
43734      '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']",
43735      '*=': "[contains(@#{1}, '#{3}')]",
43736      '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]",
43737      '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]"
43738    },
43739    pseudos: {
43740      'first-child': '[not(preceding-sibling::*)]',
43741      'last-child':  '[not(following-sibling::*)]',
43742      'only-child':  '[not(preceding-sibling::* or following-sibling::*)]',
43743      'empty':       "[count(*) = 0 and (count(text()) = 0)]",
43744      'checked':     "[@checked]",
43745      'disabled':    "[(@disabled) and (@type!='hidden')]",
43746      'enabled':     "[not(@disabled) and (@type!='hidden')]",
43747      'not': function(m) {
43748        var e = m[6], p = Selector.patterns,
43749            x = Selector.xpath, le, v, len = p.length, name;
43750
43751        var exclusion = [];
43752        while (e && le != e && (/\S/).test(e)) {
43753          le = e;
43754          for (var i = 0; i<len; i++) {
43755            name = p[i].name
43756            if (m = e.match(p[i].re)) {
43757              v = Object.isFunction(x[name]) ? x[name](m) : new Template(x[name]).evaluate(m);
43758              exclusion.push("(" + v.substring(1, v.length - 1) + ")");
43759              e = e.replace(m[0], '');
43760              break;
43761            }
43762          }
43763        }
43764        return "[not(" + exclusion.join(" and ") + ")]";
43765      },
43766      'nth-child':      function(m) {
43767        return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m);
43768      },
43769      'nth-last-child': function(m) {
43770        return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m);
43771      },
43772      'nth-of-type':    function(m) {
43773        return Selector.xpath.pseudos.nth("position() ", m);
43774      },
43775      'nth-last-of-type': function(m) {
43776        return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m);
43777      },
43778      'first-of-type':  function(m) {
43779        m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m);
43780      },
43781      'last-of-type':   function(m) {
43782        m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m);
43783      },
43784      'only-of-type':   function(m) {
43785        var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m);
43786      },
43787      nth: function(fragment, m) {
43788        var mm, formula = m[6], predicate;
43789        if (formula == 'even') formula = '2n+0';
43790        if (formula == 'odd')  formula = '2n+1';
43791        if (mm = formula.match(/^(\d+)$/)) // digit only
43792          return '[' + fragment + "= " + mm[1] + ']';
43793        if (mm = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
43794          if (mm[1] == "-") mm[1] = -1;
43795          var a = mm[1] ? Number(mm[1]) : 1;
43796          var b = mm[2] ? Number(mm[2]) : 0;
43797          predicate = "[((#{fragment} - #{b}) mod #{a} = 0) and " +
43798          "((#{fragment} - #{b}) div #{a} >= 0)]";
43799          return new Template(predicate).evaluate({
43800            fragment: fragment, a: a, b: b });
43801        }
43802      }
43803    }
43804  },
43805
43806  criteria: {
43807    tagName:      'n = h.tagName(n, r, "#{1}", c);      c = false;',
43808    className:    'n = h.className(n, r, "#{1}", c);    c = false;',
43809    id:           'n = h.id(n, r, "#{1}", c);           c = false;',
43810    attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;',
43811    attr: function(m) {
43812      m[3] = (m[5] || m[6]);
43813      return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m);
43814    },
43815    pseudo: function(m) {
43816      if (m[6]) m[6] = m[6].replace(/"/g, '\\"');
43817      return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m);
43818    },
43819    descendant:   'c = "descendant";',
43820    child:        'c = "child";',
43821    adjacent:     'c = "adjacent";',
43822    laterSibling: 'c = "laterSibling";'
43823  },
43824
43825  patterns: [
43826    { name: 'laterSibling', re: /^\s*~\s*/ },
43827    { name: 'child',        re: /^\s*>\s*/ },
43828    { name: 'adjacent',     re: /^\s*\+\s*/ },
43829    { name: 'descendant',   re: /^\s/ },
43830
43831    { name: 'tagName',      re: /^\s*(\*|[\w\-]+)(\b|$)?/ },
43832    { name: 'id',           re: /^#([\w\-\*]+)(\b|$)/ },
43833    { name: 'className',    re: /^\.([\w\-\*]+)(\b|$)/ },
43834    { name: 'pseudo',       re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ },
43835    { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ },
43836    { name: 'attr',         re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ }
43837  ],
43838
43839  assertions: {
43840    tagName: function(element, matches) {
43841      return matches[1].toUpperCase() == element.tagName.toUpperCase();
43842    },
43843
43844    className: function(element, matches) {
43845      return Element.hasClassName(element, matches[1]);
43846    },
43847
43848    id: function(element, matches) {
43849      return element.id === matches[1];
43850    },
43851
43852    attrPresence: function(element, matches) {
43853      return Element.hasAttribute(element, matches[1]);
43854    },
43855
43856    attr: function(element, matches) {
43857      var nodeValue = Element.readAttribute(element, matches[1]);
43858      return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]);
43859    }
43860  },
43861
43862  handlers: {
43863    concat: function(a, b) {
43864      for (var i = 0, node; node = b[i]; i++)
43865        a.push(node);
43866      return a;
43867    },
43868
43869    mark: function(nodes) {
43870      var _true = Prototype.emptyFunction;
43871      for (var i = 0, node; node = nodes[i]; i++)
43872        node._countedByPrototype = _true;
43873      return nodes;
43874    },
43875
43876    unmark: (function(){
43877
43878      var PROPERTIES_ATTRIBUTES_MAP = (function(){
43879        var el = document.createElement('div'),
43880            isBuggy = false,
43881            propName = '_countedByPrototype',
43882            value = 'x'
43883        el[propName] = value;
43884        isBuggy = (el.getAttribute(propName) === value);
43885        el = null;
43886        return isBuggy;
43887      })();
43888
43889      return PROPERTIES_ATTRIBUTES_MAP ?
43890        function(nodes) {
43891          for (var i = 0, node; node = nodes[i]; i++)
43892            node.removeAttribute('_countedByPrototype');
43893          return nodes;
43894        } :
43895        function(nodes) {
43896          for (var i = 0, node; node = nodes[i]; i++)
43897            node._countedByPrototype = void 0;
43898          return nodes;
43899        }
43900    })(),
43901
43902    index: function(parentNode, reverse, ofType) {
43903      parentNode._countedByPrototype = Prototype.emptyFunction;
43904      if (reverse) {
43905        for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) {
43906          var node = nodes[i];
43907          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
43908        }
43909      } else {
43910        for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++)
43911          if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++;
43912      }
43913    },
43914
43915    unique: function(nodes) {
43916      if (nodes.length == 0) return nodes;
43917      var results = [], n;
43918      for (var i = 0, l = nodes.length; i < l; i++)
43919        if (typeof (n = nodes[i])._countedByPrototype == 'undefined') {
43920          n._countedByPrototype = Prototype.emptyFunction;
43921          results.push(Element.extend(n));
43922        }
43923      return Selector.handlers.unmark(results);
43924    },
43925
43926    descendant: function(nodes) {
43927      var h = Selector.handlers;
43928      for (var i = 0, results = [], node; node = nodes[i]; i++)
43929        h.concat(results, node.getElementsByTagName('*'));
43930      return results;
43931    },
43932
43933    child: function(nodes) {
43934      var h = Selector.handlers;
43935      for (var i = 0, results = [], node; node = nodes[i]; i++) {
43936        for (var j = 0, child; child = node.childNodes[j]; j++)
43937          if (child.nodeType == 1 && child.tagName != '!') results.push(child);
43938      }
43939      return results;
43940    },
43941
43942    adjacent: function(nodes) {
43943      for (var i = 0, results = [], node; node = nodes[i]; i++) {
43944        var next = this.nextElementSibling(node);
43945        if (next) results.push(next);
43946      }
43947      return results;
43948    },
43949
43950    laterSibling: function(nodes) {
43951      var h = Selector.handlers;
43952      for (var i = 0, results = [], node; node = nodes[i]; i++)
43953        h.concat(results, Element.nextSiblings(node));
43954      return results;
43955    },
43956
43957    nextElementSibling: function(node) {
43958      while (node = node.nextSibling)
43959        if (node.nodeType == 1) return node;
43960      return null;
43961    },
43962
43963    previousElementSibling: function(node) {
43964      while (node = node.previousSibling)
43965        if (node.nodeType == 1) return node;
43966      return null;
43967    },
43968
43969    tagName: function(nodes, root, tagName, combinator) {
43970      var uTagName = tagName.toUpperCase();
43971      var results = [], h = Selector.handlers;
43972      if (nodes) {
43973        if (combinator) {
43974          if (combinator == "descendant") {
43975            for (var i = 0, node; node = nodes[i]; i++)
43976              h.concat(results, node.getElementsByTagName(tagName));
43977            return results;
43978          } else nodes = this[combinator](nodes);
43979          if (tagName == "*") return nodes;
43980        }
43981        for (var i = 0, node; node = nodes[i]; i++)
43982          if (node.tagName.toUpperCase() === uTagName) results.push(node);
43983        return results;
43984      } else return root.getElementsByTagName(tagName);
43985    },
43986
43987    id: function(nodes, root, id, combinator) {
43988      var targetNode = $(id), h = Selector.handlers;
43989
43990      if (root == document) {
43991        if (!targetNode) return [];
43992        if (!nodes) return [targetNode];
43993      } else {
43994        if (!root.sourceIndex || root.sourceIndex < 1) {
43995          var nodes = root.getElementsByTagName('*');
43996          for (var j = 0, node; node = nodes[j]; j++) {
43997            if (node.id === id) return [node];
43998          }
43999        }
44000      }
44001
44002      if (nodes) {
44003        if (combinator) {
44004          if (combinator == 'child') {
44005            for (var i = 0, node; node = nodes[i]; i++)
44006              if (targetNode.parentNode == node) return [targetNode];
44007          } else if (combinator == 'descendant') {
44008            for (var i = 0, node; node = nodes[i]; i++)
44009              if (Element.descendantOf(targetNode, node)) return [targetNode];
44010          } else if (combinator == 'adjacent') {
44011            for (var i = 0, node; node = nodes[i]; i++)
44012              if (Selector.handlers.previousElementSibling(targetNode) == node)
44013                return [targetNode];
44014          } else nodes = h[combinator](nodes);
44015        }
44016        for (var i = 0, node; node = nodes[i]; i++)
44017          if (node == targetNode) return [targetNode];
44018        return [];
44019      }
44020      return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : [];
44021    },
44022
44023    className: function(nodes, root, className, combinator) {
44024      if (nodes && combinator) nodes = this[combinator](nodes);
44025      return Selector.handlers.byClassName(nodes, root, className);
44026    },
44027
44028    byClassName: function(nodes, root, className) {
44029      if (!nodes) nodes = Selector.handlers.descendant([root]);
44030      var needle = ' ' + className + ' ';
44031      for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) {
44032        nodeClassName = node.className;
44033        if (nodeClassName.length == 0) continue;
44034        if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle))
44035          results.push(node);
44036      }
44037      return results;
44038    },
44039
44040    attrPresence: function(nodes, root, attr, combinator) {
44041      if (!nodes) nodes = root.getElementsByTagName("*");
44042      if (nodes && combinator) nodes = this[combinator](nodes);
44043      var results = [];
44044      for (var i = 0, node; node = nodes[i]; i++)
44045        if (Element.hasAttribute(node, attr)) results.push(node);
44046      return results;
44047    },
44048
44049    attr: function(nodes, root, attr, value, operator, combinator) {
44050      if (!nodes) nodes = root.getElementsByTagName("*");
44051      if (nodes && combinator) nodes = this[combinator](nodes);
44052      var handler = Selector.operators[operator], results = [];
44053      for (var i = 0, node; node = nodes[i]; i++) {
44054        var nodeValue = Element.readAttribute(node, attr);
44055        if (nodeValue === null) continue;
44056        if (handler(nodeValue, value)) results.push(node);
44057      }
44058      return results;
44059    },
44060
44061    pseudo: function(nodes, name, value, root, combinator) {
44062      if (nodes && combinator) nodes = this[combinator](nodes);
44063      if (!nodes) nodes = root.getElementsByTagName("*");
44064      return Selector.pseudos[name](nodes, value, root);
44065    }
44066  },
44067
44068  pseudos: {
44069    'first-child': function(nodes, value, root) {
44070      for (var i = 0, results = [], node; node = nodes[i]; i++) {
44071        if (Selector.handlers.previousElementSibling(node)) continue;
44072          results.push(node);
44073      }
44074      return results;
44075    },
44076    'last-child': function(nodes, value, root) {
44077      for (var i = 0, results = [], node; node = nodes[i]; i++) {
44078        if (Selector.handlers.nextElementSibling(node)) continue;
44079          results.push(node);
44080      }
44081      return results;
44082    },
44083    'only-child': function(nodes, value, root) {
44084      var h = Selector.handlers;
44085      for (var i = 0, results = [], node; node = nodes[i]; i++)
44086        if (!h.previousElementSibling(node) && !h.nextElementSibling(node))
44087          results.push(node);
44088      return results;
44089    },
44090    'nth-child':        function(nodes, formula, root) {
44091      return Selector.pseudos.nth(nodes, formula, root);
44092    },
44093    'nth-last-child':   function(nodes, formula, root) {
44094      return Selector.pseudos.nth(nodes, formula, root, true);
44095    },
44096    'nth-of-type':      function(nodes, formula, root) {
44097      return Selector.pseudos.nth(nodes, formula, root, false, true);
44098    },
44099    'nth-last-of-type': function(nodes, formula, root) {
44100      return Selector.pseudos.nth(nodes, formula, root, true, true);
44101    },
44102    'first-of-type':    function(nodes, formula, root) {
44103      return Selector.pseudos.nth(nodes, "1", root, false, true);
44104    },
44105    'last-of-type':     function(nodes, formula, root) {
44106      return Selector.pseudos.nth(nodes, "1", root, true, true);
44107    },
44108    'only-of-type':     function(nodes, formula, root) {
44109      var p = Selector.pseudos;
44110      return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root);
44111    },
44112
44113    getIndices: function(a, b, total) {
44114      if (a == 0) return b > 0 ? [b] : [];
44115      return $R(1, total).inject([], function(memo, i) {
44116        if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i);
44117        return memo;
44118      });
44119    },
44120
44121    nth: function(nodes, formula, root, reverse, ofType) {
44122      if (nodes.length == 0) return [];
44123      if (formula == 'even') formula = '2n+0';
44124      if (formula == 'odd')  formula = '2n+1';
44125      var h = Selector.handlers, results = [], indexed = [], m;
44126      h.mark(nodes);
44127      for (var i = 0, node; node = nodes[i]; i++) {
44128        if (!node.parentNode._countedByPrototype) {
44129          h.index(node.parentNode, reverse, ofType);
44130          indexed.push(node.parentNode);
44131        }
44132      }
44133      if (formula.match(/^\d+$/)) { // just a number
44134        formula = Number(formula);
44135        for (var i = 0, node; node = nodes[i]; i++)
44136          if (node.nodeIndex == formula) results.push(node);
44137      } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b
44138        if (m[1] == "-") m[1] = -1;
44139        var a = m[1] ? Number(m[1]) : 1;
44140        var b = m[2] ? Number(m[2]) : 0;
44141        var indices = Selector.pseudos.getIndices(a, b, nodes.length);
44142        for (var i = 0, node, l = indices.length; node = nodes[i]; i++) {
44143          for (var j = 0; j < l; j++)
44144            if (node.nodeIndex == indices[j]) results.push(node);
44145        }
44146      }
44147      h.unmark(nodes);
44148      h.unmark(indexed);
44149      return results;
44150    },
44151
44152    'empty': function(nodes, value, root) {
44153      for (var i = 0, results = [], node; node = nodes[i]; i++) {
44154        if (node.tagName == '!' || node.firstChild) continue;
44155        results.push(node);
44156      }
44157      return results;
44158    },
44159
44160    'not': function(nodes, selector, root) {
44161      var h = Selector.handlers, selectorType, m;
44162      var exclusions = new Selector(selector).findElements(root);
44163      h.mark(exclusions);
44164      for (var i = 0, results = [], node; node = nodes[i]; i++)
44165        if (!node._countedByPrototype) results.push(node);
44166      h.unmark(exclusions);
44167      return results;
44168    },
44169
44170    'enabled': function(nodes, value, root) {
44171      for (var i = 0, results = [], node; node = nodes[i]; i++)
44172        if (!node.disabled && (!node.type || node.type !== 'hidden'))
44173          results.push(node);
44174      return results;
44175    },
44176
44177    'disabled': function(nodes, value, root) {
44178      for (var i = 0, results = [], node; node = nodes[i]; i++)
44179        if (node.disabled) results.push(node);
44180      return results;
44181    },
44182
44183    'checked': function(nodes, value, root) {
44184      for (var i = 0, results = [], node; node = nodes[i]; i++)
44185        if (node.checked) results.push(node);
44186      return results;
44187    }
44188  },
44189
44190  operators: {
44191    '=':  function(nv, v) { return nv == v; },
44192    '!=': function(nv, v) { return nv != v; },
44193    '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); },
44194    '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); },
44195    '*=': function(nv, v) { return nv == v || nv && nv.include(v); },
44196    '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); },
44197    '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() +
44198     '-').include('-' + (v || "").toUpperCase() + '-'); }
44199  },
44200
44201  split: function(expression) {
44202    var expressions = [];
44203    expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) {
44204      expressions.push(m[1].strip());
44205    });
44206    return expressions;
44207  },
44208
44209  matchElements: function(elements, expression) {
44210    var matches = $$(expression), h = Selector.handlers;
44211    h.mark(matches);
44212    for (var i = 0, results = [], element; element = elements[i]; i++)
44213      if (element._countedByPrototype) results.push(element);
44214    h.unmark(matches);
44215    return results;
44216  },
44217
44218  findElement: function(elements, expression, index) {
44219    if (Object.isNumber(expression)) {
44220      index = expression; expression = false;
44221    }
44222    return Selector.matchElements(elements, expression || '*')[index || 0];
44223  },
44224
44225  findChildElements: function(element, expressions) {
44226    expressions = Selector.split(expressions.join(','));
44227    var results = [], h = Selector.handlers;
44228    for (var i = 0, l = expressions.length, selector; i < l; i++) {
44229      selector = new Selector(expressions[i].strip());
44230      h.concat(results, selector.findElements(element));
44231    }
44232    return (l > 1) ? h.unique(results) : results;
44233  }
44234});
44235
44236if (Prototype.Browser.IE) {
44237  Object.extend(Selector.handlers, {
44238    concat: function(a, b) {
44239      for (var i = 0, node; node = b[i]; i++)
44240        if (node.tagName !== "!") a.push(node);
44241      return a;
44242    }
44243  });
44244}
44245
44246function $$() {
44247  return Selector.findChildElements(document, $A(arguments));
44248}
44249
44250var Form = {
44251  reset: function(form) {
44252    form = $(form);
44253    form.reset();
44254    return form;
44255  },
44256
44257  serializeElements: function(elements, options) {
44258    if (typeof options != 'object') options = { hash: !!options };
44259    else if (Object.isUndefined(options.hash)) options.hash = true;
44260    var key, value, submitted = false, submit = options.submit;
44261
44262    var data = elements.inject({ }, function(result, element) {
44263      if (!element.disabled && element.name) {
44264        key = element.name; value = $(element).getValue();
44265        if (value != null && element.type != 'file' && (element.type != 'submit' || (!submitted &&
44266            submit !== false && (!submit || key == submit) && (submitted = true)))) {
44267          if (key in result) {
44268            if (!Object.isArray(result[key])) result[key] = [result[key]];
44269            result[key].push(value);
44270          }
44271          else result[key] = value;
44272        }
44273      }
44274      return result;
44275    });
44276
44277    return options.hash ? data : Object.toQueryString(data);
44278  }
44279};
44280
44281Form.Methods = {
44282  serialize: function(form, options) {
44283    return Form.serializeElements(Form.getElements(form), options);
44284  },
44285
44286  getElements: function(form) {
44287    var elements = $(form).getElementsByTagName('*'),
44288        element,
44289        arr = [ ],
44290        serializers = Form.Element.Serializers;
44291    for (var i = 0; element = elements[i]; i++) {
44292      arr.push(element);
44293    }
44294    return arr.inject([], function(elements, child) {
44295      if (serializers[child.tagName.toLowerCase()])
44296        elements.push(Element.extend(child));
44297      return elements;
44298    })
44299  },
44300
44301  getInputs: function(form, typeName, name) {
44302    form = $(form);
44303    var inputs = form.getElementsByTagName('input');
44304
44305    if (!typeName && !name) return $A(inputs).map(Element.extend);
44306
44307    for (var i = 0, matchingInputs = [], length = inputs.length; i < length; i++) {
44308      var input = inputs[i];
44309      if ((typeName && input.type != typeName) || (name && input.name != name))
44310        continue;
44311      matchingInputs.push(Element.extend(input));
44312    }
44313
44314    return matchingInputs;
44315  },
44316
44317  disable: function(form) {
44318    form = $(form);
44319    Form.getElements(form).invoke('disable');
44320    return form;
44321  },
44322
44323  enable: function(form) {
44324    form = $(form);
44325    Form.getElements(form).invoke('enable');
44326    return form;
44327  },
44328
44329  findFirstElement: function(form) {
44330    var elements = $(form).getElements().findAll(function(element) {
44331      return 'hidden' != element.type && !element.disabled;
44332    });
44333    var firstByIndex = elements.findAll(function(element) {
44334      return element.hasAttribute('tabIndex') && element.tabIndex >= 0;
44335    }).sortBy(function(element) { return element.tabIndex }).first();
44336
44337    return firstByIndex ? firstByIndex : elements.find(function(element) {
44338      return /^(?:input|select|textarea)$/i.test(element.tagName);
44339    });
44340  },
44341
44342  focusFirstElement: function(form) {
44343    form = $(form);
44344    form.findFirstElement().activate();
44345    return form;
44346  },
44347
44348  request: function(form, options) {
44349    form = $(form), options = Object.clone(options || { });
44350
44351    var params = options.parameters, action = form.readAttribute('action') || '';
44352    if (action.blank()) action = window.location.href;
44353    options.parameters = form.serialize(true);
44354
44355    if (params) {
44356      if (Object.isString(params)) params = params.toQueryParams();
44357      Object.extend(options.parameters, params);
44358    }
44359
44360    if (form.hasAttribute('method') && !options.method)
44361      options.method = form.method;
44362
44363    return new Ajax.Request(action, options);
44364  }
44365};
44366
44367/*--------------------------------------------------------------------------*/
44368
44369
44370Form.Element = {
44371  focus: function(element) {
44372    $(element).focus();
44373    return element;
44374  },
44375
44376  select: function(element) {
44377    $(element).select();
44378    return element;
44379  }
44380};
44381
44382Form.Element.Methods = {
44383
44384  serialize: function(element) {
44385    element = $(element);
44386    if (!element.disabled && element.name) {
44387      var value = element.getValue();
44388      if (value != undefined) {
44389        var pair = { };
44390        pair[element.name] = value;
44391        return Object.toQueryString(pair);
44392      }
44393    }
44394    return '';
44395  },
44396
44397  getValue: function(element) {
44398    element = $(element);
44399    var method = element.tagName.toLowerCase();
44400    return Form.Element.Serializers[method](element);
44401  },
44402
44403  setValue: function(element, value) {
44404    element = $(element);
44405    var method = element.tagName.toLowerCase();
44406    Form.Element.Serializers[method](element, value);
44407    return element;
44408  },
44409
44410  clear: function(element) {
44411    $(element).value = '';
44412    return element;
44413  },
44414
44415  present: function(element) {
44416    return $(element).value != '';
44417  },
44418
44419  activate: function(element) {
44420    element = $(element);
44421    try {
44422      element.focus();
44423      if (element.select && (element.tagName.toLowerCase() != 'input' ||
44424          !(/^(?:button|reset|submit)$/i.test(element.type))))
44425        element.select();
44426    } catch (e) { }
44427    return element;
44428  },
44429
44430  disable: function(element) {
44431    element = $(element);
44432    element.disabled = true;
44433    return element;
44434  },
44435
44436  enable: function(element) {
44437    element = $(element);
44438    element.disabled = false;
44439    return element;
44440  }
44441};
44442
44443/*--------------------------------------------------------------------------*/
44444
44445var Field = Form.Element;
44446
44447var $F = Form.Element.Methods.getValue;
44448
44449/*--------------------------------------------------------------------------*/
44450
44451Form.Element.Serializers = {
44452  input: function(element, value) {
44453    switch (element.type.toLowerCase()) {
44454      case 'checkbox':
44455      case 'radio':
44456        return Form.Element.Serializers.inputSelector(element, value);
44457      default:
44458        return Form.Element.Serializers.textarea(element, value);
44459    }
44460  },
44461
44462  inputSelector: function(element, value) {
44463    if (Object.isUndefined(value)) return element.checked ? element.value : null;
44464    else element.checked = !!value;
44465  },
44466
44467  textarea: function(element, value) {
44468    if (Object.isUndefined(value)) return element.value;
44469    else element.value = value;
44470  },
44471
44472  select: function(element, value) {
44473    if (Object.isUndefined(value))
44474      return this[element.type == 'select-one' ?
44475        'selectOne' : 'selectMany'](element);
44476    else {
44477      var opt, currentValue, single = !Object.isArray(value);
44478      for (var i = 0, length = element.length; i < length; i++) {
44479        opt = element.options[i];
44480        currentValue = this.optionValue(opt);
44481        if (single) {
44482          if (currentValue == value) {
44483            opt.selected = true;
44484            return;
44485          }
44486        }
44487        else opt.selected = value.include(currentValue);
44488      }
44489    }
44490  },
44491
44492  selectOne: function(element) {
44493    var index = element.selectedIndex;
44494    return index >= 0 ? this.optionValue(element.options[index]) : null;
44495  },
44496
44497  selectMany: function(element) {
44498    var values, length = element.length;
44499    if (!length) return null;
44500
44501    for (var i = 0, values = []; i < length; i++) {
44502      var opt = element.options[i];
44503      if (opt.selected) values.push(this.optionValue(opt));
44504    }
44505    return values;
44506  },
44507
44508  optionValue: function(opt) {
44509    return Element.extend(opt).hasAttribute('value') ? opt.value : opt.text;
44510  }
44511};
44512
44513/*--------------------------------------------------------------------------*/
44514
44515
44516Abstract.TimedObserver = Class.create(PeriodicalExecuter, {
44517  initialize: function($super, element, frequency, callback) {
44518    $super(callback, frequency);
44519    this.element   = $(element);
44520    this.lastValue = this.getValue();
44521  },
44522
44523  execute: function() {
44524    var value = this.getValue();
44525    if (Object.isString(this.lastValue) && Object.isString(value) ?
44526        this.lastValue != value : String(this.lastValue) != String(value)) {
44527      this.callback(this.element, value);
44528      this.lastValue = value;
44529    }
44530  }
44531});
44532
44533Form.Element.Observer = Class.create(Abstract.TimedObserver, {
44534  getValue: function() {
44535    return Form.Element.getValue(this.element);
44536  }
44537});
44538
44539Form.Observer = Class.create(Abstract.TimedObserver, {
44540  getValue: function() {
44541    return Form.serialize(this.element);
44542  }
44543});
44544
44545/*--------------------------------------------------------------------------*/
44546
44547Abstract.EventObserver = Class.create({
44548  initialize: function(element, callback) {
44549    this.element  = $(element);
44550    this.callback = callback;
44551
44552    this.lastValue = this.getValue();
44553    if (this.element.tagName.toLowerCase() == 'form')
44554      this.registerFormCallbacks();
44555    else
44556      this.registerCallback(this.element);
44557  },
44558
44559  onElementEvent: function() {
44560    var value = this.getValue();
44561    if (this.lastValue != value) {
44562      this.callback(this.element, value);
44563      this.lastValue = value;
44564    }
44565  },
44566
44567  registerFormCallbacks: function() {
44568    Form.getElements(this.element).each(this.registerCallback, this);
44569  },
44570
44571  registerCallback: function(element) {
44572    if (element.type) {
44573      switch (element.type.toLowerCase()) {
44574        case 'checkbox':
44575        case 'radio':
44576          Event.observe(element, 'click', this.onElementEvent.bind(this));
44577          break;
44578        default:
44579          Event.observe(element, 'change', this.onElementEvent.bind(this));
44580          break;
44581      }
44582    }
44583  }
44584});
44585
44586Form.Element.EventObserver = Class.create(Abstract.EventObserver, {
44587  getValue: function() {
44588    return Form.Element.getValue(this.element);
44589  }
44590});
44591
44592Form.EventObserver = Class.create(Abstract.EventObserver, {
44593  getValue: function() {
44594    return Form.serialize(this.element);
44595  }
44596});
44597(function() {
44598
44599  var Event = {
44600    KEY_BACKSPACE: 8,
44601    KEY_TAB:       9,
44602    KEY_RETURN:   13,
44603    KEY_ESC:      27,
44604    KEY_LEFT:     37,
44605    KEY_UP:       38,
44606    KEY_RIGHT:    39,
44607    KEY_DOWN:     40,
44608    KEY_DELETE:   46,
44609    KEY_HOME:     36,
44610    KEY_END:      35,
44611    KEY_PAGEUP:   33,
44612    KEY_PAGEDOWN: 34,
44613    KEY_INSERT:   45,
44614
44615    cache: {}
44616  };
44617
44618  var docEl = document.documentElement;
44619  var MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED = 'onmouseenter' in docEl
44620    && 'onmouseleave' in docEl;
44621
44622  var _isButton;
44623  if (Prototype.Browser.IE) {
44624    var buttonMap = { 0: 1, 1: 4, 2: 2 };
44625    _isButton = function(event, code) {
44626      return event.button === buttonMap[code];
44627    };
44628  } else if (Prototype.Browser.WebKit) {
44629    _isButton = function(event, code) {
44630      switch (code) {
44631        case 0: return event.which == 1 && !event.metaKey;
44632        case 1: return event.which == 1 && event.metaKey;
44633        default: return false;
44634      }
44635    };
44636  } else {
44637    _isButton = function(event, code) {
44638      return event.which ? (event.which === code + 1) : (event.button === code);
44639    };
44640  }
44641
44642  function isLeftClick(event)   { return _isButton(event, 0) }
44643
44644  function isMiddleClick(event) { return _isButton(event, 1) }
44645
44646  function isRightClick(event)  { return _isButton(event, 2) }
44647
44648  function element(event) {
44649    event = Event.extend(event);
44650
44651    var node = event.target, type = event.type,
44652     currentTarget = event.currentTarget;
44653
44654    if (currentTarget && currentTarget.tagName) {
44655      if (type === 'load' || type === 'error' ||
44656        (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
44657          && currentTarget.type === 'radio'))
44658            node = currentTarget;
44659    }
44660
44661    if (node.nodeType == Node.TEXT_NODE)
44662      node = node.parentNode;
44663
44664    return Element.extend(node);
44665  }
44666
44667  function findElement(event, expression) {
44668    var element = Event.element(event);
44669    if (!expression) return element;
44670    var elements = [element].concat(element.ancestors());
44671    return Selector.findElement(elements, expression, 0);
44672  }
44673
44674  function pointer(event) {
44675    return { x: pointerX(event), y: pointerY(event) };
44676  }
44677
44678  function pointerX(event) {
44679    var docElement = document.documentElement,
44680     body = document.body || { scrollLeft: 0 };
44681
44682    return event.pageX || (event.clientX +
44683      (docElement.scrollLeft || body.scrollLeft) -
44684      (docElement.clientLeft || 0));
44685  }
44686
44687  function pointerY(event) {
44688    var docElement = document.documentElement,
44689     body = document.body || { scrollTop: 0 };
44690
44691    return  event.pageY || (event.clientY +
44692       (docElement.scrollTop || body.scrollTop) -
44693       (docElement.clientTop || 0));
44694  }
44695
44696
44697  function stop(event) {
44698    Event.extend(event);
44699    event.preventDefault();
44700    event.stopPropagation();
44701
44702    event.stopped = true;
44703  }
44704
44705  Event.Methods = {
44706    isLeftClick: isLeftClick,
44707    isMiddleClick: isMiddleClick,
44708    isRightClick: isRightClick,
44709
44710    element: element,
44711    findElement: findElement,
44712
44713    pointer: pointer,
44714    pointerX: pointerX,
44715    pointerY: pointerY,
44716
44717    stop: stop
44718  };
44719
44720
44721  var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
44722    m[name] = Event.Methods[name].methodize();
44723    return m;
44724  });
44725
44726  if (Prototype.Browser.IE) {
44727    function _relatedTarget(event) {
44728      var element;
44729      switch (event.type) {
44730        case 'mouseover': element = event.fromElement; break;
44731        case 'mouseout':  element = event.toElement;   break;
44732        default: return null;
44733      }
44734      return Element.extend(element);
44735    }
44736
44737/*
44738    Object.extend(methods, {
44739      stopPropagation: function() { this.cancelBubble = true },
44740      preventDefault:  function() { this.returnValue = false },
44741      inspect: function() { return '[object Event]' }
44742    });
44743*/
44744      // davidf: patch to fix IE9 drag and drop.
44745      // this is a mix of the patches found here:
44746      //   http://mandagreen.com/prototype-1-6-event-stop-ie9-quick-patch/
44747      // and here:
44748      //   https://prototype.lighthouseapp.com/projects/8886/tickets/1142-ie9-event-bug
44749      var isLegacyEvent = function(event) { return false; };
44750      
44751      if (window.attachEvent) {
44752	  if (window.addEventListener) {
44753	      isLegacyEvent = function(event) {
44754		  return !(event instanceof window.Event);
44755	      };
44756	  } else {
44757	      isLegacyEvent = function(event) { return true; };
44758	  }
44759      }
44760
44761      var additionalMethods = {
44762	  stopPropagation: function() { this.cancelBubble = true },
44763	  preventDefault:  function() { this.returnValue = false },
44764	  inspect: function() { return '[object Event]' }
44765      }
44766
44767    Event.extend = function(event, element) {
44768      if (!event) return false;
44769      if (event._extendedByPrototype) return event;
44770
44771      event._extendedByPrototype = Prototype.emptyFunction;
44772      var pointer = Event.pointer(event);
44773
44774      Object.extend(event, {
44775        target: event.srcElement || element,
44776        relatedTarget: _relatedTarget(event),
44777        pageX:  pointer.x,
44778        pageY:  pointer.y
44779      });
44780
44781	if (isLegacyEvent(event)) {
44782	    Object.extend(event, additionalMethods);
44783	}
44784
44785	return Object.extend(event, methods);
44786    };
44787  } else {
44788    Event.prototype = window.Event.prototype || document.createEvent('HTMLEvents').__proto__;
44789    Object.extend(Event.prototype, methods);
44790    Event.extend = Prototype.K;
44791  }
44792
44793  function _createResponder(element, eventName, handler) {
44794    var registry = Element.retrieve(element, 'prototype_event_registry');
44795
44796    if (Object.isUndefined(registry)) {
44797      CACHE.push(element);
44798      registry = Element.retrieve(element, 'prototype_event_registry', $H());
44799    }
44800
44801    var respondersForEvent = registry.get(eventName);
44802    if (Object.isUndefined(respondersForEvent)) {
44803      respondersForEvent = [];
44804      registry.set(eventName, respondersForEvent);
44805    }
44806
44807    if (respondersForEvent.pluck('handler').include(handler)) return false;
44808
44809    var responder;
44810    if (eventName.include(":")) {
44811      responder = function(event) {
44812        if (Object.isUndefined(event.eventName))
44813          return false;
44814
44815        if (event.eventName !== eventName)
44816          return false;
44817
44818        Event.extend(event, element);
44819        handler.call(element, event);
44820      };
44821    } else {
44822      if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED &&
44823       (eventName === "mouseenter" || eventName === "mouseleave")) {
44824        if (eventName === "mouseenter" || eventName === "mouseleave") {
44825          responder = function(event) {
44826            Event.extend(event, element);
44827
44828            var parent = event.relatedTarget;
44829            while (parent && parent !== element) {
44830              try { parent = parent.parentNode; }
44831              catch(e) { parent = element; }
44832            }
44833
44834            if (parent === element) return;
44835
44836            handler.call(element, event);
44837          };
44838        }
44839      } else {
44840        responder = function(event) {
44841          Event.extend(event, element);
44842          handler.call(element, event);
44843        };
44844      }
44845    }
44846
44847    responder.handler = handler;
44848    respondersForEvent.push(responder);
44849    return responder;
44850  }
44851
44852  function _destroyCache() {
44853    for (var i = 0, length = CACHE.length; i < length; i++) {
44854      Event.stopObserving(CACHE[i]);
44855      CACHE[i] = null;
44856    }
44857  }
44858
44859  var CACHE = [];
44860
44861  if (Prototype.Browser.IE)
44862    window.attachEvent('onunload', _destroyCache);
44863
44864  if (Prototype.Browser.WebKit)
44865    window.addEventListener('unload', Prototype.emptyFunction, false);
44866
44867
44868  var _getDOMEventName = Prototype.K;
44869
44870  if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) {
44871    _getDOMEventName = function(eventName) {
44872      var translations = { mouseenter: "mouseover", mouseleave: "mouseout" };
44873      return eventName in translations ? translations[eventName] : eventName;
44874    };
44875  }
44876
44877  function observe(element, eventName, handler) {
44878    element = $(element);
44879
44880    var responder = _createResponder(element, eventName, handler);
44881
44882    if (!responder) return element;
44883
44884    if (eventName.include(':')) {
44885      if (element.addEventListener)
44886        element.addEventListener("dataavailable", responder, false);
44887      else {
44888        element.attachEvent("ondataavailable", responder);
44889        element.attachEvent("onfilterchange", responder);
44890      }
44891    } else {
44892      var actualEventName = _getDOMEventName(eventName);
44893
44894      if (element.addEventListener)
44895        element.addEventListener(actualEventName, responder, false);
44896      else
44897        element.attachEvent("on" + actualEventName, responder);
44898    }
44899
44900    return element;
44901  }
44902
44903  function stopObserving(element, eventName, handler) {
44904    element = $(element);
44905
44906    var registry = Element.retrieve(element, 'prototype_event_registry');
44907
44908    if (Object.isUndefined(registry)) return element;
44909
44910    if (eventName && !handler) {
44911      var responders = registry.get(eventName);
44912
44913      if (Object.isUndefined(responders)) return element;
44914
44915      responders.each( function(r) {
44916        Element.stopObserving(element, eventName, r.handler);
44917      });
44918      return element;
44919    } else if (!eventName) {
44920      registry.each( function(pair) {
44921        var eventName = pair.key, responders = pair.value;
44922
44923        responders.each( function(r) {
44924          Element.stopObserving(element, eventName, r.handler);
44925        });
44926      });
44927      return element;
44928    }
44929
44930    var responders = registry.get(eventName);
44931
44932    if (!responders) return;
44933
44934    var responder = responders.find( function(r) { return r.handler === handler; });
44935    if (!responder) return element;
44936
44937    var actualEventName = _getDOMEventName(eventName);
44938
44939    if (eventName.include(':')) {
44940      if (element.removeEventListener)
44941        element.removeEventListener("dataavailable", responder, false);
44942      else {
44943        element.detachEvent("ondataavailable", responder);
44944        element.detachEvent("onfilterchange",  responder);
44945      }
44946    } else {
44947      if (element.removeEventListener)
44948        element.removeEventListener(actualEventName, responder, false);
44949      else
44950        element.detachEvent('on' + actualEventName, responder);
44951    }
44952
44953    registry.set(eventName, responders.without(responder));
44954
44955    return element;
44956  }
44957
44958  function fire(element, eventName, memo, bubble) {
44959    element = $(element);
44960
44961    if (Object.isUndefined(bubble))
44962      bubble = true;
44963
44964    if (element == document && document.createEvent && !element.dispatchEvent)
44965      element = document.documentElement;
44966
44967    var event;
44968    if (document.createEvent) {
44969      event = document.createEvent('HTMLEvents');
44970      event.initEvent('dataavailable', true, true);
44971    } else {
44972      event = document.createEventObject();
44973      event.eventType = bubble ? 'ondataavailable' : 'onfilterchange';
44974    }
44975
44976    event.eventName = eventName;
44977    event.memo = memo || { };
44978
44979    if (document.createEvent)
44980      element.dispatchEvent(event);
44981    else
44982      element.fireEvent(event.eventType, event);
44983
44984    return Event.extend(event);
44985  }
44986
44987
44988  Object.extend(Event, Event.Methods);
44989
44990  Object.extend(Event, {
44991    fire:          fire,
44992    observe:       observe,
44993    stopObserving: stopObserving
44994  });
44995
44996  Element.addMethods({
44997    fire:          fire,
44998
44999    observe:       observe,
45000
45001    stopObserving: stopObserving
45002  });
45003
45004  Object.extend(document, {
45005    fire:          fire.methodize(),
45006
45007    observe:       observe.methodize(),
45008
45009    stopObserving: stopObserving.methodize(),
45010
45011    loaded:        false
45012  });
45013
45014  if (window.Event) Object.extend(window.Event, Event);
45015  else window.Event = Event;
45016})();
45017
45018(function() {
45019  /* Support for the DOMContentLoaded event is based on work by Dan Webb,
45020     Matthias Miller, Dean Edwards, John Resig, and Diego Perini. */
45021
45022  var timer;
45023
45024  function fireContentLoadedEvent() {
45025    if (document.loaded) return;
45026    if (timer) window.clearTimeout(timer);
45027    document.loaded = true;
45028    document.fire('dom:loaded');
45029  }
45030
45031  function checkReadyState() {
45032    if (document.readyState === 'complete') {
45033      document.stopObserving('readystatechange', checkReadyState);
45034      fireContentLoadedEvent();
45035    }
45036  }
45037
45038  function pollDoScroll() {
45039    try { document.documentElement.doScroll('left'); }
45040    catch(e) {
45041      timer = pollDoScroll.defer();
45042      return;
45043    }
45044    fireContentLoadedEvent();
45045  }
45046
45047  if (document.addEventListener) {
45048    document.addEventListener('DOMContentLoaded', fireContentLoadedEvent, false);
45049  } else {
45050    document.observe('readystatechange', checkReadyState);
45051    if (window == top)
45052      timer = pollDoScroll.defer();
45053  }
45054
45055  Event.observe(window, 'load', fireContentLoadedEvent);
45056})();
45057
45058Element.addMethods();
45059
45060/*------------------------------- DEPRECATED -------------------------------*/
45061
45062Hash.toQueryString = Object.toQueryString;
45063
45064var Toggle = { display: Element.toggle };
45065
45066Element.Methods.childOf = Element.Methods.descendantOf;
45067
45068var Insertion = {
45069  Before: function(element, content) {
45070    return Element.insert(element, {before:content});
45071  },
45072
45073  Top: function(element, content) {
45074    return Element.insert(element, {top:content});
45075  },
45076
45077  Bottom: function(element, content) {
45078    return Element.insert(element, {bottom:content});
45079  },
45080
45081  After: function(element, content) {
45082    return Element.insert(element, {after:content});
45083  }
45084};
45085
45086var $continue = new Error('"throw $continue" is deprecated, use "return" instead');
45087
45088var Position = {
45089  includeScrollOffsets: false,
45090
45091  prepare: function() {
45092    this.deltaX =  window.pageXOffset
45093                || document.documentElement.scrollLeft
45094                || document.body.scrollLeft
45095                || 0;
45096    this.deltaY =  window.pageYOffset
45097                || document.documentElement.scrollTop
45098                || document.body.scrollTop
45099                || 0;
45100  },
45101
45102  within: function(element, x, y) {
45103    if (this.includeScrollOffsets)
45104      return this.withinIncludingScrolloffsets(element, x, y);
45105    this.xcomp = x;
45106    this.ycomp = y;
45107    this.offset = Element.cumulativeOffset(element);
45108
45109    return (y >= this.offset[1] &&
45110            y <  this.offset[1] + element.offsetHeight &&
45111            x >= this.offset[0] &&
45112            x <  this.offset[0] + element.offsetWidth);
45113  },
45114
45115  withinIncludingScrolloffsets: function(element, x, y) {
45116    var offsetcache = Element.cumulativeScrollOffset(element);
45117
45118    this.xcomp = x + offsetcache[0] - this.deltaX;
45119    this.ycomp = y + offsetcache[1] - this.deltaY;
45120    this.offset = Element.cumulativeOffset(element);
45121
45122    return (this.ycomp >= this.offset[1] &&
45123            this.ycomp <  this.offset[1] + element.offsetHeight &&
45124            this.xcomp >= this.offset[0] &&
45125            this.xcomp <  this.offset[0] + element.offsetWidth);
45126  },
45127
45128  overlap: function(mode, element) {
45129    if (!mode) return 0;
45130    if (mode == 'vertical')
45131      return ((this.offset[1] + element.offsetHeight) - this.ycomp) /
45132        element.offsetHeight;
45133    if (mode == 'horizontal')
45134      return ((this.offset[0] + element.offsetWidth) - this.xcomp) /
45135        element.offsetWidth;
45136  },
45137
45138
45139  cumulativeOffset: Element.Methods.cumulativeOffset,
45140
45141  positionedOffset: Element.Methods.positionedOffset,
45142
45143  absolutize: function(element) {
45144    Position.prepare();
45145    return Element.absolutize(element);
45146  },
45147
45148  relativize: function(element) {
45149    Position.prepare();
45150    return Element.relativize(element);
45151  },
45152
45153  realOffset: Element.Methods.cumulativeScrollOffset,
45154
45155  offsetParent: Element.Methods.getOffsetParent,
45156
45157  page: Element.Methods.viewportOffset,
45158
45159  clone: function(source, target, options) {
45160    options = options || { };
45161    return Element.clonePosition(target, source, options);
45162  }
45163};
45164
45165/*--------------------------------------------------------------------------*/
45166
45167if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
45168  function iter(name) {
45169    return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
45170  }
45171
45172  instanceMethods.getElementsByClassName = Prototype.BrowserFeatures.XPath ?
45173  function(element, className) {
45174    className = className.toString().strip();
45175    var cond = /\s/.test(className) ? $w(className).map(iter).join('') : iter(className);
45176    return cond ? document._getElementsByXPath('.//*' + cond, element) : [];
45177  } : function(element, className) {
45178    className = className.toString().strip();
45179    var elements = [], classNames = (/\s/.test(className) ? $w(className) : null);
45180    if (!classNames && !className) return elements;
45181
45182    var nodes = $(element).getElementsByTagName('*');
45183    className = ' ' + className + ' ';
45184
45185    for (var i = 0, child, cn; child = nodes[i]; i++) {
45186      if (child.className && (cn = ' ' + child.className + ' ') && (cn.include(className) ||
45187          (classNames && classNames.all(function(name) {
45188            return !name.toString().blank() && cn.include(' ' + name + ' ');
45189          }))))
45190        elements.push(Element.extend(child));
45191    }
45192    return elements;
45193  };
45194
45195  return function(className, parentElement) {
45196    return $(parentElement || document.body).getElementsByClassName(className);
45197  };
45198}(Element.Methods);
45199
45200/*--------------------------------------------------------------------------*/
45201
45202Element.ClassNames = Class.create();
45203Element.ClassNames.prototype = {
45204  initialize: function(element) {
45205    this.element = $(element);
45206  },
45207
45208  _each: function(iterator) {
45209    this.element.className.split(/\s+/).select(function(name) {
45210      return name.length > 0;
45211    })._each(iterator);
45212  },
45213
45214  set: function(className) {
45215    this.element.className = className;
45216  },
45217
45218  add: function(classNameToAdd) {
45219    if (this.include(classNameToAdd)) return;
45220    this.set($A(this).concat(classNameToAdd).join(' '));
45221  },
45222
45223  remove: function(classNameToRemove) {
45224    if (!this.include(classNameToRemove)) return;
45225    this.set($A(this).without(classNameToRemove).join(' '));
45226  },
45227
45228  toString: function() {
45229    return $A(this).join(' ');
45230  }
45231};
45232
45233Object.extend(Element.ClassNames.prototype, Enumerable);
45234
45235/*--------------------------------------------------------------------------*/
45236;
45237// script.aculo.us effects.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
45238
45239// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
45240// Contributors:
45241//  Justin Palmer (http://encytemedia.com/)
45242//  Mark Pilgrim (http://diveintomark.org/)
45243//  Martin Bialasinki
45244//
45245// script.aculo.us is freely distributable under the terms of an MIT-style license.
45246// For details, see the script.aculo.us web site: http://script.aculo.us/
45247
45248// converts rgb() and #xxx to #xxxxxx format,
45249// returns self (or first argument) if not convertable
45250String.prototype.parseColor = function() {
45251  var color = '#';
45252  if (this.slice(0,4) == 'rgb(') {
45253    var cols = this.slice(4,this.length-1).split(',');
45254    var i=0; do { color += parseInt(cols[i]).toColorPart() } while (++i<3);
45255  } else {
45256    if (this.slice(0,1) == '#') {
45257      if (this.length==4) for(var i=1;i<4;i++) color += (this.charAt(i) + this.charAt(i)).toLowerCase();
45258      if (this.length==7) color = this.toLowerCase();
45259    }
45260  }
45261  return (color.length==7 ? color : (arguments[0] || this));
45262};
45263
45264/*--------------------------------------------------------------------------*/
45265
45266Element.collectTextNodes = function(element) {
45267  return $A($(element).childNodes).collect( function(node) {
45268    return (node.nodeType==3 ? node.nodeValue :
45269      (node.hasChildNodes() ? Element.collectTextNodes(node) : ''));
45270  }).flatten().join('');
45271};
45272
45273Element.collectTextNodesIgnoreClass = function(element, className) {
45274  return $A($(element).childNodes).collect( function(node) {
45275    return (node.nodeType==3 ? node.nodeValue :
45276      ((node.hasChildNodes() && !Element.hasClassName(node,className)) ?
45277        Element.collectTextNodesIgnoreClass(node, className) : ''));
45278  }).flatten().join('');
45279};
45280
45281Element.setContentZoom = function(element, percent) {
45282  element = $(element);
45283  element.setStyle({fontSize: (percent/100) + 'em'});
45284  if (Prototype.Browser.WebKit) window.scrollBy(0,0);
45285  return element;
45286};
45287
45288Element.getInlineOpacity = function(element){
45289  return $(element).style.opacity || '';
45290};
45291
45292Element.forceRerendering = function(element) {
45293  try {
45294    element = $(element);
45295    var n = document.createTextNode(' ');
45296    element.appendChild(n);
45297    element.removeChild(n);
45298  } catch(e) { }
45299};
45300
45301/*--------------------------------------------------------------------------*/
45302
45303var Effect = {
45304  _elementDoesNotExistError: {
45305    name: 'ElementDoesNotExistError',
45306    message: 'The specified DOM element does not exist, but is required for this effect to operate'
45307  },
45308  Transitions: {
45309    linear: Prototype.K,
45310    sinoidal: function(pos) {
45311      return (-Math.cos(pos*Math.PI)/2) + .5;
45312    },
45313    reverse: function(pos) {
45314      return 1-pos;
45315    },
45316    flicker: function(pos) {
45317      var pos = ((-Math.cos(pos*Math.PI)/4) + .75) + Math.random()/4;
45318      return pos > 1 ? 1 : pos;
45319    },
45320    wobble: function(pos) {
45321      return (-Math.cos(pos*Math.PI*(9*pos))/2) + .5;
45322    },
45323    pulse: function(pos, pulses) {
45324      return (-Math.cos((pos*((pulses||5)-.5)*2)*Math.PI)/2) + .5;
45325    },
45326    spring: function(pos) {
45327      return 1 - (Math.cos(pos * 4.5 * Math.PI) * Math.exp(-pos * 6));
45328    },
45329    none: function(pos) {
45330      return 0;
45331    },
45332    full: function(pos) {
45333      return 1;
45334    }
45335  },
45336  DefaultOptions: {
45337    duration:   1.0,   // seconds
45338    fps:        100,   // 100= assume 66fps max.
45339    sync:       false, // true for combining
45340    from:       0.0,
45341    to:         1.0,
45342    delay:      0.0,
45343    queue:      'parallel'
45344  },
45345  tagifyText: function(element) {
45346    var tagifyStyle = 'position:relative';
45347    if (Prototype.Browser.IE) tagifyStyle += ';zoom:1';
45348
45349    element = $(element);
45350    $A(element.childNodes).each( function(child) {
45351      if (child.nodeType==3) {
45352        child.nodeValue.toArray().each( function(character) {
45353          element.insertBefore(
45354            new Element('span', {style: tagifyStyle}).update(
45355              character == ' ' ? String.fromCharCode(160) : character),
45356              child);
45357        });
45358        Element.remove(child);
45359      }
45360    });
45361  },
45362  multiple: function(element, effect) {
45363    var elements;
45364    if (((typeof element == 'object') ||
45365        Object.isFunction(element)) &&
45366       (element.length))
45367      elements = element;
45368    else
45369      elements = $(element).childNodes;
45370
45371    var options = Object.extend({
45372      speed: 0.1,
45373      delay: 0.0
45374    }, arguments[2] || { });
45375    var masterDelay = options.delay;
45376
45377    $A(elements).each( function(element, index) {
45378      new effect(element, Object.extend(options, { delay: index * options.speed + masterDelay }));
45379    });
45380  },
45381  PAIRS: {
45382    'slide':  ['SlideDown','SlideUp'],
45383    'blind':  ['BlindDown','BlindUp'],
45384    'appear': ['Appear','Fade']
45385  },
45386  toggle: function(element, effect, options) {
45387    element = $(element);
45388    effect  = (effect || 'appear').toLowerCase();
45389    
45390    return Effect[ Effect.PAIRS[ effect ][ element.visible() ? 1 : 0 ] ](element, Object.extend({
45391      queue: { position:'end', scope:(element.id || 'global'), limit: 1 }
45392    }, options || {}));
45393  }
45394};
45395
45396Effect.DefaultOptions.transition = Effect.Transitions.sinoidal;
45397
45398/* ------------- core effects ------------- */
45399
45400Effect.ScopedQueue = Class.create(Enumerable, {
45401  initialize: function() {
45402    this.effects  = [];
45403    this.interval = null;
45404  },
45405  _each: function(iterator) {
45406    this.effects._each(iterator);
45407  },
45408  add: function(effect) {
45409    var timestamp = new Date().getTime();
45410
45411    var position = Object.isString(effect.options.queue) ?
45412      effect.options.queue : effect.options.queue.position;
45413
45414    switch(position) {
45415      case 'front':
45416        // move unstarted effects after this effect
45417        this.effects.findAll(function(e){ return e.state=='idle' }).each( function(e) {
45418            e.startOn  += effect.finishOn;
45419            e.finishOn += effect.finishOn;
45420          });
45421        break;
45422      case 'with-last':
45423        timestamp = this.effects.pluck('startOn').max() || timestamp;
45424        break;
45425      case 'end':
45426        // start effect after last queued effect has finished
45427        timestamp = this.effects.pluck('finishOn').max() || timestamp;
45428        break;
45429    }
45430
45431    effect.startOn  += timestamp;
45432    effect.finishOn += timestamp;
45433
45434    if (!effect.options.queue.limit || (this.effects.length < effect.options.queue.limit))
45435      this.effects.push(effect);
45436
45437    if (!this.interval)
45438      this.interval = setInterval(this.loop.bind(this), 15);
45439  },
45440  remove: function(effect) {
45441    this.effects = this.effects.reject(function(e) { return e==effect });
45442    if (this.effects.length == 0) {
45443      clearInterval(this.interval);
45444      this.interval = null;
45445    }
45446  },
45447  loop: function() {
45448    var timePos = new Date().getTime();
45449    for(var i=0, len=this.effects.length;i<len;i++)
45450      this.effects[i] && this.effects[i].loop(timePos);
45451  }
45452});
45453
45454Effect.Queues = {
45455  instances: $H(),
45456  get: function(queueName) {
45457    if (!Object.isString(queueName)) return queueName;
45458
45459    return this.instances.get(queueName) ||
45460      this.instances.set(queueName, new Effect.ScopedQueue());
45461  }
45462};
45463Effect.Queue = Effect.Queues.get('global');
45464
45465Effect.Base = Class.create({
45466  position: null,
45467  start: function(options) {
45468    if (options && options.transition === false) options.transition = Effect.Transitions.linear;
45469    this.options      = Object.extend(Object.extend({ },Effect.DefaultOptions), options || { });
45470    this.currentFrame = 0;
45471    this.state        = 'idle';
45472    this.startOn      = this.options.delay*1000;
45473    this.finishOn     = this.startOn+(this.options.duration*1000);
45474    this.fromToDelta  = this.options.to-this.options.from;
45475    this.totalTime    = this.finishOn-this.startOn;
45476    this.totalFrames  = this.options.fps*this.options.duration;
45477
45478    this.render = (function() {
45479      function dispatch(effect, eventName) {
45480        if (effect.options[eventName + 'Internal'])
45481          effect.options[eventName + 'Internal'](effect);
45482        if (effect.options[eventName])
45483          effect.options[eventName](effect);
45484      }
45485
45486      return function(pos) {
45487        if (this.state === "idle") {
45488          this.state = "running";
45489          dispatch(this, 'beforeSetup');
45490          if (this.setup) this.setup();
45491          dispatch(this, 'afterSetup');
45492        }
45493        if (this.state === "running") {
45494          pos = (this.options.transition(pos) * this.fromToDelta) + this.options.from;
45495          this.position = pos;
45496          dispatch(this, 'beforeUpdate');
45497          if (this.update) this.update(pos);
45498          dispatch(this, 'afterUpdate');
45499        }
45500      };
45501    })();
45502
45503    this.event('beforeStart');
45504    if (!this.options.sync)
45505      Effect.Queues.get(Object.isString(this.options.queue) ?
45506        'global' : this.options.queue.scope).add(this);
45507  },
45508  loop: function(timePos) {
45509    if (timePos >= this.startOn) {
45510      if (timePos >= this.finishOn) {
45511        this.render(1.0);
45512        this.cancel();
45513        this.event('beforeFinish');
45514        if (this.finish) this.finish();
45515        this.event('afterFinish');
45516        return;
45517      }
45518      var pos   = (timePos - this.startOn) / this.totalTime,
45519          frame = (pos * this.totalFrames).round();
45520      if (frame > this.currentFrame) {
45521        this.render(pos);
45522        this.currentFrame = frame;
45523      }
45524    }
45525  },
45526  cancel: function() {
45527    if (!this.options.sync)
45528      Effect.Queues.get(Object.isString(this.options.queue) ?
45529        'global' : this.options.queue.scope).remove(this);
45530    this.state = 'finished';
45531  },
45532  event: function(eventName) {
45533    if (this.options[eventName + 'Internal']) this.options[eventName + 'Internal'](this);
45534    if (this.options[eventName]) this.options[eventName](this);
45535  },
45536  inspect: function() {
45537    var data = $H();
45538    for(property in this)
45539      if (!Object.isFunction(this[property])) data.set(property, this[property]);
45540    return '#<Effect:' + data.inspect() + ',options:' + $H(this.options).inspect() + '>';
45541  }
45542});
45543
45544Effect.Parallel = Class.create(Effect.Base, {
45545  initialize: function(effects) {
45546    this.effects = effects || [];
45547    this.start(arguments[1]);
45548  },
45549  update: function(position) {
45550    this.effects.invoke('render', position);
45551  },
45552  finish: function(position) {
45553    this.effects.each( function(effect) {
45554      effect.render(1.0);
45555      effect.cancel();
45556      effect.event('beforeFinish');
45557      if (effect.finish) effect.finish(position);
45558      effect.event('afterFinish');
45559    });
45560  }
45561});
45562
45563Effect.Tween = Class.create(Effect.Base, {
45564  initialize: function(object, from, to) {
45565    object = Object.isString(object) ? $(object) : object;
45566    var args = $A(arguments), method = args.last(),
45567      options = args.length == 5 ? args[3] : null;
45568    this.method = Object.isFunction(method) ? method.bind(object) :
45569      Object.isFunction(object[method]) ? object[method].bind(object) :
45570      function(value) { object[method] = value };
45571    this.start(Object.extend({ from: from, to: to }, options || { }));
45572  },
45573  update: function(position) {
45574    this.method(position);
45575  }
45576});
45577
45578Effect.Event = Class.create(Effect.Base, {
45579  initialize: function() {
45580    this.start(Object.extend({ duration: 0 }, arguments[0] || { }));
45581  },
45582  update: Prototype.emptyFunction
45583});
45584
45585Effect.Opacity = Class.create(Effect.Base, {
45586  initialize: function(element) {
45587    this.element = $(element);
45588    if (!this.element) throw(Effect._elementDoesNotExistError);
45589    // make this work on IE on elements without 'layout'
45590    if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
45591      this.element.setStyle({zoom: 1});
45592    var options = Object.extend({
45593      from: this.element.getOpacity() || 0.0,
45594      to:   1.0
45595    }, arguments[1] || { });
45596    this.start(options);
45597  },
45598  update: function(position) {
45599    this.element.setOpacity(position);
45600  }
45601});
45602
45603Effect.Move = Class.create(Effect.Base, {
45604  initialize: function(element) {
45605    this.element = $(element);
45606    if (!this.element) throw(Effect._elementDoesNotExistError);
45607    var options = Object.extend({
45608      x:    0,
45609      y:    0,
45610      mode: 'relative'
45611    }, arguments[1] || { });
45612    this.start(options);
45613  },
45614  setup: function() {
45615    this.element.makePositioned();
45616    this.originalLeft = parseFloat(this.element.getStyle('left') || '0');
45617    this.originalTop  = parseFloat(this.element.getStyle('top')  || '0');
45618    if (this.options.mode == 'absolute') {
45619      this.options.x = this.options.x - this.originalLeft;
45620      this.options.y = this.options.y - this.originalTop;
45621    }
45622  },
45623  update: function(position) {
45624    this.element.setStyle({
45625      left: (this.options.x  * position + this.originalLeft).round() + 'px',
45626      top:  (this.options.y  * position + this.originalTop).round()  + 'px'
45627    });
45628  }
45629});
45630
45631// for backwards compatibility
45632Effect.MoveBy = function(element, toTop, toLeft) {
45633  return new Effect.Move(element,
45634    Object.extend({ x: toLeft, y: toTop }, arguments[3] || { }));
45635};
45636
45637Effect.Scale = Class.create(Effect.Base, {
45638  initialize: function(element, percent) {
45639    this.element = $(element);
45640    if (!this.element) throw(Effect._elementDoesNotExistError);
45641    var options = Object.extend({
45642      scaleX: true,
45643      scaleY: true,
45644      scaleContent: true,
45645      scaleFromCenter: false,
45646      scaleMode: 'box',        // 'box' or 'contents' or { } with provided values
45647      scaleFrom: 100.0,
45648      scaleTo:   percent
45649    }, arguments[2] || { });
45650    this.start(options);
45651  },
45652  setup: function() {
45653    this.restoreAfterFinish = this.options.restoreAfterFinish || false;
45654    this.elementPositioning = this.element.getStyle('position');
45655
45656    this.originalStyle = { };
45657    ['top','left','width','height','fontSize'].each( function(k) {
45658      this.originalStyle[k] = this.element.style[k];
45659    }.bind(this));
45660
45661    this.originalTop  = this.element.offsetTop;
45662    this.originalLeft = this.element.offsetLeft;
45663
45664    var fontSize = this.element.getStyle('font-size') || '100%';
45665    ['em','px','%','pt'].each( function(fontSizeType) {
45666      if (fontSize.indexOf(fontSizeType)>0) {
45667        this.fontSize     = parseFloat(fontSize);
45668        this.fontSizeType = fontSizeType;
45669      }
45670    }.bind(this));
45671
45672    this.factor = (this.options.scaleTo - this.options.scaleFrom)/100;
45673
45674    this.dims = null;
45675    if (this.options.scaleMode=='box')
45676      this.dims = [this.element.offsetHeight, this.element.offsetWidth];
45677    if (/^content/.test(this.options.scaleMode))
45678      this.dims = [this.element.scrollHeight, this.element.scrollWidth];
45679    if (!this.dims)
45680      this.dims = [this.options.scaleMode.originalHeight,
45681                   this.options.scaleMode.originalWidth];
45682  },
45683  update: function(position) {
45684    var currentScale = (this.options.scaleFrom/100.0) + (this.factor * position);
45685    if (this.options.scaleContent && this.fontSize)
45686      this.element.setStyle({fontSize: this.fontSize * currentScale + this.fontSizeType });
45687    this.setDimensions(this.dims[0] * currentScale, this.dims[1] * currentScale);
45688  },
45689  finish: function(position) {
45690    if (this.restoreAfterFinish) this.element.setStyle(this.originalStyle);
45691  },
45692  setDimensions: function(height, width) {
45693    var d = { };
45694    if (this.options.scaleX) d.width = width.round() + 'px';
45695    if (this.options.scaleY) d.height = height.round() + 'px';
45696    if (this.options.scaleFromCenter) {
45697      var topd  = (height - this.dims[0])/2;
45698      var leftd = (width  - this.dims[1])/2;
45699      if (this.elementPositioning == 'absolute') {
45700        if (this.options.scaleY) d.top = this.originalTop-topd + 'px';
45701        if (this.options.scaleX) d.left = this.originalLeft-leftd + 'px';
45702      } else {
45703        if (this.options.scaleY) d.top = -topd + 'px';
45704        if (this.options.scaleX) d.left = -leftd + 'px';
45705      }
45706    }
45707    this.element.setStyle(d);
45708  }
45709});
45710
45711Effect.Highlight = Class.create(Effect.Base, {
45712  initialize: function(element) {
45713    this.element = $(element);
45714    if (!this.element) throw(Effect._elementDoesNotExistError);
45715    var options = Object.extend({ startcolor: '#ffff99' }, arguments[1] || { });
45716    this.start(options);
45717  },
45718  setup: function() {
45719    // Prevent executing on elements not in the layout flow
45720    if (this.element.getStyle('display')=='none') { this.cancel(); return; }
45721    // Disable background image during the effect
45722    this.oldStyle = { };
45723    if (!this.options.keepBackgroundImage) {
45724      this.oldStyle.backgroundImage = this.element.getStyle('background-image');
45725      this.element.setStyle({backgroundImage: 'none'});
45726    }
45727    if (!this.options.endcolor)
45728      this.options.endcolor = this.element.getStyle('background-color').parseColor('#ffffff');
45729    if (!this.options.restorecolor)
45730      this.options.restorecolor = this.element.getStyle('background-color');
45731    // init color calculations
45732    this._base  = $R(0,2).map(function(i){ return parseInt(this.options.startcolor.slice(i*2+1,i*2+3),16) }.bind(this));
45733    this._delta = $R(0,2).map(function(i){ return parseInt(this.options.endcolor.slice(i*2+1,i*2+3),16)-this._base[i] }.bind(this));
45734  },
45735  update: function(position) {
45736    this.element.setStyle({backgroundColor: $R(0,2).inject('#',function(m,v,i){
45737      return m+((this._base[i]+(this._delta[i]*position)).round().toColorPart()); }.bind(this)) });
45738  },
45739  finish: function() {
45740    this.element.setStyle(Object.extend(this.oldStyle, {
45741      backgroundColor: this.options.restorecolor
45742    }));
45743  }
45744});
45745
45746Effect.ScrollTo = function(element) {
45747  var options = arguments[1] || { },
45748  scrollOffsets = document.viewport.getScrollOffsets(),
45749  elementOffsets = $(element).cumulativeOffset();
45750
45751  if (options.offset) elementOffsets[1] += options.offset;
45752
45753  return new Effect.Tween(null,
45754    scrollOffsets.top,
45755    elementOffsets[1],
45756    options,
45757    function(p){ scrollTo(scrollOffsets.left, p.round()); }
45758  );
45759};
45760
45761/* ------------- combination effects ------------- */
45762
45763Effect.Fade = function(element) {
45764  element = $(element);
45765  var oldOpacity = element.getInlineOpacity();
45766  var options = Object.extend({
45767    from: element.getOpacity() || 1.0,
45768    to:   0.0,
45769    afterFinishInternal: function(effect) {
45770      if (effect.options.to!=0) return;
45771      effect.element.hide().setStyle({opacity: oldOpacity});
45772    }
45773  }, arguments[1] || { });
45774  return new Effect.Opacity(element,options);
45775};
45776
45777Effect.Appear = function(element) {
45778  element = $(element);
45779  var options = Object.extend({
45780  from: (element.getStyle('display') == 'none' ? 0.0 : element.getOpacity() || 0.0),
45781  to:   1.0,
45782  // force Safari to render floated elements properly
45783  afterFinishInternal: function(effect) {
45784    effect.element.forceRerendering();
45785  },
45786  beforeSetup: function(effect) {
45787    effect.element.setOpacity(effect.options.from).show();
45788  }}, arguments[1] || { });
45789  return new Effect.Opacity(element,options);
45790};
45791
45792Effect.Puff = function(element) {
45793  element = $(element);
45794  var oldStyle = {
45795    opacity: element.getInlineOpacity(),
45796    position: element.getStyle('position'),
45797    top:  element.style.top,
45798    left: element.style.left,
45799    width: element.style.width,
45800    height: element.style.height
45801  };
45802  return new Effect.Parallel(
45803   [ new Effect.Scale(element, 200,
45804      { sync: true, scaleFromCenter: true, scaleContent: true, restoreAfterFinish: true }),
45805     new Effect.Opacity(element, { sync: true, to: 0.0 } ) ],
45806     Object.extend({ duration: 1.0,
45807      beforeSetupInternal: function(effect) {
45808        Position.absolutize(effect.effects[0].element);
45809      },
45810      afterFinishInternal: function(effect) {
45811         effect.effects[0].element.hide().setStyle(oldStyle); }
45812     }, arguments[1] || { })
45813   );
45814};
45815
45816Effect.BlindUp = function(element) {
45817  element = $(element);
45818  element.makeClipping();
45819  return new Effect.Scale(element, 0,
45820    Object.extend({ scaleContent: false,
45821      scaleX: false,
45822      restoreAfterFinish: true,
45823      afterFinishInternal: function(effect) {
45824        effect.element.hide().undoClipping();
45825      }
45826    }, arguments[1] || { })
45827  );
45828};
45829
45830Effect.BlindDown = function(element) {
45831  element = $(element);
45832  var elementDimensions = element.getDimensions();
45833  return new Effect.Scale(element, 100, Object.extend({
45834    scaleContent: false,
45835    scaleX: false,
45836    scaleFrom: 0,
45837    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
45838    restoreAfterFinish: true,
45839    afterSetup: function(effect) {
45840      effect.element.makeClipping().setStyle({height: '0px'}).show();
45841    },
45842    afterFinishInternal: function(effect) {
45843      effect.element.undoClipping();
45844    }
45845  }, arguments[1] || { }));
45846};
45847
45848Effect.SwitchOff = function(element) {
45849  element = $(element);
45850  var oldOpacity = element.getInlineOpacity();
45851  return new Effect.Appear(element, Object.extend({
45852    duration: 0.4,
45853    from: 0,
45854    transition: Effect.Transitions.flicker,
45855    afterFinishInternal: function(effect) {
45856      new Effect.Scale(effect.element, 1, {
45857        duration: 0.3, scaleFromCenter: true,
45858        scaleX: false, scaleContent: false, restoreAfterFinish: true,
45859        beforeSetup: function(effect) {
45860          effect.element.makePositioned().makeClipping();
45861        },
45862        afterFinishInternal: function(effect) {
45863          effect.element.hide().undoClipping().undoPositioned().setStyle({opacity: oldOpacity});
45864        }
45865      });
45866    }
45867  }, arguments[1] || { }));
45868};
45869
45870Effect.DropOut = function(element) {
45871  element = $(element);
45872  var oldStyle = {
45873    top: element.getStyle('top'),
45874    left: element.getStyle('left'),
45875    opacity: element.getInlineOpacity() };
45876  return new Effect.Parallel(
45877    [ new Effect.Move(element, {x: 0, y: 100, sync: true }),
45878      new Effect.Opacity(element, { sync: true, to: 0.0 }) ],
45879    Object.extend(
45880      { duration: 0.5,
45881        beforeSetup: function(effect) {
45882          effect.effects[0].element.makePositioned();
45883        },
45884        afterFinishInternal: function(effect) {
45885          effect.effects[0].element.hide().undoPositioned().setStyle(oldStyle);
45886        }
45887      }, arguments[1] || { }));
45888};
45889
45890Effect.Shake = function(element) {
45891  element = $(element);
45892  var options = Object.extend({
45893    distance: 20,
45894    duration: 0.5
45895  }, arguments[1] || {});
45896  var distance = parseFloat(options.distance);
45897  var split = parseFloat(options.duration) / 10.0;
45898  var oldStyle = {
45899    top: element.getStyle('top'),
45900    left: element.getStyle('left') };
45901    return new Effect.Move(element,
45902      { x:  distance, y: 0, duration: split, afterFinishInternal: function(effect) {
45903    new Effect.Move(effect.element,
45904      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
45905    new Effect.Move(effect.element,
45906      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
45907    new Effect.Move(effect.element,
45908      { x: -distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
45909    new Effect.Move(effect.element,
45910      { x:  distance*2, y: 0, duration: split*2,  afterFinishInternal: function(effect) {
45911    new Effect.Move(effect.element,
45912      { x: -distance, y: 0, duration: split, afterFinishInternal: function(effect) {
45913        effect.element.undoPositioned().setStyle(oldStyle);
45914  }}); }}); }}); }}); }}); }});
45915};
45916
45917Effect.SlideDown = function(element) {
45918  element = $(element).cleanWhitespace();
45919  // SlideDown need to have the content of the element wrapped in a container element with fixed height!
45920  var oldInnerBottom = element.down().getStyle('bottom');
45921  var elementDimensions = element.getDimensions();
45922  return new Effect.Scale(element, 100, Object.extend({
45923    scaleContent: false,
45924    scaleX: false,
45925    scaleFrom: window.opera ? 0 : 1,
45926    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
45927    restoreAfterFinish: true,
45928    afterSetup: function(effect) {
45929      effect.element.makePositioned();
45930      effect.element.down().makePositioned();
45931      if (window.opera) effect.element.setStyle({top: ''});
45932      effect.element.makeClipping().setStyle({height: '0px'}).show();
45933    },
45934    afterUpdateInternal: function(effect) {
45935      effect.element.down().setStyle({bottom:
45936        (effect.dims[0] - effect.element.clientHeight) + 'px' });
45937    },
45938    afterFinishInternal: function(effect) {
45939      effect.element.undoClipping().undoPositioned();
45940      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom}); }
45941    }, arguments[1] || { })
45942  );
45943};
45944
45945Effect.SlideUp = function(element) {
45946  element = $(element).cleanWhitespace();
45947  var oldInnerBottom = element.down().getStyle('bottom');
45948  var elementDimensions = element.getDimensions();
45949  return new Effect.Scale(element, window.opera ? 0 : 1,
45950   Object.extend({ scaleContent: false,
45951    scaleX: false,
45952    scaleMode: 'box',
45953    scaleFrom: 100,
45954    scaleMode: {originalHeight: elementDimensions.height, originalWidth: elementDimensions.width},
45955    restoreAfterFinish: true,
45956    afterSetup: function(effect) {
45957      effect.element.makePositioned();
45958      effect.element.down().makePositioned();
45959      if (window.opera) effect.element.setStyle({top: ''});
45960      effect.element.makeClipping().show();
45961    },
45962    afterUpdateInternal: function(effect) {
45963      effect.element.down().setStyle({bottom:
45964        (effect.dims[0] - effect.element.clientHeight) + 'px' });
45965    },
45966    afterFinishInternal: function(effect) {
45967      effect.element.hide().undoClipping().undoPositioned();
45968      effect.element.down().undoPositioned().setStyle({bottom: oldInnerBottom});
45969    }
45970   }, arguments[1] || { })
45971  );
45972};
45973
45974// Bug in opera makes the TD containing this element expand for a instance after finish
45975Effect.Squish = function(element) {
45976  return new Effect.Scale(element, window.opera ? 1 : 0, {
45977    restoreAfterFinish: true,
45978    beforeSetup: function(effect) {
45979      effect.element.makeClipping();
45980    },
45981    afterFinishInternal: function(effect) {
45982      effect.element.hide().undoClipping();
45983    }
45984  });
45985};
45986
45987Effect.Grow = function(element) {
45988  element = $(element);
45989  var options = Object.extend({
45990    direction: 'center',
45991    moveTransition: Effect.Transitions.sinoidal,
45992    scaleTransition: Effect.Transitions.sinoidal,
45993    opacityTransition: Effect.Transitions.full
45994  }, arguments[1] || { });
45995  var oldStyle = {
45996    top: element.style.top,
45997    left: element.style.left,
45998    height: element.style.height,
45999    width: element.style.width,
46000    opacity: element.getInlineOpacity() };
46001
46002  var dims = element.getDimensions();
46003  var initialMoveX, initialMoveY;
46004  var moveX, moveY;
46005
46006  switch (options.direction) {
46007    case 'top-left':
46008      initialMoveX = initialMoveY = moveX = moveY = 0;
46009      break;
46010    case 'top-right':
46011      initialMoveX = dims.width;
46012      initialMoveY = moveY = 0;
46013      moveX = -dims.width;
46014      break;
46015    case 'bottom-left':
46016      initialMoveX = moveX = 0;
46017      initialMoveY = dims.height;
46018      moveY = -dims.height;
46019      break;
46020    case 'bottom-right':
46021      initialMoveX = dims.width;
46022      initialMoveY = dims.height;
46023      moveX = -dims.width;
46024      moveY = -dims.height;
46025      break;
46026    case 'center':
46027      initialMoveX = dims.width / 2;
46028      initialMoveY = dims.height / 2;
46029      moveX = -dims.width / 2;
46030      moveY = -dims.height / 2;
46031      break;
46032  }
46033
46034  return new Effect.Move(element, {
46035    x: initialMoveX,
46036    y: initialMoveY,
46037    duration: 0.01,
46038    beforeSetup: function(effect) {
46039      effect.element.hide().makeClipping().makePositioned();
46040    },
46041    afterFinishInternal: function(effect) {
46042      new Effect.Parallel(
46043        [ new Effect.Opacity(effect.element, { sync: true, to: 1.0, from: 0.0, transition: options.opacityTransition }),
46044          new Effect.Move(effect.element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition }),
46045          new Effect.Scale(effect.element, 100, {
46046            scaleMode: { originalHeight: dims.height, originalWidth: dims.width },
46047            sync: true, scaleFrom: window.opera ? 1 : 0, transition: options.scaleTransition, restoreAfterFinish: true})
46048        ], Object.extend({
46049             beforeSetup: function(effect) {
46050               effect.effects[0].element.setStyle({height: '0px'}).show();
46051             },
46052             afterFinishInternal: function(effect) {
46053               effect.effects[0].element.undoClipping().undoPositioned().setStyle(oldStyle);
46054             }
46055           }, options)
46056      );
46057    }
46058  });
46059};
46060
46061Effect.Shrink = function(element) {
46062  element = $(element);
46063  var options = Object.extend({
46064    direction: 'center',
46065    moveTransition: Effect.Transitions.sinoidal,
46066    scaleTransition: Effect.Transitions.sinoidal,
46067    opacityTransition: Effect.Transitions.none
46068  }, arguments[1] || { });
46069  var oldStyle = {
46070    top: element.style.top,
46071    left: element.style.left,
46072    height: element.style.height,
46073    width: element.style.width,
46074    opacity: element.getInlineOpacity() };
46075
46076  var dims = element.getDimensions();
46077  var moveX, moveY;
46078
46079  switch (options.direction) {
46080    case 'top-left':
46081      moveX = moveY = 0;
46082      break;
46083    case 'top-right':
46084      moveX = dims.width;
46085      moveY = 0;
46086      break;
46087    case 'bottom-left':
46088      moveX = 0;
46089      moveY = dims.height;
46090      break;
46091    case 'bottom-right':
46092      moveX = dims.width;
46093      moveY = dims.height;
46094      break;
46095    case 'center':
46096      moveX = dims.width / 2;
46097      moveY = dims.height / 2;
46098      break;
46099  }
46100
46101  return new Effect.Parallel(
46102    [ new Effect.Opacity(elemen
46102t, { sync: true, to: 0.0, from: 1.0, transition: options.opacityTransition }),
46103      new Effect.Scale(element, window.opera ? 1 : 0, { sync: true, transition: options.scaleTransition, restoreAfterFinish: true}),
46104      new Effect.Move(element, { x: moveX, y: moveY, sync: true, transition: options.moveTransition })
46105    ], Object.extend({
46106         beforeStartInternal: function(effect) {
46107           effect.effects[0].element.makePositioned().makeClipping();
46108         },
46109         afterFinishInternal: function(effect) {
46110           effect.effects[0].element.hide().undoClipping().undoPositioned().setStyle(oldStyle); }
46111       }, options)
46112  );
46113};
46114
46115Effect.Pulsate = function(element) {
46116  element = $(element);
46117  var options    = arguments[1] || { },
46118    oldOpacity = element.getInlineOpacity(),
46119    transition = options.transition || Effect.Transitions.linear,
46120    reverser   = function(pos){
46121      return 1 - transition((-Math.cos((pos*(options.pulses||5)*2)*Math.PI)/2) + .5);
46122    };
46123
46124  return new Effect.Opacity(element,
46125    Object.extend(Object.extend({  duration: 2.0, from: 0,
46126      afterFinishInternal: function(effect) { effect.element.setStyle({opacity: oldOpacity}); }
46127    }, options), {transition: reverser}));
46128};
46129
46130Effect.Fold = function(element) {
46131  element = $(element);
46132  var oldStyle = {
46133    top: element.style.top,
46134    left: element.style.left,
46135    width: element.style.width,
46136    height: element.style.height };
46137  element.makeClipping();
46138  return new Effect.Scale(element, 5, Object.extend({
46139    scaleContent: false,
46140    scaleX: false,
46141    afterFinishInternal: function(effect) {
46142    new Effect.Scale(element, 1, {
46143      scaleContent: false,
46144      scaleY: false,
46145      afterFinishInternal: function(effect) {
46146        effect.element.hide().undoClipping().setStyle(oldStyle);
46147      } });
46148  }}, arguments[1] || { }));
46149};
46150
46151Effect.Morph = Class.create(Effect.Base, {
46152  initialize: function(element) {
46153    this.element = $(element);
46154    if (!this.element) throw(Effect._elementDoesNotExistError);
46155    var options = Object.extend({
46156      style: { }
46157    }, arguments[1] || { });
46158
46159    if (!Object.isString(options.style)) this.style = $H(options.style);
46160    else {
46161      if (options.style.include(':'))
46162        this.style = options.style.parseStyle();
46163      else {
46164        this.element.addClassName(options.style);
46165        this.style = $H(this.element.getStyles());
46166        this.element.removeClassName(options.style);
46167        var css = this.element.getStyles();
46168        this.style = this.style.reject(function(style) {
46169          return style.value == css[style.key];
46170        });
46171        options.afterFinishInternal = function(effect) {
46172          effect.element.addClassName(effect.options.style);
46173          effect.transforms.each(function(transform) {
46174            effect.element.style[transform.style] = '';
46175          });
46176        };
46177      }
46178    }
46179    this.start(options);
46180  },
46181
46182  setup: function(){
46183    function parseColor(color){
46184      if (!color || ['rgba(0, 0, 0, 0)','transparent'].include(color)) color = '#ffffff';
46185      color = color.parseColor();
46186      return $R(0,2).map(function(i){
46187        return parseInt( color.slice(i*2+1,i*2+3), 16 );
46188      });
46189    }
46190    this.transforms = this.style.map(function(pair){
46191      var property = pair[0], value = pair[1], unit = null;
46192
46193      if (value.parseColor('#zzzzzz') != '#zzzzzz') {
46194        value = value.parseColor();
46195        unit  = 'color';
46196      } else if (property == 'opacity') {
46197        value = parseFloat(value);
46198        if (Prototype.Browser.IE && (!this.element.currentStyle.hasLayout))
46199          this.element.setStyle({zoom: 1});
46200      } else if (Element.CSS_LENGTH.test(value)) {
46201          var components = value.match(/^([\+\-]?[0-9\.]+)(.*)$/);
46202          value = parseFloat(components[1]);
46203          unit = (components.length == 3) ? components[2] : null;
46204      }
46205
46206      var originalValue = this.element.getStyle(property);
46207      return {
46208        style: property.camelize(),
46209        originalValue: unit=='color' ? parseColor(originalValue) : parseFloat(originalValue || 0),
46210        targetValue: unit=='color' ? parseColor(value) : value,
46211        unit: unit
46212      };
46213    }.bind(this)).reject(function(transform){
46214      return (
46215        (transform.originalValue == transform.targetValue) ||
46216        (
46217          transform.unit != 'color' &&
46218          (isNaN(transform.originalValue) || isNaN(transform.targetValue))
46219        )
46220      );
46221    });
46222  },
46223  update: function(position) {
46224    var style = { }, transform, i = this.transforms.length;
46225    while(i--)
46226      style[(transform = this.transforms[i]).style] =
46227        transform.unit=='color' ? '#'+
46228          (Math.round(transform.originalValue[0]+
46229            (transform.targetValue[0]-transform.originalValue[0])*position)).toColorPart() +
46230          (Math.round(transform.originalValue[1]+
46231            (transform.targetValue[1]-transform.originalValue[1])*position)).toColorPart() +
46232          (Math.round(transform.originalValue[2]+
46233            (transform.targetValue[2]-transform.originalValue[2])*position)).toColorPart() :
46234        (transform.originalValue +
46235          (transform.targetValue - transform.originalValue) * position).toFixed(3) +
46236            (transform.unit === null ? '' : transform.unit);
46237    this.element.setStyle(style, true);
46238  }
46239});
46240
46241Effect.Transform = Class.create({
46242  initialize: function(tracks){
46243    this.tracks  = [];
46244    this.options = arguments[1] || { };
46245    this.addTracks(tracks);
46246  },
46247  addTracks: function(tracks){
46248    tracks.each(function(track){
46249      track = $H(track);
46250      var data = track.values().first();
46251      this.tracks.push($H({
46252        ids:     track.keys().first(),
46253        effect:  Effect.Morph,
46254        options: { style: data }
46255      }));
46256    }.bind(this));
46257    return this;
46258  },
46259  play: function(){
46260    return new Effect.Parallel(
46261      this.tracks.map(function(track){
46262        var ids = track.get('ids'), effect = track.get('effect'), options = track.get('options');
46263        var elements = [$(ids) || $$(ids)].flatten();
46264        return elements.map(function(e){ return new effect(e, Object.extend({ sync:true }, options)) });
46265      }).flatten(),
46266      this.options
46267    );
46268  }
46269});
46270
46271Element.CSS_PROPERTIES = $w(
46272  'backgroundColor backgroundPosition borderBottomColor borderBottomStyle ' +
46273  'borderBottomWidth borderLeftColor borderLeftStyle borderLeftWidth ' +
46274  'borderRightColor borderRightStyle borderRightWidth borderSpacing ' +
46275  'borderTopColor borderTopStyle borderTopWidth bottom clip color ' +
46276  'fontSize fontWeight height left letterSpacing lineHeight ' +
46277  'marginBottom marginLeft marginRight marginTop markerOffset maxHeight '+
46278  'maxWidth minHeight minWidth opacity outlineColor outlineOffset ' +
46279  'outlineWidth paddingBottom paddingLeft paddingRight paddingTop ' +
46280  'right textIndent top width wordSpacing zIndex');
46281
46282Element.CSS_LENGTH = /^(([\+\-]?[0-9\.]+)(em|ex|px|in|cm|mm|pt|pc|\%))|0$/;
46283
46284String.__parseStyleElement = document.createElement('div');
46285String.prototype.parseStyle = function(){
46286  var style, styleRules = $H();
46287  if (Prototype.Browser.WebKit)
46288    style = new Element('div',{style:this}).style;
46289  else {
46290    String.__parseStyleElement.innerHTML = '<div style="' + this + '"></div>';
46291    style = String.__parseStyleElement.childNodes[0].style;
46292  }
46293
46294  Element.CSS_PROPERTIES.each(function(property){
46295    if (style[property]) styleRules.set(property, style[property]);
46296  });
46297
46298  if (Prototype.Browser.IE && this.include('opacity'))
46299    styleRules.set('opacity', this.match(/opacity:\s*((?:0|1)?(?:\.\d*)?)/)[1]);
46300
46301  return styleRules;
46302};
46303
46304if (document.defaultView && document.defaultView.getComputedStyle) {
46305  Element.getStyles = function(element) {
46306    var css = document.defaultView.getComputedStyle($(element), null);
46307    return Element.CSS_PROPERTIES.inject({ }, function(styles, property) {
46308      styles[property] = css[property];
46309      return styles;
46310    });
46311  };
46312} else {
46313  Element.getStyles = function(element) {
46314    element = $(element);
46315    var css = element.currentStyle, styles;
46316    styles = Element.CSS_PROPERTIES.inject({ }, function(results, property) {
46317      results[property] = css[property];
46318      return results;
46319    });
46320    if (!styles.opacity) styles.opacity = element.getOpacity();
46321    return styles;
46322  };
46323}
46324
46325Effect.Methods = {
46326  morph: function(element, style) {
46327    element = $(element);
46328    new Effect.Morph(element, Object.extend({ style: style }, arguments[2] || { }));
46329    return element;
46330  },
46331  visualEffect: function(element, effect, options) {
46332    element = $(element);
46333    var s = effect.dasherize().camelize(), klass = s.charAt(0).toUpperCase() + s.substring(1);
46334    new Effect[klass](element, options);
46335    return element;
46336  },
46337  highlight: function(element, options) {
46338    element = $(element);
46339    new Effect.Highlight(element, options);
46340    return element;
46341  }
46342};
46343
46344$w('fade appear grow shrink fold blindUp blindDown slideUp slideDown '+
46345  'pulsate shake puff squish switchOff dropOut').each(
46346  function(effect) {
46347    Effect.Methods[effect] = function(element, options){
46348      element = $(element);
46349      Effect[effect.charAt(0).toUpperCase() + effect.substring(1)](element, options);
46350      return element;
46351    };
46352  }
46353);
46354
46355$w('getInlineOpacity forceRerendering setContentZoom collectTextNodes collectTextNodesIgnoreClass getStyles').each(
46356  function(f) { Effect.Methods[f] = Element[f]; }
46357);
46358
46359Element.addMethods(Effect.Methods);
46360// script.aculo.us controls.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
46361
46362// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
46363//           (c) 2005-2009 Ivan Krstic (http://blogs.law.harvard.edu/ivan)
46364//           (c) 2005-2009 Jon Tirsen (http://www.tirsen.com)
46365// Contributors:
46366//  Richard Livsey
46367//  Rahul Bhargava
46368//  Rob Wills
46369//
46370// script.aculo.us is freely distributable under the terms of an MIT-style license.
46371// For details, see the script.aculo.us web site: http://script.aculo.us/
46372
46373// Autocompleter.Base handles all the autocompletion functionality
46374// that's independent of the data source for autocompletion. This
46375// includes drawing the autocompletion menu, observing keyboard
46376// and mouse events, and similar.
46377//
46378// Specific autocompleters need to provide, at the very least,
46379// a getUpdatedChoices function that will be invoked every time
46380// the text inside the monitored textbox changes. This method
46381// should get the text for which to provide autocompletion by
46382// invoking this.getToken(), NOT by directly accessing
46383// this.element.value. This is to allow incremental tokenized
46384// autocompletion. Specific auto-completion logic (AJAX, etc)
46385// belongs in getUpdatedChoices.
46386//
46387// Tokenized incremental autocompletion is enabled automatically
46388// when an autocompleter is instantiated with the 'tokens' option
46389// in the options parameter, e.g.:
46390// new Ajax.Autocompleter('id','upd', '/url/', { tokens: ',' });
46391// will incrementally autocomplete with a comma as the token.
46392// Additionally, ',' in the above example can be replaced with
46393// a token array, e.g. { tokens: [',', '\n'] } which
46394// enables autocompletion on multiple tokens. This is most
46395// useful when one of the tokens is \n (a newline), as it
46396// allows smart autocompletion after linebreaks.
46397
46398if(typeof Effect == 'undefined')
46399  throw("controls.js requires including script.aculo.us' effects.js library");
46400
46401var Autocompleter = { };
46402Autocompleter.Base = Class.create({
46403  baseInitialize: function(element, update, options) {
46404    element          = $(element);
46405    this.element     = element;
46406    this.update      = $(update);
46407    this.hasFocus    = false;
46408    this.changed     = false;
46409    this.active      = false;
46410    this.index       = 0;
46411    this.entryCount  = 0;
46412    this.oldElementValue = this.element.value;
46413
46414    if(this.setOptions)
46415      this.setOptions(options);
46416    else
46417      this.options = options || { };
46418
46419    this.options.paramName    = this.options.paramName || this.element.name;
46420    this.options.tokens       = this.options.tokens || [];
46421    this.options.frequency    = this.options.frequency || 0.4;
46422    this.options.minChars     = this.options.minChars || 1;
46423    this.options.onShow       = this.options.onShow ||
46424      function(element, update){
46425        if(!update.style.position || update.style.position=='absolute') {
46426          update.style.position = 'absolute';
46427          Position.clone(element, update, {
46428            setHeight: false,
46429            offsetTop: element.offsetHeight
46430          });
46431        }
46432        Effect.Appear(update,{duration:0.15});
46433      };
46434    this.options.onHide = this.options.onHide ||
46435      function(element, update){ new Effect.Fade(update,{duration:0.15}) };
46436
46437    if(typeof(this.options.tokens) == 'string')
46438      this.options.tokens = new Array(this.options.tokens);
46439    // Force carriage returns as token delimiters anyway
46440    if (!this.options.tokens.include('\n'))
46441      this.options.tokens.push('\n');
46442
46443    this.observer = null;
46444
46445    this.element.setAttribute('autocomplete','off');
46446
46447    Element.hide(this.update);
46448
46449    Event.observe(this.element, 'blur', this.onBlur.bindAsEventListener(this));
46450    Event.observe(this.element, 'keydown', this.onKeyPress.bindAsEventListener(this));
46451  },
46452
46453  show: function() {
46454    if(Element.getStyle(this.update, 'display')=='none') this.options.onShow(this.element, this.update);
46455    if(!this.iefix &&
46456      (Prototype.Browser.IE) &&
46457      (Element.getStyle(this.update, 'position')=='absolute')) {
46458      new Insertion.After(this.update,
46459       '<iframe id="' + this.update.id + '_iefix" '+
46460       'style="display:none;position:absolute;filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);" ' +
46461       'src="javascript:false;" frameborder="0" scrolling="no"></iframe>');
46462      this.iefix = $(this.update.id+'_iefix');
46463    }
46464    if(this.iefix) setTimeout(this.fixIEOverlapping.bind(this), 50);
46465  },
46466
46467  fixIEOverlapping: function() {
46468    Position.clone(this.update, this.iefix, {setTop:(!this.update.style.height)});
46469    this.iefix.style.zIndex = 1;
46470    this.update.style.zIndex = 2;
46471    Element.show(this.iefix);
46472  },
46473
46474  hide: function() {
46475    this.stopIndicator();
46476    if(Element.getStyle(this.update, 'display')!='none') this.options.onHide(this.element, this.update);
46477    if(this.iefix) Element.hide(this.iefix);
46478  },
46479
46480  startIndicator: function() {
46481    if(this.options.indicator) Element.show(this.options.indicator);
46482  },
46483
46484  stopIndicator: function() {
46485    if(this.options.indicator) Element.hide(this.options.indicator);
46486  },
46487
46488  onKeyPress: function(event) {
46489    if(this.active)
46490      switch(event.keyCode) {
46491       case Event.KEY_TAB:
46492       case Event.KEY_RETURN:
46493         this.selectEntry();
46494         Event.stop(event);
46495       case Event.KEY_ESC:
46496         this.hide();
46497         this.active = false;
46498         Event.stop(event);
46499         return;
46500       case Event.KEY_LEFT:
46501       case Event.KEY_RIGHT:
46502         return;
46503       case Event.KEY_UP:
46504         this.markPrevious();
46505         this.render();
46506         Event.stop(event);
46507         return;
46508       case Event.KEY_DOWN:
46509         this.markNext();
46510         this.render();
46511         Event.stop(event);
46512         return;
46513      }
46514     else
46515       if(event.keyCode==Event.KEY_TAB || event.keyCode==Event.KEY_RETURN ||
46516         (Prototype.Browser.WebKit > 0 && event.keyCode == 0)) return;
46517
46518    this.changed = true;
46519    this.hasFocus = true;
46520
46521    if(this.observer) clearTimeout(this.observer);
46522      this.observer =
46523        setTimeout(this.onObserverEvent.bind(this), this.options.frequency*1000);
46524  },
46525
46526  activate: function() {
46527    this.changed = false;
46528    this.hasFocus = true;
46529    this.getUpdatedChoices();
46530  },
46531
46532  onHover: function(event) {
46533    var element = Event.findElement(event, 'LI');
46534    if(this.index != element.autocompleteIndex)
46535    {
46536        this.index = element.autocompleteIndex;
46537        this.render();
46538    }
46539    Event.stop(event);
46540  },
46541
46542  onClick: function(event) {
46543    var element = Event.findElement(event, 'LI');
46544    this.index = element.autocompleteIndex;
46545    this.selectEntry();
46546    this.hide();
46547  },
46548
46549  onBlur: function(event) {
46550    // needed to make click events working
46551    setTimeout(this.hide.bind(this), 250);
46552    this.hasFocus = false;
46553    this.active = false;
46554  },
46555
46556  render: function() {
46557    if(this.entryCount > 0) {
46558      for (var i = 0; i < this.entryCount; i++)
46559        this.index==i ?
46560          Element.addClassName(this.getEntry(i),"selected") :
46561          Element.removeClassName(this.getEntry(i),"selected");
46562      if(this.hasFocus) {
46563        this.show();
46564        this.active = true;
46565      }
46566    } else {
46567      this.active = false;
46568      this.hide();
46569    }
46570  },
46571
46572  markPrevious: function() {
46573    if(this.index > 0) this.index--;
46574      else this.index = this.entryCount-1;
46575    this.getEntry(this.index).scrollIntoView(true);
46576  },
46577
46578  markNext: function() {
46579    if(this.index < this.entryCount-1) this.index++;
46580      else this.index = 0;
46581    this.getEntry(this.index).scrollIntoView(false);
46582  },
46583
46584  getEntry: function(index) {
46585    return this.update.firstChild.childNodes[index];
46586  },
46587
46588  getCurrentEntry: function() {
46589    return this.getEntry(this.index);
46590  },
46591
46592  selectEntry: function() {
46593    this.active = false;
46594    this.updateElement(this.getCurrentEntry());
46595  },
46596
46597  updateElement: function(selectedElement) {
46598    if (this.options.updateElement) {
46599      this.options.updateElement(selectedElement);
46600      return;
46601    }
46602    var value = '';
46603    if (this.options.select) {
46604      var nodes = $(selectedElement).select('.' + this.options.select) || [];
46605      if(nodes.length>0) value = Element.collectTextNodes(nodes[0], this.options.select);
46606    } else
46607      value = Element.collectTextNodesIgnoreClass(selectedElement, 'informal');
46608
46609    var bounds = this.getTokenBounds();
46610    if (bounds[0] != -1) {
46611      var newValue = this.element.value.substr(0, bounds[0]);
46612      var whitespace = this.element.value.substr(bounds[0]).match(/^\s+/);
46613      if (whitespace)
46614        newValue += whitespace[0];
46615      this.element.value = newValue + value + this.element.value.substr(bounds[1]);
46616    } else {
46617      this.element.value = value;
46618    }
46619    this.oldElementValue = this.element.value;
46620    this.element.focus();
46621
46622    if (this.options.afterUpdateElement)
46623      this.options.afterUpdateElement(this.element, selectedElement);
46624  },
46625
46626  updateChoices: function(choices) {
46627    if(!this.changed && this.hasFocus) {
46628      this.update.innerHTML = choices;
46629      Element.cleanWhitespace(this.update);
46630      Element.cleanWhitespace(this.update.down());
46631
46632      if(this.update.firstChild && this.update.down().childNodes) {
46633        this.entryCount =
46634          this.update.down().childNodes.length;
46635        for (var i = 0; i < this.entryCount; i++) {
46636          var entry = this.getEntry(i);
46637          entry.autocompleteIndex = i;
46638          this.addObservers(entry);
46639        }
46640      } else {
46641        this.entryCount = 0;
46642      }
46643
46644      this.stopIndicator();
46645      this.index = 0;
46646
46647      if(this.entryCount==1 && this.options.autoSelect) {
46648        this.selectEntry();
46649        this.hide();
46650      } else {
46651        this.render();
46652      }
46653    }
46654  },
46655
46656  addObservers: function(element) {
46657    Event.observe(element, "mouseover", this.onHover.bindAsEventListener(this));
46658    Event.observe(element, "click", this.onClick.bindAsEventListener(this));
46659  },
46660
46661  onObserverEvent: function() {
46662    this.changed = false;
46663    this.tokenBounds = null;
46664    if(this.getToken().length>=this.options.minChars) {
46665      this.getUpdatedChoices();
46666    } else {
46667      this.active = false;
46668      this.hide();
46669    }
46670    this.oldElementValue = this.element.value;
46671  },
46672
46673  getToken: function() {
46674    var bounds = this.getTokenBounds();
46675    return this.element.value.substring(bounds[0], bounds[1]).strip();
46676  },
46677
46678  getTokenBounds: function() {
46679    if (null != this.tokenBounds) return this.tokenBounds;
46680    var value = this.element.value;
46681    if (value.strip().empty()) return [-1, 0];
46682    var diff = arguments.callee.getFirstDifferencePos(value, this.oldElementValue);
46683    var offset = (diff == this.oldElementValue.length ? 1 : 0);
46684    var prevTokenPos = -1, nextTokenPos = value.length;
46685    var tp;
46686    for (var index = 0, l = this.options.tokens.length; index < l; ++index) {
46687      tp = value.lastIndexOf(this.options.tokens[index], diff + offset - 1);
46688      if (tp > prevTokenPos) prevTokenPos = tp;
46689      tp = value.indexOf(this.options.tokens[index], diff + offset);
46690      if (-1 != tp && tp < nextTokenPos) nextTokenPos = tp;
46691    }
46692    return (this.tokenBounds = [prevTokenPos + 1, nextTokenPos]);
46693  }
46694});
46695
46696Autocompleter.Base.prototype.getTokenBounds.getFirstDifferencePos = function(newS, oldS) {
46697  var boundary = Math.min(newS.length, oldS.length);
46698  for (var index = 0; index < boundary; ++index)
46699    if (newS[index] != oldS[index])
46700      return index;
46701  return boundary;
46702};
46703
46704Ajax.Autocompleter = Class.create(Autocompleter.Base, {
46705  initialize: function(element, update, url, options) {
46706    this.baseInitialize(element, update, options);
46707    this.options.asynchronous  = true;
46708    this.options.onComplete    = this.onComplete.bind(this);
46709    this.options.defaultParams = this.options.parameters || null;
46710    this.url                   = url;
46711  },
46712
46713  getUpdatedChoices: function() {
46714    this.startIndicator();
46715
46716    var entry = encodeURIComponent(this.options.paramName) + '=' +
46717      encodeURIComponent(this.getToken());
46718
46719    this.options.parameters = this.options.callback ?
46720      this.options.callback(this.element, entry) : entry;
46721
46722    if(this.options.defaultParams)
46723      this.options.parameters += '&' + this.options.defaultParams;
46724
46725    new Ajax.Request(this.url, this.options);
46726  },
46727
46728  onComplete: function(request) {
46729    this.updateChoices(request.responseText);
46730  }
46731});
46732
46733// The local array autocompleter. Used when you'd prefer to
46734// inject an array of autocompletion options into the page, rather
46735// than sending out Ajax queries, which can be quite slow sometimes.
46736//
46737// The constructor takes four parameters. The first two are, as usual,
46738// the id of the monitored textbox, and id of the autocompletion menu.
46739// The third is the array you want to autocomplete from, and the fourth
46740// is the options block.
46741//
46742// Extra local autocompletion options:
46743// - choices - How many autocompletion choices to offer
46744//
46745// - partialSearch - If false, the autocompleter will match entered
46746//                    text only at the beginning of strings in the
46747//                    autocomplete array. Defaults to true, which will
46748//                    match text at the beginning of any *word* in the
46749//                    strings in the autocomplete array. If you want to
46750//                    search anywhere in the string, additionally set
46751//                    the option fullSearch to true (default: off).
46752//
46753// - fullSsearch - Search anywhere in autocomplete array strings.
46754//
46755// - partialChars - How many characters to enter before triggering
46756//                   a partial match (unlike minChars, which defines
46757//                   how many characters are required to do any match
46758//                   at all). Defaults to 2.
46759//
46760// - ignoreCase - Whether to ignore case when autocompleting.
46761//                 Defaults to true.
46762//
46763// It's possible to pass in a custom function as the 'selector'
46764// option, if you prefer to write your own autocompletion logic.
46765// In that case, the other options above will not apply unless
46766// you support them.
46767
46768Autocompleter.Local = Class.create(Autocompleter.Base, {
46769  initialize: function(element, update, array, options) {
46770    this.baseInitialize(element, update, options);
46771    this.options.array = array;
46772  },
46773
46774  getUpdatedChoices: function() {
46775    this.updateChoices(this.options.selector(this));
46776  },
46777
46778  setOptions: function(options) {
46779    this.options = Object.extend({
46780      choices: 10,
46781      partialSearch: true,
46782      partialChars: 2,
46783      ignoreCase: true,
46784      fullSearch: false,
46785      selector: function(instance) {
46786        var ret       = []; // Beginning matches
46787        var partial   = []; // Inside matches
46788        var entry     = instance.getToken();
46789        var count     = 0;
46790
46791        for (var i = 0; i < instance.options.array.length &&
46792          ret.length < instance.options.choices ; i++) {
46793
46794          var elem = instance.options.array[i];
46795          var foundPos = instance.options.ignoreCase ?
46796            elem.toLowerCase().indexOf(entry.toLowerCase()) :
46797            elem.indexOf(entry);
46798
46799          while (foundPos != -1) {
46800            if (foundPos == 0 && elem.length != entry.length) {
46801              ret.push("<li><strong>" + elem.substr(0, entry.length) + "</strong>" +
46802                elem.substr(entry.length) + "</li>");
46803              break;
46804            } else if (entry.length >= instance.options.partialChars &&
46805              instance.options.partialSearch && foundPos != -1) {
46806              if (instance.options.fullSearch || /\s/.test(elem.substr(foundPos-1,1))) {
46807                partial.push("<li>" + elem.substr(0, foundPos) + "<strong>" +
46808                  elem.substr(foundPos, entry.length) + "</strong>" + elem.substr(
46809                  foundPos + entry.length) + "</li>");
46810                break;
46811              }
46812            }
46813
46814            foundPos = instance.options.ignoreCase ?
46815              elem.toLowerCase().indexOf(entry.toLowerCase(), foundPos + 1) :
46816              elem.indexOf(entry, foundPos + 1);
46817
46818          }
46819        }
46820        if (partial.length)
46821          ret = ret.concat(partial.slice(0, instance.options.choices - ret.length));
46822        return "<ul>" + ret.join('') + "</ul>";
46823      }
46824    }, options || { });
46825  }
46826});
46827
46828// AJAX in-place editor and collection editor
46829// Full rewrite by Christophe Porteneuve <[email protected]> (April 2007).
46830
46831// Use this if you notice weird scrolling problems on some browsers,
46832// the DOM might be a bit confused when this gets called so do this
46833// waits 1 ms (with setTimeout) until it does the activation
46834Field.scrollFreeActivate = function(field) {
46835  setTimeout(function() {
46836    Field.activate(field);
46837  }, 1);
46838};
46839
46840Ajax.InPlaceEditor = Class.create({
46841  initialize: function(element, url, options) {
46842    this.url = url;
46843    this.element = element = $(element);
46844    this.prepareOptions();
46845    this._controls = { };
46846    arguments.callee.dealWithDeprecatedOptions(options); // DEPRECATION LAYER!!!
46847    Object.extend(this.options, options || { });
46848    if (!this.options.formId && this.element.id) {
46849      this.options.formId = this.element.id + '-inplaceeditor';
46850      if ($(this.options.formId))
46851        this.options.formId = '';
46852    }
46853    if (this.options.externalControl)
46854      this.options.externalControl = $(this.options.externalControl);
46855    if (!this.options.externalControl)
46856      this.options.externalControlOnly = false;
46857    this._originalBackground = this.element.getStyle('background-color') || 'transparent';
46858    this.element.title = this.options.clickToEditText;
46859    this._boundCancelHandler = this.handleFormCancellation.bind(this);
46860    this._boundComplete = (this.options.onComplete || Prototype.emptyFunction).bind(this);
46861    this._boundFailureHandler = this.handleAJAXFailure.bind(this);
46862    this._boundSubmitHandler = this.handleFormSubmission.bind(this);
46863    this._boundWrapperHandler = this.wrapUp.bind(this);
46864    this.registerListeners();
46865  },
46866  checkForEscapeOrReturn: function(e) {
46867    if (!this._editing || e.ctrlKey || e.altKey || e.shiftKey) return;
46868    if (Event.KEY_ESC == e.keyCode)
46869      this.handleFormCancellation(e);
46870    else if (Event.KEY_RETURN == e.keyCode)
46871      this.handleFormSubmission(e);
46872  },
46873  createControl: function(mode, handler, extraClasses) {
46874    var control = this.options[mode + 'Control'];
46875    var text = this.options[mode + 'Text'];
46876    if ('button' == control) {
46877      var btn = document.createElement('input');
46878      btn.type = 'submit';
46879      btn.value = text;
46880      btn.className = 'editor_' + mode + '_button';
46881      if ('cancel' == mode)
46882        btn.onclick = this._boundCancelHandler;
46883      this._form.appendChild(btn);
46884      this._controls[mode] = btn;
46885    } else if ('link' == control) {
46886      var link = document.createElement('a');
46887      link.href = '#';
46888      link.appendChild(document.createTextNode(text));
46889      link.onclick = 'cancel' == mode ? this._boundCancelHandler : this._boundSubmitHandler;
46890      link.className = 'editor_' + mode + '_link';
46891      if (extraClasses)
46892        link.className += ' ' + extraClasses;
46893      this._form.appendChild(link);
46894      this._controls[mode] = link;
46895    }
46896  },
46897  createEditField: function() {
46898    var text = (this.options.loadTextURL ? this.options.loadingText : this.getText());
46899    var fld;
46900    if (1 >= this.options.rows && !/\r|\n/.test(this.getText())) {
46901      fld = document.createElement('input');
46902      fld.type = 'text';
46903      var size = this.options.size || this.options.cols || 0;
46904      if (0 < size) fld.size = size;
46905    } else {
46906      fld = document.createElement('textarea');
46907      fld.rows = (1 >= this.options.rows ? this.options.autoRows : this.options.rows);
46908      fld.cols = this.options.cols || 40;
46909    }
46910    fld.name = this.options.paramName;
46911    fld.value = text; // No HTML breaks conversion anymore
46912    fld.className = 'editor_field';
46913    if (this.options.submitOnBlur)
46914      fld.onblur = this._boundSubmitHandler;
46915    this._controls.editor = fld;
46916    if (this.options.loadTextURL)
46917      this.loadExternalText();
46918    this._form.appendChild(this._controls.editor);
46919  },
46920  createForm: function() {
46921    var ipe = this;
46922    function addText(mode, condition) {
46923      var text = ipe.options['text' + mode + 'Controls'];
46924      if (!text || condition === false) return;
46925      ipe._form.appendChild(document.createTextNode(text));
46926    };
46927    this._form = $(document.createElement('form'));
46928    this._form.id = this.options.formId;
46929    this._form.addClassName(this.options.formClassName);
46930    this._form.onsubmit = this._boundSubmitHandler;
46931    this.createEditField();
46932    if ('textarea' == this._controls.editor.tagName.toLowerCase())
46933      this._form.appendChild(document.createElement('br'));
46934    if (this.options.onFormCustomization)
46935      this.options.onFormCustomization(this, this._form);
46936    addText('Before', this.options.okControl || this.options.cancelControl);
46937    this.createControl('ok', this._boundSubmitHandler);
46938    addText('Between', this.options.okControl && this.options.cancelControl);
46939    this.createControl('cancel', this._boundCancelHandler, 'editor_cancel');
46940    addText('After', this.options.okControl || this.options.cancelControl);
46941  },
46942  destroy: function() {
46943    if (this._oldInnerHTML)
46944      this.element.innerHTML = this._oldInnerHTML;
46945    this.leaveEditMode();
46946    this.unregisterListeners();
46947  },
46948  enterEditMode: function(e) {
46949    if (this._saving || this._editing) return;
46950    this._editing = true;
46951    this.triggerCallback('onEnterEditMode');
46952    if (this.options.externalControl)
46953      this.options.externalControl.hide();
46954    this.element.hide();
46955    this.createForm();
46956    this.element.parentNode.insertBefore(this._form, this.element);
46957    if (!this.options.loadTextURL)
46958      this.postProcessEditField();
46959    if (e) Event.stop(e);
46960  },
46961  enterHover: function(e) {
46962    if (this.options.hoverClassName)
46963      this.element.addClassName(this.options.hoverClassName);
46964    if (this._saving) return;
46965    this.triggerCallback('onEnterHover');
46966  },
46967  getText: function() {
46968    return this.element.innerHTML.unescapeHTML();
46969  },
46970  handleAJAXFailure: function(transport) {
46971    this.triggerCallback('onFailure', transport);
46972    if (this._oldInnerHTML) {
46973      this.element.innerHTML = this._oldInnerHTML;
46974      this._oldInnerHTML = null;
46975    }
46976  },
46977  handleFormCancellation: function(e) {
46978    this.wrapUp();
46979    if (e) Event.stop(e);
46980  },
46981  handleFormSubmission: function(e) {
46982    var form = this._form;
46983    var value = $F(this._controls.editor);
46984    this.prepareSubmission();
46985    var params = this.options.callback(form, value) || '';
46986    if (Object.isString(params))
46987      params = params.toQueryParams();
46988    params.editorId = this.element.id;
46989    if (this.options.htmlResponse) {
46990      var options = Object.extend({ evalScripts: true }, this.options.ajaxOptions);
46991      Object.extend(options, {
46992        parameters: params,
46993        onComplete: this._boundWrapperHandler,
46994        onFailure: this._boundFailureHandler
46995      });
46996      new Ajax.Updater({ success: this.element }, this.url, options);
46997    } else {
46998      var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
46999      Object.extend(options, {
47000        parameters: params,
47001        onComplete: this._boundWrapperHandler,
47002        onFailure: this._boundFailureHandler
47003      });
47004      new Ajax.Request(this.url, options);
47005    }
47006    if (e) Event.stop(e);
47007  },
47008  leaveEditMode: function() {
47009    this.element.removeClassName(this.options.savingClassName);
47010    this.removeForm();
47011    this.leaveHover();
47012    this.element.style.backgroundColor = this._originalBackground;
47013    this.element.show();
47014    if (this.options.externalControl)
47015      this.options.externalControl.show();
47016    this._saving = false;
47017    this._editing = false;
47018    this._oldInnerHTML = null;
47019    this.triggerCallback('onLeaveEditMode');
47020  },
47021  leaveHover: function(e) {
47022    if (this.options.hoverClassName)
47023      this.element.removeClassName(this.options.hoverClassName);
47024    if (this._saving) return;
47025    this.triggerCallback('onLeaveHover');
47026  },
47027  loadExternalText: function() {
47028    this._form.addClassName(this.options.loadingClassName);
47029    this._controls.editor.disabled = true;
47030    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
47031    Object.extend(options, {
47032      parameters: 'editorId=' + encodeURIComponent(this.element.id),
47033      onComplete: Prototype.emptyFunction,
47034      onSuccess: function(transport) {
47035        this._form.removeClassName(this.options.loadingClassName);
47036        var text = transport.responseText;
47037        if (this.options.stripLoadedTextTags)
47038          text = text.stripTags();
47039        this._controls.editor.value = text;
47040        this._controls.editor.disabled = false;
47041        this.postProcessEditField();
47042      }.bind(this),
47043      onFailure: this._boundFailureHandler
47044    });
47045    new Ajax.Request(this.options.loadTextURL, options);
47046  },
47047  postProcessEditField: function() {
47048    var fpc = this.options.fieldPostCreation;
47049    if (fpc)
47050      $(this._controls.editor)['focus' == fpc ? 'focus' : 'activate']();
47051  },
47052  prepareOptions: function() {
47053    this.options = Object.clone(Ajax.InPlaceEditor.DefaultOptions);
47054    Object.extend(this.options, Ajax.InPlaceEditor.DefaultCallbacks);
47055    [this._extraDefaultOptions].flatten().compact().each(function(defs) {
47056      Object.extend(this.options, defs);
47057    }.bind(this));
47058  },
47059  prepareSubmission: function() {
47060    this._saving = true;
47061    this.removeForm();
47062    this.leaveHover();
47063    this.showSaving();
47064  },
47065  registerListeners: function() {
47066    this._listeners = { };
47067    var listener;
47068    $H(Ajax.InPlaceEditor.Listeners).each(function(pair) {
47069      listener = this[pair.value].bind(this);
47070      this._listeners[pair.key] = listener;
47071      if (!this.options.externalControlOnly)
47072        this.element.observe(pair.key, listener);
47073      if (this.options.externalControl)
47074        this.options.externalControl.observe(pair.key, listener);
47075    }.bind(this));
47076  },
47077  removeForm: function() {
47078    if (!this._form) return;
47079    this._form.remove();
47080    this._form = null;
47081    this._controls = { };
47082  },
47083  showSaving: function() {
47084    this._oldInnerHTML = this.element.innerHTML;
47085    this.element.innerHTML = this.options.savingText;
47086    this.element.addClassName(this.options.savingClassName);
47087    this.element.style.backgroundColor = this._originalBackgroun
47087d;
47088    this.element.show();
47089  },
47090  triggerCallback: function(cbName, arg) {
47091    if ('function' == typeof this.options[cbName]) {
47092      this.options[cbName](this, arg);
47093    }
47094  },
47095  unregisterListeners: function() {
47096    $H(this._listeners).each(function(pair) {
47097      if (!this.options.externalControlOnly)
47098        this.element.stopObserving(pair.key, pair.value);
47099      if (this.options.externalControl)
47100        this.options.externalControl.stopObserving(pair.key, pair.value);
47101    }.bind(this));
47102  },
47103  wrapUp: function(transport) {
47104    this.leaveEditMode();
47105    // Can't use triggerCallback due to backward compatibility: requires
47106    // binding + direct element
47107    this._boundComplete(transport, this.element);
47108  }
47109});
47110
47111Object.extend(Ajax.InPlaceEditor.prototype, {
47112  dispose: Ajax.InPlaceEditor.prototype.destroy
47113});
47114
47115Ajax.InPlaceCollectionEditor = Class.create(Ajax.InPlaceEditor, {
47116  initialize: function($super, element, url, options) {
47117    this._extraDefaultOptions = Ajax.InPlaceCollectionEditor.DefaultOptions;
47118    $super(element, url, options);
47119  },
47120
47121  createEditField: function() {
47122    var list = document.createElement('select');
47123    list.name = this.options.paramName;
47124    list.size = 1;
47125    this._controls.editor = list;
47126    this._collection = this.options.collection || [];
47127    if (this.options.loadCollectionURL)
47128      this.loadCollection();
47129    else
47130      this.checkForExternalText();
47131    this._form.appendChild(this._controls.editor);
47132  },
47133
47134  loadCollection: function() {
47135    this._form.addClassName(this.options.loadingClassName);
47136    this.showLoadingText(this.options.loadingCollectionText);
47137    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
47138    Object.extend(options, {
47139      parameters: 'editorId=' + encodeURIComponent(this.element.id),
47140      onComplete: Prototype.emptyFunction,
47141      onSuccess: function(transport) {
47142        var js = transport.responseText.strip();
47143        if (!/^\[.*\]$/.test(js)) // TODO: improve sanity check
47144          throw('Server returned an invalid collection representation.');
47145        this._collection = eval(js);
47146        this.checkForExternalText();
47147      }.bind(this),
47148      onFailure: this.onFailure
47149    });
47150    new Ajax.Request(this.options.loadCollectionURL, options);
47151  },
47152
47153  showLoadingText: function(text) {
47154    this._controls.editor.disabled = true;
47155    var tempOption = this._controls.editor.firstChild;
47156    if (!tempOption) {
47157      tempOption = document.createElement('option');
47158      tempOption.value = '';
47159      this._controls.editor.appendChild(tempOption);
47160      tempOption.selected = true;
47161    }
47162    tempOption.update((text || '').stripScripts().stripTags());
47163  },
47164
47165  checkForExternalText: function() {
47166    this._text = this.getText();
47167    if (this.options.loadTextURL)
47168      this.loadExternalText();
47169    else
47170      this.buildOptionList();
47171  },
47172
47173  loadExternalText: function() {
47174    this.showLoadingText(this.options.loadingText);
47175    var options = Object.extend({ method: 'get' }, this.options.ajaxOptions);
47176    Object.extend(options, {
47177      parameters: 'editorId=' + encodeURIComponent(this.element.id),
47178      onComplete: Prototype.emptyFunction,
47179      onSuccess: function(transport) {
47180        this._text = transport.responseText.strip();
47181        this.buildOptionList();
47182      }.bind(this),
47183      onFailure: this.onFailure
47184    });
47185    new Ajax.Request(this.options.loadTextURL, options);
47186  },
47187
47188  buildOptionList: function() {
47189    this._form.removeClassName(this.options.loadingClassName);
47190    this._collection = this._collection.map(function(entry) {
47191      return 2 === entry.length ? entry : [entry, entry].flatten();
47192    });
47193    var marker = ('value' in this.options) ? this.options.value : this._text;
47194    var textFound = this._collection.any(function(entry) {
47195      return entry[0] == marker;
47196    }.bind(this));
47197    this._controls.editor.update('');
47198    var option;
47199    this._collection.each(function(entry, index) {
47200      option = document.createElement('option');
47201      option.value = entry[0];
47202      option.selected = textFound ? entry[0] == marker : 0 == index;
47203      option.appendChild(document.createTextNode(entry[1]));
47204      this._controls.editor.appendChild(option);
47205    }.bind(this));
47206    this._controls.editor.disabled = false;
47207    Field.scrollFreeActivate(this._controls.editor);
47208  }
47209});
47210
47211//**** DEPRECATION LAYER FOR InPlace[Collection]Editor! ****
47212//**** This only  exists for a while,  in order to  let ****
47213//**** users adapt to  the new API.  Read up on the new ****
47214//**** API and convert your code to it ASAP!            ****
47215
47216Ajax.InPlaceEditor.prototype.initialize.dealWithDeprecatedOptions = function(options) {
47217  if (!options) return;
47218  function fallback(name, expr) {
47219    if (name in options || expr === undefined) return;
47220    options[name] = expr;
47221  };
47222  fallback('cancelControl', (options.cancelLink ? 'link' : (options.cancelButton ? 'button' :
47223    options.cancelLink == options.cancelButton == false ? false : undefined)));
47224  fallback('okControl', (options.okLink ? 'link' : (options.okButton ? 'button' :
47225    options.okLink == options.okButton == false ? false : undefined)));
47226  fallback('highlightColor', options.highlightcolor);
47227  fallback('highlightEndColor', options.highlightendcolor);
47228};
47229
47230Object.extend(Ajax.InPlaceEditor, {
47231  DefaultOptions: {
47232    ajaxOptions: { },
47233    autoRows: 3,                                // Use when multi-line w/ rows == 1
47234    cancelControl: 'link',                      // 'link'|'button'|false
47235    cancelText: 'cancel',
47236    clickToEditText: 'Click to edit',
47237    externalControl: null,                      // id|elt
47238    externalControlOnly: false,
47239    fieldPostCreation: 'activate',              // 'activate'|'focus'|false
47240    formClassName: 'inplaceeditor-form',
47241    formId: null,                               // id|elt
47242    highlightColor: '#ffff99',
47243    highlightEndColor: '#ffffff',
47244    hoverClassName: '',
47245    htmlResponse: true,
47246    loadingClassName: 'inplaceeditor-loading',
47247    loadingText: 'Loading...',
47248    okControl: 'button',                        // 'link'|'button'|false
47249    okText: 'ok',
47250    paramName: 'value',
47251    rows: 1,                                    // If 1 and multi-line, uses autoRows
47252    savingClassName: 'inplaceeditor-saving',
47253    savingText: 'Saving...',
47254    size: 0,
47255    stripLoadedTextTags: false,
47256    submitOnBlur: false,
47257    textAfterControls: '',
47258    textBeforeControls: '',
47259    textBetweenControls: ''
47260  },
47261  DefaultCallbacks: {
47262    callback: function(form) {
47263      return Form.serialize(form);
47264    },
47265    onComplete: function(transport, element) {
47266      // For backward compatibility, this one is bound to the IPE, and passes
47267      // the element directly.  It was too often customized, so we don't break it.
47268      new Effect.Highlight(element, {
47269        startcolor: this.options.highlightColor, keepBackgroundImage: true });
47270    },
47271    onEnterEditMode: null,
47272    onEnterHover: function(ipe) {
47273      ipe.element.style.backgroundColor = ipe.options.highlightColor;
47274      if (ipe._effect)
47275        ipe._effect.cancel();
47276    },
47277    onFailure: function(transport, ipe) {
47278      alert('Error communication with the server: ' + transport.responseText.stripTags());
47279    },
47280    onFormCustomization: null, // Takes the IPE and its generated form, after editor, before controls.
47281    onLeaveEditMode: null,
47282    onLeaveHover: function(ipe) {
47283      ipe._effect = new Effect.Highlight(ipe.element, {
47284        startcolor: ipe.options.highlightColor, endcolor: ipe.options.highlightEndColor,
47285        restorecolor: ipe._originalBackground, keepBackgroundImage: true
47286      });
47287    }
47288  },
47289  Listeners: {
47290    click: 'enterEditMode',
47291    keydown: 'checkForEscapeOrReturn',
47292    mouseover: 'enterHover',
47293    mouseout: 'leaveHover'
47294  }
47295});
47296
47297Ajax.InPlaceCollectionEditor.DefaultOptions = {
47298  loadingCollectionText: 'Loading options...'
47299};
47300
47301// Delayed observer, like Form.Element.Observer,
47302// but waits for delay after last key input
47303// Ideal for live-search fields
47304
47305Form.Element.DelayedObserver = Class.create({
47306  initialize: function(element, delay, callback) {
47307    this.delay     = delay || 0.5;
47308    this.element   = $(element);
47309    this.callback  = callback;
47310    this.timer     = null;
47311    this.lastValue = $F(this.element);
47312    Event.observe(this.element,'keyup',this.delayedListener.bindAsEventListener(this));
47313  },
47314  delayedListener: function(event) {
47315    if(this.lastValue == $F(this.element)) return;
47316    if(this.timer) clearTimeout(this.timer);
47317    this.timer = setTimeout(this.onTimerEvent.bind(this), this.delay * 1000);
47318    this.lastValue = $F(this.element);
47319  },
47320  onTimerEvent: function() {
47321    this.timer = null;
47322    this.callback(this.element, $F(this.element));
47323  }
47324});
47325// script.aculo.us scriptaculous.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
47326
47327// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
47328//
47329// Permission is hereby granted, free of charge, to any person obtaining
47330// a copy of this software and associated documentation files (the
47331// "Software"), to deal in the Software without restriction, including
47332// without limitation the rights to use, copy, modify, merge, publish,
47333// distribute, sublicense, and/or sell copies of the Software, and to
47334// permit persons to whom the Software is furnished to do so, subject to
47335// the following conditions:
47336//
47337// The above copyright notice and this permission notice shall be
47338// included in all copies or substantial portions of the Software.
47339//
47340// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
47341// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
47342// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
47343// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
47344// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
47345// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
47346// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
47347//
47348// For details, see the script.aculo.us web site: http://script.aculo.us/
47349
47350var Scriptaculous = {
47351  Version: '1.8.3',
47352  require: function(libraryName) {
47353    try{
47354      // inserting via DOM fails in Safari 2.0, so brute force approach
47355      document.write('<script type="text/javascript" src="'+libraryName+'"><\/script>');
47356    } catch(e) {
47357      // for xhtml+xml served content, fall back to DOM methods
47358      var script = document.createElement('script');
47359      script.type = 'text/javascript';
47360      script.src = libraryName;
47361      document.getElementsByTagName('head')[0].appendChild(script);
47362    }
47363  },
47364  REQUIRED_PROTOTYPE: '1.6.0.3',
47365  load: function() {
47366    function convertVersionString(versionString) {
47367      var v = versionString.replace(/_.*|\./g, '');
47368      v = parseInt(v + '0'.times(4-v.length));
47369      return versionString.indexOf('_') > -1 ? v-1 : v;
47370    }
47371
47372    if((typeof Prototype=='undefined') ||
47373       (typeof Element == 'undefined') ||
47374       (typeof Element.Methods=='undefined') ||
47375       (convertVersionString(Prototype.Version) <
47376        convertVersionString(Scriptaculous.REQUIRED_PROTOTYPE)))
47377       throw("script.aculo.us requires the Prototype JavaScript framework >= " +
47378        Scriptaculous.REQUIRED_PROTOTYPE);
47379
47380    var js = /scriptaculous\.js(\?.*)?$/;
47381    $$('head script[src]').findAll(function(s) {
47382      return s.src.match(js);
47383    }).each(function(s) {
47384      var path = s.src.replace(js, ''),
47385      includes = s.src.match(/\?.*load=([a-z,]*)/);
47386      (includes ? includes[1] : 'builder,effects,dragdrop,controls,slider,sound').split(',').each(
47387       function(include) { Scriptaculous.require(path+include+'.js') });
47388    });
47389  }
47390};
47391
47392Scriptaculous.load();
47393var __debugging__ = false;
47394
47395
47396function trace(msg) {
47397  if(!__debugging__)
47398    return;
47399
47400  var dbg = $('debug_monitor');
47401  dbg.value += "\n" + msg + "\n";
47402  dbg.scrollTop = dbg.scrollHeight;
47403}
47404
47405function inspect(obj) {
47406    var s = "";
47407    for(key in obj) {
47408        s += "\nkey: " + key + ", value: " + obj[key];
47409    }
47410    return(s);
47411}
47412
47413function setupDebug(dbg) {
47414    __debugging__= dbg;
47415     if(!__debugging__)
47416          return;
47417
47418     $('debug_window').show();
47419     $('debug_monitor').value = "";
47420     new Draggable('debug_window', {handle:'debug_handle'});
47421     $('debug_window').setOpacity(0.5);
47422     $('debug_window').observe('mouseover',overDebug, false);
47423     $('debug_window').observe('mouseout',offDebug, false);
47424     $('debug_opener').observe('click',toggleDebug, false);
47425
47426     trace("Debug started");
47427}
47428
47429function overDebug(evt) {
47430  $('debug_window').setOpacity(1);
47431}
47432
47433function offDebug(evt) {
47434  $('debug_window').setOpacity(0.5);
47435}
47436
47437function toggleDebug(evt) {
47438      evt = (evt) ? evt : event;
47439      var sender = (evt.target) ? evt.target : evt.srcElement;
47440
47441  if(sender.innerHTML == '+') {
47442    Effect.BlindDown('debug_monitor', {duration:0.2});
47443    sender.innerHTML = '-';
47444  } else {
47445    Effect.BlindUp('debug_monitor', {duration:0.2});
47446    sender.innerHTML = '+';
47447  }
47448}
47449
47450setupDebug();
47451// script.aculo.us builder.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
47452
47453// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
47454//
47455// script.aculo.us is freely distributable under the terms of an MIT-style license.
47456// For details, see the script.aculo.us web site: http://script.aculo.us/
47457
47458var Builder = {
47459  NODEMAP: {
47460    AREA: 'map',
47461    CAPTION: 'table',
47462    COL: 'table',
47463    COLGROUP: 'table',
47464    LEGEND: 'fieldset',
47465    OPTGROUP: 'select',
47466    OPTION: 'select',
47467    PARAM: 'object',
47468    TBODY: 'table',
47469    TD: 'table',
47470    TFOOT: 'table',
47471    TH: 'table',
47472    THEAD: 'table',
47473    TR: 'table'
47474  },
47475  // note: For Firefox < 1.5, OPTION and OPTGROUP tags are currently broken,
47476  //       due to a Firefox bug
47477  node: function(elementName) {
47478    elementName = elementName.toUpperCase();
47479
47480    // try innerHTML approach
47481    var parentTag = this.NODEMAP[elementName] || 'div';
47482    var parentElement = document.createElement(parentTag);
47483    try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
47484      parentElement.innerHTML = "<" + elementName + "></" + elementName + ">";
47485    } catch(e) {}
47486    var element = parentElement.firstChild || null;
47487
47488    // see if browser added wrapping tags
47489    if(element && (element.tagName.toUpperCase() != elementName))
47490      element = element.getElementsByTagName(elementName)[0];
47491
47492    // fallback to createElement approach
47493    if(!element) element = document.createElement(elementName);
47494
47495    // abort if nothing could be created
47496    if(!element) return;
47497
47498    // attributes (or text)
47499    if(arguments[1])
47500      if(this._isStringOrNumber(arguments[1]) ||
47501        (arguments[1] instanceof Array) ||
47502        arguments[1].tagName) {
47503          this._children(element, arguments[1]);
47504        } else {
47505          var attrs = this._attributes(arguments[1]);
47506          if(attrs.length) {
47507            try { // prevent IE "feature": http://dev.rubyonrails.org/ticket/2707
47508              parentElement.innerHTML = "<" +elementName + " " +
47509                attrs + "></" + elementName + ">";
47510            } catch(e) {}
47511            element = parentElement.firstChild || null;
47512            // workaround firefox 1.0.X bug
47513            if(!element) {
47514              element = document.createElement(elementName);
47515              for(attr in arguments[1])
47516                element[attr == 'class' ? 'className' : attr] = arguments[1][attr];
47517            }
47518            if(element.tagName.toUpperCase() != elementName)
47519              element = parentElement.getElementsByTagName(elementName)[0];
47520          }
47521        }
47522
47523    // text, or array of children
47524    if(arguments[2])
47525      this._children(element, arguments[2]);
47526
47527     return $(element);
47528  },
47529  _text: function(text) {
47530     return document.createTextNode(text);
47531  },
47532
47533  ATTR_MAP: {
47534    'className': 'class',
47535    'htmlFor': 'for'
47536  },
47537
47538  _attributes: function(attributes) {
47539    var attrs = [];
47540    for(attribute in attributes)
47541      attrs.push((attribute in this.ATTR_MAP ? this.ATTR_MAP[attribute] : attribute) +
47542          '="' + attributes[attribute].toString().escapeHTML().gsub(/"/,'&quot;') + '"');
47543    return attrs.join(" ");
47544  },
47545  _children: function(element, children) {
47546    if(children.tagName) {
47547      element.appendChild(children);
47548      return;
47549    }
47550    if(typeof children=='object') { // array can hold nodes and text
47551      children.flatten().each( function(e) {
47552        if(typeof e=='object')
47553          element.appendChild(e);
47554        else
47555          if(Builder._isStringOrNumber(e))
47556            element.appendChild(Builder._text(e));
47557      });
47558    } else
47559      if(Builder._isStringOrNumber(children))
47560        element.appendChild(Builder._text(children));
47561  },
47562  _isStringOrNumber: function(param) {
47563    return(typeof param=='string' || typeof param=='number');
47564  },
47565  build: function(html) {
47566    var element = this.node('div');
47567    $(element).update(html.strip());
47568    return element.down();
47569  },
47570  dump: function(scope) {
47571    if(typeof scope != 'object' && typeof scope != 'function') scope = window; //global scope
47572
47573    var tags = ("A ABBR ACRONYM ADDRESS APPLET AREA B BASE BASEFONT BDO BIG BLOCKQUOTE BODY " +
47574      "BR BUTTON CAPTION CENTER CITE CODE COL COLGROUP DD DEL DFN DIR DIV DL DT EM FIELDSET " +
47575      "FONT FORM FRAME FRAMESET H1 H2 H3 H4 H5 H6 HEAD HR HTML I IFRAME IMG INPUT INS ISINDEX "+
47576      "KBD LABEL LEGEND LI LINK MAP MENU META NOFRAMES NOSCRIPT OBJECT OL OPTGROUP OPTION P "+
47577      "PARAM PRE Q S SAMP SCRIPT SELECT SMALL SPAN STRIKE STRONG STYLE SUB SUP TABLE TBODY TD "+
47578      "TEXTAREA TFOOT TH THEAD TITLE TR TT U UL VAR").split(/\s+/);
47579
47580    tags.each( function(tag){
47581      scope[tag] = function() {
47582        return Builder.node.apply(Builder, [tag].concat($A(arguments)));
47583      };
47584    });
47585  }
47586};
47587// script.aculo.us dragdrop.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
47588
47589// Copyright (c) 2005-2009 Thomas Fuchs (http://script.aculo.us, http://mir.aculo.us)
47590//
47591// script.aculo.us is freely distributable under the terms of an MIT-style license.
47592// For details, see the script.aculo.us web site: http://script.aculo.us/
47593
47594if(Object.isUndefined(Effect))
47595  throw("dragdrop.js requires including script.aculo.us' effects.js library");
47596
47597var Droppables = {
47598  drops: [],
47599
47600  remove: function(element) {
47601    this.drops = this.drops.reject(function(d) { return d.element==$(element) });
47602  },
47603
47604  add: function(element) {
47605    element = $(element);
47606    var options = Object.extend({
47607      greedy:     true,
47608      hoverclass: null,
47609      tree:       false
47610    }, arguments[1] || { });
47611
47612    // cache containers
47613    if(options.containment) {
47614      options._containers = [];
47615      var containment = options.containment;
47616      if(Object.isArray(containment)) {
47617        containment.each( function(c) { options._containers.push($(c)) });
47618      } else {
47619        options._containers.push($(containment));
47620      }
47621    }
47622
47623    if(options.accept) options.accept = [options.accept].flatten();
47624
47625    Element.makePositioned(element); // fix IE
47626    options.element = element;
47627
47628    this.drops.push(options);
47629  },
47630
47631  findDeepestChild: function(drops) {
47632    deepest = drops[0];
47633
47634    for (i = 1; i < drops.length; ++i)
47635      if (Element.isParent(drops[i].element, deepest.element))
47636        deepest = drops[i];
47637
47638    return deepest;
47639  },
47640
47641  isContained: function(element, drop) {
47642    var containmentNode;
47643    if(drop.tree) {
47644      containmentNode = element.treeNode;
47645    } else {
47646      containmentNode = element.parentNode;
47647    }
47648    return drop._containers.detect(function(c) { return containmentNode == c });
47649  },
47650
47651  isAffected: function(point, element, drop) {
47652    return (
47653      (drop.element!=element) &&
47654      ((!drop._containers) ||
47655        this.isContained(element, drop)) &&
47656      ((!drop.accept) ||
47657        (Element.classNames(element).detect(
47658          function(v) { return drop.accept.include(v) } ) )) &&
47659      Position.within(drop.element, point[0], point[1]) );
47660  },
47661
47662  deactivate: function(drop) {
47663    if(drop.hoverclass)
47664      Element.removeClassName(drop.element, drop.hoverclass);
47665    this.last_active = null;
47666  },
47667
47668  activate: function(drop) {
47669    if(drop.hoverclass)
47670      Element.addClassName(drop.element, drop.hoverclass);
47671    this.last_active = drop;
47672  },
47673
47674  show: function(point, element) {
47675    if(!this.drops.length) return;
47676    var drop, affected = [];
47677
47678    this.drops.each( function(drop) {
47679      if(Droppables.isAffected(point, element, drop))
47680        affected.push(drop);
47681    });
47682
47683    if(affected.length>0)
47684      drop = Droppables.findDeepestChild(affected);
47685
47686    if(this.last_active && this.last_active != drop) this.deactivate(this.last_active);
47687    if (drop) {
47688      Position.within(drop.element, point[0], point[1]);
47689      if(drop.onHover)
47690        drop.onHover(element, drop.element, Position.overlap(drop.overlap, drop.element));
47691
47692      if (drop != this.last_active) Droppables.activate(drop);
47693    }
47694  },
47695
47696  fire: function(event, element) {
47697    if(!this.last_active) return;
47698    Position.prepare();
47699
47700    if (this.isAffected([Event.pointerX(event), Event.pointerY(event)], element, this.last_active))
47701      if (this.last_active.onDrop) {
47702        this.last_active.onDrop(element, this.last_active.element, event);
47703        return true;
47704      }
47705  },
47706
47707  reset: function() {
47708    if(this.last_active)
47709      this.deactivate(this.last_active);
47710  }
47711};
47712
47713var Draggables = {
47714  drags: [],
47715  observers: [],
47716
47717  register: function(draggable) {
47718    if(this.drags.length == 0) {
47719      this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
47720      this.eventMouseMove = this.updateDrag.bindAsEventListener(this);
47721      this.eventKeypress  = this.keyPress.bindAsEventListener(this);
47722
47723      Event.observe(document, "mouseup", this.eventMouseUp);
47724      Event.observe(document, "mousemove", this.eventMouseMove);
47725      Event.observe(document, "keypress", this.eventKeypress);
47726    }
47727    this.drags.push(draggable);
47728  },
47729
47730  unregister: function(draggable) {
47731    this.drags = this.drags.reject(function(d) { return d==draggable });
47732    if(this.drags.length == 0) {
47733      Event.stopObserving(document, "mouseup", this.eventMouseUp);
47734      Event.stopObserving(document, "mousemove", this.eventMouseMove);
47735      Event.stopObserving(document, "keypress", this.eventKeypress);
47736    }
47737  },
47738
47739  activate: function(draggable) {
47740    if(draggable.options.delay) {
47741      this._timeout = setTimeout(function() {
47742        Draggables._timeout = null;
47743        window.focus();
47744        Draggables.activeDraggable = draggable;
47745      }.bind(this), draggable.options.delay);
47746    } else {
47747      window.focus(); // allows keypress events if window isn't currently focused, fails for Safari
47748      this.activeDraggable = draggable;
47749    }
47750  },
47751
47752  deactivate: function() {
47753    this.activeDraggable = null;
47754  },
47755
47756  updateDrag: function(event) {
47757    if(!this.activeDraggable) return;
47758    var pointer = [Event.pointerX(event), Event.pointerY(event)];
47759    // Mozilla-based browsers fire successive mousemove events with
47760    // the same coordinates, prevent needless redrawing (moz bug?)
47761    if(this._lastPointer && (this._lastPointer.inspect() == pointer.inspect())) return;
47762    this._lastPointer = pointer;
47763
47764    this.activeDraggable.updateDrag(event, pointer);
47765  },
47766
47767  endDrag: function(event) {
47768    if(this._timeout) {
47769      clearTimeout(this._timeout);
47770      this._timeout = null;
47771    }
47772    if(!this.activeDraggable) return;
47773    this._lastPointer = null;
47774    this.activeDraggable.endDrag(event);
47775    this.activeDraggable = null;
47776  },
47777
47778  keyPress: function(event) {
47779    if(this.activeDraggable)
47780      this.activeDraggable.keyPress(event);
47781  },
47782
47783  addObserver: function(observer) {
47784    this.observers.push(observer);
47785    this._cacheObserverCallbacks();
47786  },
47787
47788  removeObserver: function(element) {  // element instead of observer fixes mem leaks
47789    this.observers = this.observers.reject( function(o) { return o.element==element });
47790    this._cacheObserverCallbacks();
47791  },
47792
47793  notify: function(eventName, draggable, event) {  // 'onStart', 'onEnd', 'onDrag'
47794    if(this[eventName+'Count'] > 0)
47795      this.observers.each( function(o) {
47796        if(o[eventName]) o[eventName](eventName, draggable, event);
47797      });
47798    if(draggable.options[eventName]) draggable.options[eventName](draggable, event);
47799  },
47800
47801  _cacheObserverCallbacks: function() {
47802    ['onStart','onEnd','onDrag'].each( function(eventName) {
47803      Draggables[eventName+'Count'] = Draggables.observers.select(
47804        function(o) { return o[eventName]; }
47805      ).length;
47806    });
47807  }
47808};
47809
47810/*--------------------------------------------------------------------------*/
47811
47812var Draggable = Class.create({
47813  initialize: function(element) {
47814    var defaults = {
47815      handle: false,
47816      reverteffect: function(element, top_offset, left_offset) {
47817        var dur = Math.sqrt(Math.abs(top_offset^2)+Math.abs(left_offset^2))*0.02;
47818        new Effect.Move(element, { x: -left_offset, y: -top_offset, duration: dur,
47819          queue: {scope:'_draggable', position:'end'}
47820        });
47821      },
47822      endeffect: function(element) {
47823        var toOpacity = Object.isNumber(element._opacity) ? element._opacity : 1.0;
47824        new Effect.Opacity(element, {duration:0.2, from:0.7, to:toOpacity,
47825          queue: {scope:'_draggable', position:'end'},
47826          afterFinish: function(){
47827            Draggable._dragging[element] = false
47828          }
47829        });
47830      },
47831      zindex: 1000,
47832      revert: false,
47833      quiet: false,
47834      scroll: false,
47835      scrollSensitivity: 20,
47836      scrollSpeed: 15,
47837      snap: false,  // false, or xy or [x,y] or function(x,y){ return [x,y] }
47838      delay: 0
47839    };
47840
47841    if(!arguments[1] || Object.isUndefined(arguments[1].endeffect))
47842      Object.extend(defaults, {
47843        starteffect: function(element) {
47844          element._opacity = Element.getOpacity(element);
47845          Draggable._dragging[element] = true;
47846          new Effect.Opacity(element, {duration:0.2, from:element._opacity, to:0.7});
47847        }
47848      });
47849
47850    var options = Object.extend(defaults, arguments[1] || { });
47851
47852    this.element = $(element);
47853
47854    if(options.handle && Object.isString(options.handle))
47855      this.handle = this.element.down('.'+options.handle, 0);
47856
47857    if(!this.handle) this.handle = $(options.handle);
47858    if(!this.handle) this.handle = this.element;
47859
47860    if(options.scroll && !options.scroll.scrollTo && !options.scroll.outerHTML) {
47861      options.scroll = $(options.scroll);
47862      this._isScrollChild = Element.childOf(this.element, options.scroll);
47863    }
47864
47865    Element.makePositioned(this.element); // fix IE
47866
47867    this.options  = options;
47868    this.dragging = false;
47869
47870    this.eventMouseDown = this.initDrag.bindAsEventListener(this);
47871    Event.observe(this.handle, "mousedown", this.eventMouseDown);
47872
47873    Draggables.register(this);
47874  },
47875
47876  destroy: function() {
47877    Event.stopObserving(this.handle, "mousedown", this.eventMouseDown);
47878    Draggables.unregister(this);
47879  },
47880
47881  currentDelta: function() {
47882    return([
47883      parseInt(Element.getStyle(this.element,'left') || '0'),
47884      parseInt(Element.getStyle(this.element,'top') || '0')]);
47885  },
47886
47887  initDrag: function(event) {
47888    if(!Object.isUndefined(Draggable._dragging[this.element]) &&
47889      Draggable._dragging[this.element]) return;
47890    if(Event.isLeftClick(event)) {
47891      // abort on form elements, fixes a Firefox issue
47892      var src = Event.element(event);
47893      if((tag_name = src.tagName.toUpperCase()) && (
47894        tag_name=='INPUT' ||
47895        tag_name=='SELECT' ||
47896        tag_name=='OPTION' ||
47897        tag_name=='BUTTON' ||
47898        tag_name=='TEXTAREA')) return;
47899
47900      var pointer = [Event.pointerX(event), Event.pointerY(event)];
47901      var pos     = this.element.cumulativeOffset();
47902      this.offset = [0,1].map( function(i) { return (pointer[i] - pos[i]) });
47903
47904      Draggables.activate(this);
47905      Event.stop(event);
47906    }
47907  },
47908
47909  startDrag: function(event) {
47910    this.dragging = true;
47911    if(!this.delta)
47912      this.delta = this.currentDelta();
47913
47914    if(this.options.zindex) {
47915      this.originalZ = parseInt(Element.getStyle(this.element,'z-index') || 0);
47916      this.element.style.zIndex = this.options.zindex;
47917    }
47918
47919    if(this.options.ghosting) {
47920      this._clone = this.element.cloneNode(true);
47921      this._originallyAbsolute = (this.element.getStyle('position') == 'absolute');
47922      if (!this._originallyAbsolute)
47923        Position.absolutize(this.element);
47924      this.element.parentNode.insertBefore(this._clone, this.element);
47925    }
47926
47927    if(this.options.scroll) {
47928      if (this.options.scroll == window) {
47929        var where = this._getWindowScroll(this.options.scroll);
47930        this.originalScrollLeft = where.left;
47931        this.originalScrollTop = where.top;
47932      } else {
47933        this.originalScrollLeft = this.options.scroll.scrollLeft;
47934        this.originalScrollTop = this.options.scroll.scrollTop;
47935      }
47936    }
47937
47938    Draggables.notify('onStart', this, event);
47939
47940    if(this.options.starteffect) this.options.starteffect(this.element);
47941  },
47942
47943  updateDrag: function(event, pointer) {
47944    if(!this.dragging) this.startDrag(event);
47945
47946    if(!this.options.quiet){
47947      Position.prepare();
47948      Droppables.show(pointer, this.element);
47949    }
47950
47951    Draggables.notify('onDrag', this, event);
47952
47953    this.draw(pointer);
47954    if(this.options.change) this.options.change(this);
47955
47956    if(this.options.scroll) {
47957      this.stopScrolling();
47958
47959      var p;
47960      if (this.options.scroll == window) {
47961        with(this._getWindowScroll(this.options.scroll)) { p = [ left, top, left+width, top+height ]; }
47962      } else {
47963        p = Position.page(this.options.scroll);
47964        p[0] += this.options.scroll.scrollLeft + Position.deltaX;
47965        p[1] += this.options.scroll.scrollTop + Position.deltaY;
47966        p.push(p[0]+this.options.scroll.offsetWidth);
47967        p.push(p[1]+this.options.scroll.offsetHeight);
47968      }
47969      var speed = [0,0];
47970      if(pointer[0] < (p[0]+this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[0]+this.options.scrollSensitivity);
47971      if(pointer[1] < (p[1]+this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[1]+this.options.scrollSensitivity);
47972      if(pointer[0] > (p[2]-this.options.scrollSensitivity)) speed[0] = pointer[0]-(p[2]-this.options.scrollSensitivity);
47973      if(pointer[1] > (p[3]-this.options.scrollSensitivity)) speed[1] = pointer[1]-(p[3]-this.options.scrollSensitivity);
47974      this.startScrolling(speed);
47975    }
47976
47977    // fix AppleWebKit rendering
47978    if(Prototype.Browser.WebKit) window.scrollBy(0,0);
47979
47980    Event.stop(event);
47981  },
47982
47983  finishDrag: function(event, success) {
47984    this.dragging = false;
47985
47986    if(this.options.quiet){
47987      Position.prepare();
47988      var pointer = [Event.pointerX(event), Event.pointerY(event)];
47989      Droppables.show(pointer, this.element);
47990    }
47991
47992    if(this.options.ghosting) {
47993      if (!this._originallyAbsolute)
47994        Position.relativize(this.element);
47995      delete this._originallyAbsolute;
47996      Element.remove(this._clone);
47997      this._clone = null;
47998    }
47999
48000    var dropped = false;
48001    if(success) {
48002      dropped = Droppables.fire(event, this.element);
48003      if (!dropped) dropped = false;
48004    }
48005    if(dropped && this.options.onDropped) this.options.onDropped(this.element);
48006    Draggables.notify('onEnd', this, event);
48007
48008    var revert = this.options.revert;
48009    if(revert && Object.isFunction(revert)) revert = revert(this.element);
48010
48011    var d = this.currentDelta();
48012    if(revert && this.options.reverteffect) {
48013      if (dropped == 0 || revert != 'failure')
48014        this.options.reverteffect(this.element,
48015          d[1]-this.delta[1], d[0]-this.delta[0]);
48016    } else {
48017      this.delta = d;
48018    }
48019
48020    if(this.options.zindex)
48021      this.element.style.zIndex = this.originalZ;
48022
48023    if(this.options.endeffect)
48024      this.options.endeffect(this.element);
48025
48026    Draggables.deactivate(this);
48027    Droppables.reset();
48028  },
48029
48030  keyPress: function(event) {
48031    if(event.keyCode!=Event.KEY_ESC) return;
48032    this.finishDrag(event, false);
48033    Event.stop(event);
48034  },
48035
48036  endDrag: function(event) {
48037    if(!this.dragging) return;
48038    this.stopScrolling();
48039    this.finishDrag(event, true);
48040    Event.stop(event);
48041  },
48042
48043  draw: function(point) {
48044    var pos = this.element.cumulativeOffset();
48045    if(this.options.ghosting) {
48046      var r   = Position.realOffset(this.element);
48047      pos[0] += r[0] - Position.deltaX; pos[1] += r[1] - Position.deltaY;
48048    }
48049
48050    var d = this.currentDelta();
48051    pos[0] -= d[0]; pos[1] -= d[1];
48052
48053    if(this.options.scroll && (this.options.scroll != window && this._isScrollChild)) {
48054      pos[0] -= this.options.scroll.scrollLeft-this.originalScrollLeft;
48055      pos[1] -= this.options.scroll.scrollTop-this.originalScrollTop;
48056    }
48057
48058    var p = [0,1].map(function(i){
48059      return (point[i]-pos[i]-this.offset[i])
48060    }.bind(this));
48061
48062    if(this.options.snap) {
48063      if(Object.isFunction(this.options.snap)) {
48064        p = this.options.snap(p[0],p[1],this);
48065      } else {
48066      if(Object.isArray(this.options.snap)) {
48067        p = p.map( function(v, i) {
48068          return (v/this.options.snap[i]).round()*this.options.snap[i] }.bind(this));
48069      } else {
48070        p = p.map( function(v) {
48071          return (v/this.options.snap).round()*this.options.snap }.bind(this));
48072      }
48073    }}
48074
48075    var style = this.element.style;
48076    if((!this.options.constraint) || (this.options.constraint=='horizontal'))
48077      style.left = p[0] + "px";
48078    if((!this.options.constraint) || (this.options.constraint=='vertical'))
48079      style.top  = p[1] + "px";
48080
48081    if(style.visibility=="hidden") style.visibility = ""; // fix gecko rendering
48082  },
48083
48084  stopScrolling: function() {
48085    if(this.scrollInterval) {
48086      clearInterval(this.scrollInterval);
48087      this.scrollInterval = null;
48088      Draggables._lastScrollPointer = null;
48089    }
48090  },
48091
48092  startScrolling: function(speed) {
48093    if(!(speed[0] || speed[1])) return;
48094    this.scrollSpeed = [speed[0]*this.options.scrollSpeed,speed[1]*this.options.scrollSpeed];
48095    this.lastScrolled = new Date();
48096    this.scrollInterval = setInterval(this.scroll.bind(this), 10);
48097  },
48098
48099  scroll: function() {
48100    var current = new Date();
48101    var delta = current - this.lastScrolled;
48102    this.lastScrolled = current;
48103    if(this.options.scroll == window) {
48104      with (this._getWindowScroll(this.options.scroll)) {
48105        if (this.scrollSpeed[0] || this.scrollSpeed[1]) {
48106          var d = delta / 1000;
48107          this.options.scroll.scrollTo( left + d*this.scrollSpeed[0], top + d*this.scrollSpeed[1] );
48108        }
48109      }
48110    } else {
48111      this.options.scroll.scrollLeft += this.scrollSpeed[0] * delta / 1000;
48112      this.options.scroll.scrollTop  += this.scrollSpeed[1] * delta / 1000;
48113    }
48114
48115    Position.prepare();
48116    Droppables.show(Draggables._lastPointer, this.element);
48117    Draggables.notify('onDrag', this);
48118    if (this._isScrollChild) {
48119      Draggables._lastScrollPointer = Draggables._lastScrollPointer || $A(Draggables._lastPointer);
48120      Draggables._lastScrollPointer[0] += this.scrollSpeed[0] * delta / 1000;
48121      Draggables._lastScrollPointer[1] += this.scrollSpeed[1] * delta / 1000;
48122      if (Draggables._lastScrollPointer[0] < 0)
48123        Draggables._lastScrollPointer[0] = 0;
48124      if (Draggables._lastScrollPointer[1] < 0)
48125        Draggables._lastScrollPointer[1] = 0;
48126      this.draw(Draggables._lastScrollPointer);
48127    }
48128
48129    if(this.options.change) this.options.change(this);
48130  },
48131
48132  _getWindowScroll: function(w) {
48133    var T, L, W, H;
48134    with (w.document) {
48135      if (w.document.documentElement && documentElement.scrollTop) {
48136        T = documentElement.scrollTop;
48137        L = documentElement.scrollLeft;
48138      } else if (w.document.body) {
48139        T = body.scrollTop;
48140        L = body.scrollLeft;
48141      }
48142      if (w.innerWidth) {
48143        W = w.innerWidth;
48144        H = w.innerHeight;
48145      } else if (w.document.documentElement && documentElement.clientWidth) {
48146        W = documentElement.clientWidth;
48147        H = documentElement.clientHeight;
48148      } else {
48149        W = body.offsetWidth;
48150        H = body.offsetHeight;
48151      }
48152    }
48153    return { top: T, left: L, width: W, height: H };
48154  }
48155});
48156
48157Draggable._dragging = { };
48158
48159/*--------------------------------------------------------------------------*/
48160
48161var SortableObserver = Class.create({
48162  initialize: function(element, observer) {
48163    this.element   = $(element);
48164    this.observer  = observer;
48165    this.lastValue = Sortable.serialize(this.element);
48166  },
48167
48168  onStart: function() {
48169    this.lastValue = Sortable.serialize(this.element);
48170  },
48171
48172  onEnd: function() {
48173    Sortable.unmark();
48174    if(this.lastValue != Sortable.serialize(this.element))
48175      this.observer(this.element)
48176  }
48177});
48178
48179var Sortable = {
48180  SERIALIZE_RULE: /^[^_\-](?:[A-Za-z0-9\-\_]*)[_](.*)$/,
48181
48182  sortables: { },
48183
48184  _findRootElement: function(element) {
48185    while (element.tagName.toUpperCase() != "BODY") {
48186      if(element.id && Sortable.sortables[element.id]) return element;
48187      element = element.parentNode;
48188    }
48189  },
48190
48191  options: function(element) {
48192    element = Sortable._findRootElement($(element));
48193    if(!element) return;
48194    return Sortable.sortables[element.id];
48195  },
48196
48197  destroy: function(element){
48198    element = $(element);
48199    var s = Sortable.sortables[element.id];
48200
48201    if(s) {
48202      Draggables.removeObserver(s.element);
48203      s.droppables.each(function(d){ Droppables.remove(d) });
48204      s.draggables.invoke('destroy');
48205
48206      delete Sortable.sortables[s.element.id];
48207    }
48208  },
48209
48210  create: function(element) {
48211    element = $(element);
48212    var options = Object.extend({
48213      element:     element,
48214      tag:         'li',       // assumes li children, override with tag: 'tagname'
48215      dropOnEmpty: false,
48216      tree:        false,
48217      treeTag:     'ul',
48218      overlap:     'vertical', // one of 'vertical', 'horizontal'
48219      constraint:  'vertical', // one of 'vertical', 'horizontal', false
48220      containment: element,    // also takes array of elements (or id's); or false
48221      handle:      false,      // or a CSS class
48222      only:        false,
48223      delay:       0,
48224      hoverclass:  null,
48225      ghosting:    false,
48226      quiet:       false,
48227      scroll:      false,
48228      scrollSensitivity: 20,
48229      scrollSpeed: 15,
48230      format:      this.SERIALIZE_RULE,
48231
48232      // these take arrays of elements or ids and can be
48233      // used for better initialization performance
48234      elements:    false,
48235      handles:     false,
48236
48237      onChange:    Prototype.emptyFunction,
48238      onUpdate:    Prototype.emptyFunction
48239    }, arguments[1] || { });
48240
48241    // clear any old sortable with same element
48242    this.destroy(element);
48243
48244    // build options for the draggables
48245    var options_for_draggable = {
48246      revert:      true,
48247      quiet:       options.quiet,
48248      scroll:      options.scroll,
48249      scrollSpeed: options.scrollSpeed,
48250      scrollSensitivity: options.scrollSensitivity,
48251      delay:       options.delay,
48252      ghosting:    options.ghosting,
48253      constraint:  options.constraint,
48254      handle:      options.handle };
48255
48256    if(options.starteffect)
48257      options_for_draggable.starteffect = options.starteffect;
48258
48259    if(options.reverteffect)
48260      options_for_draggable.reverteffect = options.reverteffect;
48261    else
48262      if(options.ghosting) options_for_draggable.reverteffect = function(element) {
48263        element.style.top  = 0;
48264        element.style.left = 0;
48265      };
48266
48267    if(options.endeffect)
48268      options_for_draggable.endeffect = options.endeffect;
48269
48270    if(options.zindex)
48271      options_for_draggable.zindex = options.zindex;
48272
48273    // build options for the droppables
48274    var options_for_droppable = {
48275      overlap:     options.overlap,
48276      containment: options.containment,
48277      tree:        options.tree,
48278      hoverclass:  options.hoverclass,
48279      onHover:     Sortable.onHover
48280    };
48281
48282    var options_for_tree = {
48283      onHover:      Sortable.onEmptyHover,
48284      overlap:      options.overlap,
48285      containment:  options.containment,
48286      hoverclass:   options.hoverclass
48287    };
48288
48289    // fix for gecko engine
48290    Element.cleanWhitespace(element);
48291
48292    options.draggables = [];
48293    options.droppables = [];
48294
48295    // drop on empty handling
48296    if(options.dropOnEmpty || options.tree) {
48297      Droppables.add(element, options_for_tree);
48298      options.droppables.push(element);
48299    }
48300
48301    (options.elements || this.findElements(element, options) || []).each( function(e,i) {
48302      var handle = options.handles ? $(options.handles[i]) :
48303        (options.handle ? $(e).select('.' + options.handle)[0] : e);
48304      options.draggables.push(
48305        new Draggable(e, Object.extend(options_for_draggable, { handle: handle })));
48306      Droppables.add(e, options_for_droppable);
48307      if(options.tree) e.treeNode = element;
48308      options.droppables.push(e);
48309    });
48310
48311    if(options.tree) {
48312      (Sortable.findTreeElements(element, options) || []).each( function(e) {
48313        Droppables.add(e, options_for_tree);
48314        e.treeNode = element;
48315        options.droppables.push(e);
48316      });
48317    }
48318
48319    // keep reference
48320    this.sortables[element.identify()] = options;
48321
48322    // for onupdate
48323    Draggables.addObserver(new SortableObserver(element, options.onUpdate));
48324
48325  },
48326
48327  // return all suitable-for-sortable elements in a guaranteed order
48328  findElements: function(element, options) {
48329    return Element.findChildren(
48330      element, options.only, options.tree ? true : false, options.tag);
48331  },
48332
48333  findTreeElements: function(element, options) {
48334    return Element.findChildren(
48335      element, options.only, options.tree ? true : false, options.treeTag);
48336  },
48337
48338  onHover: function(element, dropon, overlap) {
48339    if(Element.isParent(dropon, element)) return;
48340
48341    if(overlap > .33 && overlap < .66 && Sortable.options(dropon).tree) {
48342      return;
48343    } else if(overlap>0.5) {
48344      Sortable.mark(dropon, 'before');
48345      if(dropon.previousSibling != element) {
48346        var oldParentNode = element.parentNode;
48347        element.style.visibility = "hidden"; // fix gecko rendering
48348        dropon.parentNode.insertBefore(element, dropon);
48349        if(dropon.parentNode!=oldParentNode)
48350          Sortable.options(oldParentNode).onChange(element);
48351        Sortable.options(dropon.parentNode).onChange(element);
48352      }
48353    } else {
48354      Sortable.mark(dropon, 'after');
48355      var nextElement = dropon.nextSibling || null;
48356      if(nextElement != element) {
48357        var oldParentNode = element.parentNode;
48358        element.style.visibility = "hidden"; // fix gecko rendering
48359        dropon.parentNode.insertBefore(element, nextElement);
48360        if(dropon.parentNode!=oldParentNode)
48361          Sortable.options(oldParentNode).onChange(element);
48362        Sortable.options(dropon.parentNode).onChange(element);
48363      }
48364    }
48365  },
48366
48367  onEmptyHover: function(element, dropon, overlap) {
48368    var oldParentNode = element.parentNode;
48369    var droponOptions = Sortable.options(dropon);
48370
48371    if(!Element.isParent(dropon, element)) {
48372      var index;
48373
48374      var children = Sortable.findElements(dropon, {tag: droponOptions.tag, only: droponOptions.only});
48375      var child = null;
48376
48377      if(children) {
48378        var offset = Element.offsetSize(dropon, droponOptions.overlap) * (1.0 - overlap);
48379
48380        for (index = 0; index < children.length; index += 1) {
48381          if (offset - Element.offsetSize (children[index], droponOptions.overlap) >= 0) {
48382            offset -= Element.offsetSize (children[index], droponOptions.overlap);
48383          } else if (offset - (Element.offsetSize (children[index], droponOptions.overlap) / 2) >= 0) {
48384            child = index + 1 < children.length ? children[index + 1] : null;
48385            break;
48386          } else {
48387            child = children[index];
48388            break;
48389          }
48390        }
48391      }
48392
48393      dropon.insertBefore(element, child);
48394
48395      Sortable.options(oldParentNode).onChange(element);
48396      droponOptions.onChange(element);
48397    }
48398  },
48399
48400  unmark: function() {
48401    if(Sortable._marker) Sortable._marker.hide();
48402  },
48403
48404  mark: function(dropon, position) {
48405    // mark on ghosting only
48406    var sortable = Sortable.options(dropon.parentNode);
48407    if(sortable && !sortable.ghosting) return;
48408
48409    if(!Sortable._marker) {
48410      Sortable._marker =
48411        ($('dropmarker') || Element.extend(document.createElement('DIV'))).
48412          hide().addClassName('dropmarker').setStyle({position:'absolute'});
48413      document.getElementsByTagName("body").item(0).appendChild(Sortable._marker);
48414    }
48415    var offsets = dropon.cumulativeOffset();
48416    Sortable._marker.setStyle({left: offsets[0]+'px', top: offsets[1] + 'px'});
48417
48418    if(position=='after')
48419      if(sortable.overlap == 'horizontal')
48420        Sortable._marker.setStyle({left: (offsets[0]+dropon.clientWidth) + 'px'});
48421      else
48422        Sortable._marker.setStyle({top: (offsets[1]+dropon.clientHeight) + 'px'});
48423
48424    Sortable._marker.show();
48425  },
48426
48427  _tree: function(element, options, parent) {
48428    var children = Sortable.findElements(element, options) || [];
48429
48430    for (var i = 0; i < children.length; ++i) {
48431      var match = children[i].id.match(options.format);
48432
48433      if (!match) continue;
48434
48435      var child = {
48436        id: encodeURIComponent(match ? match[1] : null),
48437        element: element,
48438        parent: parent,
48439        children: [],
48440        position: parent.children.length,
48441        container: $(children[i]).down(options.treeTag)
48442      };
48443
48444      /* Get the element containing the children and recurse over it */
48445      if (child.container)
48446        this._tree(child.container, options, child);
48447
48448      parent.children.push (child);
48449    }
48450
48451    return parent;
48452  },
48453
48454  tree: function(element) {
48455    element = $(element);
48456    var sortableOptions = this.options(element);
48457    var options = Object.extend({
48458      tag: sortableOptions.tag,
48459      treeTag: sortableOptions.treeTag,
48460      only: sortableOptions.only,
48461      name: element.id,
48462      format: sortableOptions.format
48463    }, arguments[1] || { });
48464
48465    var root = {
48466      id: null,
48467      parent: null,
48468      children: [],
48469      container: element,
48470      position: 0
48471    };
48472
48473    return Sortable._tree(element, options, root);
48474  },
48475
48476  /* Construct a [i] index for a particular node */
48477  _constructIndex: function(node) {
48478    var index = '';
48479    do {
48480      if (node.id) index = '[' + node.position + ']' + index;
48481    } while ((node = node.parent) != null);
48482    return index;
48483  },
48484
48485  sequence: function(element) {
48486    element = $(element);
48487    var options = Object.extend(this.options(element), arguments[1] || { });
48488
48489    return $(this.findElements(element, options) || []).map( function(item) {
48490      return item.id.match(options.format) ? item.id.match(options.format)[1] : '';
48491    });
48492  },
48493
48494  setSequence: function(element, new_sequence) {
48495    element = $(element);
48496    var options = Object.extend(this.options(element), arguments[2] || { });
48497
48498    var nodeMap = { };
48499    this.findElements(element, options).each( function(n) {
48500        if (n.id.match(options.format))
48501            nodeMap[n.id.match(options.format)[1]] = [n, n.parentNode];
48502        n.parentNode.removeChild(n);
48503    });
48504
48505    new_sequence.each(function(ident) {
48506      var n = nodeMap[ident];
48507      if (n) {
48508        n[1].appendChild(n[0]);
48509        delete nodeMap[ident];
48510      }
48511    });
48512  },
48513
48514  serialize: function(element) {
48515    element = $(element);
48516    var options = Object.extend(Sortable.options(element), arguments[1] || { });
48517    var name = encodeURIComponent(
48518      (arguments[1] && arguments[1].name) ? arguments[1].name : element.id);
48519
48520    if (options.tree) {
48521      return Sortable.tree(element, arguments[1]).children.map( function (item) {
48522        return [name + Sortable._constructIndex(item) + "[id]=" +
48523                encodeURIComponent(item.id)].concat(item.children.map(arguments.callee));
48524      }).flatten().join('&');
48525    } else {
48526      return Sortable.sequence(element, arguments[1]).map( function(item) {
48527        return name + "[]=" + encodeURIComponent(item);
48528      }).join('&');
48529    }
48530  }
48531};
48532
48533// Returns true if child is contained within element
48534Element.isParent = function(child, element) {
48535  if (!child.parentNode || child == element) return false;
48536  if (child.parentNode == element) return true;
48537  return Element.isParent(child.parentNode, element);
48538};
48539
48540Element.findChildren = function(element, only, recursive, tagName) {
48541  if(!element.hasChildNodes()) return null;
48542  tagName = tagName.toUpperCase();
48543  if(only) only = [only].flatten();
48544  var elements = [];
48545  $A(element.childNodes).each( function(e) {
48546    if(e.tagName && e.tagName.toUpperCase()==tagName &&
48547      (!only || (Element.classNames(e).detect(function(v) { return only.include(v) }))))
48548        elements.push(e);
48549    if(recursive) {
48550      var grandchildren = Element.findChildren(e, only, recursive, tagName);
48551      if(grandchildren) elements.push(grandchildren);
48552    }
48553  });
48554
48555  return (elements.length>0 ? elements.flatten() : []);
48556};
48557
48558Element.offsetSize = function (element, type) {
48559  return element['offset' + ((type=='vertical' || type=='height') ? 'Height' : 'Width')];
48560};
48561// script.aculo.us slider.js v1.8.3, Thu Oct 08 11:23:33 +0200 2009
48562
48563// Copyright (c) 2005-2009 Marty Haught, Thomas Fuchs
48564//
48565// script.aculo.us is freely distributable under the terms of an MIT-style license.
48566// For details, see the script.aculo.us web site: http://script.aculo.us/
48567
48568if (!Control) var Control = { };
48569
48570// options:
48571//  axis: 'vertical', or 'horizontal' (default)
48572//
48573// callbacks:
48574//  onChange(value)
48575//  onSlide(value)
48576Control.Slider = Class.create({
48577  initialize: function(handle, track, options) {
48578    var slider = this;
48579
48580    if (Object.isArray(handle)) {
48581      this.handles = handle.collect( function(e) { return $(e) });
48582    } else {
48583      this.handles = [$(handle)];
48584    }
48585
48586    this.track   = $(track);
48587    this.options = options || { };
48588
48589    this.axis      = this.options.axis || 'horizontal';
48590    this.increment = this.options.increment || 1;
48591    this.step      = parseInt(this.options.step || '1');
48592    this.range     = this.options.range || $R(0,1);
48593
48594    this.value     = 0; // assure backwards compat
48595    this.values    = this.handles.map( function() { return 0 });
48596    this.spans     = this.options.spans ? this.options.spans.map(function(s){ return $(s) }) : false;
48597    this.options.startSpan = $(this.options.startSpan || null);
48598    this.options.endSpan   = $(this.options.endSpan || null);
48599
48600    this.restricted = this.options.restricted || false;
48601
48602    this.maximum   = this.options.maximum || this.range.end;
48603    this.minimum   = this.options.minimum || this.range.start;
48604
48605    // Will be used to align the handle onto the track, if necessary
48606    this.alignX = parseInt(this.options.alignX || '0');
48607    this.alignY = parseInt(this.options.alignY || '0');
48608
48609    this.trackLength = this.maximumOffset() - this.minimumOffset();
48610
48611    this.handleLength = this.isVertical() ?
48612      (this.handles[0].offsetHeight != 0 ?
48613        this.handles[0].offsetHeight : this.handles[0].style.height.replace(/px$/,"")) :
48614      (this.handles[0].offsetWidth != 0 ? this.handles[0].offsetWidth :
48615        this.handles[0].style.width.replace(/px$/,""));
48616
48617    this.active   = false;
48618    this.dragging = false;
48619    this.disabled = false;
48620
48621    if (this.options.disabled) this.setDisabled();
48622
48623    // Allowed values array
48624    this.allowedValues = this.options.values ? this.options.values.sortBy(Prototype.K) : false;
48625    if (this.allowedValues) {
48626      this.minimum = this.allowedValues.min();
48627      this.maximum = this.allowedValues.max();
48628    }
48629
48630    this.eventMouseDown = this.startDrag.bindAsEventListener(this);
48631    this.eventMouseUp   = this.endDrag.bindAsEventListener(this);
48632    this.eventMouseMove = this.update.bindAsEventListener(this);
48633
48634    // Initialize handles in reverse (make sure first handle is active)
48635    this.handles.each( function(h,i) {
48636      i = slider.handles.length-1-i;
48637      slider.setValue(parseFloat(
48638        (Object.isArray(slider.options.sliderValue) ?
48639          slider.options.sliderValue[i] : slider.options.sliderValue) ||
48640         slider.range.start), i);
48641      h.makePositioned().observe("mousedown", slider.eventMouseDown);
48642    });
48643
48644    this.track.observe("mousedown", this.eventMouseDown);
48645    document.observe("mouseup", this.eventMouseUp);
48646    document.observe("mousemove", this.eventMouseMove);
48647
48648    this.initialized = true;
48649  },
48650  dispose: function() {
48651    var slider = this;
48652    Event.stopObserving(this.track, "mousedown", this.eventMouseDown);
48653    Event.stopObserving(document, "mouseup", this.eventMouseUp);
48654    Event.stopObserving(document, "mousemove", this.eventMouseMove);
48655    this.handles.each( function(h) {
48656      Event.stopObserving(h, "mousedown", slider.eventMouseDown);
48657    });
48658  },
48659  setDisabled: function(){
48660    this.disabled = true;
48661  },
48662  setEnabled: function(){
48663    this.disabled = false;
48664  },
48665  getNearestValue: function(value){
48666    if (this.allowedValues){
48667      if (value >= this.allowedValues.max()) return(this.allowedValues.max());
48668      if (value <= this.allowedValues.min()) return(this.allowedValues.min());
48669
48670      var offset = Math.abs(this.allowedValues[0] - value);
48671      var newValue = this.allowedValues[0];
48672      this.allowedValues.each( function(v) {
48673        var currentOffset = Math.abs(v - value);
48674        if (currentOffset <= offset){
48675          newValue = v;
48676          offset = currentOffset;
48677        }
48678      });
48679      return newValue;
48680    }
48681    if (value > this.range.end) return this.range.end;
48682    if (value < this.range.start) return this.range.start;
48683    return value;
48684  },
48685  setValue: function(sliderValue, handleIdx){
48686    if (!this.active) {
48687      this.activeHandleIdx = handleIdx || 0;
48688      this.activeHandle    = this.handles[this.activeHandleIdx];
48689      this.updateStyles();
48690    }
48691    handleIdx = handleIdx || this.activeHandleIdx || 0;
48692    if (this.initialized && this.restricted) {
48693      if ((handleIdx>0) && (sliderValue<this.values[handleIdx-1]))
48694        sliderValue = this.values[handleIdx-1];
48695      if ((handleIdx < (this.handles.length-1)) && (sliderValue>this.values[handleIdx+1]))
48696        sliderValue = this.values[handleIdx+1];
48697    }
48698    sliderValue = this.getNearestValue(sliderValue);
48699    this.values[handleIdx] = sliderValue;
48700    this.value = this.values[0]; // assure backwards compat
48701
48702    this.handles[handleIdx].style[this.isVertical() ? 'top' : 'left'] =
48703      this.translateToPx(sliderValue);
48704
48705    this.drawSpans();
48706    if (!this.dragging || !this.event) this.updateFinished();
48707  },
48708  setValueBy: function(delta, handleIdx) {
48709    this.setValue(this.values[handleIdx || this.activeHandleIdx || 0] + delta,
48710      handleIdx || this.activeHandleIdx || 0);
48711  },
48712  translateToPx: function(value) {
48713    return Math.round(
48714      ((this.trackLength-this.handleLength)/(this.range.end-this.range.start)) *
48715      (value - this.range.start)) + "px";
48716  },
48717  translateToValue: function(offset) {
48718    return ((offset/(this.trackLength-this.handleLength) *
48719      (this.range.end-this.range.start)) + this.range.start);
48720  },
48721  getRange: function(range) {
48722    var v = this.values.sortBy(Prototype.K);
48723    range = range || 0;
48724    return $R(v[range],v[range+1]);
48725  },
48726  minimumOffset: function(){
48727    return(this.isVertical() ? this.alignY : this.alignX);
48728  },
48729  maximumOffset: function(){
48730    return(this.isVertical() ?
48731      (this.track.offsetHeight != 0 ? this.track.offsetHeight :
48732        this.track.style.height.replace(/px$/,"")) - this.alignY :
48733      (this.track.offsetWidth != 0 ? this.track.offsetWidth :
48734        this.track.style.width.replace(/px$/,"")) - this.alignX);
48735  },
48736  isVertical:  function(){
48737    return (this.axis == 'vertical');
48738  },
48739  drawSpans: function() {
48740    var slider = this;
48741    if (this.spans)
48742      $R(0, this.spans.length-1).each(function(r) { slider.setSpan(slider.spans[r], slider.getRange(r)) });
48743    if (this.options.startSpan)
48744      this.setSpan(this.options.startSpan,
48745        $R(0, this.values.length>1 ? this.getRange(0).min() : this.value ));
48746    if (this.options.endSpan)
48747      this.setSpan(this.options.endSpan,
48748        $R(this.values.length>1 ? this.getRange(this.spans.length-1).max() : this.value, this.maximum));
48749  },
48750  setSpan: function(span, range) {
48751    if (this.isVertical()) {
48752      span.style.top = this.translateToPx(range.start);
48753      span.style.height = this.translateToPx(range.end - range.start + this.range.start);
48754    } else {
48755      span.style.left = this.translateToPx(range.start);
48756      span.style.width = this.translateToPx(range.end - range.start + this.range.start);
48757    }
48758  },
48759  updateStyles: function() {
48760    this.handles.each( function(h){ Element.removeClassName(h, 'selected') });
48761    Element.addClassName(this.activeHandle, 'selected');
48762  },
48763  startDrag: function(event) {
48764    if (Event.isLeftClick(event)) {
48765      if (!this.disabled){
48766        this.active = true;
48767
48768        var handle = Event.element(event);
48769        var pointer  = [Event.pointerX(event), Event.pointerY(event)];
48770        var track = handle;
48771        if (track==this.track) {
48772          var offsets  = this.track.cumulativeOffset();
48773          this.event = event;
48774          this.setValue(this.translateToValue(
48775           (this.isVertical() ? pointer[1]-offsets[1] : pointer[0]-offsets[0])-(this.handleLength/2)
48776          ));
48777          var offsets  = this.activeHandle.cumulativeOffset();
48778          this.offsetX = (pointer[0] - offsets[0]);
48779          this.offsetY = (pointer[1] - offsets[1]);
48780        } else {
48781          // find the handle (prevents issues with Safari)
48782          while((this.handles.indexOf(handle) == -1) && handle.parentNode)
48783            handle = handle.parentNode;
48784
48785          if (this.handles.indexOf(handle)!=-1) {
48786            this.activeHandle    = handle;
48787            this.activeHandleIdx = this.handles.indexOf(this.activeHandle);
48788            this.updateStyles();
48789
48790            var offsets  = this.activeHandle.cumulativeOffset();
48791            this.offsetX = (pointer[0] - offsets[0]);
48792            this.offsetY = (pointer[1] - offsets[1]);
48793          }
48794        }
48795      }
48796      Event.stop(event);
48797    }
48798  },
48799  update: function(event) {
48800   if (this.active) {
48801      if (!this.dragging) this.dragging = true;
48802      this.draw(event);
48803      if (Prototype.Browser.WebKit) window.scrollBy(0,0);
48804      Event.stop(event);
48805   }
48806  },
48807  draw: function(event) {
48808    var pointer = [Event.pointerX(event), Event.pointerY(event)];
48809    var offsets = this.track.cumulativeOffset();
48810    pointer[0] -= this.offsetX + offsets[0];
48811    pointer[1] -= this.offsetY + offsets[1];
48812    this.event = event;
48813    this.setValue(this.translateToValue( this.isVertical() ? pointer[1] : pointer[0] ));
48814    if (this.initialized && this.options.onSlide)
48815      this.options.onSlide(this.values.length>1 ? this.values : this.value, this);
48816  },
48817  endDrag: function(event) {
48818    if (this.active && this.dragging) {
48819      this.finishDrag(event, true);
48820      Event.stop(event);
48821    }
48822    this.active = false;
48823    this.dragging = false;
48824  },
48825  finishDrag: function(event, success) {
48826    this.active = false;
48827    this.dragging = false;
48828    this.updateFinished();
48829  },
48830  updateFinished: function() {
48831    if (this.initialized && this.options.onChange)
48832      this.options.onChange(this.values.length>1 ? this.values : this.value, this);
48833    this.event = null;
48834  }
48835});
48836// an empty file referenced by scriptaculous.js
48837;
48838// Brain Explorer Download Prompt -- brain_explorer_prompt.js
48839// Copyright (c) 2007-2011 Allen Institute for Brain Science. All Rights Reserved.
48840
48841BrainExplorerDownloadPrompt = Class.create({
48842/* 	Uses jQuery's dialog to prompt the user to download brain explorer
48843	if they don't have the cookie (or the cookie is false).
48844	
48845	To use, create an empty div on the web page (will serve as the prompt) and
48846	a link element that points to the correct location (e.g. aibe://...).  The
48847	prompt hijacks the onclick property of the link element to open the prompt
48848	if a supplied cookie is false or unset.  If the user clicks 'yes', the
48849	cookie is set and the link is followed.  If the user clicks 'no', the 
48850	supplied download page is opened in a new page.
48851
48852	The prompt is initially disabled and must be enabled via 
48853	prompt.enable(destination). Disable the	link via prompt.disable(). */
48854
48855	initialize: function (container, cookie, settings, download_page, anchor)
48856	{
48857		this.parent = container;
48858		this.anchor = anchor;
48859		this.settings = settings;
48860		this.cookie = cookie;
48861		this.download_page = download_page;
48862		this.enabled = false;
48863
48864		$j(this.parent)
48865			.html('<p>Brain Explorer is a desktop application for viewing brain anatomy and gene expression in 3D.</p><p>Choose "Download" to proceed to the Brain Explorer download page.</p><p>Choose "View My Sample" if you have already installed Brain Explorer.</p>')
48866			.dialog({ autoOpen: false,
48867					  modal: true,
48868					  title: "Is Brain Explorer installed?",
48869					  width: "500px",
48870					  buttons: { "Download" : this.handle_prompt_no.bind(this),
48871								 "View My Sample": this.handle_prompt_yes.bind(this) } });
48872								 
48873
48874		this.anchor.onclick = this.check_cookie.bind(this);
48875	},
48876
48877	enable: function (destination) 
48878	{
48879		this.anchor.href = destination;
48880		this.enabled = true;
48881	},
48882	
48883	disable: function ()
48884	{
48885		this.anchor.href = "";
48886		this.enabled = false;
48887	},
48888
48889	check_cookie: function ()
48890	{
48891		if (!this.enabled)
48892		{
48893			return false;
48894		}
48895		else if (this.settings.get(this.cookie))
48896		{
48897			return true;
48898		}
48899		else
48900		{
48901			$j(this.parent).dialog('open');
48902			return false;
48903		}
48904	},
48905
48906	handle_prompt_yes: function ()
48907	{
48908		$j(this.parent).dialog('close');
48909		this.settings.set(this.cookie,true);
48910		window.location = (this.anchor.href);
48911	},
48912
48913	handle_prompt_no: function ()
48914	{
48915		$j(this.parent).dialog('close');
48916		window.open(this.download_page);
48917	}
48918});
48919
48920var __colormap_widget_count__ = 0;
48921
48922/*
48923 * NOTE that the sliderWidgets.js fileis required to be loaded before this file.
48924 * 
48925 * The colormap_widget implements a couple of different things.  
48926 * First is a window/level slider control that can operate in either 
48927 * symmetric or non-symmetric mode; this is just a wrapping around 
48928 * the sliderWidgets.js component, see that file for more detail.
48929 * The slider widget is combined with a colormap chooser control,
48930 * built to operate like a standard dropdown <select> element, except
48931 * with pictures of the available color maps instead of text.
48932 * 
48933 */
48934function colormap_widget(container, options, event_options) {
48935
48936// public properties
48937    this.container = container;
48938
48939    // an array of {name, url, normal, selected} objects describing the possible colormaps.
48940    // name & url are strings, normal (normalized) and selected are booleans.
48941    this.colormap_set = options ? (options.colormap_set ? options.colormap_set : []) : [];
48942
48943    // the colormap can operate over either raw or normalized data, each of which has its own limits
48944    this.normalized_limits = options ? (options.zscore_limits ? options.zscore_limits : [-6, 6]) : [-6, 6];
48945    this.raw_limits = options ? (options.expression_limits ? options.expression_limits : [0, 16]) : [0, 16];
48946
48947    // switching between raw and and normalized should be possible.  These are their defaults.
48948    this.default_raw_expression_range = options ? (options.raw_expression_range ? options.raw_expression_range : [ 2.0, 10.0 ]) : [ 2.0, 10.0 ];
48949    this.default_norm_expression_range = options ? (options.norm_expression_range ? options.norm_expression_range : [ -3, 3 ]) : [ -3, 3 ];
48950
48951    // the current range depends on whehter or not we're using normalized data
48952    this.normalized = options ? (options.normalized ? options.normalized : false) : false;
48953    this.expression_range = this.normalized ? this.default_norm_expression_range : this.default_raw_expression_range;
48954    this.limits = this.normalized ? this.normalized_limits : this.raw_limits;
48955
48956// event hooks
48957    this.on_colormap_change = event_options? (event_options.on_colormap_change ? event_options.on_colormap_change : null) : null;
48958    this.on_level_change = event_options? (event_options.on_level_change ? event_options.on_level_change : null) : null;
48959
48960
48961
48962// public methods
48963    this.set_range = function(lower, upper) {
48964        the_level_widget.setRange(lower, upper);
48965    };
48966
48967    this.set_colormap = function(name) {
48968      select_colormap(self.colormap_set,name);
48969    };
48970
48971// private properties
48972    var self = this;
48973    var the_level_widget = null;
48974    var the_select_box = null;
48975    var normalized = false;
48976    var select_container = null;
48977    var setting = false;
48978
48979
48980// private methods
48981    function create_window_level_widget(container) {
48982
48983        var map_count = self.colormap_set.length;
48984        var extra_container = container;
48985        if(map_count > 1) {
48986            select_container = new Element('div', {'class':'colormap_select_container', title:'Click to change color map'});
48987            container.appendChild(select_container);
48988
48989            var open_button = new Element('div', {'class':'colormap_chooser_button'});
48990            open_button.observe('click', do_colormap_chooser);
48991            select_container.appendChild(open_button);
48992            extra_container = select_container;
48993        }
48994
48995        var ramp_box = new Element('div', {id:'control_bar_ramp_box'+__colormap_widget_count__, 'class':'colormap_ramp_box'});
48996        extra_container.appendChild(ramp_box);
48997
48998        the_select_box = new Element('div', {id:'control_bar_select_box'+__colormap_widget_count__, 'class':'colormap_select_box'});
48999        var colormap_list = new Element('ul', {id:'control_bar_select_list'+__colormap_widget_count__, 'class':'colormap_select_list'});
49000
49001        if(map_count > 1) {
49002            for(var i = 0; i < self.colormap_set.length; i++) {
49003                var colormap_item = new Element('li', {id:'control_bar_select_list_item'+__colormap_widget_count__, 'class':'colormap_select_item'});
49004                var colormap_img = new Element('img', {id:'control_bar_select_list_img'+__colormap_widget_count__, 'class':'colormap_select_img'});
49005
49006                colormap_img.setAttribute('src', self.colormap_set[i].url);
49007                colormap_img.setAttribute('normal', self.colormap_set[i].normal);
49008                colormap_img.setAttribute('name', self.colormap_set[i].name);
49009
49010                colormap_item.setAttribute('src', self.colormap_set[i].url);
49011                colormap_item.setAttribute('normal', self.colormap_set[i].normal);
49012                colormap_item.setAttribute('name', self.colormap_set[i].name);
49013                colormap_item.update(self.colormap_set[i].normal ? 'z-score' : 'log2 intensity');
49014
49015                colormap_item.observe('click', do_select_item_click);
49016                colormap_item.appendChild(colormap_img);
49017                colormap_list.appendChild(colormap_item);
49018            }
49019        }
49020        the_select_box.appendChild(colormap_list);
49021        container.appendChild(the_select_box);
49022        the_select_box.hide();
49023
49024        var map = get_current_colormap();
49025        normalized = map.normal || map.normal == 'true';
49026        self.limits = normalized ? self.normalized_limits : self.raw_limits;
49027        self.expression_range = normalized ? self.default_norm_expression_range : self.default_raw_expression_range;
49028
49029        the_level_widget = new WindowLevelWidget(
49030                                ramp_box,
49031                                map.url,
49032                                self.limits[0],
49033                                self.limits[1],
49034                                self.expression_range[0],
49035                                self.expression_range[1],
49036                                do_window_level_change,
49037                                1);
49038
49039        the_level_widget.setSymmetric(normalized);
49040    }
49041
49042    function position_select_list() {
49043        var list_dims = the_select_box.getDimensions();
49044        var cont_pos = select_container.cumulativeOffset();
49045        the_select_box.setStyle({top:(cont_pos.top-(list_dims.height+1))+'px',left:cont_pos.left+'px'});
49046    }
49047
49048
49049    function do_select_item_click(evt) {
49050      evt = (evt) ? evt : event;
49051      var sender = ((evt.target) ? evt.target : evt.srcElement);
49052
49053      the_select_box.hide();
49054      var new_name = sender.getAttribute('name');
49055      var new_map = select_colormap(self.colormap_set, new_name);
49056
49057      select_container.removeClassName('open_top');
49058
49059      if(self.on_colormap_change && new_map)
49060          self.on_colormap_change(self, new_map.name, new_map.normal, self.expression_range[0], self.expression_range[1]);
49061    }
49062
49063    function select_colormap(map_set, name) {
49064      var new_map = null;
49065        for(var i = 0; i < map_set.length; i++) {
49066          if(map_set[i].name == name) {
49067            map_set[i].selected = true;
49068            new_map = map_set[i];
49069          } else {
49070	    map_set[i].selected = false;
49071	  }
49072        }
49073
49074      if (new_map) {
49075	normalized = new_map.normal;
49076	var limits = new_map.limits;
49077	self.expression_range = new_map.range;
49078	var new_url = new_map.url;
49079	
49080	setting = true;
49081	the_level_widget.setColormapImage(new_url);
49082	the_level_widget.setLimits(limits[0], limits[1]);
49083	the_level_widget.setSymmetric(normalized);
49084	setting = false;
49085      }
49086
49087      return new_map;
49088    }
49089
49090    function do_colormap_chooser() {
49091        position_select_list();
49092        the_select_box.toggle();
49093        if(the_select_box.visible())
49094            select_container.addClassName('open_top');
49095        else
49096            select_container.removeClassName('open_top');
49097    }
49098
49099    function do_window_level_change(lower, upper) {
49100
49101        if(normalized)
49102            self.default_norm_expression_range = [lower, upper];
49103        else
49104            self.default_raw_expression_range = [lower, upper];
49105
49106        if(setting)
49107            return;
49108        
49109        if(self.on_level_change)
49110            self.on_level_change(lower, upper);
49111    }
49112
49113    function get_current_colormap() {
49114
49115        for(var i = 0; i < self.colormap_set.length; i++) {
49116            var map = self.colormap_set[i];
49117            if(map.selected)
49118                return(map);
49119        }
49120
49121        return(null);
49122    }
49123
49124    function init() {
49125
49126        create_window_level_widget(self.container)
49127        __colormap_widget_count__++;
49128    }
49129
49130    init();
49131}
49132
49133;
49134
49135function heatmap_control_options_config(options_from_json, urls_from_env)
49136{
49137    var _options = options_from_json;
49138
49139    // use the config info from environment.rb to override the application.json config
49140    if(urls_from_env.analysis_service)
49141          _options.env.env_mri_image_server = urls_from_env.analysis_service;
49142
49143    if(urls_from_env.related_data)
49144          _options.env.env_related_projects_service = urls_from_env.related_data;
49145
49146    if(urls_from_env.brain_explorer_host)
49147          _options.env.env_brain_explorer_host = urls_from_env.brain_explorer_host;
49148
49149    if(urls_from_env.correlation_help)
49150          _options.env.app_url_for_correlation_help_path = urls_from_env.correlation_help;
49151
49152    var _cookies = _options.cookies;
49153
49154    var _settings = new user_data(_options.user_defaults);
49155    var _app_colormap_set = configure_colormap_set(_options.app.app_name, _options.env.env_mri_image_server, _cookies, _settings);
49156    var _default_grouping = get_current_grouping(_options.query.search_type, _cookies, _settings);
49157    var _current_colormap = select_colormap(_app_colormap_set, _settings.get(_cookies.cookie_colormap));
49158    var _current_sort = get_current_sort(_options.query.search_type, _cookies, _settings);
49159
49160    _options.app.settings = _settings;
49161    _options.app.colormap_set = _app_colormap_set;
49162    _options.query.default_grouping = _default_grouping;
49163    _options.query.current_colormap = _current_colormap;
49164    _options.query.current_sort = _current_sort;
49165    _options.select_colormap = select_colormap;    // pass in the function
49166
49167
49168
49169    this.get_options = function() {
49170        return _options;
49171    };
49172
49173    //helper functions for setting the options to heatmap_control
49174    //function configure_colormap_set(selected_map_name) {
49175      function configure_colormap_set(app_name, env_mri_image_server, cookies, settings) {
49176
49177        switch ( app_name )
49178        {
49179            case 'ahba':
49180            case 'nhp':
49181            case 'brainspan_lcm':
49182                var colormap_set =
49183                [{
49184                  name:'redgreen',
49185                  url:env_mri_image_server + 'colormap/show/redgreen',
49186                  normal:true,
49187                  selected:false,
49188                  limits:[-6,6],
49189                  range:settings.get(cookies.cookie_gene_norm_level)
49190                },
49191                {
49192                  name:'bluered',
49193                  url:env_mri_image_server + 'colormap/show/bluered',
49194                  normal:true,
49195                  selected:false,
49196                  limits:[-6,6],
49197                  range:settings.get(cookies.cookie_gene_norm_level)
49198                },
49199                {
49200                  name:'ryb',
49201                  url:env_mri_image_server + 'colormap/show/ryb',
49202                  normal:true,
49203                  selected:false,
49204                  limits:[-6,6],
49205                  range:settings.get(cookies.cookie_gene_norm_level)
49206                },
49207                {
49208                  name:'jet',
49209                  url:env_mri_image_server + 'colormap/show/jet',
49210                  normal:false,
49211                  selected:false,
49212                  limits:[0,16],
49213                  range:settings.get(cookies.cookie_gene_raw_level)
49214                }];
49215            break;
49216
49217            case 'brainspan_rnaseq':
49218                var colormap_set =
49219                [{
49220                  name:'jet',
49221                  url:env_mri_image_server + 'colormap/show/jet',
49222                  normal:false,
49223                  selected:true,
49224                  limits:[0,16],
49225                  range:settings.get(cookies.cookie_gene_raw_level)
49226                }];
49227
49228            break;
49229
49230
49231        }
49232
49233        return(colormap_set);
49234    }
49235
49236
49237
49238    function get_current_grouping(type, cookies, settings) {
49239
49240      var grouping = 0;
49241      switch(type) {
49242       case 'search':
49243         grouping = settings.get(cookies.cookie_gene_grouping);
49244         break;
49245       case 'differential':
49246         grouping = settings.get(cookies.cookie_diff_grouping);
49247         break;
49248       case 'correlation':
49249         grouping = settings.get(cookies.cookie_correlation_grouping);
49250         break;
49251      }
49252
49253      return(grouping);
49254    }
49255
49256    function get_current_sort(type, cookies, settings) {
49257
49258      var sort = '';
49259      switch(type) {
49260       case 'differential':
49261         sort = settings.get(cookies.cookie_differential_sort_column);
49262         break;
49263       case 'correlation':
49264         sort = settings.get(cookies.cookie_correlation_sort_order);
49265         break;
49266      }
49267
49268      return(sort);
49269    }
49270
49271
49272    function select_colormap(map_set, name) {
49273      for(var i = 0; i < map_set.length; i++) {
49274        if(map_set[i].name == name) {
49275          map_set[i].selected = true;
49276          return(map_set[i]);
49277        }
49278      }
49279      if(map_set.length == 1)
49280          return(map_set[0]);
49281      else
49282          return(null);
49283    }
49284
49285
49286}
49287;
49288 /*
49289  * This widget consists of a single button that will place itself into
49290  * the upper right corner of the given container.  The button
49291  * indicates a switch between a top & bottom item & raises a 'change' event.
49292 */
49293
49294    function grouping_widget(container, options, event_options) {
49295
49296     // public properties
49297        this.container = container;
49298        this.default_grouping = options ? options.default_grouping ? options.default_grouping : 0 : 0;
49299        this.groupings = options ? options.groupings ? options.groupings : ['donors,structures','structures,donors'] : ['donors,structures','structures,donors'];
49300        this.tool_tip = options ? options.tool_tip ? options.tool_tip : 'Toggle sort by structure or sort by donor' : 'Toggle sort by structure or sort by donor';
49301
49302     // event hooks
49303        this.on_grouping_change = event_options ? event_options.on_grouping_change ? event_options.on_grouping_change : null : null;
49304
49305     // public methods
49306        this.reposition = function() {
49307           position_button();
49308        }
49309
49310        this.set_grouping = function(new_grouping) {
49311            if(new_grouping != this.groupings[current_grouping]) {
49312                toggle_grouping();
49313            }
49314        }
49315
49316     // private properties
49317        var self = this;
49318        var the_button = null;
49319        var current_grouping = self.default_grouping;
49320
49321     // private methods
49322        function build_widget() {
49323
49324            var title = self.tool_tip;
49325            the_button = new Element('div', {'class':'grouping_switcheroo', title:title});
49326
49327            self.container.appendChild(the_button);
49328            $j(the_button).button({text:false, icons:{secondary:'ui-icon-refresh'}});
49329            position_button();
49330        }
49331
49332        function position_button() {
49333            // put the button in the upper right-hand corner of the container
49334            var container_pos = self.container.cumulativeOffset();
49335            var container_dims = self.container.getDimensions();
49336
49337            var button_dims = the_button.getDimensions();
49338            var pos = {top:(container_pos.top+1)+'px',
49339                       left:(container_dims.width /*+ container_pos.left*/ - 10)+'px'};
49340                       // -10 probably to make up for 5px of padding in the container.
49341            the_button.setStyle(pos);
49342            the_button.observe('mousedown', do_button_down);
49343            the_button.observe('click', toggle_grouping);
49344            the_button.observe('mouseup', do_button_up);
49345        }
49346
49347        function do_button_down() {
49348            the_button.addClassName('button_down');
49349        }
49350
49351        function do_button_up() {
49352            the_button.removeClassName('button_down');
49353        }
49354
49355        function toggle_grouping() {
49356            current_grouping = (current_grouping < 2) ? (current_grouping ? 0 : 1) : 5 - current_grouping;  // for NHP, there are two new group_by settings with value 2 and 3 here
49357            if(self.on_grouping_change)
49358                self.on_grouping_change(self.groupings[current_grouping]);
49359        }
49360
49361        function init() {
49362            build_widget();
49363        }
49364
49365        init();
49366    }
49367;
49368
49369// a busy indicator is added to the prototype Element to allow consistent business.
49370var busifier = {
49371
49372  busified:false,
49373  busy_panel:null,
49374
49375  clone_position:function(element, position_target) {
49376    if(!element)
49377        return(element);
49378
49379    // this is why we're using a custom clone position--
49380    // Prototype's cumulativeScrollOffset() implementation takes a long time on
49381    // IE (on the order of 5 seconds sometimes) while jquery's offset()
49382    // returns very quickly.
49383    var offsets = $j(position_target).offset();
49384    //////
49385
49386    // the rest of this method is still using Prototype.
49387    element = $(element);
49388    var dims = $(position_target).getDimensions();
49389
49390    element.setStyle({
49391      position: 'absolute',
49392      top: offsets.top + 'px',
49393      left: offsets.left + 'px',
49394      width: dims.width + 'px',
49395      height: dims.height + 'px'
49396    });
49397    return(element);
49398  },
49399
49400  // adds an partialy opaque overlayed panel to the element, along with an icon.
49401  // the overlay is usually hidden, but can be made visible by calling
49402  // element.show_busy(true) and hidden by element.show_busy(false);
49403  //
49404  // options include:
49405  //    color:    the color of the overlay.  Default = #000
49406  //    opacity:  the percent opacity of the overlay.  Default = 0.6
49407  //    icon:     a path to an icon to show centered in the overlay.  Default = /assets/images/ajax-loader.gif
49408  busify:function(element, options) {
49409    if(!element)
49410        return(element);
49411
49412    element = $(element);
49413
49414    // create a div and position it to cover the given element
49415    element.busy_panel = new Element('div');
49416    element.busy_panel.hide();
49417    var busy_image = new Element('img', {src:(options ? options.icon ? options.icon : "/assets/heatmap_ui/ajax-loader.gif" : "/assets/heatmap_ui/ajax-loader.gif")});
49418    element.busy_panel.appendChild(busy_image);
49419    element.busy_panel.observe('click', function(evt) { Event.stop(evt); });
49420
49421    element.appendChild(element.busy_panel);
49422
49423    element.busy_panel.setStyle({
49424      backgroundColor:(options ? options.color ? options.color : '#aaa' : '#aaa'),
49425      position:'absolute',
49426      top:'0px',
49427      left:'0px',
49428      verticalAlign:'middle',
49429      cursor:'wait',
49430      textAlign:'center',
49431      zIndex:'99999'
49432    });
49433    busy_image.setStyle({
49434      marginLeft:'auto',
49435      marginRight:'auto',
49436      marginTop:'25%'
49437    });
49438    element.busy_panel.clone_position(element);
49439    element.busy_panel.setOpacity(options ? options.opacity ? options.opacity : 0.5 : 0.5);
49440
49441
49442    element.busified = true;
49443    return(element);
49444  },
49445
49446  // busifies the element if it isn't already, then shows/hides the busy indicator
49447  show_busy:function(element, busy) {
49448    element = $(element);
49449
49450    if(!element.busified)
49451      element = element.busify();
49452
49453    if(busy) {
49454      $(element.busy_panel).clone_position(element);
49455      element.busy_panel.show();
49456    }
49457    else
49458      element.busy_panel.hide();
49459  },
49460
49461  // if the element is resized you'll want to rebusy it
49462  rebusy:function(element) {
49463    element = $(element);
49464
49465    if(!element.busified)
49466      element = element.busify();
49467    else
49468      element.busy_panel.clone_position(element);
49469  },
49470
49471  // remove the busifier elements
49472  debusy:function(element) {
49473      if(!element.busified)
49474          return;
49475
49476      element.removeChild(element.busy_panel);
49477      element.busified = false;
49478  }
49479}
49480
49481Element.addMethods(busifier);
49482
49483
49484// A basket of cookies
49485function user_settings(defaults) {
49486
49487    this.defaults = defaults ? defaults : {};
49488
49489    this.set = function(name, value) {
49490        set_cookie(name, Object.toJSON(value), 365);
49491    }
49492
49493    this.get = function(name) {
49494        var cook = get_cookie(name);
49495        if(cook && cook.length > 0 && cook != '""') {
49496            var value = cook.evalJSON();
49497            if(value == 'false' || value == 'true')
49498                value = (value == 'true');
49499
49500            return(value);
49501        }
49502        else
49503            cook = defaults[name];
49504
49505        return(cook);
49506    }
49507
49508    function set_cookie(c_name, value, expiredays) {
49509        var exdate=new Date();
49510        exdate.setDate(exdate.getDate()+expiredays);
49511        document.cookie=c_name+ "=" + escape(value) + ((expiredays==null) ? "" : ";path=/;expires="+exdate.toGMTString());
49512    }
49513
49514    function get_cookie(c_name) {
49515      if(document.cookie.length > 0) {
49516          var c_start=document.cookie.indexOf(c_name + "=")
49517          if(c_start != -1) {
49518
49519            c_start = c_start + c_name.length+1;
49520            var c_end = document.cookie.indexOf(";", c_start);
49521
49522            if(c_end == -1)
49523                c_end = document.cookie.length;
49524
49525            return unescape(document.cookie.substring(c_start, c_end));
49526          }
49527      }
49528      return null;
49529    }
49530}
49531
49532
49533var BrowserSupport = Class.create({
49534
49535  // initialize with an object containing keys & values for browser names & versions as in:
49536  // {webkit:'531.0', msie:'8.0', mozilla:'1.9.2'}
49537  //
49538  // this.supported means the current browser has version >= the minumum_list,
49539  // this.not_supported means the current browser is in the minumum_list and
49540  // has vesion less than that given.
49541  //
49542  // NOTE that both supported & not_supported may legitimately be false, if the
49543  // current browser is not in the list.
49544  initialize: function(minimum_list) {
49545
49546    var userAgent = navigator.userAgent.toLowerCase();
49547    this.minimum_list = minimum_list ? minimum_list : {};
49548
49549    this.version = (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [])[1];
49550    this.extended_version = this._version_to_number(this.version);
49551
49552    this.webkit = /webkit/.test( userAgent );
49553    this.opera = /opera/.test( userAgent );
49554    this.msie = /msie/.test( userAgent ) && !/opera/.test( userAgent );
49555    this.mozilla = /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent );
49556
49557    this.name = this.webkit ? 'webkit' : this.opera ? 'opera' : this.msie ? 'msie' : this.mozilla ? 'mozilla' : 'unknown';
49558
49559    this.supported = this._is_supported(this.minimum_list);
49560    this.not_supported = this._is_not_supported(this.minimum_list);
49561  },
49562
49563  _is_not_supported: function(list) {
49564
49565    for(var key in list) {
49566        if(key == this.name) {
49567            var supported_version = this._version_to_number(list[key]);
49568
49569            if(supported_version.major > this.extended_version.major)
49570                return(true);
49571
49572            if((supported_version.major == this.extended_version.major)
49573                &&
49574              (supported_version.minor > this.extended_version.minor))
49575                return(true);
49576
49577            if((supported_version.major == this.extended_version.major)
49578                &&
49579              (supported_version.minor == this.extended_version.minor)
49580                &&
49581              (supported_version.build > this.extended_version.build))
49582                return(true);
49583        }
49584    }
49585    return(false);
49586  },
49587
49588  _is_supported: function(list) {
49589    for(var key in list) {
49590        if(key == this.name) {
49591            var supported_version = this._version_to_number(list[key]);
49592
49593            if((supported_version.major == this.extended_version.major)
49594                &&
49595              (supported_version.minor == this.extended_version.minor)
49596                &&
49597              (supported_version.build <= this.extended_version.build))
49598                return(true);
49599
49600            if((supported_version.major == this.extended_version.major)
49601                &&
49602              (supported_version.minor <= this.extended_version.minor))
49603                return(true);
49604
49605            if(supported_version.major <= this.extended_version.major)
49606                return(true);
49607        }
49608    }
49609    return(false);
49610  },
49611
49612  _version_to_number: function(version) {
49613
49614    var version_components = version ? version.split('.') : [0];
49615    var ret = {};
49616
49617    ret.major = parseInt(version_components[0]);
49618    ret.minor = version_components.length > 1 ? parseInt(version_components[1]) : 0;
49619    ret.build = version_components.length > 2 ? parseInt(version_components[2]) : 0;
49620
49621    return(ret);
49622  }
49623
49624});
49625
49626// the value of __IE__ should be the version of the actual rendering engine
49627var __IE__ = false;
49628
49629/*@cc_on
49630   @if ( @_jscript_version >= 5.7 )
49631      __IE__ = true;
49632   @elif ( @_jscript_version == 5.6 )
49633      __IE__ = 6;
49634   @else
49635      __IE__ = 1;
49636   @end
49637@*/
49638
49639;
49640function heatmap_cart_view(container, probe_list, donor_list, options, event_options) {
49641    
49642    this.container = $(container);
49643    this.probe_list = probe_list;
49644    this.donor_list = donor_list;
49645    
49646    this.colormap_set = options ? (options.colormap_set ? options.colormap_set : []) : [];
49647
49648    this.box_width = options.box_width ? options.box_width : 200;
49649    this.box_height = options.box_height ? options.box_height : 150;
49650    this.probes_per_page = options.probes_per_page ? options.probes_per_page : 5;
49651    this.host = options.host ? options.host : '/mri_viewer/data';
49652    this.num_rotations = 36;
49653
49654    this.surface_set = options.surface_set ? options.surface_set : { "lh.cortical":"Left Cortex",
49655                                                                     "rh.cortical":"Right Cortex",
49656                                                                     "subcortical":"Subcortex" };
49657    this.default_surface = options.default_surface ? options.default_surface : "lh.cortical";
49658
49659    this.pick_callback = event_options.pick_callback ? event_options.pick_callback : null;
49660    this.on_colormap_change = event_options? (event_options.on_colormap_change ? event_options.on_colormap_change : null) : null;
49661    this.on_level_change = event_options? (event_options.on_level_change ? event_options.on_level_change : null) : null;
49662    this.on_view_click = event_options? (event_options.on_view_click ? event_options.on_view_click : null) : null;
49663    this.on_clear_click = event_options? (event_options.on_clear_click ? event_options.on_clear_click : null) : null;
49664    
49665    // private properties
49666    var self = this;
49667    var the_surface_widgets = [];
49668    var the_current_page = 0;
49669    var the_current_surface = "lh.cortical"; 
49670    var the_angle = 9; // default left lateral
49671    var the_table_rows = [];
49672    var the_buttons = {};
49673    var the_slider_tracks = [];
49674    var the_slider_handles = [];
49675    var the_sliders = [];
49676    var the_colormap_boxes = [];
49677    var the_colormaps = [];
49678    var the_control_list = [];
49679    var the_colormap_range = [-3,3];
49680    var the_cart_table = null;
49681    var the_top_button_box = null;
49682    var the_bottom_button_box = null;
49683
49684    // public methods
49685    this.set_visibility = function(visible) {
49686        if (visible) 
49687            $j(self.container).show();
49688        else 
49689            $j(self.container).hide();
49690
49691        self.resize();
49692    };
49693
49694    this.set_colormap_range = function(lower,upper) {
49695        for (var i=0; i<the_colormaps.length; i++) 
49696            the_colormaps[i].set_range(lower,upper);
49697
49698        do_level_change(lower,upper);
49699    };
49700
49701    this.set_colormap = function(name) {
49702        for (var i=0; i<the_colormaps.length; i++) 
49703            the_colormaps[i].set_colormap(name);            
49704
49705        self.update_images();
49706    };
49707
49708    this.set_probe_list = function(probe_list) {
49709        self.probe_list = probe_list;
49710	build_view();
49711    };
49712
49713    this.set_donor_list = function(donor_list) {
49714        self.donor_list = donor_list;
49715	build_view();
49716    };
49717
49718    this.set_page_index = function(page_index) {
49719	if (the_current_page != page_index) {
49720	    the_current_page = page_index;
49721	    build_view();	    
49722	}
49723    };
49724
49725    this.update_images = function() {
49726        map = get_selected_colormap(self.colormap_set);
49727
49728        for (var i=0; i<the_surface_widgets.length; i++) {
49729            var widget = the_surface_widgets[i];
49730            widget.colormap_range = the_colormap_range;
49731            widget.colormap_name = map.name;
49732            widget.colormap_normal = map.normal;
49733
49734            widget.set_sample({ sample_probe: self.probe_list[i].id });
49735            widget.update_image();
49736        }
49737    };
49738
49739    this.update_data = function(info) {
49740        // figure out which page the selected probe is on
49741        var info_page = the_current_page;
49742        for (var i=0; i<self.probe_list.length; i++) {
49743            if (self.probe_list[i].id == info.probe.id)
49744                info_page = Math.floor(i / self.probes_per_page);
49745        }
49746
49747        // if this probe is on a different page, switch to it
49748        if (info_page != the_current_page) {
49749            the_current_page = info_page;
49750            build_view();
49751        } 
49752
49753	if (info.surface)
49754            the_current_surface = info.surface;
49755
49756        update_button_styles();
49757        
49758        for (var i=0; i<the_surface_widgets.length; i++) {
49759            var widget = the_surface_widgets[i];
49760
49761            // this is ugly
49762            var widget_probe_id = widget.image_settings[0].probe.id;
49763            if (info.probe.id == widget_probe_id) {
49764                the_table_rows[i].addClassName("highlighted");
49765            } else {
49766                the_table_rows[i].removeClassName("highlighted");
49767            }
49768
49769            var sample = { 
49770                probe: info.probe.id,
49771                donor: info.donor.id,
49772                rna_well: info.rna_well
49773            };
49774            
49775            widget.set_current_surface(the_current_surface);
49776            widget.set_sample(sample);
49777            widget.update_image();
49778        }
49779    };
49780
49781    this.resize = function() {
49782        // the table resizes itself.  Resize the controls to match.
49783        if (the_cart_table && the_control_list) {
49784            var table_width = the_cart_table.getWidth();
49785
49786            for (var i=0; i<the_control_list.length; i++) 
49787                the_control_list[i].style.width = table_width+'px';
49788
49789	    the_bottom_button_box.style.width = table_width+'px';
49790	    the_top_button_box.style.width = table_width+'px';
49791        }
49792    };
49793
49794    
49795
49796    function on_slider_change(event) {
49797        the_angle = event;
49798
49799        for (var i=0; i<the_sliders.length; i++) {
49800            var slider = the_sliders[i];
49801            if (slider.value != event)
49802                slider.setValue(event);
49803        }
49804
49805        for (var i=0; i<the_surface_widgets.length; i++) {
49806            the_surface_widgets[i].set_angle(self.num_rotations - event);
49807            the_surface_widgets[i].update_image();
49808        }
49809
49810    };
49811
49812    function on_image_pick(response) {
49813        // tell the other images to update themselves
49814        for (var i=0; i<the_surface_widgets.length; i++) {
49815            var widget = the_surface_widgets[i];
49816            widget.set_sample(response);
49817            widget.update_image();
49818        }
49819        
49820        // tell the outside world about the new sample
49821        if (self.pick_callback) {
49822            // non-obvious syntax for deep-copying an object
49823            var info = jQuery.extend(true,{},response);
49824
49825            for (var i=0; i<self.probe_list.length; i++)
49826                if (self.probe_list[i].id == response.probe) info.probe = this.probe_list[i];
49827            for (var i=0; i<self.donor_list.length; i++)
49828                if (self.donor_list[i].donor.id == response.donor) info.donor = this.donor_list[i].donor;
49829
49830            self.pick_callback(self,info);
49831        }
49832    };
49833
49834    function on_surface_change(value) {
49835        the_current_surface = value;
49836
49837        update_button_styles();
49838
49839        // tell the other images to update themselves
49840        for (var i=0; i<the_surface_widgets.length; i++) {
49841            var widget = the_surface_widgets[i];
49842            widget.set_current_surface(the_current_surface);
49843            widget.update_image();
49844        }
49845    }
49846
49847    
49848    function on_page_select(page_index, jq) {
49849        alert(['hit',page_index]);
49850    }
49851
49852    function clear_view() {
49853        // clear out the parent
49854        while (self.container.hasChildNodes()) {
49855            self.container.removeChild(self.container.lastChild);
49856        }
49857        
49858        the_surface_widgets.clear();
49859	the_table_rows.clear();
49860	the_buttons = {};
49861	the_slider_tracks.clear();
49862	the_slider_handles.clear();
49863	the_sliders.clear();
49864	the_colormap_boxes.clear();
49865	the_colormaps.clear();
49866	the_control_list.clear();
49867	the_cart_table = null;
49868	the_top_button_box = null;
49869	the_bottom_button_box = null;
49870    }
49871
49872    // private methods
49873    function build_view() {
49874	clear_view();
49875
49876        // build the table
49877        the_cart_table = new Element('table', { 'class': 'hm_cart_table' });
49878        the_cart_table.cellSpacing = "0";
49879        the_cart_table.cellPadding = "5";
49880
49881        // initialize the header
49882        var cart_header = new Element('thead');
49883        var cart_header_row = new Element('tr');
49884
49885        cart_header_row.setStyle({  color: '#fff', 
49886                                    backgroundColor: "#4d6d86" });
49887
49888        for (var i=0; i<self.donor_list.length; i++) {
49889            var h = new Element('th', { 'class': 'hm_cart_table_header' });
49890            h.innerHTML = self.donor_list[i].donor.name;
49891            cart_header_row.appendChild(h);                     
49892        }
49893
49894        cart_header.appendChild(cart_header_row);
49895        the_cart_table.appendChild(cart_header);
49896
49897        the_cart_table_body = new Element('tbody', { 'class': 'heatmap_cart_table_body' });
49898        the_cart_table.appendChild(the_cart_table_body);
49899
49900        // initialize array of buttons
49901        the_buttons = {};
49902        for (var key in self.surface_set)
49903            the_buttons[key] = [];
49904
49905        var start = self.probes_per_page * the_current_page;
49906        var end = Math.min(start + self.probes_per_page, self.probe_list.length);
49907	var no_probes = end <= start;
49908
49909        var control_list_top = make_control_list(the_buttons,'hm_cart_anchor_top');
49910        var control_list_bottom = make_control_list(the_buttons,'hm_cart_anchor_bottom');
49911
49912	the_top_button_box = make_button_box();
49913	the_bottom_button_box = make_button_box();
49914	
49915        update_button_styles();
49916
49917	self.container.appendChild(the_top_button_box);
49918        self.container.appendChild(control_list_top);
49919        self.container.appendChild(the_cart_table);
49920        self.container.appendChild(control_list_bottom);
49921	self.container.appendChild(the_bottom_button_box);
49922
49923
49924        var selected_colormap = get_selected_colormap(self.colormap_set);
49925
49926
49927	// if there are no probes, indicate that and hide the controls
49928	if (no_probes) {
49929	    var row = new Element('tr');
49930	    var td = new Element('td', { 'class': 'hm_cart_table_error' });
49931	    td.colSpan = cart_header_row.children.length;
49932	    td.innerHTML = "No probes selected";
49933
49934	    row.appendChild(td);
49935	    the_cart_table_body.appendChild(row);
49936
49937	    $j(control_list_top).hide();
49938	    $j(control_list_bottom).hide();
49939	} else {
49940            for (var i=start; i<end; i++) {
49941		var row = new Element('tr');
49942
49943		var widget = new Element('div', { 'class': 'hm_cart_table_widget' });
49944		widget.style.width = self.box_width*self.donor_list.length+'px';
49945		widget.style.height = self.box_height+'px';
49946
49947		var geneLabel = new Element('div', { 'class': 'hm_cart_table_label vtip' });
49948		geneLabel.title = "Probe: " + self.probe_list[i].name;
49949		geneLabel.innerHTML = self.probe_list[i]["gene-symbol"];
49950
49951		var wrapper = new Element('div', { 'class': 'hm_cart_table_row_wrapper' });
49952		
49953		wrapper.appendChild(widget);
49954		wrapper.appendChild(geneLabel);
49955		
49956		var widget_container = new Element('td', { 'class': 'hm_cart_table_row' });
49957		widget_container.colSpan = self.donor_list.length;
49958
49959		widget_container.appendChild(wrapper);
49960
49961		row.appendChild(widget_container);
49962		
49963		the_cart_table_body.appendChild(row);
49964		
49965		var options = {
49966                    image_settings: self.donor_list.map(function(d) { return {probe: self.probe_list[i], donor: d}; }),
49967                    host: self.host,
49968                    colormap_name: selected_colormap.name,
49969                    colormap_range: selected_colormap.range,
49970                    colormap_normal: selected_colormap.normal,
49971                    slider_position: i == start ? "top" : (i == (end-1) ? "bottom" : "none"),
49972                    num_rotations: self.num_rotations
49973		};
49974		
49975		var event_options = {
49976                    pick_callback: on_image_pick.bind(self),
49977                    slider_callback: on_slider_change.bind(self)
49978		};
49979		
49980		var surface_widget = new SurfaceWidget(widget, options, event_options);
49981		
49982		
49983		the_surface_widgets.push(surface_widget);
49984		
49985		surface_widget.set_angle(self.num_rotations-the_angle);
49986		surface_widget.set_current_surface(the_current_surface);
49987		surface_widget.update_image();
49988		
49989		the_table_rows.push(row);
49990            }
49991	}	
49992
49993        the_control_list.push(control_list_top);
49994        the_control_list.push(control_list_bottom);
49995        self.resize();
49996
49997        for (var i=0; i<the_slider_handles.length; i++) {
49998            var slider = new Control.Slider(the_slider_handles[i], the_slider_tracks[i],
49999                                            {
50000                                                onChange:on_slider_change,
50001                                                onSlide:on_slider_change,
50002                                                range:$R(0,self.num_rotations-1),
50003                                                sliderValue:the_angle
50004                                            });
50005            the_sliders.push(slider);
50006        }
50007
50008        var colormap_options = {
50009            colormap_set:self.colormap_set,
50010            normalized:selected_colormap.normal,
50011            zscore_limits:selected_colormap.limits,
50012            raw_limits:selected_colormap.limits,
50013            raw_expression_range:selected_colormap.range,
50014            norm_expression_range:selected_colormap.range
50015        };
50016
50017        var colormap_events = {
50018            on_level_change:do_level_change,
50019            on_colormap_change:do_colormap_change
50020        };
50021
50022        for (var i=0; i<the_colormap_boxes.length; i++) {
50023            var widget = new colormap_widget(the_colormap_boxes[i], colormap_options, colormap_events);
50024            the_colormaps.push(widget);
50025        }
50026
50027	vtip();
50028    }
50029
50030    function make_button_box() {
50031	var button_box = new Element('div', { 'class': 'hm_cart_button_box' });
50032
50033	var view_button = new Element('button', { 'class': 'hm_control_button' });
50034	view_button.style.marginBottom = "2px";
50035	view_button.style.marginTop = "2px";
50036        view_button.update("View Selection Heatmap");
50037        view_button.observe('click',do_view_click);
50038        $j(view_button).button();
50039
50040	button_box.appendChild(view_button);
50041
50042	var clear_button = new Element('button', { 'class': 'hm_control_button' });
50043	clear_button.style.marginBottom = "2px";
50044	clear_button.style.marginTop = "2px";
50045        clear_button.update("Clear Selections");
50046        clear_button.observe('click',do_clear_click);
50047        $j(clear_button).button();
50048
50049	button_box.appendChild(clear_button);
50050
50051	return button_box;
50052    }
50053
50054    function make_control_list(button_map, anchor_id) {
50055
50056        var control_container = new Element('div', { 'class': 'hm_cart_controls' });
50057
50058        // add the buttons
50059        for (var key in self.surface_set) {
50060            var button = new Element('button', { 'class': 'hm_cart_button' });
50061            button.update(self.surface_set[key]);
50062            button.observe('click',function(surface) { return function() { on_surface_change(surface); }; }(key));
50063            $j(button).button();
50064            button_map[key].push(button);
50065
50066            var div = new Element('div', { 'class': 'hm_cart_button_container' });
50067            div.appendChild(button);
50068            control_container.appendChild(div);
50069        }
50070
50071        // add the pagination buttons in reverse order (float right)
50072        var pagination_element = new Element('div', { 'class': 'hm_cart_pagination_box' } );
50073
50074        $j(pagination_element).pagination(self.probe_list.length, {
50075                                              callback: function(page_index) { self.set_page_index(page_index); },
50076                                              items_per_page: self.probes_per_page,
50077                                              prev_text: "&lt;",
50078                                              next_text: "&gt;",
50079                                              link_to: "#"+anchor_id,
50080                                              num_display_entries: 2,
50081                                              num_edge_entries: 1,
50082                                              current_page: the_current_page });
50083
50084        // add the label, and a clear break
50085        var label = new Element('div', { 'class': 'hm_cart_pagination_label' });
50086        label.update('Jump to page:&nbsp;');
50087        pagination_element.appendChild(label);
50088
50089        control_container.appendChild(pagination_element);
50090
50091	// add the color map box
50092        var ramp_box = new Element('div', {'class':'windowLevelContainer'});
50093        control_container.appendChild(ramp_box);
50094
50095        var ramp_label_box = new Element('div', {'class':'ramp_label_box'});
50096        ramp_label_box.update('Color Map:');
50097        ramp_box.appendChild(ramp_label_box);
50098
50099        the_colormap_boxes.push(ramp_box);
50100
50101        // add the angle slider
50102        var slider_box = new Element('div', {'class':'status_zoom_box', title:'Rotate images'});
50103        $j(slider_box).css('float','right');
50104
50105        var slider_label_box = new Element('div', {'class':'zoom_label_box'});
50106        slider_label_box.update('Rotate:');
50107        slider_box.appendChild(slider_label_box);
50108        control_container.appendChild(slider_box);
50109        
50110        var slider_track = new Element('div', {'class':'status_zoom_track' });
50111        var slider_handle = new Element('div', {'class':'status_zoom_handle' });
50112        slider_track.appendChild(slider_handle);
50113        slider_box.appendChild(slider_track);
50114
50115        the_slider_tracks.push(slider_track);
50116        the_slider_handles.push(slider_handle);
50117
50118        control_container.appendChild(new Element('div', { style: 'clear:both' }));
50119
50120        return control_container;
50121    }
50122
50123    function update_button_styles() {
50124        // update the button styles
50125        for (var key in the_buttons) {
50126            for (var i=0; i<the_buttons[key].length; i++) {
50127                var button = the_buttons[key][i];
50128                if (key == the_current_surface) {
50129                    $j(button).addClass('ui-priority-primary');
50130                    $j(button).removeClass('ui-priority-secondary');
50131                } else {
50132                    $j(button).addClass('ui-priority-secondary');
50133                    $j(button).removeClass('ui-priority-primary');
50134                }
50135            }
50136        }
50137    }
50138
50139    function do_level_change(lower,upper) {
50140        if (lower == the_colormap_range[0] && upper == the_colormap_range[1])
50141            return;
50142
50143        the_colormap_range = [lower,upper];
50144        self.update_images();
50145
50146        if (self.on_level_change)
50147            self.on_level_change(self,lower,upper);
50148    }
50149
50150    function do_colormap_change(sender, name, normalized, lower, upper) {
50151        self.set_colormap(name);
50152        self.update_images();
50153
50154        if(self.on_colormap_change)
50155            self.on_colormap_change(self, name, normalized, lower, upper);
50156    }
50157
50158    function get_selected_colormap(map_set) {
50159        for(var i = 0; i < map_set.length; i++)
50160            if(map_set[i].selected)
50161                return(map_set[i]);
50162        
50163        return(null);
50164    }
50165
50166    function do_view_click() {
50167	if (self.on_view_click)
50168	    self.on_view_click(self);
50169    }
50170
50171    function do_clear_click() {
50172	if (self.on_clear_click)
50173	    self.on_clear_click(self);
50174    }
50175
50176    function init() {
50177        build_view();
50178    }
50179
50180    init();
50181}
50182
50183
50184
50185;
50186function heatmap_control(options) {
50187    var app_name = options.app.app_name;
50188    var debug = options.query.debug;
50189    var search_type = options.query.search_type;
50190    var donor_ids = options.query.donor_ids;
50191    var rma_dao = options.rma_dao;
50192    var hide_details_pane = options.hide_details_pane;
50193
50194    var env_mri_image_server = options.env.env_mri_image_server;
50195    var heatmap_download_service = options.heatmap_download_service;
50196    var atlas_viewer_service = options.atlas_viewer_service;
50197    if (atlas_viewer_service == undefined) {
50198        // maintain backwards compatibility with old service name
50199        atlas_viewer_service = options.legend_service;
50200    }
50201
50202    var download_service_data_set = options.download_service_data_set;
50203    var env_related_projects_service = options.env.env_related_projects_service;
50204    var env_brain_explorer_host = options.env.env_brain_explorer_host;
50205
50206    var env_project_code_hba_microarray = options.env.env_project_code_hba_microarray;
50207    var env_supported_browsers = options.env.env_supported_browsers;
50208
50209    var app_explorer_path = options.app.app_explorer_path;
50210    var app_url_for_help_path = options.app.app_url_for_help_path;
50211    var app_url_for_correlation_help_path = options.env.app_url_for_correlation_help_path ?
50212        options.env.app_url_for_correlation_help_path : options.app.app_url_for_correlation_help_path;
50213    var app_url_for_search_controller_show_action = options.app.app_url_for_search_controller_show_action;
50214
50215    //var settings = new user_settings(options.user_defaults);
50216    var settings = options.app.settings;
50217
50218    var query_result_bag = options.query;
50219
50220    //var groupings = ['donors,structures','structures,donors'];
50221    var groupings = options.app.groupings;
50222    
50223    //var resolutions = {100:'Coarse', 200:'Structures', samples:'Samples'};
50224    var resolutions = options.app.resolutions;
50225    
50226    var default_grouping = options.query.default_grouping;
50227    var colormap_set = options.app.colormap_set;
50228    var current_colormap = options.query.current_colormap;
50229    var current_sort = options.query.current_sort;
50230    var user_selection_mode = options.query.user_selection_mode;
50231    var donor_list = options.donor_list;
50232    var donor_brief_info = options.donor_brief_info;
50233    var stage_list = options.stage_list;
50234    var structure_list = options.structure_list;
50235
50236    var show_display_filters = options.show_display_filters;
50237    var display_donor_filter_type = options.display_donor_filter_type;
50238    var display_structure_filter_type = options.display_structure_filter_type;
50239    var ontology_options = settings.defaults ? (settings.defaults.ontology_options ? settings.defaults.ontology_options : {}) : {};
50240
50241    var display_donors_specified = options.display_donors_specified;
50242    var display_donors_mapping_callbacks = options.display_donors_mapping_callbacks;
50243
50244    var app_page = options.app_page;
50245    var on_correlation_search = options.on_correlation_search;
50246    var on_view_selection_click = options.on_view_selection_click;
50247    var on_view_loaded = options.on_view_loaded;
50248    var on_heatmap_click = options.on_heatmap_click;
50249    var on_get_atlas_id = options.on_get_atlas_id;
50250
50251    var show_expression_value_on_status_bar = options ? ( options.show_expression_value_on_status_bar ? options.show_expression_value_on_status_bar : false ) : false;
50252
50253    var cookies = options.cookies;
50254    var cookie_gene_grouping = cookies.cookie_gene_grouping;
50255    var cookie_diff_grouping = cookies.cookie_diff_grouping;
50256    var cookie_correlation_grouping = cookies.cookie_correlation_grouping;
50257    var cookie_gene_zoom = cookies.cookie_gene_zoom;
50258    var cookie_differential_sort_column = cookies.cookie_differential_sort_column;
50259    var cookie_correlation_sort_order = cookies.cookie_correlation_sort_order;
50260    var cookie_gene_raw_level = cookies.cookie_gene_raw_level;
50261    var cookie_gene_norm_level = cookies.cookie_gene_norm_level;
50262    var cookie_restriction = cookies.cookie_restriction;
50263    var cookie_display_donors = cookies.cookie_display_donors;
50264    var cookie_display_structures = cookies.cookie_display_structures;
50265    var cookie_row_selections = cookies.cookie_row_selections;
50266    var cookie_current_cell_selection = cookies.cookie_current_cell_selection;
50267    var cookie_correlation_domain = cookies.cookie_correlation_domain;
50268    var cookie_correlation_stage = cookies.cookie_correlation_stage;
50269    var cookie_colormap = cookies.cookie_colormap;
50270    var cookie_resolution = cookies.cookie_resolution;
50271    var cookie_correlation_donors = cookies.cookie_correlation_donors;
50272    var cookie_first_click = cookies.cookie_first_click;
50273    var cookie_brain_explorer_installed = cookies.cookie_brain_explorer_installed;
50274    var cookie_grey_browser_notified = cookies.cookie_grey_browser_notified;
50275
50276    var the_heatmap_container = $('heatmap_view_container');
50277    var the_detail_container = $('detail_view_container');
50278    var the_cart_container = $('cart_view_container');
50279
50280    var the_row_data = null;
50281    var the_image_data = null;
50282    var the_heatmap_view = null;
50283    var the_detail_view = null;
50284    var the_cart_view = null;
50285
50286    var link_options = {
50287        probe: '/microarray/gene/show/',
50288        gene: '/microarray/gene/show/',
50289        planar: '/mri_viewer?',
50290        structure: '?atlas=',
50291        atlas: '?atlas=',
50292        neuroblast: '',
50293        donor: '/mri_viewers/data?',
50294        brain_explorer: env_brain_explorer_host + '/mri_viewer/data/heatmap/probe_values'
50295    };
50296
50297    var initialized = false;
50298
50299    var _self = this;
50300
50301    var select_colormap = options.select_colormap;
50302
50303
50304    function do_busy(sender, busy) {
50305        if (the_heatmap_view)
50306            the_heatmap_view.show_busy(busy);
50307    }
50308
50309// the values generated on the server are used here to talk to the heatmap service.
50310    function do_initial_query() {
50311        if (initialized)
50312            return;
50313
50314        //if(!debug)
50315        //  $('page_block').show_busy(true);
50316
50317        var search_request = build_request(search_type);
50318        if (!search_request) {
50319            data_status_callback(DataStatus.STATUS_NO_RESULTS_FOUND, '');
50320            return;
50321        }
50322
50323        run_query(search_request);
50324    }
50325
50326    function display_initial_state(type) {
50327
50328        switch (type) {
50329            case 'search':
50330                break;
50331            case 'differential':
50332                break;
50333            case 'correlation':
50334                var data = settings.get(cookie_current_cell_selection);
50335                if (data && data.probe)
50336                    if (the_detail_view) the_detail_view.update_all(data);
50337                break;
50338        }
50339    }
50340
50341    function build_request(type) {
50342
50343        var request = {};
50344        switch (type) {
50345            case 'search':
50346                request = build_gene_search_request();
50347                break;
50348            case 'differential':
50349                request = build_diff_search_request();
50350                break;
50351            case 'correlation':
50352                request = build_correlation_search_request();
50353                break;
50354        }
50355
50356        return(request);
50357    }
50358
50359    /*    function get_current_sort(type) {
50360
50361     var sort = '';
50362     switch(type) {
50363     case 'differential':
50364     sort = settings.get(cookie_differential_sort_column);
50365     break;
50366     case 'correlation':
50367     sort = settings.get(cookie_correlation_sort_order);
50368     break;
50369     }
50370
50371     return(sort);
50372     }*/
50373
50374    /*    function get_current_grouping(type) {
50375
50376     var grouping = 0;
50377     switch(type) {
50378     case 'search':
50379     grouping = settings.get(cookie_gene_grouping);
50380     break;
50381     case 'differential':
50382     grouping = settings.get(cookie_diff_grouping);
50383     break;
50384     case 'correlation':
50385     grouping = settings.get(cookie_correlation_grouping);
50386     break;
50387     }
50388
50389     return(grouping);
50390     }*/
50391
50392    function get_cookie_for_search(search_type, cookie_type) {
50393
50394        var cookie = '';
50395
50396        switch (search_type) {
50397            case 'search':
50398                switch (cookie_type) {
50399                    case 'grouping':
50400                        cookie = cookie_gene_grouping;
50401                        break;
50402                }
50403                break;
50404            case 'differential':
50405                switch (cookie_type) {
50406                    case 'grouping':
50407                        cookie = cookie_diff_grouping;
50408                        break;
50409                    case 'column':
50410                        cookie = cookie_differential_sort_column;
50411                        break;
50412                }
50413                break;
50414            case 'correlation':
50415                switch (cookie_type) {
50416                    case 'grouping':
50417                        cookie = cookie_correlation_grouping;
50418                        break;
50419                    case 'column':
50420                        cookie = cookie_correlation_sort_order;
50421                        break;
50422                }
50423                break;
50424        }
50425
50426        return(cookie);
50427    }
50428
50429    function set_cookie_for_search(search_type, cookie_type, value) {
50430
50431        var cookie = '';
50432
50433        switch (search_type) {
50434            case 'search':
50435                break;
50436            case 'differential':
50437                switch (cookie_type) {
50438                    case 'column':
50439                        settings.set(cookie_differential_sort_column, value);
50440                        break;
50441                }
50442                break;
50443            case 'correlation':
50444                switch (cookie_type) {
50445                    case 'column':
50446                        settings.set(cookie_correlation_sort_order, value);
50447                        break;
50448                }
50449                break;
50450        }
50451
50452        return(cookie);
50453    }
50454
50455
50456    function run_query(search_request) {
50457        var dao_options;
50458
50459        switch (app_name) {
50460            case "nhp":
50461                dao_options = {trace_func: trace,
50462                    on_busy: do_busy,
50463                    //donor_base_url: '/microarray/',
50464                    //donor_key: 'microarray_donor'};
50465                    rma_dao: rma_dao};
50466                break;
50467            case "brainspan_rnaseq":
50468                dao_options = {trace_func: trace,
50469                    on_busy: do_busy,
50470                    //donor_base_url:'/rnaseq/',
50471                    //donor_key:'rnaseq_donor',
50472                    rma_dao: rma_dao};
50473                break;
50474            case "brainspan_lcm":
50475                dao_options = {trace_func: trace,
50476                    on_busy: do_busy,
50477                    //donor_base_url:'/lcm/',
50478                    //donor_key:'lcm_donor',
50479                    rma_dao: rma_dao};
50480                break;
50481            case "ahba":
50482                dao_options = {trace_func: trace,
50483                    on_busy: do_busy,
50484                    rma_dao: rma_dao};
50485                break;
50486
50487        }
50488
50489        dao_options.donor_list = donor_list;  // the donor_list is passed in from the options to heatmap_control
50490
50491        dao_options.cart_view_setup_callback = set_up_cart_view;
50492
50493        the_row_data = new heatmap_dao(null, data_status_callback, env_mri_image_server, dao_options);
50494        // successful session initialization will callback to the init_heatmap(session) function,
50495        // failure will call data_status_callback(error_status).
50496        the_row_data.initialize_session(search_type, search_request, init_heatmap);
50497        initialized = true;
50498    }
50499
50500    function build_gene_search_request() {
50501        var search_request = new Object();
50502
50503        if (query_result_bag.user_selection_mode != HM_VIEW_SELECTION_OFF) {
50504            var id_cook = settings.get(cookie_row_selections);
50505            var ids = [];
50506            for (var key in id_cook) {
50507                ids.push(new Number(key));
50508            }
50509            search_request.row_ids = ids;
50510        }
50511        else {
50512            search_request.row_ids = query_result_bag.all_ids;
50513        }
50514
50515        switch (app_name) {
50516            case "nhp":
50517            case "brainspan_rnaseq":
50518            case "brainspan_lcm":
50519                search_request.column_set = 'samples';
50520                break;
50521            case "ahba":
50522
50523                search_request.column_set = settings.get(cookie_resolution);
50524                break;
50525
50526        }
50527
50528        //search_request.display_columns = [{donors:(query_result_bag.donor_ids ? query_result_bag.donor_ids : [] ), structures:[]}];
50529        var donors_array = (display_donors_specified && display_donors_specified.length > 0) ? display_donors_specified.split(',') : [];
50530        donors_array = $j.map(donors_array, function (elem) {
50531            return parseInt(elem);
50532        });  // ensure it's a numeric array
50533
50534        // if display_donors_specified, we need to turn on display restriction, but the display_structures should be cleared
50535        // if display_donors_specified is undefined, we need to turn off display restriction/filtering, in this case, display_structures
50536        //   is ignored when generating search_request.display_columns
50537        search_request.display_columns = [
50538            {donors: donors_array, structures: []}
50539        ];
50540        search_request.group_by = groupings[settings.get(cookie_gene_grouping)];
50541        trace("gene search request: " + inspect(search_request));
50542
50543        if (search_request.row_ids.length == 0)
50544            return(null);
50545
50546        return(search_request);
50547    }
50548
50549    function build_diff_search_request() {
50550        var search_request = new Object();
50551
50552
50553        search_request.group_by = groupings[settings.get(cookie_diff_grouping)];
50554        search_request.sort_by = settings.get(cookie_differential_sort_column);
50555
50556        switch (app_name) {
50557            case "nhp":
50558                //case "brainspan_rnaseq":
50559                search_request.column_set = 'samples';
50560                var restrict = settings.get(cookie_restriction);
50561
50562                search_request.domain2 = [
50563                    {donors: query_result_bag.target_donors, structures: query_result_bag.target_domain}
50564                ];
50565                search_request.domain1 = [
50566                    {donors: query_result_bag.contrast_donors, structures: query_result_bag.contrast_domain}
50567                ];
50568                search_request.display_columns = [
50569                    {donors: (restrict ? query_result_bag.contrast_donors.concat(query_result_bag.target_donors) : []),
50570                        structures: (restrict ? query_result_bag.contrast_domain.concat(query_result_bag.target_domain) : [])}
50571                ];
50572
50573                break;
50574            case "brainspan_rnaseq":
50575                search_request.column_set = 'samples';
50576                //var restrict = settings.get(cookie_restriction);
50577
50578                search_request.domain2 = [
50579                    {donors: query_result_bag.target_donors, structures: query_result_bag.target_domain}
50580                ];
50581                search_request.domain1 = [
50582                    {donors: query_result_bag.contrast_donors, structures: query_result_bag.contrast_domain}
50583                ];
50584//          search_request.display_columns = [{donors:(restrict ? query_result_bag.contrast_donors.concat( query_result_bag.target_donors ) : []),
50585//                                            structures:(restrict ? query_result_bag.contrast_domain.concat( query_result_bag.target_domain ) : [])}];
50586                // heatmap service should be able to handle repeated donor IDs or structure IDs
50587                search_request.display_columns = [
50588                    { donors: query_result_bag.contrast_donors.concat(query_result_bag.target_donors),
50589                        structures: query_result_bag.contrast_domain.concat(query_result_bag.target_domain) }
50590                ];
50591                break;
50592            case "brainspan_lcm":
50593                search_request.column_set = 'samples';
50594                var restrict = settings.get(cookie_restriction);
50595
50596                search_request.domain2 = [
50597                    {donors: query_result_bag.donor_ids, structures: query_result_bag.target_domain}
50598                ];
50599                search_request.domain1 = [
50600                    {donors: query_result_bag.donor_ids, structures: query_result_bag.contrast_domain}
50601                ];
50602                search_request.display_columns = [
50603                    {donors: (restrict ? query_result_bag.donor_ids : []),
50604                        structures: (restrict ? query_result_bag.contrast_domain.concat(query_result_bag.target_domain) : [])}
50605                ];
50606
50607                break;
50608            case "ahba":
50609
50610                search_request.column_set = settings.get(cookie_resolution);
50611
50612                search_request.domain2 = [
50613                    {donors: donor_ids, structures: query_result_bag.target_domain}
50614                ];
50615                search_request.domain1 = [
50616                    {donors: donor_ids, structures: query_result_bag.contrast_domain}
50617                ];
50618                search_request.display_columns = [
50619                    {donors: donor_ids, structures: query_result_bag.contrast_domain.concat(query_result_bag.target_domain)}
50620                ];
50621                break;
50622        }
50623        trace("diff params: " + inspect(search_request));
50624
50625        return(search_request);
50626    }
50627
50628    function build_correlation_search_request() {
50629        var search_request = new Object();
50630        var restrict = settings.get(cookie_restriction);
50631
50632        search_request.group_by = groupings[settings.get(cookie_correlation_grouping)];
50633        search_request.sort_order = current_sort;
50634        //search_request.column_set = (app_name == 'brainspan')? 'samples':settings.get(cookie_resolution);
50635        search_request.probes = query_result_bag.seed;   // [<%= @seed %>];
50636        //search_request.domain = [{donors:[<%=@correlation_donors%>], structures:[<%=@correlation_domain%>]}];
50637        //search_request.display_columns = [{donors:(restrict ? [<%=@correlation_donors%>] : []), structures:(restrict ? [<%= @correlation_domain %>] : [])}];
50638
50639        switch (app_name) {
50640            case 'nhp':
50641                search_request.column_set = 'samples';
50642                search_request.domain = [
50643                    {donors: query_result_bag.donor_ids, structures: query_result_bag.correlation_domain}
50644                ];
50645                search_request.display_columns = [
50646//                    {donors: (restrict ? query_result_bag.donor_ids : []),
50647//                        structures: (restrict ? query_result_bag.correlation_domain : [])}
50648                    // always restrict the display columns when making the initial heatmap request
50649                    // user can trigger calls to do_display_change() to toggle the setting, and update the
50650                    // display columns
50651                    {donors: query_result_bag.donor_ids ,
50652                        structures: query_result_bag.correlation_domain }
50653                ];
50654                break;
50655
50656            case 'brainspan_rnaseq':
50657                search_request.column_set = 'samples';
50658                search_request.domain = [
50659                    {donors: query_result_bag.donor_ids,
50660                        structures: query_result_bag.correlation_domain}
50661                ];
50662                search_request.display_columns = [
50663                    {donors: query_result_bag.donor_ids,
50664                        structures: query_result_bag.correlation_domain}
50665                ];
50666
50667                break;
50668
50669            case 'brainspan_lcm':
50670                search_request.column_set = 'samples';
50671                search_request.domain = [
50672                    {donors: query_result_bag.donor_ids,
50673                        structures: query_result_bag.correlation_domain}
50674                ];
50675                search_request.display_columns = [
50676                    {donors: (restrict ? query_result_bag.donor_ids : []),
50677                        structures: (restrict ? query_result_bag.correlation_domain : [])}
50678                ];
50679
50680                break;
50681
50682            case 'ahba':
50683                search_request.column_set = settings.get(cookie_resolution);
50684                search_request.domain = [
50685                    {donors: query_result_bag.donor_ids, structures: query_result_bag.correlation_domain}
50686                ];
50687                search_request.display_columns = [
50688                    {donors: query_result_bag.donor_ids, structures: (restrict ? query_result_bag.correlation_domain : [])}
50689                ];
50690
50691                break;
50692        }
50693
50694        trace("correlation params: " + inspect(search_request));
50695
50696        return(search_request);
50697    }
50698
50699    function do_restriction_change(sender, restrict) {
50700        var params = the_row_data.get_session_params();
50701
50702        switch (app_name) {
50703            case 'nhp':
50704                if (search_type == 'differential')
50705                    params.display_columns = [
50706                        {donors: (restrict ? query_result_bag.contrast_donors.concat(query_result_bag.target_donors) : []),
50707                            structures: (restrict ? query_result_bag.contrast_domain.concat(query_result_bag.target_domain) : [])}
50708                    ];
50709
50710                else if (search_type == 'correlation')
50711                    params.display_columns = [
50712                        {donors: (restrict ? query_result_bag.correlation_donors : []),
50713                            structures: (restrict ? query_result_bag.correlation_domain : [])}
50714                    ];
50715
50716                break;
50717            case 'brainspan_rnaseq':
50718                if (search_type == 'differential')
50719                    params.display_columns = [
50720                        {donors: (restrict ? query_result_bag.contrast_donors.concat(query_result_bag.target_donors) : []),
50721                            structures: (restrict ? query_result_bag.contrast_domain.concat(query_result_bag.target_domain) : [])}
50722                    ];
50723
50724                else if (search_type == 'correlation')
50725                    params.display_columns = [
50726                        {donors: (restrict ? query_result_bag.donor_ids : []),
50727                            structures: (restrict ? query_result_bag.correlation_domain : [])}
50728                    ];
50729
50730                break;
50731
50732            case 'brainspan_lcm':
50733                if (search_type == 'differential')
50734                    params.display_columns = [
50735                        {donors: (restrict ? query_result_bag.donor_ids : []),
50736                            structures: (restrict ? query_result_bag.contrast_domain.concat(query_result_bag.target_domain) : [])}
50737                    ];
50738
50739                else if (search_type == 'correlation')
50740                    params.display_columns = [
50741                        {donors: (restrict ? query_result_bag.donor_ids : []),
50742                            structures: (restrict ? query_result_bag.correlation_domain : [])}
50743                    ];
50744
50745                break;
50746
50747            case 'ahba':
50748
50749                if (search_type == 'differential')
50750                //params.display_columns = [{donors:donor_ids, structures:(restrict ? [<@contrast_domain.join(',')  unless @target_domain.nil? >,<@target_domain.join(',')  unless @target_domain.nil?>] : [])}];
50751                    params.display_columns = [
50752                        {donors: donor_ids, structures: (restrict ? query_result_bag.contrast_domain.concat(query_result_bag.target_domain) : [])}
50753                    ];
50754                //params.display_columns = [{donors:donor_ids, structures:(restrict ? [query_result_bag.contrast_domain, query_result_bag.target_domain] : [])}];
50755                else if (search_type == 'correlation')
50756                //params.display_columns = [{donors:donor_ids, structures:(restrict ? [<@correlation_domain.join(',') unless @correlation_domain.nil%>] : [])}];
50757                    params.display_columns = [
50758                        {donors: donor_ids, structures: (restrict ? query_result_bag.correlation_domain : [])}
50759                    ];
50760
50761                break;
50762
50763        }
50764
50765        settings.set(cookie_restriction, restrict);
50766        the_row_data.update_session(params);
50767    }
50768
50769
50770    function do_display_change(sender, new_display_donors, new_display_structures, restriction_is_on) {
50771        //alert(new_display_structures);
50772        var current_restriction_state = settings.get(cookie_restriction);
50773        // persist info in cookies(localStorage)
50774        settings.set(cookie_restriction, restriction_is_on);
50775
50776        var donor_array = [], new_display_donor_str = '';
50777        // transfer the new_display_donors when it is actually "new display donor stages"
50778        if (display_donor_filter_type == 'stages' && display_donors_mapping_callbacks) {
50779            // need to apply the mappings on the passed in new_display_donors
50780            // in this case, the new_display_donors is really "new display stages"
50781            if (new_display_donors && new_display_donors.length > 0) {
50782                donor_array = display_donors_mapping_callbacks.stage_to_donor_mapping(new_dis
50782play_donors);
50783                new_display_donor_str = donor_array.join(',');   // now this is a list of donors
50784            }
50785
50786        } else {
50787            new_display_donor_str = new_display_donors;
50788        }
50789        settings.set(cookie_display_donors, new_display_donor_str);
50790
50791        settings.set(cookie_display_structures, new_display_structures);
50792
50793        if (current_restriction_state == false && restriction_is_on == false) {
50794            // when user changes the display filter setting while the "display filter" is off
50795            // we only need to save the display_donors and display_structures change
50796            // there is no need to trigger any additional request to server
50797            return;
50798        }
50799
50800        // update the data session and trigger the heatmap display change
50801        var params = the_row_data.get_session_params();
50802        var display_donors = [], display_structures = [];
50803
50804
50805        if (restriction_is_on && new_display_donor_str && new_display_donor_str.length > 0) {
50806            display_donors = new_display_donor_str.split(',');
50807            display_donors = $j.map(display_donors, function (elem) {
50808                return parseInt(elem);
50809            });  // ensure it's a numeric array
50810        }
50811
50812
50813        if (restriction_is_on && new_display_structures && new_display_structures.length > 0) {
50814            display_structures = new_display_structures.split(',');
50815            display_structures = $j.map(display_structures, function (elem) {
50816                return parseInt(elem);
50817            });
50818        }
50819
50820        params.display_columns = [
50821            {donors: display_donors, structures: display_structures}
50822        ];
50823
50824        the_row_data.update_session(params);
50825    }
50826
50827
50828    function do_grouping_change(sender, new_grouping) {
50829        var params = the_row_data.get_session_params();
50830        params.group_by = new_grouping;
50831
50832        //var new_session = the_row_data.update_session(params);
50833
50834        //trace("received new session ");
50835        //the_heatmap_view.attach_session(new_session);
50836
50837        // track the grouping as a user setting
50838        for (var i = 0; i < groupings.length; i++) {
50839            if (groupings[i] == new_grouping) {
50840                var cook = get_cookie_for_search(search_type, 'grouping');
50841                settings.set(cook, i);
50842                break;
50843            }
50844        }
50845        the_row_data.update_session(params);
50846    }
50847
50848    function do_level_change(sender, lower, upper) {
50849        var value = [(new Number(lower)).valueOf(), (new Number(upper)).valueOf()];
50850
50851        switch (app_name) {
50852            case 'brainspan_rnaseq':
50853                var setting = cookie_gene_raw_level;
50854                break;
50855            case 'nhp':
50856            case 'ahba':
50857            case 'brainspan_lcm':
50858                var map_name = settings.get(cookie_colormap);
50859                var map = select_colormap(colormap_set, map_name);
50860                var setting = map.normal ? cookie_gene_norm_level : cookie_gene_raw_level;
50861
50862                if (the_heatmap_view && sender != the_heatmap_view)
50863                    the_heatmap_view.set_colormap_range(value[0], value[1]);
50864
50865                if (the_cart_view && sender != the_cart_view)
50866                    the_cart_view.set_colormap_range(value[0], value[1]);
50867                break;
50868        }
50869
50870
50871        trace("saving this setting: " + setting + " with this value: " + value);
50872        settings.set(setting, value);
50873        if (the_detail_view) the_detail_view.update_colormap(map_name, value);
50874    }
50875
50876// This function is only found in ahba. It has not be adapted for brainspan
50877    function do_colormap_change(sender, name, normalized, upper, lower) {
50878        settings.set(cookie_colormap, name);
50879        var map = select_colormap(colormap_set, name);
50880        var setting = map.normal ? cookie_gene_norm_level : cookie_gene_raw_level;
50881        var range = settings.get(setting);
50882
50883        if (heatmap_view)
50884            the_heatmap_view.set_colormap_range(range[0], range[1]);
50885
50886        if (heatmap_view && sender != the_heatmap_view)
50887            the_heatmap_view.set_colormap(name);
50888
50889        if (app_name == 'ahba' && the_cart_view && sender != the_cart_view) {
50890            the_cart_view.set_colormap(name);
50891            the_cart_view.set_colormap_range(range[0], range[1]);
50892        }
50893
50894
50895        if (the_detail_view) the_detail_view.update_colormap(name, range);
50896    }
50897
50898
50899    function do_zoom_change(sender, new_zoom) {
50900        settings.set(cookie_gene_zoom, new_zoom);
50901    }
50902
50903
50904    function do_column_click(sender, column_label) {
50905        var column = column_label.replace(' ', '-').toLowerCase();
50906
50907        if (column == current_sort)
50908            return;
50909
50910        var params = {};
50911        if (search_type == 'differential') {
50912            current_sort = column;
50913            params.sort_by = column;
50914        }
50915        else if (search_type == 'correlation') {
50916            current_sort = toggle_correlation_sort();
50917            params.sort_order = current_sort;
50918        }
50919
50920        set_cookie_for_search(search_type, 'column', current_sort);
50921
50922        //var new_session = the_row_data.sort(params);
50923
50924        //trace("received sorted session ");
50925        //the_heatmap_view.attach_session(new_session);
50926
50927        if (search_type == 'differential') {
50928            the_heatmap_view.set_column_sort_icon((current_sort == 'fold-change' ? 'Fold Change' : 'p-value'), 'desc');
50929            the_heatmap_view.set_column_sort_icon((current_sort == 'fold-change' ? 'p-value' : 'Fold Change'), 'none');
50930        }
50931        else if (search_type == 'correlation') {
50932            the_heatmap_view.set_column_sort_icon(column, current_sort);
50933        }
50934
50935        the_row_data.sort(params);
50936    }
50937
50938    function toggle_correlation_sort() {
50939        //var cook = settings.get('<%= COOKIE_CORRELATION_SORT_ORDER %>');
50940        return(current_sort == 'desc' ? 'asc' : 'desc');
50941    }
50942
50943    function init_heatmap(new_session) {
50944
50945        try {
50946            // known session initialization errors, like invalid request params
50947            // will end up in one of these first two clauses:
50948            if (new_session === null) {
50949                alert('An error occurred processing your query');
50950                return;
50951            }
50952            if (new_session.status == undefined) {
50953                //data_status_callback(DataStatus.STATUS_SERVER_ERROR, '<%= @error %>' );
50954                data_status_callback(DataStatus.STATUS_SERVER_ERROR, query_result_bag.error);
50955                return;
50956            }
50957            if (new_session.status.id != 0) {
50958                data_status_callback(new_session.status.id, new_session.status.message);
50959                return;
50960            }
50961            if (new_session.status.id == 0 && new_session.number_of_probes == 0) {
50962                data_status_callback(DataStatus.STATUS_NO_RESULTS_FOUND, "");
50963                return;
50964            }
50965
50966            $('page_block').show_busy(true);
50967
50968            // the current grouping should be part of the session, but isn't returned
50969            // from the service, so add it here.
50970            new_session.group_by = groupings[default_grouping];
50971            // the data access components have to be initialized before the heatmap
50972            // UI component can be built.
50973            configure_data_access(new_session, current_colormap);
50974//      var detail_options = {
50975//          env_related_projects_service:env_related_projects_service,
50976//          env_project_code:env_project_code_hba_microarray,
50977//          cookies:cookies,
50978//          env_brain_explorer_host:env_brain_explorer_host,
50979//          app_explorer_path:app_explorer_path,
50980//
50981//      }
50982
50983            manage_display_filtering_from_new_request();
50984
50985            if (!hide_details_pane)
50986                the_detail_view = configure_detail_ui();
50987            $('page_block').rebusy();
50988
50989            the_heatmap_view = configure_heatmap_ui(session, the_row_data, the_image_data, search_type, colormap_set);
50990
50991            trace("got a heatmap view");
50992
50993            Event.observe(window, 'resize', do_resize);
50994            display_initial_state(search_type);
50995            $('page_block').show_busy(true);
50996        } catch (e) {
50997            $('page_block').show_busy(false);
50998            alert("Search initialization failed, please try again.");
50999            alert(e.message);
51000            trace("init failed: " + e.message);
51001        }
51002    }
51003
51004    function set_up_cart_view(){
51005
51006        if (app_name == 'ahba' && search_type == 'search' && user_selection_mode == HM_VIEW_SELECTION_THUMB ) {
51007            the_cart_view = configure_cart_ui();
51008
51009            // set the default visibility
51010            if (the_cart_view) {
51011                the_cart_view.set_visibility(user_selection_mode == HM_VIEW_SELECTION_THUMB);
51012            }
51013
51014        }
51015    }
51016
51017    /***
51018     a function managing the display filteing info persisted in localStorage
51019     ***/
51020    function manage_display_filtering_from_new_request() {
51021//        if(supplied_display_filtering_logic) {
51022//            supplied_display_filtering_logic();  // callback function in the controller that initialized this heatmap_control object
51023//            return;
51024//        }
51025
51026        // HBA should work without using the supplied_display_filtering_logic()
51027        var temp_structures, temp_donors;
51028
51029        // add a helper array function borrowed from an online discussion
51030        // in this case, the Array members will be strings like "4001", "4002", etc
51031        Array.prototype.getUnique = function () {
51032            var u = {}, a = [];
51033            for (var i = 0, l = this.length; i < l; i++) {
51034                // skip the  for duplicated key values
51035                if (u.hasOwnProperty(this[i])) {
51036                    continue;
51037                }
51038                a.push(this[i]);
51039                u[this[i]] = 1;
51040            }
51041            return a;
51042        };
51043
51044        switch (search_type) {
51045            case 'search':
51046                if (display_donors_specified) {
51047                    settings.set(cookie_restriction, true);
51048                    settings.set(cookie_display_donors, display_donors_specified);
51049                    settings.set(cookie_display_structures, '');  // need to clear out the display structure filters
51050                }
51051                else {
51052                    settings.set(cookie_restriction, false);  // turn off display restriction/filtering
51053                    // but keep the display_donors, display_structures as is
51054                }
51055                break;
51056
51057            case 'differential':
51058                settings.set(cookie_restriction, true);
51059
51060                // update the display_donors and display_structures with the query info
51061                if (app_name == "brainspan_rnaseq" || app_name == "nhp") {
51062                    temp_donors = query_result_bag.contrast_donors.concat(query_result_bag.target_donors).getUnique();
51063                    temp_donors = temp_donors.join(',');
51064                } else {
51065                    // if(app_name == "ahba") {
51066                    temp_donors = donor_ids.join(',');    // convert the id array to a comma-separated string
51067                }
51068
51069                settings.set(cookie_display_donors, temp_donors);
51070
51071                // contrast_domain may overlap with target_domain, so convert the concatenated string to an array and filter out dup values
51072                temp_structures = query_result_bag.contrast_domain.concat(query_result_bag.target_domain).getUnique();
51073                settings.set(cookie_display_structures, temp_structures.join(','));
51074                break;
51075
51076            case 'correlation':
51077                settings.set(cookie_restriction, true);
51078
51079                // update the display_donors and display_structures with the query info
51080                temp_donors = donor_ids.join(',');    // convert the id array to a comma-separated string
51081                settings.set(cookie_display_donors, temp_donors);
51082                temp_structures = query_result_bag.correlation_domain.join(',');    // convert the id array to a comma-separated string
51083                settings.set(cookie_display_structures, temp_structures);
51084
51085                break;
51086
51087            default:
51088            //case 'user_selections', etc
51089
51090            // do not change the display filtering settings here
51091        }
51092
51093
51094    }
51095
51096
51097    function configure_detail_ui() {
51098        var related_projects_img = env_related_projects_service + '';
51099        var related_projects_html = env_related_projects_service.replace("jpg", "html");
51100
51101        var map_name = settings.get(cookie_colormap);
51102        var map = select_colormap(colormap_set, map_name);
51103        var setting = map.normal ? cookie_gene_norm_level : cookie_gene_raw_level;
51104        var range = settings.get(setting);
51105
51106        var options = {
51107            related_data_html_url: related_projects_html,
51108            related_data_img_url: related_projects_img,
51109            structure_list: structure_list,
51110            stage_list: stage_list,
51111            selected_structure_list: settings.get(cookie_correlation_domain),
51112            selected_stage_list: settings.get(cookie_correlation_stage),
51113            donor_list: donor_list,
51114            donor_brief_info: donor_brief_info,
51115            selected_donor_list: settings.get(cookie_correlation_donors),
51116            brain_explorer_url: 'aibe://' + env_brain_explorer_host + '/mri_viewer/data/heatmap/probe_values?atlas=351&resolution=samples',
51117            planar_url: '/mri_viewer?',
51118            explorer_install_path: app_explorer_path,
51119            explorer_installed: settings.get(cookie_brain_explorer_installed),
51120            explorer_cookie: cookie_brain_explorer_installed,
51121            colormap_name: map_name,
51122            colormap_range: range,
51123            settings: settings
51124        };
51125
51126        var event_options = {
51127            on_link_click: do_link_click,
51128            on_correlates_click: do_correlates_click,
51129            on_correlates_help_click: do_correlates_help_click
51130        };
51131
51132        return( new info_pane(the_detail_container, options, event_options) );
51133    }
51134
51135
51136// this is probably the bit that will vary the most from page to page.
51137    function configure_heatmap_ui(session, dao, the_image_data, search_type, colormap_set) {
51138
51139
51140        // each dataset (or search type) will have its own schema describing the
51141        // columns to be displayed.
51142        var schema = get_table_schema_for_search(search_type);
51143
51144        // these events will be routed here
51145        var event_options = {
51146            on_cell_click: do_cell_click,
51147            on_restriction_change: do_restriction_change,
51148            on_display_change: do_display_change,
51149            on_grouping_change: do_grouping_change,
51150            on_level_change: do_level_change,
51151            on_colormap_change: do_colormap_change,
51152            on_zoom: do_zoom_change,
51153            on_column_click: do_column_click,
51154            on_view_selection_click: do_view_selection_click,
51155            on_clear_click: do_clear_click,
51156            on_select_click: do_select_click,
51157            //on_row_click:do_row_click,    //This event should not be handled by ahba
51158            on_resolution_change: do_resolution_change,
51159            on_visibility_change: null//do_visibility_change // toggle button disabled
51160        };
51161
51162        // the heatmap component can be configured to fit itself in between a
51163        // top & bottom html element
51164        var top_element = $('searchArea') ? $('searchArea') : $('pTabBar');
51165
51166        // which controls to show in the heatmap control bar
51167        var control_mask = HM_CONTROL_ZOOM | HM_CONTROL_COLOR_MAP | HM_CONTROL_VIEW_SELECTION_HM | HM_CONTROL_CLEAR_SELECTION;
51168        control_mask |= (search_type == 'differential' || search_type == 'correlation') ? HM_CONTROL_RESTRICT_DOMAINS : 0;
51169        if (app_name == 'ahba') {
51170            control_mask |= HM_CONTROL_RESOLUTION;
51171            control_mask |= HM_VIEW_SELECTION_HM;
51172            control_mask |= HM_CONTROL_VIEW_SELECTION_THUMB;
51173        }
51174
51175
51176        // for display restriction filtering
51177        // donor selection can have individual donors as options, or have donor age stages as options
51178        var display_donors_temp = settings.get(cookie_display_donors);
51179        if (typeof display_donors_temp != 'string') {
51180            display_donors_temp = '';
51181        }
51182        var display_donor_groups = [];  //donor_brief_info;
51183        var temp_donor_object;
51184
51185
51186        if (display_donor_filter_type == 'donors') {
51187            // construct an array display_donor_groups based on info from donor_list object, while maintaining a structure similar to array donor_brief_info
51188            for (var i = 0; i < donor_brief_info.length; i++) {
51189                temp_donor_object = {id: donor_brief_info[i].donor.id,
51190                    name: donor_brief_info[i].donor.name,
51191                    sex: donor_list[donor_brief_info[i].donor.id].sex,
51192                    age: donor_list[donor_brief_info[i].donor.id].age.replace("yrs", "y"),  // special request to change "24 yrs" to "24 y"
51193                    race_or_strain: donor_list[donor_brief_info[i].donor.id].race_or_strain
51194                }
51195                display_donor_groups[i] = {donor: temp_donor_object};
51196            }
51197        } else if (display_donor_filter_type == 'stages' && display_donors_mapping_callbacks) {
51198            display_donor_groups = stage_list;
51199            display_donors_temp = display_donors_mapping_callbacks.donor_to_stage_mapping(display_donors_temp);
51199 // mapped to stage string, such as 'II,III,IV'
51200        }
51201
51202        var heatmap_options = {
51203            fit_to_page: true,
51204            fit_upper_element_id: the_detail_container,
51205            fit_lower_element_ids: ['pFooter'],
51206            min_height: 300,
51207            table_schema: schema,
51208            show_selected: true,                 // show the selection checkboxes to the left of each row
51209            show_row_position: true,             // show
51210            colormap_set: colormap_set,          // as provided
51211            control_mask: control_mask,
51212            groupings: groupings,                // globally scoped groupins array
51213            default_grouping: default_grouping,   // the grouping string used at initialization
51214            default_restriction: settings.get(cookie_restriction),
51215            display_donors: display_donors_temp,
51216            display_structures: settings.get(cookie_display_structures),
51217            show_display_filters: show_display_filters,
51218            display_donor_groups: display_donor_groups,   // this is for constructing the donor selection UI in the status bar
51219            display_donor_filter_type: display_donor_filter_type,
51220            display_structure_groups: structure_list,  // for constructing the structure selection UI in the status bar, when the ontolgoy widget not used
51221            display_structure_filter_type: display_structure_filter_type,
51222            selections: settings.get(cookie_row_selections),
51223            user_selection_mode: user_selection_mode,   // for ahba cart view
51224            tool_tip: 'Toggle sort by structure or sort by donor', //  the default 'Toggle sort by structure or sort by donor' will be used
51225            show_expression_value_on_status_bar: show_expression_value_on_status_bar,
51226            heatmap_download_service: heatmap_download_service,
51227            download_service_data_set: download_service_data_set,
51228            ontology_options: ontology_options
51229        };
51230
51231        // set additional options for each app
51232        switch (app_name) {
51233            case 'ahba':
51234                heatmap_options.resolutions = resolutions;
51235                heatmap_options.default_resolution = settings.get(cookie_resolution);
51236
51237                //custom option for hiding the donor age display, as needed in ahba
51238                heatmap_options.hide_donor_age_display = true;
51239
51240                //pass in a special option to support cart view ( this is only in ahba now )
51241                heatmap_options.enable_cart_view = true;
51242
51243                // the heatmap is invisible initially in the hba
51244                heatmap_options.visible = user_selection_mode == HM_VIEW_SELECTION_OFF ||
51245                    user_selection_mode == HM_VIEW_SELECTION_HM;
51246
51247                event_options.on_row_click = do_row_click;
51248                break;
51249
51250            case 'nhp':
51251                heatmap_options.tool_tip = 'Toggle sort by structure or sort by age'  // nhp special case overriding
51252                event_options.on_row_click = do_row_click;   // This event will be generated by the ahba heatmap, however do_row_click should not be called in the case of ahba heatmap.
51253                break;
51254            case 'brainspan_rnaseq':
51255                event_options.on_row_click = do_row_click;   // This event will be generated by the ahba heatmap, however do_row_click should not be called in the case of ahba heatmap.
51256                break;
51257            case 'brainspan_lcm':
51258                event_options.on_row_click = do_row_click;   // This event will be generated by the ahba heatmap, however do_row_click should not be called in the case of ahba heatmap.
51259
51260                break;
51261
51262        }
51263
51264        trace("setting these selections: " + inspect(settings.get(cookie_row_selections)));
51265
51266        var view = new heatmap_view(the_heatmap_container,
51267            session.number_of_columns,
51268            the_image_data,
51269            dao,
51270            event_options,
51271            heatmap_options);
51272        return(view);
51273
51274    }
51275
51276
51277    function configure_cart_ui() {
51278        var options = {
51279            colormap_set: colormap_set,
51280            default_structure: [
51281                {id: 4008}
51282            ],
51283            host: env_mri_image_server
51284        };
51285
51286        var event_options = {
51287            pick_callback: do_cell_click,
51288            on_colormap_change: do_colormap_change,
51289            on_level_change: do_level_change,
51290            on_clear_click: do_clear_click,
51291            on_view_click: do_view_selection_click };
51292
51293        var i = 0;
51294        var donor_list_array = [];         // heatmap_cart_view expects an array
51295        for (var donor_id in donor_list) {
51296            donor_list_array[i] = {donor: donor_list[donor_id] };
51297            donor_list_array[i++].donor.id = parseInt(donor_id);
51298        }
51299
51300        return( new heatmap_cart_view(the_cart_container,
51301            get_selected_probes(),
51302            donor_list_array,
51303            options,
51304            event_options) );
51305    }
51306
51307
51308    function do_correlates_help_click() {
51309        var helpWindow = window.open(app_url_for_correlation_help_path,
51310            "ViewerHelp", "width=860,height=500,resizable=1,scrollbars=1");
51311    }
51312
51313    function configure_data_access(for_session, for_colormap) {
51314        session = for_session;
51315        the_row_data.attach_session(session);
51316        // these values will probably be the same for all datasets
51317        var DEFAULT_TILE_HEIGHT = 41;     // HACK for IT-1779
51318        var tile_height = Math.min(DEFAULT_TILE_HEIGHT, session.number_of_probes);
51319        var tile_format_options = {
51320            colormap: for_colormap.name,
51321            normalize: for_colormap.normal,
51322            pixel_height: 4,
51323            pixel_width: 1,
51324            group_by: session.group_by,
51325            expr_window: for_colormap.range
51326        };
51327        trace("building the the_image_data with these options: " + inspect(tile_format_options));
51328        the_image_data = new heatmap_tiler(env_mri_image_server, session, session.number_of_columns, DEFAULT_TILE_HEIGHT, tile_height, tile_format_options);
51329        trace("built the tiler");
51330    }
51331
51332// this function is only found in ahba
51333    /*    function select_colormap(map_set, name) {
51334     for(var i = 0; i < map_set.length; i++) {
51335     if(map_set[i].name == name) {
51336     map_set[i].selected = true;
51337     return(map_set[i]);
51338     }
51339     }
51340
51341     return(null);
51342     }*/
51343
51344
51345    function do_correlates_click(sender, info, selected_structures, selected_stages) {
51346        switch (app_name) {
51347            case 'ahba':
51348                settings.set(cookie_correlation_domain, selected_structures);
51349                settings.set(cookie_correlation_donors, selected_stages);
51350                //var path = app_url_for_search_controller_show_action;
51351                //window.location.href = path+'?type=correlation&domain='+encodeURIComponent(selected_structures)+'&seed='+info.probe.id + "&donors=" + selected_stages.join(',');
51352                //window.location.href = path+'?type=correlation&domain='+selected_structures.join(',')+'&seed='+info.probe.id;
51353
51354                // construct a state object
51355                var search_state = {
51356                    domain: selected_structures,
51357                    donors: selected_stages,    // "donors" for hba
51358                    search_type: 'correlation',
51359                    search_term: '',
51360                    seed: String(info.probe.id)
51361                };
51362                if (on_correlation_search)
51363                    on_correlation_search(search_state);
51364
51365
51366                break;
51367
51368            case 'brainspan_rnaseq':
51369                settings.set(cookie_correlation_domain, selected_structures);
51370                settings.set(cookie_correlation_stage, selected_stages);
51371//                var query = app_url_for_search_controller_show_action + '?type=correlation';
51372//                query += '&domain='+selected_structures.join(',');
51373//                query += '&stages='+selected_stages.join(',');
51374//                query += '&seed='+info.probe.id;
51375//                window.location.href = query;
51376
51377                // construct a state object
51378                var search_state = {
51379                    domain: selected_structures,
51380                    stages: selected_stages,    // "donors" for hba
51381                    search_type: 'correlation',
51382                    search_term: '',
51383                    seed: String(info.probe.id)
51384                };
51385                if (on_correlation_search)
51386                    on_correlation_search(search_state);
51387
51388                break;
51389
51390            case 'brainspan_lcm':
51391                settings.set(cookie_correlation_domain, selected_structures);
51392                //settings.set( cookie_correlation_stages, selected_stages);
51393//                var query = app_url_for_search_controller_show_action + '?type=correlation';
51394//                query += '&domain='+selected_structures.join(',');
51395//                //query += '&stages='+selected_stages.join(',');
51396//                query += '&seed='+info.probe.id;
51397//                window.location.href = query;
51398
51399                // construct a state object
51400                var search_state = {
51401                    domain: selected_structures,
51402                    donors: selected_stages,    // "donors" for hba
51403                    search_type: 'correlation',
51404                    search_term: '',
51405                    seed: String(info.probe.id)
51406                };
51407                if (on_correlation_search)
51408                    on_correlation_search(search_state);
51409
51410                break;
51411
51412
51413            case 'nhp':
51414                settings.set(cookie_correlation_domain, selected_structures);
51415                settings.set(cookie_correlation_stage, selected_stages);
51416
51417                // construct a state object
51418                var search_state = {
51419                    domain: selected_structures,
51420                    stage: selected_stages,    // ages such as E40, "0 mo"
51421                    search_type: 'correlation',
51422                    //search_term: '',
51423                    seed: String(info.probe.id)
51424                };
51425                if (on_correlation_search)
51426                    on_correlation_search(search_state);
51427
51428                break;
51429
51430        }
51431
51432    }
51433
51434
51435// this function is only used in ahba
51436    function do_resolution_change(sender, new_res) {
51437        var params = the_row_data.get_session_params();
51438        params.column_set = new_res;
51439        the_row_data.update_session(params);
51440        settings.set(cookie_resolution, new_res);
51441    }
51442
51443
51444    function do_link_click(source, info) {
51445        var the_atlas_id;
51446
51447        switch (app_name) {
51448            case 'ahba':
51449                switch (source) {
51450                    case 'gene':
51451                        window.location.href = link_options[source] + info.probe['gene-id'];
51452                        break;
51453                    case 'probe':
51454                        window.location.href = link_options[source] + info.probe['gene-id'];
51455                        break;
51456                    case 'donor':
51457                        window.location.href = link_options[source] + 'donor=' + info.donor.id;
51458                        break;
51459                    case 'structure':
51460                        the_atlas_id = on_get_atlas_id(info.donor);
51461                        window.open(atlas_viewer_service + link_options[source] + the_atlas_id + "&structure=" + info.structure[info.structure.length - 1].id);
51462                        break;
51463                    default:
51464                        alert('unknown link');
51465                }
51466                break;
51467
51468            case 'nhp':
51469                switch (source) {
51470                    case 'gene':
51471                        window.location.href = '/microarray/genes/show?id=' + info['probe']['gene-id'];
51472                        break;
51473                    case 'probe':
51474                        window.location.href = '/microarray/probe/show/' + info['probe']['id'];
51475                        break;
51476                    default:
51477                        trace('unknown link');
51478                }
51479                break;
51480
51481            case 'brainspan_rnaseq':
51482                switch (source) {
51483                    case 'gene':
51484                        window.location.href = '/rnaseq/gene/' + info.probe['id'];
51485                        break;
51486                    case 'atlas':
51487                        the_atlas_id = on_get_atlas_id(info.donor);
51488                        window.open(atlas_viewer_service + "?atlas=" + the_atlas_id + "&structure=" + info.structure[info.structure.length - 1].id);
51489                        break;
51490                    default:
51491                        trace('unknown link');
51492                }
51493                break;
51494
51495            case 'brainspan_lcm':
51496                switch (source) {
51497                    case 'gene':
51498                    case 'probe':
51499                        window.location.href = '/lcm/gene/' + info.probe['gene-id']
51500                        break;
51501                    case 'donor':
51502                        window.location.href = link_options[source] + 'donor=' + info.donor.id;
51503                        break;
51504                    case 'atlas':
51505                        the_atlas_id = on_get_atlas_id(info.donor);
51506                        window.open(atlas_viewer_service + "?atlas=" + the_atlas_id + "&structure=" + info.structure[info.structure.length - 1].id);         // atlas for human, 21 pcw
51507                        break;
51508                    default:
51509                        alert('unknown link');
51510                }
51511                break;
51512
51513        }
51514
51515    }
51516
51517    function do_clear_click(sender) {
51518
51519        if (!confirm("Clear all the selections?"))
51520            return;
51521
51522        settings.set(cookie_row_selections, {});
51523
51524        if (sender == the_heatmap_view && the_heatmap_view)
51525            the_heatmap_view.set_selections({});
51526
51527        if (app_name == "ahba" && the_cart_view)
51528            the_cart_view.set_probe_list([]);
51529    }
51530
51531    function do_view_selection_click(sender, mode) {
51532        switch (app_name)  // we can use one app url variable to hold a valid server URL path based on app_name
51533        {
51534            case 'ahba':
51535                mode = mode || HM_VIEW_SELECTION_HM;
51536                //window.location.href = app_url_for_search_controller_show_action +
51537                //    '?type=user_selections&user_selection_mode='+mode;
51538                if (on_view_selection_click) {
51539                    var search_state = {
51540                        search_type: 'user_selections',
51541                        user_selection_mode: mode
51542                    };
51543                    on_view_selection_click(search_state);  // calls the search method outside of the top heatmap component
51544                }
51545                else {   // on the gene page, we need to load a different page, which is the search page
51546                    window.location.href = app_url_for_search_controller_show_action +
51547                        '?search_type=user_selections&user_selection_mode=' + mode;
51548
51549                }
51550                break;
51551
51552            case 'brainspan_rnaseq':
51553                window.location.href = app_url_for_search_controller_show_action + '?search_type=user_selections';
51554                break;
51555
51556            case 'brainspan_lcm':
51557                window.location.href = app_url_for_search_controller_show_action + '?search_type=user_selections';
51558                break;
51559
51560            case 'nhp':
51561                window.location.href = app_url_for_search_controller_show_action + '?search_type=user_selections';
51562                break;
51563
51564        }
51565    }
51566
51567
51568    function do_select_click(sender, selected_id, selected_state) {
51569
51570        var selections = settings.get(cookie_row_selections);
51571        if (selected_state)
51572            selections[selected_id] = true;
51573        else
51574            delete selections[selected_id];
51575
51576        settings.set(cookie_row_selections, selections);
51577        trace("just set selections as: " + settings.get(cookie_row_selections));
51578    }
51579
51580    function do_cell_click(sender, data) {
51581        do_cover_click();
51582
51583        if (app_name == 'ahba') {
51584
51585            // populate the donor with any missing information
51586            if (!data.donor.color) {
51587                the_row_data.get_donor_column_info(data.donor);
51588            }
51589        }
51590
51591
51592        settings.set(cookie_current_cell_selection, data);
51593        settings.set(cookie_first_click, false);
51594        if (the_detail_view) the_detail_view.update_all(data);
51595
51596
51597        if (app_name == 'ahba' && the_cart_view)
51598            the_cart_view.update_data(data);
51599
51600        if (on_heatmap_click)       // for the gene page's info widget
51601            on_heatmap_click(data.probe.id);  // the gene info widget only needs the probe id
51602
51603    }
51604
51605    function do_row_click(sender, row, data) {
51606        do_cover_click();
51607        settings.set(cookie_first_click, false);
51608        settings.set(cookie_current_cell_selection, data);
51609        if (the_detail_view) the_detail_view.update_all(data);
51610
51611        if (on_heatmap_click)     // for the gene page's info widget
51612            on_heatmap_click(data.probe.id);  // the gene info widget only needs the probe id
51613
51614    }
51615
51616    function do_visibility_change(sender) {
51617        if (app_name == 'ahba') {
51618            visibility = the_heatmap_view.get_visibility();
51619
51620            selections = settings.get(cookie_row_selections);
51621            probes = get_selected_probes();
51622
51623            if (sender == the_cart_view) {
51624                the_heatmap_view.set_visibility(!visibility);
51625                the_cart_view.set_visibility(visibility);
51626            } else if (sender == the_heatmap_view) {
51627                the_cart_view.set_visibility(!visibility);
51628                the_cart_view.set_probe_list(probes);
51629            }
51630        }
51631    }
51632
51633    function do_view_loaded() {
51634        var last_zoom = settings.get(cookie_gene_zoom);
51635        the_heatmap_view.set_zoom(last_zoom);
51636        $('page_block').show_busy(false);
51637
51638        var never_clicked = settings.get(cookie_first_click);
51639        if (never_clicked)
51640            show_here_and_there();
51641
51642        if (on_view_loaded)
51643            on_view_loaded();
51644    }
51645
51646    function data_status_callback(status, msg) {
51647        switch (status) {
51648            case DataStatus.STATUS_DATA_READY:
51649                the_heatmap_view.init();
51650                do_view_loaded();
51651                break;
51652            case DataStatus.STATUS_SESSION_READY:
51653                if (the_heatmap_view)
51654                    the_heatmap_view.attach_session(the_row_data.session);
51655                trace("status: " + status + "; msg: " + msg);
51656                break;
51657            case DataStatus.STATUS_NO_RESULTS_FOUND:
51658                show_error('No results matched your query');
51659                break;
51660            case DataStatus.STATUS_SESSION_EXPIRED:
51661                show_error('Your session has expired due to inactivity.  Please refresh the page to continue');
51662                break;
51663            case DataStatus.STATUS_CELL_DATA_FAIL:
51664                show_error("Error retrieving data: " + msg);
51665                break;
51666            case DataStatus.STATUS_REQUEST_INTERRUPTED:
51667                show_error('Communication with the server was interrupted.  Try re-running your search, or use your browser\'s "Reload" or "Refresh" button to continue');
51668                break;
51669            case DataStatus.STATUS_SESSION_NOT_FOUND:
51670                show_error('Communication with the server was interrupted.  Try re-running your search, or use your browser\'s "Reload" or "Refresh" button to continue');
51671                break;
51672            default:
51673                show_error(msg);
51674        }
51675
51676        $('page_block').show_busy(false);
51677
51678        if (on_view_loaded)
51679            on_view_loaded();
51680
51681    }
51682
51683    function show_error(error) {
51684
51685        var content = "<div class='ui-widget'>";
51686        switch (app_name) {
51687            case 'ahba':
51688                content += "<div class='ui-state-highlight ui-corner-all' style='padding: 0 .7em; width:50%; margin:auto; margin-top: 100px;border-color:#99b !important; background-color:#f7f7ff !important;'>";
51689                content += "<p><div style='margin:auto;width:80%'><span class='ui-icon ui-icon-info' style='float: left; margin-right: .3em;'></span>";
51690                content += error + '.';
51691                content += "</p></div></div></div>";
51692                break;
51693
51694            case 'nhp':
51695            case 'brainspan_rnaseq':
51696            case 'brainspan_lcm':
51697                content += "<div class='ui-state-highlight ui-corner-all' style='padding: 0 .7em; padding-bottom:20px;width:50%; margin:auto; margin-top: 100px;border-color:#99b !important; background-color:#f7f7ff !important;'>";
51698                content += "<p><div style='margin:auto;width:80%'><span class='ui-icon ui-icon-info' style='float: left; margin-right: .3em;'></span>";
51699                content += error + '.';
51700                content += "</p></div></div></div>";
51701                break;
51702
51703        }
51704
51705        the_heatmap_container.update(content);
51706    }
51707
51708    function get_table_schema_for_search(type) {
51709
51710        var table_schema = null;
51711
51712        switch (app_name) {
51713            case 'ahba':
51714                switch (type) {
51715                    // this is redundant; the default schema again
51716                    case 'search':
51717                        table_schema = [
51718                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'gene-id'},
51719                            {id: 'probe_name', name: 'name', label: 'Probe Name', assoc_id: 'id'}
51720                        ];
51721                        break;
51722                    case 'correlation':
51723                        table_schema = [
51724                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'gene-id'},
51725                            {id: 'r', name: 'r', label: 'r', assoc_id: null, sortable: true, sort: current_sort}
51726                        ];
51727                        trace("r: " + inspect({id: 'r', name: 'r', label: 'r', assoc_id: null, sortable: true, sort: current_sort}));
51728                        break;
51729                    case 'differential':
51730                        table_schema = [
51731                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'id', sortable: false},
51732                            {id: 'p', name: 'p', label: 'p-value', assoc_id: null, sortable: true, sort: (current_sort == 'p-value' ? 'desc' : 'none')},
51733                            {id: 'fold_change', name: 'fold-change', label: 'Fold Change', assoc_id: null, sortable: true, sort: (current_sort == 'fold-change' ? 'desc' : 'none')}
51734                        ];
51735                        break;
51736                    default:
51737                        table_schema = [
51738                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'id'},
51739                            {id: 'probe_name', name: 'name', label: 'Probe Name', assoc_id: 'id'}
51740                        ];
51741                }
51742
51743                break;
51744
51745            case 'nhp':
51746
51747                switch (type) {
51748                    // this is redundant; the default schema again
51749                    case 'search':
51750                    case 'user_selections':
51751                        table_schema = [
51752                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'gene-id', alternate_name: 'name'},
51753                            {id: 'probe_name', name: 'name', label: 'Probe Name', assoc_id: 'id'}
51754                        ];
51755                        break;
51756                    case 'correlation':
51757                        table_schema = [
51758                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'gene-id', alternate_name: 'name'},
51759                            {id: 'r', name: 'r', label: 'r', assoc_id: null, sortable: true, sort: current_sort}
51760                        ];
51761                        trace("r: " + inspect({id: 'r', name: 'r', label: 'r', assoc_id: null, sortable: true, sort: current_sort}));
51762                        break;
51763                    case 'differential':
51764                        table_schema = [
51765                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'id', alternate_name: 'name', sortable: false},
51766                            {id: 'p', name: 'p', label: 'p-value', assoc_id: null, sortable: true, sort: (current_sort == 'p-value' ? 'desc' : 'none')},
51767                            {id: 'fold_change', name: 'fold-change', label: 'Fold Change', assoc_id: null, sortable: true, sort: (current_sort == 'fold-change' ? 'desc' : 'none')}
51768                        ];
51769                        break;
51770                    default:
51771                        table_schema = [
51772                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'id'},
51773                            {id: 'probe_name', name: 'name', label: 'Probe Name', assoc_id: 'id'}
51774                        ];
51775                }
51776                break;
51777
51778            case 'brainspan_rnaseq':
51779
51780                switch (type) {
51781                    // this is redundant; the default schema again
51782                    case 'microarray':
51783                        table_schema = [
51784                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'gene-id'},
51785                            {id: 'probe_name', name: 'name', label: 'Probe Name', assoc_id: 'id'}
51786                        ];
51787                        break;
51788                    case 'correlation':
51789                        table_schema = [
51790                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'gene-id'},
51791                            {id: 'r', name: 'r', label: 'r', assoc_id: null, sortable: true, sort: current_sort}
51792                        ];
51793                        trace("r: " + inspect({id: 'r', name: 'r', label: 'r', assoc_id: null, sortable: true, sort: current_sort}));
51794                        break;
51795                    case 'differential':
51796                        table_schema = [
51797                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'id', sortable: false},
51798                            {id: 'p', name: 'p', label: 'p-value', assoc_id: null, sortable: true, sort: (current_sort == 'p-value' ? 'desc' : 'none')},
51799                            {id: 'fold_change', name: 'fold-change', label: 'Fold Change', assoc_id: null, sortable: true, sort: (current_sort == 'fold-change' ? 'desc' : 'none')}
51800                        ];
51801                        break;
51802                    default:
51803                        table_schema = [
51804                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'id'},
51805                            {id: 'probe_name', name: 'name', label: 'Ensembl ID', assoc_id: 'id'}
51806                        ];
51807                }
51808                break;
51809
51810            case 'brainspan_lcm':
51811                switch (type) {
51812                    // this is redundant; the default schema again
51813                    case 'search':
51814                        table_schema = [
51815                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'gene-id'},
51816                            {id: 'probe_name', name: 'name', label: 'Probe Name', assoc_id: 'id'}
51817                        ];
51818                        break;
51819                    case 'correlation':
51820                        table_schema = [
51821                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'gene-id'},
51822                            {id: 'r', name: 'r', label: 'r', assoc_id: null, sortable: true, sort: current_sort}
51823                        ];
51824                        trace("r: " + inspect({id: 'r', name: 'r', label: 'r', assoc_id: null, sortable: true, sort: current_sort}));
51825                        break;
51826                    case 'differential':
51827                        table_schema = [
51828                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'id', sortable: false},
51829                            {id: 'p', name: 'p', label: 'p-value', assoc_id: null, sortable: true, sort: (current_sort == 'p-value' ? 'desc' : 'none')},
51830                            {id: 'fold_change', name: 'fold-change', label: 'Fold Change', assoc_id: null, sortable: true, sort: (current_sort == 'fold-change' ? 'desc' : 'none')}
51831                        ];
51832                        break;
51833                    default:
51834                        table_schema = [
51835                            {id: 'gene_symbol', name: 'gene-symbol', label: 'Gene Symbol', assoc_id: 'id'},
51836                            {id: 'probe_name', name: 'name', label: 'Probe Name', assoc_id: 'id'}
51837                        ];
51838                }
51839
51840                break;
51841        }
51842
51843        return(table_schema);
51844    }
51845
51846    function do_resize() {
51847        the_heatmap_view.resize();
51848        if (the_cart_view)
51849            the_cart_view.resize();
51850        // $('page_block').rebusy();      // refresh with the updated ahba code
51851    }
51852
51853    function show_not_supported() {
51854        show_error('Your browser is not supported in this portion of the \n\
51855      application.  <p></p>To see our list of supported browsers and platforms, please \n\
51856      visit our <a href="/help.html/#supported" style="color:#00f">Help</a> \n\
51857      page');
51858    }
51859
51860    function show_unknown_support() {
51861        $j('#browser_warning').dialog({width: 410,
51862            height: 260,
51863            modal: true,
51864            buttons: {"Ok": function () {
51865                $('page_block').show_busy(true);
51866                do_initial_query();
51867                $j(this).dialog("close");
51868            } }});
51869    }
51870
51871    function get_selected_probes() {
51872        ids = settings.get(cookie_row_selections);
51873        all_probes = the_row_data.get_probe_list(0, the_row_data.number_of_rows);
51874        selected_probes = [];
51875        for (var i = 0; i < all_probes.length; i++) {
51876            for (var id in ids) {
51877                if (ids[id] && parseInt(id) == all_probes[i].id)
51878                    selected_probes.push(all_probes[i]);
51879            }
51880        }
51881
51882        return selected_probes;
51883    };
51884
51885    this.get_heatmap_dao = function () {
51886        return the_row_data;
51887    }
51888
51889    this.init_page = function () {
51890        do_initial_query();
51891    }
51892
51893    function do_cover_click() {
51894        var img = $('here_and_there_img');
51895        img.hide();
51896    }
51897
51898    function show_here_and_there() {
51899        var img = $('here_and_there_img');
51900        var img_dims = img.getDimensions();
51901        var cont = $('heatmap_view_container');
51902        var cont_dims = cont.getDimensions();
51903
51904        var left = (cont_dims.width / 2) - (img_dims.width / 2) - 60;
51905        img.clonePosition($('heatmap_view_container'), {setWidth: false,
51906            setHeight: false,
51907            offsetTop: -96,
51908            offsetLeft: left});
51909
51910        img.observe('click', do_cover_click);
51911        img.show();
51912        $j('#here_and_there_img').fadeOut(10000);
51913    }
51914}
51915;
51916
51917
51918
51919// many of the heatmap_dao methods run asynchronously and require
51920// a callback parameter.  All callbacks will be invoked with an hdResponse
51921// object as the sole parameter.  If the asynch method succeeded the
51922// hdResponse "success" property will be true and the payload will be
51923// the method-specific data requested. If "success" is false, the payload
51924// might be null, might be an error string.
51925function hdResponse(success, payload)
51926{
51927    this.success = success;
51928    this.payload = payload;
51929}
51930
51931var DataStatus = {
51932
51933    STATUS_DATA_READY: 0,
51934    STATUS_SESSION_READY: 1,
51935    STATUS_DONOR_NOT_FOUND: 2,
51936    STATUS_INVALID_RESOLUTION: 3,
51937    STATUS_STRUCTURE_NOT_FOUND: 4,
51938    STATUS_TOO_MANY_COLUMNS: 5,
51939    STATUS_SERVER_ERROR: 6,
51940    STATUS_SESSION_NOT_FOUND: 7,
51941    STATUS_INVALID_COLUMN_GROUPING: 8,
51942    STATUS_INVALID_SORT_ORDER: 9,
51943    STATUS_INVALID_SORT_ATTRIBUTE: 10,
51944    STATUS_MISSING_SEED_PROBE: 11,
51945    STATUS_MISSING_SEARCH_DOMAIN: 12,
51946    STATUS_CANNOT_SORT: 13,
51947    STATUS_NOT_ENOUGH_SAMPLES: 16,
51948    STATUS_INVALID_START_ROW: 17,
51949    STATUS_NO_RESULTS_FOUND: 18,
51950    STATUS_PROBE_ID_FAIL: -2,
51951    STATUS_PROBE_META_FAIL: -3,
51952    STATUS_COLUMN_FAIL: -4,
51953    STATUS_CELL_DATA_FAIL: -5,
51954    STATUS_SESSION_EXPIRED: -6,
51955    STATUS_DATA_ERROR: -7,
51956    STATUS_SERVICE_400_ERROR_AS_HTML: -13,
51957    STATUS_REQUEST_INTERRUPTED: -20,
51958    HTTP_ERROR:400
51959}
51960
51961function heatmap_dao(session, status_callback, data_engine_base_url, options)
51962{
51963
51964// public properties
51965
51966    // a session is an anonymous object containing these fields:
51967    // session_id, number_of_probes, number_of_columns, status.
51968    //
51969    // status is an anonymous object contining these fields:
51970    // id, message
51971    this.session = session;
51972    this.number_of_rows = 0;
51973    this.number_of_columns = 0;
51974
51975    // errors that occur in the context of an ajax call should be handled
51976    // by the individual methods.  This is a catchall to notifiy the owning
51977    // process of initialization and other out-of-method events and errors.
51978    this.status_callback = status_callback;
51979
51980    // the host:port/service prefix for communicating with the data engine.
51981    // Assumes no trailing slash.
51982    this.data_engine_base_url = data_engine_base_url;
51983
51984    this.trace_function = options ? options.trace_func : null;  // assign a funtion(msg) to this to handle debug output
51985    this.on_busy = options ? options.on_busy : null;
51986    this.donor_base_url = options ? options.donor_base_url ? options.donor_base_url : '' : '';
51987    //this.donor_key = options ? options.donor_key ? options.donor_key : 'donor' : 'donor';
51988
51989    this.rma_dao = options? options.rma_dao ? options.rma_dao : null : null;
51990
51991    var cart_view_setup_callback = options ? (options.cart_view_setup_callback ? options.cart_view_setup_callback : null ) : null;
51992
51993// public methods
51994
51995    // initialize the session with a query.
51996    // type: the type of query to perform.  Currently 'search' or 'differenential'
51997    //       or 'correlation'
51998    // params: the params appropriate to the given search type.
51999    // callback: a function to call back into with the response from
52000    // the initialization attempt.
52001    this.initialize_session = function(type, params, callback) {
52002       do_initalize_session(type, params, callback);
52003    }
52004
52005    this.get_session_params = function() {
52006        return(request_params);
52007    }
52008
52009    this.update_session = function(params) {
52010        do_update_session(params);
52011        this.session.group_by = params.group_by;
52012        return(this.session);
52013    }
52014
52015
52016    this.sort = function(params) {
52017        do_sort(params);
52018
52019        return(this.session);
52020    }
52021
52022    // probably some initialization here,
52023    // otherwise this goes away and we can
52024    // just use the session property.
52025    this.attach_session = function(session, keep_probes) {
52026        //trace("Attaching Session:\nStatus: " + inspect(session.status) + "\nSession: " + inspect(session) );
52027        heartbeat(false);
52028
52029        this.session = session;
52030        if(this.session === null || this.session.status.id != 0) {
52031            return;
52032        }
52033        this.number_of_rows = this.session.number_of_probes;
52034        this.number_of_columns = this.session.number_of_columns;
52035
52036        QUERY_URL = this.data_engine_base_url + "heatmap/query/" + this.session.session_id;
52037        ROWS_URL = this.data_engine_base_url + "heatmap/probes/" + this.session.session_id + "?showDetail=1";
52038        COLUMNS_URL = this.data_engine_base_url + "heatmap/columns/" + this.session.session_id;
52039        SORT_URL = this.data_engine_base_url + "heatmap/sort_probes/" + this.session.session_id;
52040        INFO_URL = this.data_engine_base_url + "heatmap/session_info/" + this.session.session_id;
52041
52042        // get all the probe IDs from the data engine
52043        // get the probe meta data from the web app
52044        //
52045        // a fancier scheme would have us getting & caching
52046        // the probe metadata only as it is requested.
52047        if(!keep_probes) {
52048            // prevent race condition w/Chrome browser when doing probe column sorting in Differential view
52049            fetch_probe_list(attach_session_callback, true); // ASYNC Ajax call
52050        }
52051        else
52052            attach_session_finished();
52053    }
52054
52055    /**
52056     * Callback to fetch_probe_list() when called within attach_session()
52057     *
52058     * @param response
52059     * @param async same value as used in fetch_probe_list 2nd arg, should be 'true'
52060     */
52061    function attach_session_callback(response, async) {
52062        receive_probe_id_list(response, async);
52063
52064        // we should have received the probe info needed by the cart view now
52065        if(cart_view_setup_callback)
52066            cart_view_setup_callback();
52067
52068        attach_session_finished();
52069    }
52070
52071    /**
52072     * final actions for attach_session() actions.
52073     * Placed in this function to ensure completion of fetch_probe_list() before the asynch call to
52074     * fetch_column_list().
52075     */
52076    function attach_session_finished() {
52077        fetch_column_list(receive_column_list);
52078
52079        session_time = new Date().getTime();
52080        heartbeat(true);
52081        raise_busy(false);
52082
52083        if(self.status_callback)
52084            self.status_callback(DataStatus.STATUS_SESSION_READY, 'SESSION READY');
52085    }
52086
52087    this.row_count = function() {
52088        return(this.session.number_of_probes);
52089    }
52090
52091    this.column_count = function() {
52092        return(this.session.number_of_columns);
52093    }
52094
52095    this.get_status = function() {
52096        return(this.session.status);
52097    }
52098
52099    // returns an array of probes and associated metadata
52100    // corresponding to the current data set.
52101    this.get_probe_list = function(start_row, num_rows) {
52102        return(fetch_row_set(start_row, num_rows));
52103    }
52104
52105    // returns an array of column metadata.  Each element in the array is an
52106    // anonymous object having these fields:
52107    // donor, structure_id, name, abbreviation, sample_id.
52108    // NOTE that sample_id will only be defined if the current
52109    // column set consists of samples.
52110    this.get_column_list = function() {
52111        return(column_list);
52112    }
52113
52114    this.get_donor_list = function() {
52115        return(donor_list);
52116    }
52117
52118    // this is detailed information for the column, row.  The
52119    // return value will be an object containing raw & normalized expression
52120    // values and an array of structures describing the ontological position
52121    // of the given column (sample or structure).  THe fields are named:
52122    // expression_level, z-score, structure, where structure is an array containing:
52123    // id, name, abbreviation.
52124    //
52125    // column & row are column_id (structure or sample, depending on
52126    // the data set) and probe_id.
52127    //
52128    // ??
52129    // As a convenience, should we return the probe information
52130    // for the given row as well?
52131    this.get_cell_data_detail = function(column, row, callback) {
52132        fetch_cell_data(column, row, callback);
52133    }
52134
52135    // column & row are grid coordinates, that is indexes into the
52136    // set of columns and rows (probes).
52137    this.get_cell_data_brief = function(column, row) {
52138        var info = new Object();
52139        info.column = column_list[column];
52140        info.probe = probe_id_list[row];
52141        return(info);
52142    }
52143
52144	this.get_donor_column_info = function(donor,column) {
52145		var more_donor = null;
52146		if (column) {
52147			more_donor = column_list[column].donor;
52148		} else {
52149
52150			for (var i=0; i<column_list.length; i++) {
52151				var col = column_list[i];
52152				if (col.donor.id == donor.id) {
52153					more_donor = col.donor;
52154					break;
52155				}
52156			}
52157		}
52158
52159		donor.color = more_donor.color;
52160		donor.abbreviation = more_donor.abbreviation;
52161	}
52162
52163
52164// private properties
52165    var self = this;
52166    var INITIALIZE_URL = self.data_engine_base_url + "heatmap/initialize";
52167    var DIFFERENTIAL_URL = self.data_engine_base_url + "search/differential_probes";
52168    var CORRELATION_URL = self.data_engine_base_url + "search/correlated_probes";
52169    var DONORS_URL = self.donor_base_url + "donors.json";
52170    var QUERY_URL = "";
52171    var ROWS_URL = "";
52172    var COLUMNS_URL = "";
52173    var SORT_URL="";
52174    var INFO_URL="";
52175
52176    var probe_id_list = [];
52177    var column_list = null;
52178    var donor_list = options.donor_list;
52179
52180    var probes_ready = false;
52181    var columns_ready = false;
52182    var donors_ready = false;
52183    var ready = false;
52184    var required_rows = 0;
52185    var heartbeat_id = null;
52186
52187    var request_params = {};
52188    var session_time = null;
52189
52190    var updating = false;
52191    var sorting = false;
52192
52193
52194
52195// private methods
52196
52197    function heartbeat(start) {
52198        if(!start && heartbeat_id) {
52199            clearInterval(heartbeat_id);
52200        }
52201        else if(start) {
52202            heartbeat_id = setInterval(ping_session, 60000);
52203        }
52204    }
52205
52206    function ping_session() {
52207        var time = new Date().getTime();
52208        trace("keep-alive ping, session life time = " + (time - session_time) + " milliseconds.");
52209        new Ajax.Request(INFO_URL + '?req=' + time,
52210        {
52211            method:'get',
52212            contentType:"application/json",
52213            onSuccess:function(response) {
52214             if(response.status != 200) {
52215                 trace("info ping failed: " + inspect(response));
52216                 ping_failure(response);
52217             }
52218             else {
52219                trace("ping success: " + inspect(response.responseJSON));
52220             }
52221            },
52222            onFailure:function(response) {
52223             ping_failure(response);
52224           }
52225        });
52226    }
52227
52228    function ping_failure(response) {
52229        trace("uh oh.  A ping failed: " + inspect(response));
52230    }
52231
52232    function fetch_row_set(start_row, num_rows) {
52233        // boundary check
52234
52235        start_row = start_row < 0 ? 0 : start_row;
52236        start_row = start_row >= self.number_of_rows ? self.number_of_rows - 1 : start_row;
52237        num_rows = num_rows + start_row > self.number_of_rows ? self.number_of_rows - start_row : num_rows;
52238
52239        required_rows = start_row + num_rows;
52240        while(((start_row + num_rows) > probe_id_list.length)) {
52241            trace("fetching more data, " + required_rows + " required of " + self.number_of_rows + " available, " + probe_id_list.length + " already onboard");
52242            fetch_probe_list(receive_probe_id_list, false);
52243            raise_busy(true);
52244        }
52245
52246        var the_data = probe_id_list.slice(start_row, start_row+num_rows);
52247        //trace("the first row: " + inspect(the_data[0]));
52248        raise_busy(false);
52249        return(the_data);
52250    }
52251
52252
52253    function request_failure(response) {
52254        trace("request failure: " + inspect(response));
52255        raise_busy(false);
52256        if(!response) {
52257            if(self.status_callback)
52258                self.status_callback(DataStatus.STATUS_DATA_ERROR, 'Unknown Data Error');
52259
52260            return;
52261        }
52262
52263        // an HTTP response status of 0 generally means that the
52264        // response was returned without any headers.  This could happen behind
52265        // a firewall, for instance.  We don't really expect a firewall in this
52266        // situation, because our users have to be able to get to our web page
52267        // before the app starts making these ajax requests.
52268        //
52269        // Also possible is that the request has been interupted, as could happen
52270        // if the user leaves the page before the response is received.
52271        //
52272        // perhaps also possible if the user generates so many requests that they
52273        // interrupt each other.  Which is the assumption I'm going to try making
52274        // here.
52275        //
52276        // Also possible that the client & service are running on different
52277        // machines, and the error message is filtered out as a security risk.
52278        if(response.status == 0) {
52279            var err_text = "Communication with the server failed or was interrupted";
52280            var err_code = DataStatus.STATUS_REQUEST_INTERRUPTED;
52281            if(self.status_callback)
52282                self.status_callback(err_code, err_text);
52283
52284            return;
52285        }
52286
52287        // a response was actually received; 500 errors are returned by the
52288        // service, other HTTP status errors can be returned as well.
52289        if(self.status_callback) {
52290            try {
52291                switch(response.status) {
52292                    case 500:
52293                        var error = eval("(" + response.responseText + ")");
52294                        self.status_callback(error.status.id, error.status.message);
52295                        break;
52296                    default:
52297                        self.status_callback(DataStatus.HTTP_ERROR,
52298                            'There was an error communicating with the server: ' +
52299                            response.status + ' ' + response.statusText);
52300                        break;
52301                }
52302            } catch(e) {
52303                self.status_callback(DataStatus.STATUS_DATA_ERROR, 'Unknown Data Error: ' + e.message);
52304            }
52305        }
52306    }
52307
52308    function raise_busy(busy) {
52309        if(self.on_busy)
52310            self.on_busy(self, busy);
52311    }
52312
52313    function do_update_session(params) {
52314        if(updating)
52315            return;
52316
52317        try {
52318            updating = true;
52319            raise_busy(true);
52320
52321    //        trace("the last search was this type: " +   request_params.search_type);
52322            var type = request_params.search_type;
52323            request_params = params;
52324            request_params.search_type = type;
52325
52326            var request_body = new Object();
52327            switch(request_params.search_type) {
52328                case 'search':
52329                    request_body.resolution = params.column_set;
52330                    request_body.display_columns = params.display_columns;
52331                    request_body.group_by = params.group_by;
52332                    break;
52333                case 'differential':
52334                    request_body.group_by = params.group_by;
52335                    request_body.sort_by = params.sort_by;
52336                    request_body.resolution = params.column_set;
52337                    request_body.domain1 = params.domain1;
52338                    request_body.domain2 = params.domain2;
52339                    request_body.display_columns = params.display_columns;
52340                    if(request_body.donors)
52341                        delete request_body.donors;
52342                    break;
52343                case 'correlation':
52344                    request_body.group_by = params.group_by;
52345                    request_body.resolution = params.column_set;
52346                    request_body.display_columns = params.display_columns;
52347                    break;
52348            }
52349
52350            //trace("updating with this url: " + INITIALIZE_URL + "/" + self.session.session_id);
52351            //trace("and this request: " + inspect(request_body));
52352
52353
52354            new Ajax.Request(INITIALIZE_URL + "/" + self.session.session_id,
52355            {
52356                method:'post',
52357                contentType:"application/json",
52358                postBody: $H(request_body).toJSON(),
52359                asynchronous:true,
52360                onSuccess:function(response) {
52361                 if(response.status != 200) {
52362                     trace("update failed: " + inspect(response));
52363                     request_failure(response);
52364                 }
52365                 else {
52366                    //trace("update success: " + inspect(response));
52367                    self.attach_session(response.responseJSON, true);
52368                 }
52369                 updating = false;
52370                },
52371                onFailure:function(response) {
52372                 request_failure(response);
52373                 updating = false;
52374               }
52375            });
52376        } catch(e) {
52377            trace("exception doing an update request: " + e.message);
52378            updating = false;
52379        }
52380    }
52381
52382
52383    function do_sort(params) {
52384        if(sorting)
52385            return;
52386
52387        try {
52388            sorting = true;
52389            raise_busy(true);
52390
52391            var type = request_params.search_type;
52392            probe_id_list = [];
52393
52394            var request_body = new Object();
52395            switch(request_params.search_type) {
52396                case 'differential':
52397                    request_body.sort_by = params.sort_by;
52398                    break;
52399                case 'correlation':
52400                    request_body.sort_order = params.sort_order;
52401                    break;
52402            }
52403
52404            trace("updating with this url: " + SORT_URL + "/" + self.session.session_id);
52405            trace("and this request: " + inspect(request_body));
52406
52407            new Ajax.Request(SORT_URL + "/" + self.session.session_id,
52408            {
52409                method:'post',
52410                contentType:"application/json",
52411                postBody: $H(request_body).toJSON(),
52412                asynchronous:true,
52413                onSuccess:function(response) {
52414                 if(response.status != 200) {
52415                     trace("sort failed: " + inspect(response));
52416                     request_failure(response);
52417                 }
52418                 else {
52419                    trace("sort success: " + inspect(response));
52420                    self.attach_session(response.responseJSON);
52421                 }
52422                 sorting = false;
52423                },
52424                onFailure:function(response) {
52425                 request_failure(response);
52426                 sorting = false;
52427               }
52428            });
52429        } catch(e) {
52430            trace("exception doing a sort request: " + e.message);
52431            sorting = false;
52432        }
52433    }
52434
52435    function receive_session_update(data) {
52436        self.attach_session(data);
52437    }
52438
52439    // (these data access methods are just stubbed out now to show the return types)
52440    function do_initalize_session(type, params, callback) {
52441
52442        raise_busy(true);
52443        request_params = params;
52444        request_params.search_type = type;
52445        var request_body = new Object();
52446        var url = "";
52447        switch(type) {
52448            case 'search':
52449                request_body.probes = params.row_ids;
52450                request_body.resolution = params.column_set;
52451                request_body.display_columns = params.display_columns;
52452                request_body.group_by = params.group_by;
52453                url = INITIALIZE_URL;
52454                break;
52455            case 'differential':
52456                request_body.group_by = params.group_by;
52457                request_body.sort_by = params.sort_by;
52458                request_body.resolution = params.column_set;
52459                request_body.domain1 = params.domain1;
52460                request_body.domain2 = params.domain2;
52461                request_body.display_columns = params.display_columns;
52462                url = DIFFERENTIAL_URL;
52463                break;
52464            case 'correlation':
52465                request_body.group_by = params.group_by;
52466                request_body.sort_order = params.sort_order;
52467                request_body.resolution = params.column_set;
52468                request_body.domain = params.domain;
52469                request_body.probes = params.probes;
52470                request_body.display_columns = params.display_columns;
52471                url = CORRELATION_URL;
52472                trace("for domain: " + inspect(request_body.domain[0]));
52473                trace("for display: " + inspect(request_body.display_columns[0]));
52474                break;
52475        }
52476        trace("initializing with this url: " + url);
52477        trace(request_params.search_type + " request: " + inspect(request_body));
52478        //delete Array.prototype.toJSON;
52479
52480        //delete Object.prototype.toJSON;
52481            // test
52482            //var postcontent = $H(request_body);
52483            //var pjson = postcontent.toJSON();
52484        //var pjson = JSON.stringify(request_body);
52485
52486        new Ajax.Request(url,
52487        {
52488            method:'post',
52489            contentType:"application/json",
52490            postBody: $H(request_body).toJSON(),
52491            onSuccess:function(response) {
52492             if(response.status != 200) {
52493                 trace("success, not 200");
52494                 request_failure(response)
52495             }
52496             else
52497                callback(response.responseJSON);
52498           },
52499           onFailure:function(response) {
52500             request_failure(response);
52501           }
52502        });
52503    }
52504
52505    function fetch_probe_list(callback, asynch) {
52506
52507        trace("fetching rows with this url: " + ROWS_URL+"&startRow=" + probe_id_list.length);
52508        new Ajax.Request(ROWS_URL+"&startRow=" + probe_id_list.length,
52509                {method:'get',
52510                 asynchronous:asynch,
52511                 onSuccess:function(response) {
52512                     if(response.status != 200) {
52513                         request_failure(response)
52514                     }
52515                     else {
52516                        callback(response, asynch);
52517                     }
52518                 },
52519                 onFailure:function(response) {
52520                     request_failure(response);
52521                 }
52522             });
52523    }
52524
52525    function munge_stop_pos(data) {
52526        if(!data)
52527            return;
52528
52529        if(!data[0])
52530            return;
52531
52532        if(!data[0]['end-position'])
52533            return;
52534
52535        for(var i = 0; i < data.length; i++) {
52536            data[i]['end-position'] = new Number(data[i]['end-position']) - new Number(data[i]['start-position']);
52537        }
52538    }
52539
52540    function fetch_donor_list(callback) {
52541
52542//        new Ajax.Request(DONORS_URL,
52543//                {method:'get',
52544//                 onSuccess:function(response) {
52545//                     if(response.status != 200)
52546//                         request_failure(response)
52547//                     else {
52548//                        callback(response);
52549//                     }
52550//                 },
52551//                 onFailure:function(response) {request_failure(response);}
52552//             });
52553
52554           var dao_options = {
52555                 success_callback: callback,
52556                 error_callback: function(response) {request_failure(response);}
52557            };
52558
52559           self.rma_dao.donor_info_by_product_ids("", dao_options);  // empty product id provided, so the default product id for this app will be filled in
52560
52561    }
52562
52563    function fetch_column_list(callback) {
52564
52565        new Ajax.Request(COLUMNS_URL,
52566                {method:'get',
52567                 onSuccess:function(response) {
52568                     if(response.status != 200)
52569                         request_failure(response)
52570                     else {
52571                        callback(response);
52572                     }
52573                 },
52574                 onFailure:function(response) {request_failure(response);}
52575             });
52576    }
52577
52578    function fetch_cell_data(column, row, callback) {
52579        if(column >= self.number_of_columns || row >= self.number_of_rows
52580               || row < 0 || column < 0) {
52581            return;
52582          }
52583
52584         new Ajax.Request(QUERY_URL + "?column=" + parseInt(column) + "&row=" + parseInt(row),
52585                {method:'get',
52586                 onSuccess:function(response) {
52587                     if(response.status != 200) {
52588                         request_failure(response)
52589                     }
52590                     else {
52591                        var probe = probe_id_list[row];
52592                        if (response.responseJSON != null) {
52593                          var donor = donor_list[response.responseJSON.donor];
52594                          self.get_donor_column_info(donor,column);
52595                          response.responseJSON.probe = probe;
52596                          response.responseJSON.donor = donor;
52597                          response.responseJSON.top_level_structure = column_list[column].top_level_structure; // HACK, this is for DevHuman LCM to get the top_level_strucutre info
52598                        }
52599                        callback(response);
52600                     }
52601                 },
52602                 onFailure:function(response) {request_failure(response);}
52603             });
52604    }
52605
52606    function receive_probe_id_list(response, finish) {
52607        var probe_set = eval("(" + response.responseText + ")");
52608
52609        trace("there are " + probe_set.length + " probes in this response.");
52610        munge_stop_pos(probe_set);
52611        probe_id_list = probe_id_list.concat(probe_set);
52612
52613        if(finish) {
52614            probes_ready = true;
52615            check_ready();
52616        }
52617    }
52618
52619//    function receive_donor_list(response) {
52620//        var ret = response.responseJSON;
52621//        if(ret === null || !(ret.success == 'true')) {
52622//            if(self.status_callback)
52623//                self.status_callback(DataStatus.STATUS_PROBE_META_FAIL, ret ? ret.msg : '');
52624//            return;
52625//        }
52626//
52627//        var donors = response.responseJSON.msg;
52628//        donor_list = new Object();
52629//        for(var i = 0; i < donors.length; i++)
52630//            donor_list[donors[i][self.donor_key].id] = donors[i][self.donor_key];
52631//
52632//       donors_ready = true;
52633//       check_ready();
52634//    }
52635
52636    //rewrite this method to process RMA response
52637    function receive_donor_list(response) {
52638        if((response === null) || !(response.response.success == true)) {
52639            if(self.status_callback)
52640                self.status_callback(DataStatus.STATUS_PROBE_META_FAIL, response ? response.response ? "response code = " + response.response.code : '' : '');
52641
52642            return;
52643        }
52644
52645        // create the donor_list object from the response just received
52646        var donors = response;
52647        donor_list = new Object();
52648        for(var i = 0; i < donors.length; i++)    {
52649            donor_list[donors[i].id] = {
52650                id:donors[i].id,
52651                age:donors[i].age.name,
52652                handedness:donors[i].handedness,
52653                organism:donors[i].organism.name,
52654                sex:donors[i].sex,
52655                race_or_strain:donors[i].strain,
52656                name:donors[i].name
52657            }
52658
52659        }
52660
52661        donors_ready = true;
52662        check_ready();
52663
52664    }
52665
52666
52667    function receive_column_list(response) {
52668        column_list = eval("(" + response.responseText + ")");
52669        columns_ready = true;
52670        check_ready();
52671    }
52672
52673    function receive_cell_detail(row, response, callback) {
52674        if(!response.success)
52675            if(self.status_callback) {
52676                self.status_callback(DataStatus.STATUS_CELL_DATA_FAIL, response ? response.msg : '');
52677                return;
52678            }
52679
52680        var column_info = eval("(" + response.payload + ")");
52681        // do some validation here
52682        var info = new Object();
52683        info.column = column_info;
52684        info.probe = probe_id_list[row];
52685
52686        raise_busy(false);
52687        if(callback)
52688            callback(new hdResponse(true, info));
52689    }
52690
52691    function check_ready() {
52692        if(ready)
52693            return;
52694
52695//        if(!probes_ready || !columns_ready || !donors_ready)
52696//            return;
52697
52698        if(!probes_ready || !columns_ready )
52699        return;
52700
52701
52702        ready = true;
52703        raise_busy(false);
52704        if(self.status_callback)
52705            self.status_callback(DataStatus.STATUS_DATA_READY, 'DATA READY');
52706    }
52707
52708    // returns the probe and column ID given the row and column indexes
52709    function ids_at_grid_position(x, y) {
52710        var column = column_list[x];
52711        var col_id = column.sample_id ? (column.sample_id != 0 ? column.sample_id : column.structure_id) : column.structure_id;
52712        return( {probe_id:probe_id_list[y], column_id:col_id} );
52713    }
52714
52715
52716    function init() {
52717        if(self.session === null)
52718            return;
52719
52720        self.attach_session(self.session);
52721    }
52722
52723
52724// debuggery
52725    function trace(msg) {
52726        if(self.trace_function)
52727            self.trace_function(msg);
52728    }
52729
52730
52731
52732// initialize this instance
52733    init();
52734}
52735
52736
52737
52738
52739
52740// The heatmap dao session object may not need to be exposed at all.  Depends
52741// mostly on whether or not we implement session initialization in the dao.
52742// Doing this would mean that the heatmap_dao would also need to communicate
52743// with the search service to turn the user's search criteria into a list
52744// of probes to be used with the heatmap data engine.  The alternative to
52745// direct dao access to the search service is to have the page controller
52746// do the interaction with the search service, initialize a heatmap data
52747// engine session, and then render a view that contains the heatmap_dao,
52748// along with the already-initialized session object.
52749function hdSession(session_id, status, number_of_probes, number_of_columns)
52750{
52751    this.session_id = session_id;
52752    this.status = status;
52753    this.number_of_probes = number_of_probes;
52754    this.number_of_columns = number_of_columns;
52755}
52756;
52757
52758var __header_image_count__ = 0;
52759
52760// this is the legend or header image for the main heatmap.  The one widget that
52761// rules them all acts as a controller, keeping the heatmap & header
52762// scale & position in sync.
52763function header_image(container, header_url, column_count, initial_scale, initial_column_index)
52764{
52765
52766// public properties
52767    this.container = $(container);
52768    this.header_url = header_url;
52769    this.column_count = column_count;
52770    this.trace_function = null;
52771
52772
52773// public methods
52774    this.set_position = function(new_column) {
52775        the_image.setStyle({left:(Math.floor(new_column*scale))+'px'});
52776    }
52777
52778    this.get_position = function() {
52779        return(the_image.getStyle('left'));
52780    }
52781
52782    this.set_scale = function(new_scale) {
52783        scale = new_scale;
52784        the_image.setStyle({width:(this.column_count*scale)+'px'});
52785    }
52786
52787    this.set_marker_position = function(new_marker_pos) {
52788        var pos = Math.floor(new_marker_pos*scale)-(7.5/scale);
52789        var pos1 = the_image.positionedOffset();
52790        the_marker.setStyle({left:(pos+pos1.left)+'px'});
52791    }
52792
52793    this.set_marker_client_x = function(new_x) {
52794        var left = the_image.positionedOffset().left;
52795        var pos = the_image.cumulativeOffset();
52796        var offset = the_image.cumulativeScrollOffset();
52797        the_marker.setStyle({left:((new_x - pos.left - 7.5 + left) + offset.left)+'px'});
52798    }
52799
52800    this.set_click_marker_client_x = function(new_x) {
52801        var left = the_image.positionedOffset().left;
52802        var pos = the_image.cumulativeOffset();
52803        var offset = the_image.cumulativeScrollOffset();
52804        the_click_marker.show();   // show this marker after first user click
52805        the_click_marker.setStyle({left:((new_x - pos.left - 7.5 + left) + offset.left)+'px'});
52806    }
52807
52808    this.refresh = function(new_url) {
52809        this.header_url = new_url;
52810        the_image.src = this.header_url;
52811    }
52812
52813// private properties
52814    var self = this;
52815    var img_width = 1;
52816    var img_column = initial_column_index;  // this is the 'left' attribute of the img's position
52817    var the_image = null;
52818    var the_wrapper = null;
52819    var the_marker = null;
52820    var the_click_marker = null;
52821    var scale = 1.0;
52822
52823
52824// private methods
52825    function build_scaffold() {
52826        var wrapper_width = self.container.getWidth();
52827        the_wrapper = new Element('div', {'class':'header_image_wrapper', id:'hiw'+__header_image_count__, width:wrapper_width+'px', title:'Columns color coded-- top: donor, bottom: structure'});
52828        the_image = new Element('img', {'class':'header_image', id:'hi'+__header_image_count__,
52829                                        width:img_width, height:'100%',
52830                                        top:'0px', left:img_column});
52831
52832        the_marker = new Element('img', {id:'him'+__header_image_count__,'class':'header_marker'});
52833        the_click_marker = new Element('img', {id:'hicm'+__header_image_count__,'class':'header_marker'});
52834
52835        //the_wrapper.clonePosition(self.container);
52836        self.container.appendChild(the_wrapper);
52837        the_wrapper.appendChild(the_image);
52838        the_wrapper.appendChild(the_marker);
52839        the_wrapper.appendChild(the_click_marker);
52840
52841        the_image.src = self.header_url;
52842        the_marker.src = '/assets/heatmap_ui/marker.png';
52843        the_click_marker.src = '/assets/heatmap_ui/click_marker.png';
52844        the_click_marker.hide();
52845    }
52846
52847    function set_initial_width() {
52848        if(initial_scale == 0) // scale to width
52849            scale = self.container.getWidth() / self.column_count;
52850        else
52851            scale = initial_scale;
52852
52853        img_width = self.column_count * scale;
52854    }
52855
52856    function init() {
52857       set_initial_width();
52858       build_scaffold();
52859    }
52860
52861    function trace(msg) {
52862        if(self.trace_function)
52863            self.trace_function(msg);
52864    }
52865
52866    init();
52867}
52868
52869
52870
52871;
52872
52873var HM_CONTROL_ZOOM                 = 1;
52874var HM_CONTROL_COLOR_MAP            = 2;
52875var HM_CONTROL_RESOLUTION           = 4;
52876var HM_CONTROL_RESTRICT_DOMAINS     = 8;
52877var HM_CONTROL_VIEW_SELECTION_HM    = 16;
52878var HM_CONTROL_VIEW_SELECTION_THUMB = 32;
52879var HM_CONTROL_CLEAR_SELECTION      = 64;
52880var HM_CONTROL_TOGGLE_BUTTON        = 128;
52881var HM_CONTROL_ALL                  = 255;
52882
52883var __control_bar_count = 0;
52884
52885function heatmap_status_bar(container, options, event_options) {
52886
52887// public properties
52888    this.container = $(container);
52889    this.ramp_url = options ? (options.ramp_url ? options.ramp_url : null) : null;
52890    this.colormap_set = options ? (options.colormap_set ? options.colormap_set : []) : [];
52891    this.control_mask = options ? (options.control_mask ? options.control_mask : []) : [];
52892    this.resolution_set = options ? (options.resolution_set ? options.resolution_set : []) : [];
52893    this.control_defaults = options ? (options.control_defaults ? options.control_defaults : {}) : {};
52894    this.display_donors = options ? (options.display_donors ? options.display_donors : null) : null;
52895    this.display_structures = options ? (options.display_structures ? options.display_structures : null) : null;
52896    this.show_display_filters = options ? (options.show_display_filters ? options.show_display_filters : false ) : false;
52897    this.display_donor_groups = options ? (options.display_donor_groups ? options.display_donor_groups : null) : null;
52898    this.display_donor_filter_type = options ? (options.display_donor_filter_type ? options.display_donor_filter_type : null) : null;
52899    this.display_structure_groups = options ? (options.display_structure_groups ? options.display_structure_groups : null) : null;
52900    this.display_structure_filter_type = options ? (options.display_structure_filter_type ? options.display_structure_filter_type : null) : null;
52901    this.restriction_on = false;  /* the state read from the radio buttons */
52902    this.ontology_options = options ? (options.ontology_options ? options.ontology_options : {} ) : {};
52903
52904// event hooks
52905    this.on_zoom = event_options? (event_options.on_zoom ? event_options.on_zoom : null) : null;
52906    this.on_zoom_reset = event_options? (event_options.on_zoom_reset ? event_options.on_zoom_reset : null) : null;
52907    this.on_restriction_change = event_options? (event_options.on_restriction_change ? event_options.on_restriction_change : null) : null;
52908    this.on_display_change = event_options? (event_options.on_display_change ? event_options.on_display_change : null) : null;
52909    this.on_resolution_change = event_options? (event_options.on_resolution_change ? event_options.on_resolution_change : null) : null;
52910    this.on_view_selection_click = event_options? (event_options.on_view_selection_click ? event_options.on_view_selection_click : null) : null;
52911    this.on_clear_click = event_options? (event_options.on_clear_click ? event_options.on_clear_click : null) : null;
52912	this.on_toggle_click = event_options? (event_options.on_toggle_click ? event_options.on_toggle_click : null) : null;
52913    this.on_colormap_change = event_options? (event_options.on_colormap_change ? event_options.on_colormap_change : null) : null;
52914    this.on_level_change = event_options? (event_options.on_level_change ? event_options.on_level_change : null) : null;
52915
52916
52917// public methods
52918
52919
52920    this.set_zoom = function(new_value) {
52921        the_zoom_slider.setValue(new_value);
52922    };
52923
52924    this.set_range = function(lower, upper) {
52925        the_range_widget.set_range(lower, upper);
52926    };
52927
52928    this.set_restriction = function(restricted) {
52929
52930    };
52931
52932    this.set_resolution = function(selected) {
52933
52934    };
52935
52936    this.set_colormap = function(selected_map) {
52937       the_range_widget.set_colormap(selected_map);
52938    };
52939
52940    this.set_control_visibility = function(control_mask,visible) {
52941	controls = [];
52942	
52943	if (the_zoom_box && (control_mask & HM_CONTROL_ZOOM)) 
52944	    controls.push(the_zoom_box);
52945	
52946	if (the_ramp_box && (control_mask & HM_CONTROL_COLOR_MAP))
52947	    controls.push(the_ramp_box);
52948	
52949	if (the_resolution_box && (control_mask & HM_CONTROL_RESOLUTION))
52950	    controls.push(the_resolution_box);
52951	
52952	if (the_domain_box && (control_mask & HM_CONTROL_RESTRICT_DOMAINS))
52953	    controls.push(the_domain_box);
52954	
52955	if (the_view_hm_button && (control_mask & HM_CONTROL_VIEW_SELECTION_HM))
52956	    controls.push(the_view_hm_button);
52957	
52958	if (the_view_thumb_button && (control_mask & HM_CONTROL_VIEW_SELECTION_THUMB))
52959	    controls.push(the_view_thumb_button);
52960	
52961	if (the_clear_button && (control_mask & HM_CONTROL_CLEAR_SELECTION))
52962	    controls.push(the_clear_button);
52963	
52964	if (the_toggle_button && (control_mask & HM_CONTROL_TOGGLE_BUTTON)) 
52965	    controls.push(the_toggle_button);
52966	
52967	for (var i=0; i<controls.length; i++) {
52968	    if (visible) 
52969		$j(controls[i]).show();
52970	    else
52971		$j(controls[i]).hide();
52972	}
52973    };
52974
52975// private properties
52976    var self = this;
52977
52978    var the_ramp_box = null;
52979    var the_zoom_box = null;
52980    var the_resolution_box = null;
52981	var the_domain_box = null;
52982
52983    var the_ramp = null;
52984    var the_zoom_slider = null;
52985    var the_resolution_select = null;
52986    var the_restriction_control = null;
52987    var the_view_hm_button = null;
52988    var the_view_thumb_button = null;
52989    var the_clear_button = null;
52990	var the_toggle_button = null;
52991    var the_select_box = null;
52992	var the_button_box = null;
52993    var the_range_widget = null;
52994
52995    var the_ontology_model = options.ontology_model;
52996    var the_display_structure_list = options.display_structure_list;
52997    var the_display_ontology_dropdown = null;
52998
52999    var filter_button_on_text = "Filter Heatmap: On";
53000    var filter_button_off_text = "Filter Heatmap: Off";
53001    var filter_toggle_button;
53002
53003    var the_display_dialog_box = null;
53004
53005    var random_id_for_display_filtering;
53006    var display_structures_with_id = "display_structures";
53007    var display_donors_with_id = "display_donors_popup";
53008
53009// private methods
53010    function build_scaffold() {
53011
53012	// slide for zoom control
53013        if(self.control_mask & HM_CONTROL_ZOOM) {
53014            the_zoom_box = new Element('div', {'class':'status_zoom_box', title:'Adjust heat map zoom level'});
53015            var zoom_label_box = new Element('div', {'class':'zoom_label_box'});
53016            zoom_label_box.update('Zoom');
53017            the_zoom_box.appendChild(zoom_label_box);
53018            self.container.appendChild(the_zoom_box);
53019
53020            var zoom_track = new Element('div', {'class':'status_zoom_track'});
53021            var minus_button = new Element('img',{src:'/assets/heatmap_ui/minus_small.png', 'class':'zoom_button left_button'});
53022            the_zoom_box.appendChild(minus_button);
53023            var zoom_handle = new Element('div', {'class':'status_zoom_handle'});
53024            zoom_track.appendChild(zoom_handle);
53025            the_zoom_box.appendChild(zoom_track);
53026            the_zoom_slider = new Control.Slider(zoom_handle, zoom_track,
53027                                                 {onChange:do_zoom_change,
53028                                                  range:$R(-20,20),
53029                                                  sliderValue:0.00001});
53030            var plus_button = new Element('img',{src:'/assets/heatmap_ui/plus_small.png', 'class':'zoom_button right_button'});
53031            the_zoom_box.appendChild(plus_button);
53032            plus_button.observe('click', zoom_in);
53033            minus_button.observe('click', zoom_out);
53034        }
53035
53036	// the color map ramp display
53037        if(self.control_mask & HM_CONTROL_COLOR_MAP) {
53038            the_ramp_box = new Element('div', {'class':'windowLevelContainer'});
53039            self.container.appendChild(the_ramp_box);
53040            var ramp_label_box = new Element('div', {'class':'ramp_label_box'});
53041            ramp_label_box.update('Color Map');
53042            the_ramp_box.appendChild(ramp_label_box);
53043
53044            var selected_map = get_selected_map(self.colormap_set);
53045            var colormap_options = {
53046                colormap_set:self.colormap_set,
53047                normalized:selected_map.normal,
53048                zscore_limits:selected_map.limits,
53049                raw_limits:selected_map.limits,
53050                raw_expression_range:selected_map.range,
53051                norm_expression_range:selected_map.range
53052            };
53053            var colormap_events = {
53054                on_level_change:self.on_level_change,
53055                on_colormap_change:self.on_colormap_change
53056            }
53057            the_range_widget = new colormap_widget(the_ramp_box, colormap_options, colormap_events);
53058        }
53059
53060	// the resolution drop down
53061        if(self.control_mask & HM_CONTROL_RESOLUTION) {
53062            the_resolution_box = new Element('div', {id:'resolution_box_'+__control_bar_count, 'class':'resolution_box', title:'Change degree of expression rollup'});
53063            var the_resolution_label_box = new Element('div', {id:'resolution_label_box_'+__control_bar_count, 'class':'resolution_label_box'});
53064            the_resolution_select = new Element('select', {id:'resolution_select_'+__control_bar_count, 'class':'resolution_select'});
53065            
53066            var i = 0;
53067            for(var key in self.resolution_set) {
53068                var selected = self.control_defaults.HM_CONTROL_RESOLUTION
53069                    ? self.control_defaults.HM_CONTROL_RESOLUTION == key.toString()
53070                    : false;
53071                var option = new Element('option', {id:'opt_' + i++, value:key});
53072                option.text = self.resolution_set[key];
53073                the_resolution_select.options.add(option);
53074                if(selected)
53075                    option.setAttribute('selected', true);
53076            }
53077
53078            the_resolution_label_box.update('Resolution ');
53079            the_resolution_box.appendChild(the_resolution_label_box);
53080            the_resolution_box.appendChild(the_resolution_select);
53081            self.container.appendChild(the_resolution_box);
53082            the_resolution_select.observe('change',do_resolution_changed);
53083        }
53084
53085	// the display filtering buttons
53086        if(self.show_display_filters) {
53087
53088            //var filter_toggle_button_html = '<button id="filter_button" class="display_filtering_buttons" type="button">Filter Heatmap</button>';
53089            var filter_toggle_button_html = '<input type="checkbox" id="filter_button" name="filter_button" class="display_filtering_buttons_checkbox"/>' +
53090                                            '<label for="filter_button" class="display_filtering_buttons_checkbox" title="Toggle heatmap display filtering on/off">Filter Heatmap</label>';
53091            var edit_settings_button_html = '<button id="filter_settings_button"  class="display_filtering_buttons" type="button" title="Change the settings on heatmap display filtering">...</button>';
53092
53093            // set button state
53094            var on = self.control_defaults.HM_CONTROL_RESTRICT_DOMAINS;
53095            self.restriction_on = on;  // save the initial value
53096
53097            var filter_button_text = self.restriction_on ? filter_button_on_text : filter_button_off_text;
53098
53099
53100            var the_domain_box_html = "<span id='restriction_container'>" +
53101                                         filter_toggle_button_html +
53102                                         edit_settings_button_html +
53103                                      "</span>";
53104
53105            the_domain_box = $j(the_domain_box_html);
53106
53107            the_domain_box.appendTo($j(self.container));  // self.container is actually an element in Prototype framework
53108
53109
53110
53111            filter_toggle_button = $j('#filter_button').button({label: filter_button_text})
53112                                                           .click(function(event) {
53113                                                                toggle_domain_restriction();
53114                                                            });
53115
53116            filter_toggle_button.attr("checked",self.restriction_on ? "checked" : undefined);
53117            filter_toggle_button.button("option",{label: self.restriction_on ? filter_button_on_text : filter_button_off_text }).button("refresh");
53118
53119            var edit_settings_button = $j('#filter_settings_button').button()
53120                .click(function(event){
53121                    show_display_filtering_dialog();
53122                });
53123
53124
53125            // prepare the content to appear in the display filter popup dialog box
53126            // But do not create the dialog, dialog will be created right before open(), and destroyed right after close()
53127            the_display_dialog_box = create_display_filtering_dialog();
53128
53129        }
53130
53131        the_button_box = new Element('div', {id:'the_button_box_'+__control_bar_count, 'class':'button_box'});
53132
53133	// the view heatmap button
53134        if (self.control_mask & HM_CONTROL_VIEW_SELECTION_HM) {
53135            the_view_hm_button = new Element('button', {'class':'hm_control_button'});
53136	    the_view_hm_button.update("View Selection Heatmap");
53137            the_button_box.appendChild(the_view_hm_button);
53138	}
53139	
53140	// the view thumbnails button
53141        if (self.control_mask & HM_CONTROL_VIEW_SELECTION_THUMB) {
53142            the_view_thumb_button = new Element('button', {'class':'hm_control_button'});
53143	    the_view_thumb_button.update("View Selection Thumbnails");
53144            the_button_box.appendChild(the_view_thumb_button);
53145	}
53146
53147	// the toggle button
53148 	if (self.control_mask & HM_CONTROL_TOGGLE_BUTTON) {
53149 	    the_toggle_button = new Element('button', {'class':'hm_control_button'});
53150 	    the_toggle_button.update("Toggle Heatmap/Thumbnails");
53151            the_button_box.appendChild(the_toggle_button); 
53152 	}
53153
53154	// the clear button
53155 	if (self.control_mask & HM_CONTROL_CLEAR_SELECTION) {
53156            the_clear_button = new Element('button', {'class':'hm_control_button'});
53157	    the_clear_button.update("Clear Selections");
53158            the_button_box.appendChild(the_clear_button);
53159 	}
53160
53161        self.container.appendChild(the_button_box);
53162
53163 	if (the_view_hm_button) {
53164            the_view_hm_button.observe('click',function() { do_view_selection_click(HM_VIEW_SELECTION_HM); });
53165            $j(the_view_hm_button).button();
53166 	}
53167
53168 	if (the_view_thumb_button) {
53169            the_view_thumb_button.observe('click', function() { do_view_selection_click(HM_VIEW_SELECTION_THUMB); });
53170            $j(the_view_thumb_button).button();
53171 	}
53172	
53173 	if (the_clear_button) {
53174            the_clear_button.observe('click',do_clear_click);
53175            $j(the_clear_button).button();
53176 	}
53177	
53178 	if (the_toggle_button) {
53179            the_toggle_button.observe('click',do_toggle_click); 
53180            $j(the_toggle_button).button();
53181 	}
53182
53183    }
53184
53185    function create_display_filtering_dialog() {
53186
53187        // jquery ui dialog may have some remnant code under <body> even after dialog.destroy() is called
53188        $j("div#display_filtering_dialog").remove();  // remove the existing dialog's remaining component
53189
53190        random_id_for_display_filtering = Math.floor(Math.random() * 100000) + 1;
53191        display_donors_with_id = "display_donors_" + random_id_for_display_filtering;
53192        display_structures_with_id = "display_structures_" + random_id_for_display_filtering;
53193
53194
53195        var popup_title = "Filter Heatmap Settings";
53196        var display_filtering_desc = "Configure the heatmap view by restricting columns to donors and structures of interest.";
53197        var display_donors_desc = "Select donors:";
53198        var display_structures_desc = "Select structures:";
53199
53200        var donor_selection_html = "<span id='display_donor_selection_popup'><select id='" + display_donors_with_id + "' multiple='multiple'>";
53201        donor_selection_html += "<option value=0>[All Donors]</option>";
53202        for(var i=0; i<self.display_donor_groups.length; i++) {
53203
53204            if(self.display_donor_filter_type == 'donors') {
53205                donor_selection_html += "<option value=" + self.dis
53205play_donor_groups[i].donor.id + ">" +
53206                                        self.display_donor_groups[i].donor.name +
53207                                        "(" + self.display_donor_groups[i].donor.sex + "," + self.display_donor_groups[i].donor.age + ")" +
53208                                        "</option>";
53209            }
53210            else if(self.display_donor_filter_type == 'stages') {
53211                // in this case, self.display_donor_groups is actually the _stage_list passed in
53212                if(self.display_donor_groups[i].stage_name) { // used in devhuman
53213                   donor_selection_html += "<option value='" + self.display_donor_groups[i].stage_name + "'>" + self.display_donor_groups[i].stage_start +
53214                                                '-' + self.display_donor_groups[i].stage_end + "</option>";
53215                } else if(self.display_donor_groups[i].name) {     // nhp microdissection
53216                    donor_selection_html += "<option value='" + self.display_donor_groups[i].name + "'>" + self.display_donor_groups[i].name + "</option>";
53217                }
53218
53219
53220            }
53221        }
53222        donor_selection_html += "</select></span>";
53223
53224        var structure_input_html = "<span id='display_structure_restriction_popup'><input type='text' id='display_domain_popup' class='_sw_search_text'/></span>";
53225        if(self.display_structure_filter_type == 'structure_selection') {
53226            // in this case the structure selection is a dropdownchecklist, which is converted from a multiple select
53227            structure_input_html = "<span id='display_structure_selection_popup'><select id='" + display_structures_with_id + "' multiple='multiple'>";
53228            structure_input_html += "<option value=0>[All Structures]</option>";
53229            for(var i=0; i<self.display_structure_groups.length; i++) {
53230                structure_input_html += "<option value=" + self.display_structure_groups[i].id + ">" + self.display_structure_groups[i].acronym + " - " +
53231                                                        self.display_structure_groups[i].name + "</option>";
53232            }
53233            structure_input_html += "</select></span>";
53234        }
53235        var dialog_html = '<div id="display_filtering_dialog" title="' + popup_title + '">' +
53236                        '<div class="display_filtering_desc">' + display_filtering_desc + '</div>' +
53237                        '<div class="display_filter_ui">' + '<div class="display_filter_description">' + display_donors_desc + '</div><div>' + donor_selection_html + '</div></div>' +
53238                        '<div class="display_filter_ui">' + '<div class="display_filter_description">' + display_structures_desc + '</div><div>' + structure_input_html + '</div></div>' +
53239                        '</div>';
53240        var dialog_box = $j(dialog_html);
53241
53242        dialog_box.appendTo($j(self.container));
53243
53244        // donor selection UI
53245        $j('#' + display_donors_with_id).dropdownchecklist({icon:{placement:'right',toOpen:'ui-icon-triangle-1-s'},
53246                                      forceMultiple:true,
53247                                      firstItemChecksAll: true,
53248                                      maxDropHeight:150,
53249                                      width:490,
53250                                      zIndex:9999
53251        });
53252
53253
53254
53255        // build the display structure selection UI
53256        the_display_ontology_dropdown = $j('#display_domain_popup');
53257
53258        if(self.display_structure_filter_type == 'ontology'){
53259            if(!the_ontology_model)
53260                the_ontology_model = new ontology_model(null, 9, {on_ready:do_ontology_load, on_error:on_ontology_error});
53261            else
53262                do_ontology_load(the_ontology_model);
53263        } else {
53264            // then the type is "structure_selection", we need to build a dropdownchecklist
53265            $j('#' + display_structures_with_id ).dropdownchecklist({icon:{placement:'right',toOpen:'ui-icon-triangle-1-s'},
53266                                          forceMultiple:true,
53267                                          firstItemChecksAll: true,
53268                                          maxDropHeight:100,
53269                                          width:490,
53270                                          zIndex:9999
53271            });
53272
53273            load_saved_display_domain();     // we can call this loading here, because we don't need to wait for the ontology widget in this case
53274
53275        }
53276
53277
53278         dialog_box.hide();
53279
53280/*      // as noted before, now this will be called every time before dialog.open is called
53281        dialog_box.dialog({autoOpen: false,
53282                           modal: true,
53283                           width: 560,
53284                           height: 390,
53285                           closeOnEscape:false,
53286                           buttons:[
53287                               { text: "Save", click: function() { update_display_filter_settings(); dialog_box.dialog( "close" ); } },
53288                               { text: "Cancel", click: function() { dialog_box.dialog( "close" ); } }
53289                                   ]});
53290*/
53291
53292        return dialog_box;
53293
53294    }
53295
53296    function show_display_filtering_dialog() {
53297
53298        // do another load from display_donors and display_structures, in case user canceled the previous selection
53299        load_saved_display_domain();
53300
53301        // first create the dialog box
53302        // I moved the actual dialog creation here because I have not found a reliable way to fully remove any existing
53303        //   display filtering dialog before building a new one. When user repeatedly launch search from the UI, the page would not reload, and
53304        //   any existing display filter dialog will stay besides a new dialog will be created.
53305        // I now have the convention that a dialog box is created and opened when user launches it, and it is closed and
53306        //   destroyed when user dismisses it.
53307        the_display_dialog_box.dialog({autoOpen: false,
53308                           modal: true,
53309                           width: 560,
53310                           height: 390,
53311                           closeOnEscape:false,
53312                           buttons:[
53313                               { text: "Save", click: function() { update_display_filter_settings(); the_display_dialog_box.dialog( "close" ); the_display_dialog_box.dialog("destroy"); } },
53314                               { text: "Cancel", click: function() { the_display_dialog_box.dialog( "close" ); the_display_dialog_box.dialog("destroy"); } }
53315                                   ]});
53316
53317        the_display_dialog_box.dialog("open");
53318
53319
53320    }
53321
53322    function do_ontology_load(model) {
53323
53324//        // also hook up the event callbacks for closing the widget or deleting some structures entered
53325//        the_display_ontology_dropdown.ontology_autocomplete(model, {on_close:do_ontology_widget_state_updated,
53326//                                                           on_remove_structure:do_ontology_widget_state_updated});
53327
53328        the_display_ontology_dropdown.ontology_autocomplete(model, self.ontology_options);
53329
53330        load_saved_display_domain();    // we can load the ontology selections now
53331    }
53332
53333    /**
53334     * set the display filtering UI widgets based on self.display_structures and self.display_donors
53335     **/
53336    function load_saved_display_domain() {
53337
53338        //var display_structures_array =
53339        if(self.display_structure_filter_type == 'ontology') {
53340            if(self.display_structures && self.display_structures.length > 0) {
53341                the_display_ontology_dropdown.ontology_autocomplete('set_structure_ids', self.display_structures.split(','));
53342            }
53343        }
53344        else {
53345            // refresh the structure dropdownchecklist to reflect the passed in display_structures
53346            if(self.display_structures && self.display_structures.length > 0) {
53347                var selected_structures_array = self.display_structures.split(',');
53348                //var structure_select = $j('#display_structures option');
53349                var structure_select = $j('#' + display_structures_with_id + ' option');
53350                structure_select.removeAttr('selected');   // remove all the selected attr
53351                for( var i = 0; i < selected_structures_array.length; i ++ ){
53352                    //structure_select = $j('#display_structures option[value=' + selected_structures_array[i] +']');
53353                    structure_select = $j('#' + display_structures_with_id + ' option[value="' + selected_structures_array[i] +'"]');
53354                    structure_select.attr('selected', 'selected');
53355                }
53356            }
53357            else {
53358                //var structure_select = $j('#display_structures option[value=0]');
53359                var structure_select = $j('#' + display_structures_with_id + ' option[value="0"]');
53360                structure_select.attr('selected', 'selected');
53361            }
53362            $j('#' + display_structures_with_id ).dropdownchecklist("refresh");
53363        }
53364
53365        if(self.display_donors && self.display_donors.length > 0) {
53366            var selected_donors_array = self.display_donors.split(',');
53367            //var donor_select = $j('#display_donors_popup option');
53368            var donor_select = $j('#' + display_donors_with_id + ' option');
53369            donor_select.removeAttr('selected');   // remove all the selected attr
53370            for( var i = 0; i < selected_donors_array.length; i ++ ){
53371                //donor_select = $j('#display_donors_popup option[value=' + selected_donors_array[i] +']');
53372                donor_select = $j('#' + display_donors_with_id + ' option[value="' + selected_donors_array[i] +'"]');
53373                donor_select.attr('selected', 'selected');
53374            }
53375        }
53376        else {
53377            //var donor_select = $j('#display_donors_popup option[value=0]');
53378            var donor_select = $j('#' + display_donors_with_id +  ' option[value="0"]');
53379            donor_select.attr('selected', 'selected');
53380        }
53381        //$j('#display_donors_popup').dropdownchecklist("refresh");
53382        $j('#' + display_donors_with_id).dropdownchecklist("refresh");
53383    }
53384
53385    function on_ontology_error() {
53386        // do nothing now
53387    }
53388
53389    /**
53390     * update self.display_donors and self.display_structures based on the state of the UI widgets, and
53391     * call self.on_display_change callback if there are changes to the display filters
53392     **/
53393    function update_display_filter_settings() {
53394        var saved_display_donors = self.display_donors;
53395        var saved_display_structures = self.display_structures;
53396
53397
53398        // update display_structures
53399        if(self.display_structure_filter_type == 'ontology') {
53400            var structure_id_values = the_display_ontology_dropdown.ontology_autocomplete('get_tags');  // we need to process this result
53401            var structure_array = [];
53402            for(var i = 0; i < structure_id_values.length; i++) {
53403                structure_array.push(structure_id_values[i].value );
53404            }
53405            self.display_structures = structure_array.join(',');
53406        } else {
53407//            $j('#display_structures').dropdownchecklist('close');
53408//            $j('#display_structures').dropdownchecklist('refresh');
53409//            var selected_display_structures_temp = $j('#display_structures option:selected');
53410            $j('#' + display_structures_with_id).dropdownchecklist('close');
53411            $j('#' + display_structures_with_id).dropdownchecklist('refresh');
53412            var selected_display_structures_temp = $j('#' + display_structures_with_id + ' option:selected');
53413            var selected_display_structures = $j.map(selected_display_structures_temp, function(op){ if(op.value != '0') return op.value;});
53413  // get an array of strings
53414            self.display_structures = selected_display_structures.join(',');
53415        }
53416
53417        // now update display_donors
53418        // note we don't check the display_donor_filter_type here, the self.display_donors may need to be mapped when
53419        //   the display_donor_filter_type is "stages"
53420//        $j('#display_donors_popup').dropdownchecklist('close');
53421//        $j('#display_donors_popup').dropdownchecklist('refresh');
53422//        var selected_display_donors_temp = $j('#display_donors_popup option:selected');
53423        $j('#' + display_donors_with_id).dropdownchecklist('close');
53424        $j('#' + display_donors_with_id).dropdownchecklist('refresh');
53425        var selected_display_donors_temp = $j('#' + display_donors_with_id + ' option:selected');
53426        var selected_display_donors = $j.map(selected_display_donors_temp, function(op){ if(op.value != '0') return op.value;});  // get an array of strings
53427        self.display_donors = selected_display_donors.join(',');
53428
53429        // only need to invoke the on_display_change callback when there are changes to self.display_donors or self.display_structures
53430        if(self.on_display_change && (saved_display_donors != self.dis
53430play_donors || saved_display_structures != self.display_structures)) {
53431            self.on_display_change(self, self.display_donors, self.display_structures, self.restriction_on);
53432        }
53433
53434    }
53435
53436
53437
53438    function get_selected_map(map_set) {
53439        for(var i = 0; i < map_set.length; i++)
53440            if(map_set[i].selected)
53441                return(map_set[i]);
53442
53443        return(null);
53444    }
53445
53446
53447    function toggle_domain_restriction(){
53448        //self.restriction_on = !self.restriction_on;
53449        self.restriction_on = filter_toggle_button.is(':checked');
53450
53451        filter_toggle_button.button("option",{label: self.restriction_on ? filter_button_on_text : filter_button_off_text }).button("refresh");
53452
53453        if(self.on_display_change) {
53454            self.on_display_change(self, self.display_donors, self.display_structures, self.restriction_on);
53455        }
53456    }
53457
53458    function do_resolution_changed() {
53459        if(self.on_resolution_change) {
53460          // IE8 sauce
53461          setTimeout(function() {
53462              if (the_view_hm_button)
53463				  the_view_hm_button.focus();
53464              self.on_resolution_change(self, the_resolution_select.options[the_resolution_select.selectedIndex].value);
53465              the_resolution_select.focus();
53466          }, 100);
53467        }
53468
53469//        if(self.on_resolution_change) {
53470//          self.on_resolution_change(
53471//                self,
53472//                the_resolution_select.options[the_resolution_select.selectedIndex].value
53473//          );
53474//        }
53475    }
53476
53477    function do_view_selection_click(mode) {
53478        if(self.on_view_selection_click) 
53479          self.on_view_selection_click(self,mode);        
53480    }
53481
53482    function do_clear_click() {
53483        if(self.on_clear_click) 
53484          self.on_clear_click(self);        
53485    }
53486
53487    function do_toggle_click() {
53488        if(self.on_toggle_click) 
53489          self.on_toggle_click(self);        
53490    }
53491
53492    function do_zoom_change(value) {
53493        if(self.on_zoom)
53494            self.on_zoom(self, value);
53495    }
53496
53497    function zoom_in() {
53498        the_zoom_slider.setValue(the_zoom_slider.value + 5);
53499    }
53500
53501    function zoom_out() {
53502        the_zoom_slider.setValue(the_zoom_slider.value - 5);
53503    }
53504
53505    function init() {
53506        build_scaffold();
53507        __control_bar_count++;
53508    }
53509
53510    init();
53511}
53512;
53513
53514// this oddball is necessary to provide a globally-scoped target for a
53515// timed execution.  See thumbnail_navigator.init() where a local function
53516// is assigned to this place holder.
53517var global_up_scroller = function() {};
53518var global_down_scroller = function() {};
53519
53520// Thumbnail navigation.  see the picture.
53521THUMBNAIL_NAV_COUNT = 0;
53522
53523// roi_options include:
53524// width, height, color, top, left.
53525// postion & dimension are in px, relative to the top left
53526// corner of the thumbnail image. Numeric values should be given
53527// just as numbers without any units.  That is, {width:50} not {width:'50px'}
53528//
53529// NOTE that the roi_options apply to the ROI,
53530// not the widget itself.  They refer to the ROI box used to navigate
53531// within the widget.  The widget itself will fill its container
53532// with width & height = 100%.
53533//
53534// All are optional, defaults are:
53535// width: the same width as the image
53536// height:50px;
53537// color:#ffffff;
53538// top:0;
53539// left:0;
53540//
53541// event_options include on_load, on_move_start, on_move, on_move_end.
53542// the on_load event is raised after the thumbnail image has completely loaded
53543// and all of the internal widget layout has completed.
53544//
53545// TODO: make the container scroll as the ROI approaches the edge
53546// TODO: compensate for border width when reporting ROI dimensions
53547// TODO: recast in object notation?
53548// TODO: Add scroll_to(row, column)
53549function thumbnail_navigator(container, src, roi_initial_options, event_options)
53550{
53551// public properties
53552  this.container = container;
53553  this.image_src = src;
53554  this.scrollbar_width = roi_initial_options ? (roi_initial_options.scrollbar_width ? roi_initial_options.scrollbar_width : 10) : 10;
53555
53556// public events
53557  this.on_load = event_options ? (event_options.on_load ? event_options.on_load : null) : null;
53558  this.on_move_start = event_options ? (event_options.on_move_start ? event_options.on_move_start : null) : null;
53559  this.on_move = event_options ? (event_options.on_move ? event_options.on_move : null) : null;
53560  this.on_move_end = event_options ? (event_options.on_move_end ? event_options.on_move_end : null) : null;
53561
53562
53563// public methods
53564
53565  // use valid css to set the color, use set_roi_color('#ff00ff');
53566  this.set_roi_color = function(new_color) {
53567    the_roi.setStyle({color:new_color});
53568  }
53569
53570  this.get_roi_color = function() {
53571    return(the_roi.getStyle('borderColor'));
53572  }
53573
53574  this.set_scroll_position = function(row, column) {
53575    if(scrolling || dragging)
53576          return;
53577trace("set_scroll_position  " + row + "," + column);
53578
53579    if(the_vertical_scroller) {
53580        var img_height = the_image.getHeight();
53581        var scroll_val = row/img_height;
53582        the_vertical_scroller.set_position(scroll_val);
53583    }
53584
53585    var roi_off = get_roi_offsets();
53586trace("do scroll to  " + (roi_off.y-row) + "," + column);
53587    do_scroll_to((roi_off.y-row), column);
53588
53589  }
53590
53591  
53592  this.set_roi_position = function(new_top, new_left) {
53593      if(scrolling || dragging)
53594          return;
53595trace("setting position: " + new_left + ", " + new_top);
53596    roi_options.top = new_top;
53597    roi_options.left = new_left;
53598    set_roi_attributes();
53599    recalculateLayout();
53600    do_scroll();
53601  }
53602
53603  // the 'navigate' method differs from the 'set_roi_position'
53604  // method only in that 'navigate' will raise the same 'on_move_start'
53605  // and 'on_move_end' events as manual user interaction, while
53606  // 'set_roi_position' will not.
53607  this.navigate_to = function(new_top, new_left) {
53608      if(scrolling || dragging)
53609          return;
53610trace("navigate_to");
53611
53612    do_drag_start();
53613    this.set_roi_position(new_top, new_left);
53614    do_roi_drag();
53615    do_drag_end();
53616  }
53617
53618  this.set_roi = function(new_top, new_left, new_width, new_height) {
53619      if(scrolling || dragging)
53620          return;
53621trace("set_roi");
53622    roi_options.width = new_width;
53623    roi_options.top = new_top;
53624    roi_options.left = new_left;
53625    roi_options.height = new_height;
53626    set_roi_attributes();
53627    recalculateLayout();
53628    do_scroll();
53629  }
53630
53631  this.get_roi = function() {
53632    return(get_roi_attributes());
53633  }
53634
53635  this.get_image_dimensions = function() {
53636     return(the_image.getDimensions());
53637  }
53638
53639  this.resize = function() {
53640    var roi = this.get_roi();
53641    this.set_roi_position(roi.left, roi.top);
53642  }
53643
53644
53645// private properties
53646  var self = this;
53647  var the_roi = null;
53648  var the_image = null;
53649  var the_wrapper = null;
53650  var the_image_wrapper = null;
53651  var the_vert_scroll_wrap = null;
53652  var the_vertical_scroller = null;
53653
53654  var the_roi_dims = null;
53655  var the_image_dims = null;
53656  var the_wrapper_dims = null;
53657
53658  var roi_options = null;
53659  var last_pos = {x:0, y:0};
53660  var dragging = false;
53661  var scrolling = false;
53662  var prev_column = 0;
53663
53664
53665// private methods
53666
53667  function get_nav_position() {
53668    var image_pos = the_image.positionedOffset();
53669    var roi_left = new Number(the_roi.getStyle('left').replace('px',''));
53670    var nav_left = roi_left - image_pos.left;
53671    var roi_top = new Number(the_roi.getStyle('top').replace('px',''))
53672    var nav_top = roi_top - image_pos.top;
53673
53674    return({left:nav_left, top:nav_top});
53675  }
53676
53677  function get_roi_attributes() {
53678    var pos = get_nav_position();
53679    return({top:pos.top, left:pos.left, width:the_roi_dims.width, height:the_roi_dims.height});
53680  }
53681
53682
53683    function get_image_relative_position(x, y) {
53684        var scroll_pos = the_image.cumulativeScrollOffset();
53685        var image_pos = the_image.cumulativeOffset();
53686        var thumb_relative_mouse_pos = {left:x - (image_pos.left - scroll_pos.left),
53687                                        top:y - (image_pos.top - scroll_pos.top)};
53688        return(thumb_relative_mouse_pos);
53689    }
53690
53691
53692  function in_roi(x, y) {
53693    var nav_pos = get_nav_position();
53694    var dims = the_roi.getDimensions();
53695
53696    return(
53697           (x >= nav_pos.left)
53698            &&
53699           (x <= (dims.width+nav_pos.left))
53700            &&
53701           (y >= nav_pos.top)
53702            &&
53703           (y <= (nav_pos.top + dims.height))
53704          );
53705  }
53706
53707  // returns an anon object describing which side and by how much
53708  // an object extends outside the_wrapper.
53709  function out_of_bounds(obj) {
53710      var wrapper_pos = the_image.cumulativeOffset();
53711      var wrapper_dims = the_image.getDimensions();
53712      var obj_pos = obj.cumulativeOffset();
53713      var obj_dims = obj.getDimensions();
53714
53715      // -1, +2 to compensate for boundary widths
53716      var top = Math.abs( Math.min(0, (obj_pos.top - wrapper_pos.top) ) );
53717      var left = Math.abs( Math.min(0, (obj_pos.left -  wrapper_pos.left) ) );
53718      var right = Math.abs(Math.min(0, (( (wrapper_pos.left + (wrapper_dims.width)) - (obj_pos.left + obj_dims.width) ))));
53719      var bottom = Math.abs(Math.min(0, (( (wrapper_pos.top + (wrapper_dims.height)) - (obj_pos.top + obj_dims.height) ))));
53720
53721      return({top:top, left:left, right:right, bottom:bottom});
53722  }
53723
53724  function do_drag_start(evt) {
53725      evt.stop();
53726
53727      var mouse_pos = get_image_relative_position(evt.clientX, evt.clientY);
53728      if(!in_roi(mouse_pos.left, mouse_pos.top)) {
53729        dragging = false;
53730        return;
53731      }
53732
53733      dragging = true;
53734      last_pos.x = mouse_pos.left;
53735      last_pos.y = mouse_pos.top;
53736
53737      if(self.on_move_start)
53738        self.on_move_start(this, get_nav_position());
53739  }
53740
53741  function do_roi_drag(evt) {
53742     evt.stop();
53743
53744      if(!dragging)
53745          return;
53746
53747      var mouse_pos = get_image_relative_position(evt.clientX, evt.clientY);
53748
53749      var delta = {left:mouse_pos.left - last_pos.x, top:mouse_pos.top - last_pos.y};
53750      var roi_left = new Number(the_roi.getStyle('left').replace('px',''));
53751      var roi_top = new Number(the_roi.getStyle('top').replace('px',''));
53752
53753      last_pos.x = mouse_pos.left;
53754      last_pos.y = mouse_pos.top;
53755
53756      the_roi.setStyle({top:(roi_top + delta.top)+'px', left:(roi_left + delta.left)+'px'});
53757
53758      do_scroll();
53759      setTimeout(send_move, 10);
53760  }
53761
53762  function send_move() {
53763      if(!Prototype.Browser.IE)
53764        if(self.on_move)
53765            self.on_move(this, get_nav_position());
53766  }
53767
53768  // This makes sure the ROI stays on the image
53769  function do_scroll() {
53770      var roi_bounds = out_of_bounds(the_roi);
53771      if(roi_bounds.bottom || roi_bounds.top || roi_bounds.left || roi_bounds.right) {
53772         var roi_top_style = the_roi.getStyle('top') ? the_roi.getStyle('top') : '0';
53773         var roi_left_style = the_roi.getStyle('left') ? the_roi.getStyle('left') : '0';
53774
53775         var roi_top = new Number(roi_top_style.replace('px',''));
53776         var roi_left = new Number(roi_left_style.replace('px',''));
53777
53778         if(roi_bounds.bottom) 
53779            the_roi.setStyle({top:(roi_top-(roi_bounds.bottom-1))+'px'});
53780         
53781         if(roi_bounds.top) 
53782            the_roi.setStyle({top:(roi_top+(roi_bounds.top-1))+'px'});
53783         
53784         if(roi_bounds.left) 
53785            the_roi.setStyle({left:(roi_left+(roi_bounds.left+1))+'px'});
53786         
53787         if(roi_bounds.right) 
53788            the_roi.setStyle({left:(roi_left-(roi_bounds.right-1))+'px'});
53789     } 
53790  }
53791
53792  function do_drag_end(evt) {
53793      evt.stop();
53794
53795      if(Prototype.Browser.IE)
53796        if(self.on_move)
53797            self.on_move(this, get_nav_position());
53798
53799      if(self.on_move_end)
53800        self.on_move_end(this, get_nav_position());
53801      dragging = false;
53802  }
53803
53804  // calculates the sizes of the components (after the
53805  // thumbnail image has loaded)
53806  function recalculateLayout() {
53807    the_roi_dims = the_roi.getDimensions();
53808    the_image_dims = the_image.getDimensions();
53809    the_image_wrapper.setStyle({width:the_image_dims.width+'px'});
53810    the_wrapper_dims = the_image_wrapper.getDimensions();
53811    create_scrollbar();
53812
53813  }
53814
53815  // sets the position & dimensions of the ROI box
53816  // based on the roi_initial_options given in the constructor.
53817  function set_roi_attributes() {
53818//TODO: validate these options are viable given the size of the thumbnail.
53819    var pos = the_image.positionedOffset();
53820    var dims = the_image.getDimensions();
53821    var new_width = roi_options.width == '100%' ? dims.width : roi_options.width;
53822
53823    the_roi.setStyle({width:new_width+'px', height:roi_options.height+'px',
53824                      top:roi_options.top+pos.top+'px', left:roi_options.left+pos.left+'px',
53825                      borderColor:roi_options.color});
53826  }
53827
53828  function setup_roi_listeners() {
53829    // set up the listeners for ROI dragging
53830    the_roi.observe('mousedown',do_drag_start);
53831    the_roi.observe('mousemove',do_roi_drag);
53832    the_roi.observe('mouseup',do_drag_end);
53833    // it's easy for the mouse to get ahead of the roi,
53834    // so we need to listen for these events on the thumbnail image as well
53835    the_image.observe('mousedown',do_drag_start);
53836    the_image.observe('mousemove',do_roi_drag);
53837    the_image.observe('mouseup',do_drag_end);
53838    // If the user leaves the thumbnail area with the mouse down
53839    // we will still be in a dragging state. Listen for a mouse-up
53840    // anywhere on the page to exit dragging mode.
53841    Event.observe(window, 'mouseup', do_drag_end);
53842
53843    // this is for the benefit of a particular browser that causes a
53844    // lot of headaches and about which I will say nothing
53845    // unprofessional in the comments of my code.
53846    var filler = $('div_filler');
53847    filler.observe('mousedown',do_drag_start);
53848    filler.observe('mousemove',do_roi_drag);
53849    filler.observe('mouseup',do_drag_end);
53850
53851  }
53852
53853  // called by the image onload event, calculates dimensions
53854  // of components after the thumbnail has fully loaded.
53855  function do_image_load(evt) {
53856      set_roi_attributes()
53857      recalculateLayout();
53858      setup_roi_listeners();
53859
53860      if(self.on_load)
53861          self.on_load(this);
53862  }
53863
53864  // provide default values for roi attributes not provided in the
53865  // constructor argument "roi_initial_options."
53866  function setup_roi_options() {
53867     roi_options = new Object();
53868     roi_options.color = roi_initial_options ? (roi_initial_options.color ? roi_initial_options.color : '#f00') : '#f00';
53869     roi_options.width = roi_initial_options ? (roi_initial_options.width ? roi_initial_options.width : '100%') : '100%';
53870     roi_options.height = roi_initial_options ? (roi_initial_options.height ? roi_initial_options.height : '50') : '50';
53871     roi_options.top = roi_initial_options ? (roi_initial_options.top ? roi_initial_options.top : '0') : '0';
53872     roi_options.left = roi_initial_options ? (roi_initial_options.left ? roi_initial_options.left : '0') : '0';
53873  }
53874
53875  // create the HTML elements necessary for the widget and add them to the doc.
53876  function create_scaffold() {
53877    // The div that wraps the nav components
53878    the_wrapper = new Element('div', {'class':'thumbnail_widget', id:'tnw_'+THUMBNAIL_NAV_COUNT});
53879    // a container for the image
53880    the_image_wrapper = new Element('div', {'class':'image_wrapper', id:'iw_'+THUMBNAIL_NAV_COUNT});
53881    // The thumbnail image
53882    the_image = new Element('img', {'class':'thumbnail_img', id:'ti_'+THUMBNAIL_NAV_COUNT});
53883    // a container for the scrollbar
53884    the_vert_scroll_wrap = new Element('div', {'class':'thumb_scroll_wrapper', height:'100%', width:(self.scrollbar_width+'px'), id:'thumb_vert_scroll'+THUMBNAIL_NAV_COUNT});
53885
53886    // add them to the container
53887    $(self.container).appendChild(the_wrapper);
53888    $(self.container).appendChild(the_vert_scroll_wrap);
53889    the_wrapper.appendChild(the_image_wrapper);
53890    the_image_wrapper.appendChild(the_image);
53891
53892    // create the ROI element
53893    the_roi = new Element('div',{'class':'roi_box', id:'roib_'+THUMBNAIL_NAV_COUNT});
53894    the_roi.update('<img id="div_filler" style="height:100%;width:100%;cursor:move" src="data:image/gif;base64,R0lGODlhAQABAJH/AP///wAAAMDAwAAAACH5BAEAAAIALAAAAAABAAEAQAICVAEAOw=="/>');
53895    the_image_wrapper.appendChild(the_roi);
53896
53897  }
53898
53899  function create_scrollbar() {
53900
53901    //position the wrapper to the right of the image
53902    var container_dims = the_image_wrapper.getDimensions();
53903    var container_pos = the_image_wrapper.cumulativeOffset();
53904    var image_pos = the_image.cumulativeOffset();
53905    var image_dims = the_image.getDimensions();
53906    var viewport_height = container_dims.height; 
53907
53908    var pos = {top:(container_pos.top + 1)+'px',
53909               left:(image_pos.left + image_dims.width + 1)+'px',
53910               width:self.scrollbar_width+'px',
53911               height:viewport_height+'px'};
53912    the_vert_scroll_wrap.setStyle(pos);
53913
53914    // if we don't need a scrollbar, we're done.
53915    if(image_dims.height <= viewport_height)
53916        return;
53917
53918    if(the_vertical_scroller && the_vert_scroll_wrap.removeChild)
53919        the_vert_scroll_wrap.removeChild($(the_vertical_scroller.id));
53920    the_vertical_scroller = null;
53921
53922    var v_thumb_height = Math.max(10, (Math.min(1, viewport_height/image_dims.height)) * 100);
53923    the_vertical_scroller = new scroller(the_vert_scroll_wrap.id,
53924                                        'vertical',
53925                                        {visible:true,
53926                                        initial_thumb_size:v_thumb_height},
53927                                        {on_move:do_vscroll_move,
53928                                         on_change:do_vscroll_change});
53929  }
53930
53931    function do_vscroll_move(sender, val) {
53932        var img_height = the_image.getHeight() - the_roi.getHeight();
53933        var new_row = -Math.ceil(img_height * val);
53934        
53935        scrolling = true;
53936        do_scroll_to(new_row, prev_column);
53937        scrolling = false;
53938    }
53939
53940    function get_roi_offsets() {
53941        var roi_pos = the_roi.positionedOffset();
53942        var wrap_pos = the_image_wrapper.positionedOffset();
53943        var image_pos = the_image.positionedOffset();
53944        var img_margin = new Number($(the_image_wrapper).getStyle('margin-top').replace('px',''));
53945
53946        return({x:(roi_pos.left - image_pos.left), y:(roi_pos.top + img_margin - wrap_pos.top)});
53947    }
53948
53949    function do_scroll_to(row, column) {
53950        scrolling = true;
53951
53952        the_image.setStyle({top:row+'px'});
53953        // if the column has changed, move the ROI rather than the image
53954        if(column != prev_column) {
53955            var roi_off = get_roi_offsets();
53956            roi_options.top = -(row-roi_off.y);
53957            roi_options.left = column;
53958            set_roi_attributes();
53959            recalculateLayout();
53960            prev_column = column;
53961        }
53962        do_scroll();
53963        
53964        if(self.on_move)
53965            self.on_move(this, get_nav_position());
53966
53967        scrolling = false;
53968    }
53969
53970    function do_vscroll_change(sender, val) {
53971/*        if(navigating)
53972            return;
53973        var new_row = Math.ceil(self.tile_source.data_rows * val);
53974        navigate(current_column_index, new_row, true);
53975*/    }
53976
53977  function init() {
53978
53979    setup_roi_options();
53980    create_scaffold();
53981    // position the roi after the image has loaded
53982    the_image.observe('load', do_image_load);
53983    the_image.src = self.image_src;
53984
53985    // update the global count of widgets
53986    THUMBNAIL_NAV_COUNT++;
53987  }
53988
53989  init();
53990}
53991;
53992
53993var __tiled_data_table_count__ = 0;
53994
53995function tiled_data_table(container, column_definitions, initial_scale, event_options, options) {
53996
53997// public properties
53998    this.container = $(container);
53999    this.column_defs = column_definitions;
54000    this.scale = initial_scale;
54001    this.visible_rows = 0;
54002    this.data_rows = 0;
54003    this.show_selected = (options ? (options.show_selected ? options.show_selected : false) : false);
54004    this.index = -1;
54005    this.selections = (options ? (options.selections ? options.selections : {}) : {});
54006
54007// event hooks
54008    this.on_column_click = event_options ? event_options.on_column_click : null;
54009    this.on_select_click = event_options ? event_options.on_select_click : null;
54010    this.on_row_click = event_options ? event_options.on_row_click : null;
54011    this.on_row_over = event_options ? event_options.on_row_over : null;
54012
54013
54014// public methods
54015    this.set_scale = function(new_height) {
54016        var font_size = Math.min(14, Math.max(0, (new_height-4)));
54017        for(var i = 1; i < the_table.rows.length; i++) {
54018            $(the_table.rows[i]).setStyle({height:new_height+'px', overflow:'hidden'});
54019            $(the_table.rows[i].cells[0]).setStyle({height:new_height+'px', fontSize:font_size+3+'px', overflow:'hidden', lineHeight: new_height+'px'});
54020            for(var j = 1; j < the_table.rows[i].cells.length; j++) {
54021                $(the_table.rows[i].cells[j]).setStyle({height:new_height+'px', fontSize:font_size+'px'});
54022            }
54023        }
54024        this.scale = new_height;
54025        this.visible_rows = Math.ceil(this.container.getHeight() / this.scale);
54026    }
54027
54028    this.highlight_row = function(row) {
54029        if(highlighted_row)
54030            highlighted_row.removeClassName('highlighted');
54031
54032        if(row+1 > this.data_rows)
54033            return;
54034
54035        // +1 because row 0 is the column header
54036        highlighted_row = $(the_table.rows[(row+1)]);
54037
54038        if(highlighted_row)
54039            highlighted_row.addClassName('highlighted');
54040    }
54041
54042    this.highlight_none = function() {
54043        if(highlighted_row)
54044            highlighted_row.removeClassName('highlighted');
54045    }
54046
54047    this.select_row = function(row) {
54048        row -= data_row_offset;
54049
54050        if(selected_row)
54051            selected_row.removeClassName('selected_row');
54052        
54053        if(row+1 > this.data_rows)
54054            return;
54055
54056        // +1 because row 0 is the column header
54057        selected_row = $(the_table.rows[(row+1)]);
54058
54059        if(selected_row) {
54060            selected_row.addClassName('selected_row');
54061            selected_row_data_id = selected_row.getAttribute('data_id');
54062        }
54063    }
54064
54065    this.clear_data = function(first_column_only) {
54066        var col_count = first_column_only ? 1 : this.column_defs.length;
54067
54068        for(var i = 0; i < this.visible_rows; i++) {
54069            var row = the_table.rows[i+1];
54070            for(var col_index = 0; col_index < col_count; col_index++) {
54071                if( col_index == 0)
54072                   $(row.cells[0]).setStyle({visibility:'hidden'});
54073                else
54074                   row.cells[col_index].innerHTML = '';
54075            }
54076        }
54077    }
54078
54079
54080    this.set_data = function(new_data, row_offset) {
54081        data_row_offset = row_offset;
54082        this.data_rows = new_data.length;
54083
54084        if(selected_row)
54085            selected_row.removeClassName('selected_row');
54086
54087        // if we're showing selection checkboxes, there's one more column
54088        // that has no corresponding entry in this.column_defs.  This means we need
54089        // two different indexes, one into the html table columns and one into column_defs.
54090        var start_index = (self.show_selected ? 1 : 0);
54091        var col_count = this.column_defs.length + start_index;
54092
54093        // for each row in the data set...
54094        for(var i = 0; i < new_data.length; i++) {
54095            if(new_data[i]) {
54096                // set the selection checkbox, if applicable
54097                if(self.show_selected) {
54098                    var checker = $('r'+i+'_'+self.index);
54099                    if(checker) {
54100                      checker.setAttribute('data_id', new_data[i].id);
54101                      checker.setStyle({visibility:'visible'});
54102                      mark_selected(checker, is_selected(new_data[i].id));
54103                    }
54104                }
54105                // set the data for each cell, according to the column definitions
54106                var row = the_table.rows[i+1];
54107                row.setAttribute('data_id', new_data[i].id);
54108                if(selected_row_data_id == new_data[i].id) {
54109                    selected_row = row;
54110                    selected_row.addClassName('selected_row');
54111                }
54112                for(var col_index = start_index; col_index < col_count; col_index++) {
54113                    var index = col_index - start_index;
54114                    var cell = row.cells[col_index];
54115
54116                    cell.title = '';   // clear the title display set in special case
54117                    cell.setAttribute('class', 'dtc_gene_symbol'); // set class to default one
54118
54119                    var data = new_data[i] && this.column_defs[index].name ? new_data[i][this.column_defs[index].name] : '';
54120                    cell.innerHTML = data;
54121
54122                    if ( data == undefined || data == '')    // special case - when NHP gene info is missing, display probe info instead
54123                    {
54124                        data = new_data[i] && this.column_defs[index].alternate_name ? new_data[i][this.column_defs[index].alternate_name] : '';
54125                        cell.innerHTML = data;
54126                        cell.title = 'Gene info is unavailable. This is the Affymetrix probe set name.';
54127                        cell.setAttribute('class', 'dtc_gene_symbol_grayed');
54128
54129                    }
54130
54131                }
54132            }
54133        }
54134
54135        // for the rest of the visible rows, clear any existing data
54136        // and hide the check boxes
54137        for(var j = i; j < this.visible_rows; j++) {
54138            row = the_table.rows[j+1];
54139            for(col_index = 0; col_index < col_count; col_index++) {
54140                if(self.show_selected && col_index == 0) {
54141                    var check = $('r'+j+'_'+self.index);
54142                    if(check)
54143                        check.setStyle({visibility:'hidden'});
54144
54145                }
54146                else
54147                   row.cells[col_index].innerHTML = '';
54148            }
54149        }
54150    }
54151
54152    this.get_column_width = function(column_index) {
54153        if(column_index >= the_header.cells.length)
54154            return(0);
54155
54156        return($(the_header.cells[column_index]).getWidth());
54157    }
54158
54159    this.get_visible_column_width = function() {
54160        var a = 0;
54161        for(var i = 0; i < the_header.cells.length-1; i++)
54162            a += $(the_header.cells[i]).getWidth()
54163
54164        return(a);
54165    }
54166
54167    this.set_column_sort_icon = function(column_label, sort) {
54168        do_column_sort_icon(column_label, sort);
54169    }
54170
54171// private properties
54172    var self = this;
54173    var the_wrapper = null;
54174    var the_table = null;
54175    var the_header = null;
54176    var highlighted_row = null;
54177    var selected_row = null;
54178    var selected_row_data_id = -1;
54179    var data_row_offset = 0;
54180
54181
54182// private methods
54183    function build_scaffold() {
54184        the_wrapper = new Element('div', {id:'tdtw_1'+__tiled_data_table_count__, 'class':'data_table_wrapper'});
54185        the_table = new Element('table', {id:'tdt_1'+__tiled_data_table_count__, 'class':'data_table'});
54186        build_rows(the_table);
54187        self.container.appendChild(the_wrapper);
54188        the_wrapper.appendChild(the_table);
54189    }
54190
54191    var the_header_buttons = [];
54192    function build_rows(table) {
54193        // we will theoretically get as small as 1 pixel per row.
54194        var row_count = self.container.getHeight();
54195        var col_count = self.column_defs.length;
54196
54197        the_header = table.insertRow(-1);
54198        the_header.className = 'data_header_row';
54199
54200        // insert <th> for the checkbox column
54201        if(self.show_selected) {
54202            var th = the_header.insertCell(-1);
54203            th.className = 'data_header_column header_column_checkbox';
54204        }
54205
54206        // insert <th> for all the rest of the columns
54207        var button_index = 0;
54208        for(var h = 0; h < col_count; h++) {
54209            var th = the_header.insertCell(-1);
54210            var class_name = 'data_header_column header_column_' + self.column_defs[h
54210].id;
54211            th.className = class_name;
54212            if(self.column_defs[h].title)
54213                th.title = self.column_defs[h].title;
54214
54215            // if the column is sortable, make a button for it
54216            if(self.column_defs[h].sortable) {
54217                var column_button = new Element('div', {id:('button_'+button_index++), 'className':'column_button'});
54218                column_button.observe('click', do_column_click);
54219                th.appendChild(column_button);
54220                $j(column_button).button();
54221                the_header_buttons.push(column_button);
54222                if(self.column_defs[h].title)
54223                    column_button.title = self.column_defs[h].title;
54224            }
54225        }
54226        
54227        // one more column to fill any empty space
54228        th = the_header.insertCell(-1);
54229        class_name = 'data_header_column header_column_filler';
54230        th.className = class_name;
54231
54232        // make each row
54233        for(var i = 0; i < row_count; i++) {
54234           var row = $(table.insertRow(-1));
54235           row.setAttribute('row_index', i);
54236           row.observe('mouseover', do_row_over);
54237           row.observe('click', do_row_click);
54238
54239           var cell = null;
54240           // make the checkbox cell
54241           if(self.show_selected) {
54242               cell = $(row.insertCell(-1));
54243               cell.className = 'soft_check_cell';
54244               var check = new Element('span', {id:'r'+i+'_'+__tiled_data_table_count__, 'class':'soft_check'});
54245               check.update('&#10065;'); // this is an empty box; 9746 or 9745 are checked
54246               check.observe('click', do_select_click);
54247               check.setAttribute('row_index', i);
54248               check.title = 'Click to add to or remove from selections';
54249               cell.appendChild(check);
54250           }
54251
54252           // make the cells for each column in this row
54253           for(var j = 0; j < col_count; j++) {
54254               cell = $(row.insertCell(-1));
54255               cell.observe('mouseover', do_row_over);
54256               cell.observe('click', do_row_click);
54257               cell.setAttribute('row_index', i);
54258               cell.className = 'dtc_' + self.column_defs[j].id;
54259           }
54260           // the filler row...
54261           row.insertCell(-1);
54262        }
54263    }
54264
54265    function label_columns() {
54266        var button_index = 0;
54267        var start_index = (self.show_selected ? 1 : 0);
54268        var col_count = self.column_defs.length + start_index;
54269
54270        for(var h = start_index; h < col_count; h++) {
54271            var col_index = h - (self.show_selected ? 1 : 0);
54272            if(self.column_defs[col_index].sortable) {
54273               var header = $j(the_header_buttons[button_index++]);
54274               header.button('option', 'label', self.column_defs[col_index].label);
54275               switch(self.column_defs[col_index].sort) {
54276                 case 'asc':
54277                     header.button('option', 'icons', {secondary:'ui-icon-triangle-1-n'});
54278                     break;
54279                 case 'desc':
54280                     header.button('option', 'icons', {secondary:'ui-icon-triangle-1-s'});
54281                     break;
54282                 case 'none':
54283                    break;
54284               }
54285            }
54286            else
54287                $(the_header.cells[h]).update(self.column_defs[col_index].label);
54288        }
54289    }
54290
54291    function do_row_over(evt) {
54292      evt = (evt) ? evt : event;
54293      var sender = $(((evt.target) ? evt.target : evt.srcElement));
54294
54295      var row_index = new Number(sender.getAttribute('row_index'));
54296      self.highlight_row(row_index);
54297
54298      if(self.on_row_over)
54299          self.on_row_over(self, row_index+data_row_offset);
54300    }
54301
54302    function do_cell_over(evt) {
54303      evt = (evt) ? evt : event;
54304      var sender = $(((evt.target) ? evt.target : evt.srcElement));
54305
54306      var row = sender.parentNode.parentNode;
54307      var row_index = new Number(sender.getAttribute('row_index'));
54308
54309      self.highlight_row(row_index+data_row_offset);
54310    }
54311
54312    function do_row_click(evt) {
54313      evt = (evt) ? evt : event;
54314      var sender = $(((evt.target) ? evt.target : evt.srcElement));
54315
54316      var row_index = new Number(sender.getAttribute('row_index'));
54317
54318      if(row_index+1 > self.data_rows)
54319          return;
54320
54321      row_index += data_row_offset;
54322
54323      self.select_row(row_index);
54324      
54325      if(self.on_row_click)
54326          self.on_row_click(self, row_index);      
54327    }
54328
54329    function do_select_click(evt) {
54330      evt = (evt) ? evt : event;
54331      var sender = $(((evt.target) ? evt.target : evt.srcElement));
54332
54333      var id = sender.getAttribute('data_id');
54334      var new_state = !is_selected(id);
54335      mark_selected(sender, new_state);
54336      set_selected(id, new_state);
54337
54338      if(id && self.on_select_click)
54339          self.on_select_click(self, id, new_state);
54340    }
54341
54342
54343    function is_selected(id) {
54344        return(id in self.selections);
54345    }
54346
54347    function mark_selected(box, checked) {
54348        if(checked)
54349            box.update(' &#9745;');
54350        else
54351           box.update(' &#10065;');
54352
54353       box.setAttribute('checked', checked);
54354    }
54355
54356    function set_selected(id, selected) {
54357        // add or remove the id from the selection set
54358        if(selected) {
54359            self.selections[id] = true;
54360        } else {
54361            delete self.selections[id];
54362        }
54363    }
54364
54365    function do_column_click(evt) {
54366      evt = (evt) ? evt : event;
54367      var sender = ((evt.target) ? evt.target : evt.srcElement);
54368      var button_index = new Number(sender.parentNode.id.replace('button_', ''));
54369      var label = $j(the_header_buttons[button_index]).button('option', 'label');
54370
54371      if(self.on_column_click)
54372          self.on_column_click(self, label);
54373    }
54374
54375    function do_column_sort_icon(column_label, sort) {
54376        for(var i = 0; i < the_header_buttons.length; i++) {
54377            var header = $j(the_header_buttons[i]);
54378            var label = header.button('option', 'label');
54379            if(label == column_label) {
54380               switch(sort) {
54381                 case 'asc':
54382                     header.button('option', 'icons', {secondary:'ui-icon-triangle-1-n'});
54383                     break;
54384                 case 'desc':
54385                     header.button('option', 'icons', {secondary:'ui-icon-triangle-1-s'});
54386                     break;
54387                 case 'none':
54388                     header.button('option', 'icons', {secondary:null});
54389                     break;
54390                 default:
54391               }
54392           }
54393        }
54394    }
54395
54396    function init() {
54397        build_scaffold();
54398        label_columns();
54399        self.set_scale(self.scale);
54400        self.index = __tiled_data_table_count__++;
54401    }
54402
54403    init();
54404}
54405
54406;
54407
54408var __tiled_image_window__ = 0;
54409
54410
54411// There are three different coordinate spaces to reconcile in the tiled_image_window widget:
54412// data space, which is the matrix of data we're representing as a heatmap image,
54413// tile space, which is the matrix of sub-matrices that make up the entire data set.
54414// display space, which is a projection of (or viewport on) a portion of tile space
54415// rendered in the html page as a set of images.  When this widget renders the images,
54416// we may want to show more than one pixel per data point, verticaly and/or horizontaly.
54417//
54418// this.tile_source understands tile_space coordinates, which it
54419// translates into data space in order to request information from the data service.
54420// This widget needs to translate tile space into display space, among other things.
54421//
54422// this clean separation of coordinate spaces is complicated somewhat bacause the
54423// image service is capable of sending an image that uses something other than
54424// a single  pixel to represent a single data point.  Due to some artifacts
54425// introduced by browser image scaling, it may be desireable to have the
54426// image service  deliver an image that uses (for example) 4 vertical pixels
54427// for each data point.  Likewise more than one horizontal pixel may be used
54428// to represent a single data point.
54429//
54430// The upshot is that, when making the translation from the data/tile layer
54431// to the display layer, we have to take into account the pixel-per-data point
54432// attributes.
54433function tiled_image_window(container, tile_source, pixel_format, options, event_options)
54434{
54435//public properties
54436    this.tile_source = tile_source;
54437    this.container = container;
54438    this.viewport_dims = null;
54439    this.viewport_origin = {top:0, left:0};
54440    this.pixel_format = pixel_format; //{source_vert_pixels_per_row, display_vert_pixels_per_row, source_horiz_pixels_per_column, scale_horiz.}
54441    this.drag_content = options ? (options.drag_content ? options.drag_content : false) : false;
54442
54443    this.scrollbar_width = options ? (options.scrollbar_width ? options.scrollbar_width : 10) : 10;
54444    this.trace_function = null;  // assign a function(msg) to this to handle debug output
54445    this.index = -1;
54446
54447// event hooks
54448    this.on_position_change = event_options ? (event_options.on_position_change ? event_options.on_position_change : null) : null;
54449    this.on_layout_change = event_options ? (event_options.on_layout_change ? event_options.on_layout_change : null) : null;
54450    this.on_mouse_over = event_options ? (event_options.on_mouse_over ? event_options.on_mouse_over : null) : null;
54451    this.on_mouse_out = event_options ? (event_options.on_mouse_out ? event_options.on_mouse_out : null) : null;
54452    this.on_mouse_click = event_options ? (event_options.on_mouse_click ? event_options.on_mouse_click : null) : null;
54453    this.on_load = event_options ? (event_options.on_load ? event_options.on_load : null) : null;
54454
54455
54456// public methods
54457
54458    this.get_position = function() {
54459        return({column:current_column_index, row:current_row_index});
54460    }
54461
54462    this.set_position = function(column, row) {
54463        navigate(column, row, false);
54464    }
54465
54466    this.get_data_size = function() {
54467        return({height:rows_per_viewport, width:columns_per_viewport});
54468    }
54469
54470    // this and the set_position method are the same, except
54471    // this navigate_to method will raise navigation events, set_position won't.
54472    this.navigate_to = function(column, row) {
54473        navigate(column, row, true);
54474    }
54475
54476    this.resize = function() {
54477        recalculate_layout();
54478        raise_layout_change();
54479        refresh();
54480    }
54481
54482    this.get_scale = function() {
54483        return({width:scale_width, height:scale_height});
54484    }
54485
54486    this.set_scale = function(width, height) {
54487        do_set_scale(width, height);
54488    }
54489
54490    this.hide = function() {
54491        the_viewport.hide();
54492    }
54493
54494    this.show = function() {
54495        the_viewport.show();
54496    }
54497
54498    this.refresh = function() {
54499        refresh();
54500    }
54501
54502    this.has_dimension = function() {
54503      if(the_viewport && initialized) {
54504          var dims = the_viewport.getDimensions();
54505          if(dims.width > 0)
54506              return(true);
54507      }
54508      return(false);
54509    }
54510
54511    // non-obvious alert!  If the initial scale is given as 0 the heatmap will
54512    // rescale to fit to the container each time the layout is recalculated.
54513    // this can be cool in some situations, but it can also lead to a change
54514    // in aspect ratio, if the user manually rescales after the heat map
54515    // automatically rescales.  Once the aspect ratio has changed it is practically
54516    // impossible for the user to get back to the original image that fits
54517    // the original-sized container.  Locking the values for scale here
54518    // assures that once we've established an aspect ratio, it never changes.
54519    // The net effect is that the heatmap will pan rather than scale when its
54520    // container is resized.
54521    this.lock_aspect_ratio = function() {
54522        this.pixel_format.scale_height = scale_height;
54523        this.resize();
54524    }
54525
54526// private properties
54527
54528    var self = this;
54529    var initialized = false;
54530    var the_tiles = new Object();
54531
54532    // references to the HTML Elements of this widget
54533    var the_viewport = null;
54534    var the_canvas = null;
54535    var the_vert_scroll_wrap = null;
54536    var the_horiz_scroll_wrap = null;
54537
54538    // the scrollbar widgets
54539    var the_vertical_scroller = null;
54540    var the_horizontal_scroller = null;
54541    var navigating = false;
54542
54543    // the data set could be represented as a matrix
54544    // 1 pixel high & 1 pixel wide per data point, but we may
54545    // be using more than one pixel per data point.
54546    var tile_pixel_height = 0;
54547    var tile_pixel_width = 0;
54548
54549    // the width & height of the image you'd get if you stitched
54550    // the entire data set into a single image, one pixel per point.
54551    var image_data_width = 0;
54552    var image_data_height = 0;
54553
54554    // are we force fitting the image into the viewport?
54555    var scale_to_width = false;
54556    var scale_to_height = false;
54557
54558    // how much to stretch the tiles horizontally/vertically to fit in the viewport
54559    var scale_width = 1.0;
54560    var scale_height = 1.0;
54561    var pixel_aspect_ratio = 1.0;
54562
54563    // how many rows & columns of data can we show,
54564    // given the possible scaling and pixel formatting?
54565    var rows_per_viewport = 0;
54566    var columns_per_viewport = 0;
54567
54568    var tile_rows_per_viewport = 0;
54569    var tile_columns_per_viewport = 0;
54570
54571    // the coordinates into the data set we're currently displaying.
54572    var current_row_index = 0;
54573    var current_column_index = 0;
54574
54575    // tracking mouse up/down to determine if the event was part of a click or a drag.
54576    var click_x = 0;
54577    var click_y = 0;
54578
54579
54580// private methods
54581
54582
54583    function build_scaffold() {
54584        the_viewport = new Element('div', {'class':'tiled_image_window', id:'tiw_1'+__tiled_image_window__});
54585        the_canvas = new Element('div', {'class':'tiled_image_canvas', id:'tc_1'+__tiled_image_window__});
54586        the_vert_scroll_wrap = new Element('div', {height:'100%', width:(self.scrollbar_width+'px'), id:'vert_scroll', 'class':'vert_scroll'});
54587        the_horiz_scroll_wrap = new Element('div', {width:'100%', height:(self.scrollbar_width+'px'), id:'horiz_scroll', 'class':'horiz_scroll', display:'none'});
54588        self.container.appendChild(the_viewport);
54589        the_viewport.appendChild(the_canvas);
54590        self.container.parentNode.appendChild(the_vert_scroll_wrap);
54591        self.container.parentNode.appendChild(the_horiz_scroll_wrap);
54592
54593        the_canvas.observe('mousemove', do_canvas_over);
54594        the_canvas.observe('mousedown', prepare_canvas_click);
54595        the_canvas.observe('mouseup', do_canvas_click);
54596        the_canvas.observe('mouseout', do_canvas_out);
54597    }
54598
54599    function do_vscroll_move(sender, val) {
54600        if(navigating)
54601            return;
54602        var new_row = Math.ceil(self.tile_source.data_rows * val);
54603        navigate(current_column_index, new_row, true);
54604    }
54605
54606    function do_vscroll_change(sender, val) {
54607        if(navigating)
54608            return;
54609        var new_row = Math.ceil(self.tile_source.data_rows * val);
54610        navigate(current_column_index, new_row, true);
54611    }
54612
54613    function do_hscroll_move(sender, val) {
54614        if(navigating)
54615            return;
54616        var new_col = Math.ceil(self.tile_source.data_columns * val);
54617        navigate(new_col, current_row_index, true);
54618    }
54619
54620    function do_hscroll_change(sender, val) {
54621        if(navigating)
54622            return;
54623        var new_col = Math.ceil(self.tile_source.data_columns * val);
54624        navigate(new_col, current_row_index, true);
54625    }
54626
54627    // given the display coordinates (probably from a mouse event)
54628    // return the heatmap-relative coordinates.
54629    function display_space_to_map_space(x, y) {
54630        var off = the_viewport.cumulativeOffset();
54631        var scrolloff = the_viewport.cumulativeScrollOffset();
54632
54633        var column = Math.ceil(x + scrolloff.left - off.left);
54634        var row = Math.ceil(y + scrolloff.top - off.top);
54635        return({column:column, row:row});
54636    }
54637
54638    // given heatmap-relative coordinates, return data space coordinates
54639    function map_space_to_data_space(x, y) {
54640        var column = Math.floor((x / scale_width)) + current_column_index;
54641        var row = Math.floor((y / scale_height)) + current_row_index;
54642
54643       return({column:column, row:row});
54644    }
54645
54646    function do_canvas_over(evt) {
54647        var map_coords = display_space_to_map_space(evt.clientX, evt.clientY);
54648        var data_coords = map_space_to_data_space(map_coords.column, map_coords.row);
54649
54650        if(data_coords.row > self.tile_source.data_rows)
54651            return;
54652        
54653        if(self.on_mouse_over)
54654            self.on_mouse_over(self, {column:data_coords.column,
54655                                      row:data_coords.row,
54656                                      offset_column:current_column_index,
54657                                      offset_row:current_row_index,
54658                                      clientX:evt.clientX,
54659                                      clientY:evt.clientY});
54660    }
54661
54662    function do_canvas_out(evt) {
54663        var map_coords = display_space_to_map_space(evt.clientX, evt.clientY);
54664        var data_coords = map_space_to_data_space(map_coords.column, map_coords.row);
54665
54666        if(self.on_mouse_out)
54667            self.on_mouse_out(self, {column:data_coords.column,
54668                                      row:data_coords.row,
54669                                      offset_column:current_column_index,
54670                                      offset_row:current_row_index,
54671                                      clientX:evt.clientX,
54672                                      clientY:evt.clientY});
54673    }
54674
54675    function do_canvas_click(evt) {
54676
54677        if(click_x != evt.clientX || click_y != evt.clientY)
54678            return;
54679
54680        var map_coords = display_space_to_map_space(evt.clientX, evt.clientY);
54681        var data_coords = map_space_to_data_space(map_coords.column, map_coords.row);
54682
54683        var position = {column:data_coords.column,
54684                                      row:data_coords.row,
54685                                      offset_column:current_column_index,
54686                                      offset_row:current_row_index,
54687                                      clientX:evt.clientX,
54688                                      clientY:evt.clientY};
54689
54690        if(self.on_mouse_click)
54691            self.on_mouse_click(self, position);
54692    }
54693
54694    function prepare_canvas_click(evt) {
54695        click_x = evt.clientX;
54696        click_y = evt.clientY;
54697    }
54698
54699    function get_viewport_rect() {
54700        var dims = the_viewport.getDimensions();
54701        var pos = the_viewport.positionedOffset();
54702        return({top:pos.top, left:pos.left, width:dims.width, height:dims.height});
54703    }
54704
54705
54706    // calculate the 2D range of tiles that are required given the current
54707    // data row & column indices.
54708    function calculate_required_tiles() {
54709
54710      var tile_start_row = Math.floor( current_row_index / self.tile_source.tile_height );
54711      var tile_row_offset = Math.ceil( current_row_index - (tile_start_row * self.tile_source.tile_height) );
54712      //var tile_end_row =  Math.ceil(( ((current_row_index + rows_per_viewport) * scale_height)/ (self.tile_source.tile_height / scale_height))) -1 ;
54713      var tile_end_row = tile_start_row + tile_rows_per_viewport;// / self.tile_source.tile_height);
54714      tile_end_row = Math.min(tile_end_row, self.tile_source.tiles_high-1);
54715
54716      var tile_start_column = Math.floor( current_column_index / self.tile_source.tile_width );
54717      var tile_column_offset = Math.ceil( current_column_index - (tile_start_column * self.tile_source.tile_width) );
54718      var tile_end_column = Math.floor( ((current_column_index + columns_per_viewport) / self.tile_source.tile_width) )-1;
54719      tile_end_column = Math.max(0, Math.min(tile_end_column, self.tile_source.tiles_wide-1));
54720
54721      var ret = {start_row:tile_start_row,
54722              row_offset:tile_row_offset,
54723              end_row:tile_end_row,
54724              start_column:tile_start_column,
54725              column_offset:tile_column_offset,
54726              end_column:tile_end_column};
54727      return(ret);
54728    }
54729
54730
54731    // the tile_source may choose to continue to cache some or all
54732    // of these un-needed tiles, but this widget should release them.
54733    function release_unneeded_tiles(tile_coords) {
54734        return;  // do nothing and let heatmap_tiler keep all the image info
54735//        for(var col in self.tile_source.tiles) {
54736//            for(var row in col) {
54737//                if(self.tile_source.tiles[col][row]) {
54738//                   if(row < tile_coords.start_row
54739//                       ||
54740//                      row > tile_coords.end_row
54741//                       ||
54742//                      col < tile_coords.start_column
54743//                       ||
54744//                      col > tile_coords.end_column
54745//                    ) {
54746//                        self.tile_source.release_tile(col, row);
54747//                    }
54748//                }
54749//            }
54750//        }
54751    }
54752
54753
54754    var new_tile_count = 0;
54755    // Set the src attribute of the img elements in the canvas to match the
54756    // given range of tiles, and shift the canvas under the viewport to show
54757    // the proper rows & columns.
54758    function set_tile_srcs(tile_coords) {
54759try {
54760
54761        // if there are any existing, get rid of them
54762        var imgs = the_canvas.select('img');
54763        for(var i = 0; i < imgs.length; i++)
54764            imgs[i].src = "/assets/heatmap_ui/blank.gif";
54765
54766
54767        // set the src for each of the imgs in the canvas
54768        for(var col = tile_coords.start_column; col <= tile_coords.end_column; col++) {
54769            for(var row = tile_coords.start_row; row <= tile_coords.end_row; row++) {
54770               var tile = self.tile_source.get_tile(col, row);
54771               var img;
54772               if(tile)
54773                  img = the_tiles[col-tile_coords.start_column][row-tile_coords.start_row];
54774               if(img) {
54775                  img.src = tile ? tile.src : "/assets/heatmap_ui/blank.gif";
54776
54777                  // scale the last tile appropriately
54778                  if (row == self.tile_source.tiles_high - 1 && self.tile_source.data_rows % self.tile_source.maximum_tile_height != 0) {
54779                      scaled_tile_height = (self.tile_source.data_rows % self.tile_source.maximum_tile_height) * scale_height;
54780                      img.setStyle({height: scaled_tile_height + 'px'})
54781                  } else {
54782                      img.setStyle({height: tile_pixel_height + 'px'})
54783                  }
54784               }
54785            }
54786        }
54787        //adjust the position of the canvas that lies behind the viewport
54788        the_canvas.setStyle({top:(-tile_coords.row_offset * scale_height)+'px',
54789                                left:(-tile_coords.column_offset * scale_width)+'px'});
54790} catch(e) { trace("error setting tile src: " + e.message); }
54791    }
54792
54793
54794    // call this after any change that would affect how many tiles would
54795    // fit in the viewport.  FOr example a page resize, a change to the
54796    // vertical or horizontal scaling/zooming, etc.
54797    //
54798    // This is a relatively expensive operation.
54799    function recalculate_layout() {
54800
54801        var view_rect = get_viewport_rect();
54802
54803		// part of hiding/reshowing update -- might not be necessary
54804 		if (view_rect.width == 0 && view_rect.height == 0)
54805 			return;
54806
54807        // the source images may have other than one pixel per data point.
54808        var source_pixel_dims = self.tile_source.get_pixel_dimensions();
54809
54810        // the size (in data coords) of the complete image if all the tiles were stitched togehter
54811        image_data_width = self.tile_source.tile_width * self.tile_source.tiles_wide;
54812        image_data_height = self.tile_source.tile_height * self.tile_source.tiles_high;
54813
54814        // are we scaling tiles to fit the container?
54815        scale_to_width = (self.pixel_format.scale_width == 0);
54816        scale_to_height = (self.pixel_format.scale_height == 0);
54817
54818        // calculate the scale factor if fitting, or use the user-supplied value.
54819        // NOTE that the images coming from the tile source may 
54819not be 1 pixel
54820        // per data point, so we may need to adjust the user-supplied scale.
54821        if(scale_to_width)
54822            scale_width = view_rect.width / (self.tile_source.tile_width * self.tile_source.tiles_wide * source_pixel_dims.width);
54823        else
54824            scale_width = self.pixel_format.scale_width/source_pixel_dims.width;
54825
54826        if(scale_to_height)
54827            scale_height = view_rect.height / (self.tile_source.tile_height * self.tile_source.tiles_high * source_pixel_dims.height)
54828        else
54829            scale_height = self.pixel_format.scale_height;
54830
54831        scale_height = Math.ceil(scale_height);
54832
54833        pixel_aspect_ratio = scale_height / scale_width;
54834
54835        // how big are the tiles after scaling?
54836        tile_pixel_width = self.tile_source.tile_width * scale_width;
54837        tile_pixel_height = self.tile_source.tile_height * scale_height;
54838
54839        // rows & columns that fit in the viewport, in data coords.
54840        rows_per_viewport = Math.ceil((view_rect.height) / (scale_height));
54841        columns_per_viewport = Math.ceil(Math.min((view_rect.width / (scale_width * source_pixel_dims.width)), image_data_width));
54842
54843        tile_rows_per_viewport = Math.min(Math.ceil(view_rect.height / (scale_height * self.tile_source.tile_height)), self.tile_source.tiles_high);
54844        tile_columns_per_viewport = Math.min(Math.ceil(view_rect.width / (scale_width * self.tile_source.tile_width)), self.tile_source.tiles_wide);
54845
54846        if (view_rect.height > 0) {
54847            // do we need scrollbars?
54848            create_scrollbars(columns_per_viewport, rows_per_viewport, view_rect);
54849
54850            // create the img elements
54851            create_tile_elements(view_rect);
54852        }
54853    }
54854
54855    function create_scrollbars(columns_per_viewport, rows_per_viewport, view_rect) {
54856
54857        var h_scroll_needed = (columns_per_viewport > 0) && Math.ceil(columns_per_viewport) < self.tile_source.data_columns;
54858        var v_scroll_needed = (rows_per_viewport > 0) && Math.ceil(rows_per_viewport) < self.tile_source.data_rows;
54859
54860        if(v_scroll_needed) {
54861
54862            //var v_thumb_height = Math.max(10, (Math.min(1, rows_per_viewport/(self.tile_source.data_rows))) * 100);
54863            // 32 is the size of the heights of the up & down scroll buttons
54864            var v_thumb_height = Math.max(0, Math.min((view_rect.height - 48), Math.max(16, (view_rect.height - 32) * (rows_per_viewport/(self.tile_source.data_rows - rows_per_viewport)))));
54865            the_vert_scroll_wrap.setStyle({position:'absolute',
54866                                           top:(view_rect.top+24)+'px',
54867                                           left:(view_rect.left+view_rect.width)+'px',
54868                                           height:view_rect.height+'px',
54869                                           width:(self.scrollbar_width)+'px',
54870                                            border:'0px solid #f00'});
54871
54872            if(the_vertical_scroller && the_vert_scroll_wrap.removeChild) {
54873                the_vert_scroll_wrap.removeChild($(the_vertical_scroller.id));
54874            }
54875            the_vertical_scroller = null;
54876
54877            var position = current_row_index/(self.tile_source.data_rows ? self.tile_source.data_rows : current_row_index);
54878            the_vertical_scroller = new scroller(the_vert_scroll_wrap,
54879                                                'vertical',
54880                                                {visible:true,
54881                                                 initial_thumb_size:v_thumb_height,
54882                                                 initial_position:position},
54883                                                {on_move:do_vscroll_move,
54884                                                 on_change:do_vscroll_change,
54885                                                 on_button1_click:do_scroll_up_click,
54886                                                 on_button2_click:do_scroll_down_click});
54887        } else
54888            if(the_vertical_scroller)
54889                the_vertical_scroller.show(false);
54890
54891        if(h_scroll_needed) {
54892            //var h_thumb_height = (Math.min(1, columns_per_viewport/(self.tile_source.data_columns-columns_per_viewport)) * 100);
54893            var h_thumb_height = Math.max(16, (view_rect.width - 32) * (columns_per_viewport/(self.tile_source.data_columns)));
54894            the_horiz_scroll_wrap.setStyle({position:'absolute',
54895                                            display:'block',
54896                                            top:(view_rect.top+view_rect.height+24)+'px',
54897                                            left:view_rect.left+'px',
54898                                            height:(self.scrollbar_width)+'px',
54899                                            width:view_rect.width+'px',
54900                                            border:'0px solid #f00'});
54901
54902            if(the_horizontal_scroller && the_horiz_scroll_wrap.removeChild) {
54903                the_horiz_scroll_wrap.removeChild($(the_horizontal_scroller.id));
54904            }
54905
54906            the_horizontal_scroller = null;
54907            var position = current_column_index/(self.tile_source.data_columns ? self.tile_source.data_columns : current_column_index);
54908            the_horizontal_scroller = new scroller(the_horiz_scroll_wrap,
54909                                                   'horizontal',
54910                                                   {visible:true,
54911                                                    initial_thumb_size:h_thumb_height,
54912                                                    initial_position:position},
54913                                                   {on_move:do_hscroll_move,
54914                                                    on_change:do_hscroll_change,
54915                                                    on_button1_click:do_scroll_left_click,
54916                                                   on_button2_click:do_scroll_right_click});
54917           // the_horizontal_scroller.resize();
54918        } else
54919            if(the_horizontal_scroller)
54920                the_horizontal_scroller.show(false);
54921
54922    }
54923
54924    function create_tile_elements(view_rect) {
54925
54926        // if there are any existing, get rid of them
54927        var imgs = the_canvas.select('img');
54928        for(var i = 0; i < imgs.length; i++)
54929            imgs[i].remove();
54930
54931        //fill the canvas with the required number of img elements.
54932        var tile_rows = Math.ceil(view_rect.height / (tile_pixel_height)) + 1;
54933        var tile_cols = self.tile_source.tiles_wide;
54934        new_tile_count = tile_rows * tile_cols;
54935
54936        for(var col = 0; col < tile_cols; col++) {
54937            for(var row = 0; row < tile_rows; row++) {
54938
54939//                var left = col * Math.floor(tile_pixel_width*scale_width);
54940//                var top = row * Math.floor(tile_pixel_height*scale_height);
54941
54942                var left = col * tile_pixel_width;
54943                var top =  row * tile_pixel_height;
54944
54945                var tile = self.tile_source.get_tile(col, row);
54946                var src = tile ? tile.src : "/assets/heatmap_ui/blank.gif";
54947
54948                //var attrs = {'className':'img_tile', top:top+'px', left:left+'px'};
54949                var attrs = {'className':'img_tile'};
54950                var img = new Element('img', attrs);
54951                img.observe('load', do_tile_load);
54952                img.src = src;
54953                // IE requires this setting after creation, not as an attribute during creation.
54954                img.setStyle({width:(tile_pixel_width)+'px', height:(tile_pixel_height)+'px', left:left+'px', top:top+'px'});
54955
54956                img.observe('click', do_focus_click);
54957                the_tiles[col] = the_tiles[col] ? the_tiles[col] : new Object();
54958                the_tiles[col][row] = img;
54959                the_canvas.appendChild(img);
54960            }
54961        }
54962    }
54963
54964    function do_tile_load() {
54965        if(--new_tile_count == 0) {
54966            initialized = true;
54967            if(self.on_load)
54968                self.on_load(self);
54969        }
54970    }
54971
54972    function do_scroll_up_click() {
54973        var delta = (rows_per_viewport - 2);
54974        navigate(current_column_index, current_row_index - delta, true);
54975    }
54976
54977    function do_scroll_down_click() {
54978        var delta = (rows_per_viewport - 2);
54979        navigate(current_column_index, current_row_index + delta, true);
54980    }
54981
54982    function do_scroll_left_click() {
54983        var delta = (columns_per_viewport - 2);
54984        navigate(current_column_index - delta, current_row_index, true);
54985    }
54986
54987    function do_scroll_right_click() {
54988        var delta = (columns_per_viewport - 2);
54989        navigate(current_column_index + delta, current_row_index, true);
54990    }
54991
54992    function refresh() {
54993      var tile_coords = calculate_required_tiles();
54994      set_tile_srcs(tile_coords);
54995      release_unneeded_tiles(tile_coords);
54996    }
54997
54998    function setup_wheel_listener() {
54999        //listen to mouse wheel events. (Credit http://www.ogonek.net/mousewheel/demo.html)
55000        Object.extend(Event, {
55001                wheel:function (event){
55002                        var delta = 0;
55003                        if (!event) event = window.event;
55004                        if (event.wheelDelta) {
55005                                delta = event.wheelDelta/120;
55006                                if (window.opera) delta = -delta;
55007                        } else if (event.detail) {
55008                                delta = -event.detail/3;
55009                        }
55010                        return Math.round(delta);
55011                }
55012        });
55013        Event.observe(the_viewport, 'mousewheel', wheel_zoom, false);
55014        Event.observe(the_viewport, 'DOMMouseScroll', wheel_zoom, false);
55015    }
55016
55017    function setupKeyListener() {
55018        //listen for key strokes for zoom & pan
55019        if(Prototype.Browser.WebKit) {
55020            // In Safari (& all webkit-based browsers) a <div> is not focusable,
55021            // so will never report keystrokes.  This adds an invisable text
55022            // element that will receive focus and process keyboard events.
55023            setupWebKitKeyListener();
55024        }
55025        else {
55026            var imgs = the_canvas.select('img');
55027            for(var i = 0; i < imgs.length; i++) {
55028                imgs[i].observe('keypress', doKey);
55029                //alert("assigned keypress");
55030            }
55031        }
55032
55033    }
55034
55035    var key_catcher;
55036    function setupWebKitKeyListener() {
55037        if(Prototype.Browser.WebKit) {
55038            key_catcher = new Element('input',{id:'heatmapkey'+self.container.id, type:'text', className:'key_catcher', style:'outline:none;background-color:transparent;font-size:0pt;border:0px;width:0px;height:0px'});
55039            self.container.appendChild(key_catcher);
55040            Event.observe(self.container, 'click', catchWebkitKeys, true);
55041            key_catcher.focus();
55042            Event.observe(key_catcher, 'keypress', doKey, true);
55043        }
55044    }
55045
55046    function catchWebkitKeys() {
55047        key_catcher.focus();
55048    }
55049
55050    function do_focus_click(evt) {
55051        //alert("focus evt: " + inspect(evt));
55052// IE no likey    evt.currentTarget.focus();
55053        the_viewport.focus();
55054    }
55055
55056    function do_scale(increment) {
55057        var new_pixel_width;
55058        var new_pixel_height;
55059
55060        //increment = (scale_width > 1) ? increment : increment/10;
55061        increment = increment/10;
55062        new_pixel_width = scale_width + increment;
55063        new_pixel_height = new_pixel_width * pixel_aspect_ratio;
55064
55065        // let's not scale any smaller than this.
55066        if( (new_pixel_width < 0.1) || (new_pixel_height < 0.1) )
55067            return;
55068
55069        do_set_scale(new_pixel_width, new_pixel_height);
55070    }
55071
55072    function doKey(evt) {
55073        alert("key: ");
55074        // ctrl & alt in combination with any of these would mean something else.
55075        if(evt.ctrlKey || evt.altKey)
55076            return;
55077
55078        switch(evt.keyCode) {
55079            case Event.KEY_RIGHT:
55080                navigate(current_column_index+1, current_row_index, true);
55081                Event.stop(evt);
55082                break;
55083            case Event.KEY_LEFT:
55084                navigate(current_column_index-1, current_row_index, true);
55085                Event.stop(evt);
55086                break;
55087            case Event.KEY_UP:
55088                navigate(current_column_index, current_row_index-1, true);
55089                Event.stop(evt);
55090                break;
55091            case Event.KEY_DOWN:
55092                navigate(current_column_index, current_row_index+1, true);
55093                break;
55094            case 65://"a" to be consistent with SIV, zoom in
55095                do_scale(1);
55096                Event.stop(evt);
55097                break;
55098            case 90://"z" to be consistent with SIV, zoom out
55099                do_scale(-1);
55100                Event.stop(evt);
55101                break;
55102            case 107://"+"
55103                do_scale(1);
55104                Event.stop(evt);
55105                break;
55106            case 109://"-"
55107                do_scale(-1);
55108                Event.stop(evt);
55109                break;
55110            case 187://"+" on IE & Safari number row
55111                do_scale(1);
55112                Event.stop(evt);
55113                break;
55114            case 189://"-" on IE & Safari number row
55115                do_scale(-1);
55116                Event.stop(evt);
55117                break;
55118            case 61://"+" on FFv2 number row
55119                if(Prototype.Browser.Gecko) {
55120                    do_scale(1);
55121                }
55122                Event.stop(evt);
55123                break;
55124            case 16:// shift
55125                break;
55126            default:
55127                break;
55128        }
55129    }
55130
55131
55132    // it would be nice to call this from within recalculate_layout(), but
55133    // it turns out that this needs to be called after refresh();
55134    function raise_layout_change() {
55135        if(self.on_layout_change)
55136            self.on_layout_change(self, {row:Math.floor(current_row_index),
55137                                        column:Math.floor(current_column_index),
55138                                        visible_rows:rows_per_viewport,
55139                                        visible_columns:columns_per_viewport});
55140    }
55141
55142    function do_set_scale(pixel_width, pixel_height) {
55143        self.pixel_format.scale_width = pixel_width < 0 ? scale_width : pixel_width;
55144        self.pixel_format.scale_height = pixel_height < 0 ? scale_height : pixel_height;
55145        recalculate_layout();
55146        refresh();
55147        raise_layout_change();
55148    }
55149
55150    function navigate(x, y, raise) {
55151        navigating = true;
55152        x = Math.floor(x);
55153        y = Math.floor(y);
55154
55155        current_column_index = x < 0 ? 0 : x;
55156        current_row_index = y < 0 ? 0 : y;
55157
55158        var max_row_index = Math.max(0, (self.tile_source.data_rows - rows_per_viewport)+1);
55159        var max_col_index = Math.max(0, (self.tile_source.data_columns - columns_per_viewport)+1);
55160        current_row_index = current_row_index > max_row_index ? max_row_index : current_row_index;
55161        current_column_index = current_column_index > max_col_index ? max_col_index : current_column_index;
55162
55163
55164        if(the_horizontal_scroller && the_horizontal_scroller.visible()) {
55165            var new_h_pos = (current_column_index / self.tile_source.data_columns);
55166            the_horizontal_scroller.set_position(new_h_pos);
55167        }
55168
55169        if(the_vertical_scroller && the_vertical_scroller.visible()) {
55170            var new_v_pos = (current_row_index / self.tile_source.data_rows);
55171            the_vertical_scroller.set_position(new_v_pos);
55172        }
55173
55174        if(raise && self.on_position_change)
55175            self.on_position_change(self, {column:current_column_index, row:current_row_index});
55176        refresh();
55177
55178        navigating = false;
55179    }
55180
55181    function init() {
55182      build_scaffold();
55183      recalculate_layout();
55184      refresh();
55185      raise_layout_change();
55186      self.index = __tiled_image_window__++;
55187
55188    }
55189
55190    init();
55191}
55192
55193;
55194
55195// wraps the entire heatmap image as a virtual quilt of tiles.
55196// Serves up tiles based on their x,y coordinates in the quilt.
55197// The characteristics of the quilt (tiles wide, tiles high) is
55198// determined by the number of rows and columns in the data
55199// set (defined in the given session object) and by the given
55200// tile_width & tile_height.
55201//
55202//
55203function heatmap_tiler(service_base_url, session, tile_width, maximum_tile_height, tile_height, heatmap_format_options)
55204{
55205// public properties
55206    this.service_base_url = service_base_url;
55207    this.session = session;
55208    this.tiles = new Object();
55209    this.tile_width = tile_width;    // the number of data columns in a tile, not pixels.
55210    this.maximum_tile_height = maximum_tile_height;  // height of the tiles other than the last (or only) tile
55211    this.tile_height = tile_height;  // the number of data rows in a tile, not pixels.
55212    this.tiles_wide = 0;
55213    this.tiles_high = 0;
55214    this.data_columns = 0;
55215    this.data_rows = 0;
55216    this.heatmap_format = heatmap_format_options;
55217
55218    this.trace_function = null;  // assign a funtion(msg) to this to handle debug output
55219
55220
55221// public methods
55222
55223    this.attach_session = function(new_session) {
55224        this.tiles_wide = new_session ? Math.ceil(new_session.number_of_columns/tile_width) : 0;
55225        this.tiles_high = new_session ? Math.ceil(new_session.number_of_probes/tile_height) : 0;
55226        this.data_columns = new_session ? new_session.number_of_columns : 0;
55227        this.data_rows = new_session ? new_session.number_of_probes : 0;
55228        this.session = new_session;
55229
55230        this.tile_width = this.data_columns;
55231
55232        HEADER_URL = this.service_base_url + "heatmap/display_header/" + this.session.session_id;
55233        HEATMAP_URL = this.service_base_url + "heatmap/display/" + this.session.session_id;
55234
55235        tile_columns = this.tiles_wide;
55236        tile_rows = this.tiles_high;
55237    }
55238
55239
55240    // returns a js Image object for the tile at position
55241    // x, y in the virtual quilt of tiles.
55242    this.get_tile = function(x, y) {
55243        var tile = fetch_tile(x, y)
55244        return(tile);
55245    }
55246
55247    // returns a js Image object of a thumbnail of the entire quilt.
55248    this.get_thumbnail = function(scale) {
55249        var url = get_thumbnail_url(format_options, scale);
55250        var thumbnail = new Image();
55251        thumbnail.src = url;
55252        return(thumbnail);
55253    }
55254
55255    this.get_header_url = function() {
55256        var url = HEADER_URL + "?groupBy=" + this.session.group_by;
55257        return(url);
55258    }
55259
55260    this.release_tile = function(x, y) {
55261        ditch_tile(x, y);
55262    }
55263
55264    this.set_format_options = function(options) {
55265        format_options = options;
55266    }
55267
55268    this.get_format_options = function() {
55269        return(format_options);
55270    }
55271
55272    this.get_pixel_dimensions = function() {
55273        var width = format_options ? (format_options.pixel_width ? format_options.pixel_width : DEFAULT_PIXEL_WIDTH) : DEFAULT_PIXEL_WIDTH;
55274        var height = format_options ? (format_options.pixel_height ? format_options.pixel_height : DEFAULT_PIXEL_HEIGHT) : DEFAULT_PIXEL_HEIGHT;
55275        return({width:width, height:height});
55276    }
55277
55278// private properties
55279    var self = this;
55280    var HEADER_URL = '';
55281    var HEATMAP_URL = '';
55282    var tile_columns = self.tiles_wide;
55283    var tile_rows = self.tiles_high;
55284    var format_options = heatmap_format_options;
55285    var DEFAULT_PIXEL_WIDTH = 1;
55286    var DEFAULT_PIXEL_HEIGHT = 1;
55287
55288
55289
55290// private methods
55291
55292    function dump_tiles() {
55293        var msg = ""
55294
55295        for(var col in self.tiles) {
55296            for(var row in col) {
55297                msg += "tile url: " + self.tiles[col][row].src
55298            }
55299        }
55300
55301    }
55302
55303    function fetch_tile(x, y) {
55304        if(x >= tile_columns || x < 0)
55305            return(null);
55306
55307        if(y >= tile_rows || y < 0)
55308            return(null);
55309
55310        var tile = create_tile(x, y);;//self.tiles[x] ? (self.tiles[x][y] ? self.tiles[x][y] : create_tile(x, y)) : create_tile(x, y);
55311        return( tile );
55312    }
55313
55314    function ditch_tile(x, y) {
55315        // this gives notice that the tile is no longer in use.
55316        // manage tile caching here.  Maybe just get rid of it immediately.
55317        delete self.tiles[x][y];
55318    }
55319
55320    function create_tile(x, y) {
55321        var start_row = y * self.tile_height;
55322        self.tiles[x] = self.tiles[x] ? self.tiles[x] : new Object();
55323        self.tiles[x][y] = self.tiles[x][y] ? self.tiles[x][y] : new Object();    // new Image()
55324        self.tiles[x][y].src = get_heatmap_url(start_row, self.tile_height, format_options);
55325//        if( !self.tiles[x][y].src )
55326//            self.tiles[x][y].src = get_heatmap_url(start_row, self.tile_height, format_options);
55327        return(self.tiles[x][y]);
55328    }
55329
55330
55331    // returns a URL that can be used as the src of an HTML <img> tag.  This
55332    // is the main heatmap image url.
55333    //
55334    // options is a hash and may may include:
55335    // thumbnail, expr_window, normalize, colormap, group_by
55336    function get_heatmap_url(start_row, num_rows, options) {
55337        var url = HEATMAP_URL + "?startRow=" + start_row + "&numRows=" + num_rows;
55338        if(options) {
55339            url += options.thumbnail ? "&thumbnail" : "";
55340            url += options.expr_window ? "&exprWindow=" + options.expr_window : "";
55341            url += options.normalize ? "&normalize=" + options.normalize : "";
55342            url += options.normalize && options.colormap ? "&colormap=" + options.colormap : "";
55343            url += self.session.group_by ? "&groupBy=" + self.session.group_by : "";
55344            url += "&pixelWidth="
55345            url += options.pixel_width ? options.pixel_width : DEFAULT_PIXEL_WIDTH;
55346            url += "&pixelHeight="
55347            url += options.pixel_height ? options.pixel_height : DEFAULT_PIXEL_HEIGHT;
55348        }
55349        return(url);
55350    }
55351
55352    // returns a URL that can be used as the src of an HTML <img> tag.  This
55353    // is the thumbnail/overview heatmap image url.
55354    //
55355    // options is a hash and may may include:
55356    // expr_window, normalize, colormap, group_by
55357    function get_thumbnail_url(options, scale) {
55358        var url = HEATMAP_URL + "?thumbnail";
55359        if(options) {
55360            url += options.thumbnail ? "&thumbnail" : "";
55361            url += options.expr_window ? "&exprWindow=" + options.expr_window : "";
55362            url += options.normalize ? "&normalize=" + options.normalize : "";
55363            url += options.colormap ? "&colormap=" + options.colormap : "";
55364            url += self.session.group_by ? "&groupBy=" + self.session.group_by : "";
55365            url += "&pixelWidth=1"
55366            url += "&pixelHeight=1"
55367            url += "&scale="+scale;
55368        }
55369        return(url);
55370    }
55371
55372    function init() {
55373        self.attach_session(self.session);
55374    }
55375
55376    init();
55377
55378// debuggery
55379    function trace(msg) {
55380        if(self.trace_function)
55381            self.trace_function(msg);
55382    }
55383}
55384;
55385
55386
55387var __heatmap_view_count__ = 0;
55388
55389// // this will define a collection of widgets for the various components of the
55390// heatmap viewer, along with one widget to rule them all.
55391
55392var HM_VIEW_SELECTION_OFF = 0;
55393var HM_VIEW_SELECTION_HM = 1;
55394var HM_VIEW_SELECTION_THUMB = 2;
55395
55396function heatmap_view(container, number_of_columns, image_data, row_data, event_options, options)
55397{
55398
55399// public properties
55400    this.container = $(container);
55401    this.number_of_columns = number_of_columns;
55402    this.image_data = image_data;
55403    this.the_row_data = row_data;
55404
55405// this one is just a place holder till the controls are constructed.  OK.
55406    this.first_column_width = 230;
55407
55408    this.fit_to_page = options ? (options.fit_to_page ? options.fit_to_page : false) : false;
55409    this.fit_upper_element_id = options ? (options.fit_upper_element_id ? options.fit_upper_element_id : null) : null;
55410    this.fit_lower_element_ids = options ? (options.fit_lower_element_ids ? options.fit_lower_element_ids : null) : null;
55411    this.min_height = options ? (options.min_height ? options.min_height : 0) : 0;
55412    this.ramp_url = options ? (options.ramp_url ? options.ramp_url : '') : '';
55413    this.table_schema = options ? (options.table_schema ? options.table_schema : null) : null;
55414    this.heatmap_only = options ? (options.heatmap_only ? options.heatmap_only : false) : false;
55415    this.show_thumbnail = options ? (options.show_thumbnail ? options.show_thumbnail : false) : false;
55416    this.show_selected = (options ? (options.show_selected ? options.show_selected : false) : false);
55417    this.show_row_position = (options ? (options.show_row_position ? options.show_row_position : false) : false);
55418    this.scrollbar_width = (options ? (options.scrollbar_width ? options.scrollbar_width : 16) : 16);
55419    this.colormap_set = (options ? (options.colormap_set ? options.colormap_set : []) : []);
55420    this.control_mask = (options ? (options.control_mask ? options.control_mask : HM_CONTROL_ALL) : HM_CONTROL_ALL);
55421    this.initial_zoom = (options ? (options.initial_zoom ? options.initial_zoom : 0) : 0);
55422    this.status_label = (options ? (options.status_label ? options.status_label : '') : '');
55423    this.groupings = (options ? (options.groupings ? options.groupings : ['donors,structures','structures,donors']) : ['donors,structures','structures,donors']);
55424    this.resolutions = (options ? (options.resolutions ? options.resolutions :
55425                        {100:'Coarse',
55426                        200:'Averaged Samples', 
55427                        structures:'Structures', 
55428                        samples:'Samples'}) :
55429                        {100:'Coarse',
55430                        200:'Averaged Samples',
55431                        structures:'Structures',
55432                        samples:'Samples'});
55433    this.default_resolution = (options ? (options.default_resolution ? options.default_resolution : '200') : '200');
55434    this.default_restriction = (options ? (options.default_restriction ? options.default_restriction : false) : false);
55435    this.display_donors = (options ? (options.display_donors ? options.display_donors : null) : null);
55436    this.display_structures = (options ? (options.display_structures ? options.display_structures : null) : null);
55437    this.show_display_filters = (options ? (options.show_display_filters ? options.show_display_filters : null) : null);
55438    this.display_donor_groups = (options ? (options.display_donor_groups ? options.display_donor_groups : null) : null);
55439    this.display_donor_filter_type = (options ? (options.display_donor_filter_type ? options.display_donor_filter_type : null) : null);
55440    this.display_structure_groups = (options ? (options.display_structure_groups ? options.display_structure_groups : null) : null);
55441    this.display_structure_filter_type = (options ? (options.display_structure_filter_type ? options.display_structure_filter_type : null) : null);
55442    this.default_grouping = options ? (options.default_grouping ? options.default_grouping : 0) : 0;
55443    //this.expr_window = (options ? (options.colormap_set ? options.colormap_set : [0,10]) : [0,10]);
55444    this.selections = (options ? (options.selections ? options.selections : {}) : {});
55445	this.user_selection_mode = (options ? (options.user_selection_mode != undefined ? options.user_selection_mode : HM_VIEW_SELECTION_OFF) : HM_VIEW_SELECTION_OFF);
55446        this.enable_cart_view = (options ? (options.enable_cart_view != undefined ? options.enable_cart_view : false ) : false );
55447 	this.default_visibility = (options ? (options.visible != undefined ? options.visible : true) : true);
55448
55449    // custom option used by ahba
55450    this.hide_donor_age_display = ( options ? (options.hide_donor_age_display ? options.hide_donor_age_display : false) : false );
55451    this.tool_tip = ( options ? (options.tool_tip? options.tool_tip : 'Toggle sort by structure or sort by donor') : 'Toggle sort by structure or sort by donor');
55452
55453    this.index = -1;
55454
55455    this.show_expression_value_on_status_bar = options ? ( options.show_expression_value_on_status_bar ? options.show_expression_value_on_status_bar : false ) : false;
55456    this.heatmap_download_service = ( options ? (options.heatmap_download_service ? options.heatmap_download_service : null) : null );
55457    this.download_service_data_set = ( options ? (options.download_service_data_set ? options.download_service_data_set : null) : null );
55458    this.ontology_options = options ? (options.ontology_options ? options.ontology_options : {} ) : {};
55459
55460// event hooks
55461    this.on_cell_click = event_options ? event_options.on_cell_click : null;
55462    this.on_heatmap_mouseover = event_options ? event_options.on_heatmap_mouseover : null;
55463    this.on_zoom = event_options ? event_options.on_zoom : null;
55464    this.on_tiled_image_load = event_options ? event_options.on_tiled_image_load : null;
55465    this.on_initialized = event_options ? event_options.on_initialized : null;
55466    this.on_grouping_change = event_options ? event_options.on_grouping_change : null;
55467    this.on_restriction_change = event_options ? event_options.on_restriction_change : null;
55468    this.on_display_change = event_options? (event_options.on_display_change ? event_options.on_display_change : null) : null;
55469    this.on_resolution_change = event_options ? event_options.on_resolution_change : null;
55470    this.on_colormap_change = event_options ? event_options.on_colormap_change : null;
55471    this.on_level_change = event_options ? event_options.on_level_change : null;
55472    this.on_column_click = event_options ? event_options.on_column_click : null;
55473    this.on_clear_click = event_options ? event_options.on_clear_click : null;
55474    this.on_view_selection_click = event_options ? event_options.on_view_selection_click : null;
55475    this.on_select_click = event_options ? event_options.on_select_click : null;
55476    this.on_row_click = event_options ? event_options.on_row_click : null;
55477    this.on_row_over = event_options ? event_options.on_row_over : null;
55478    this.on_visibility_change = event_options ? event_options.on_visibility_change : null;
55479
55480
55481
55482// public methods
55483    this.resize = function() {
55484
55485        try {
55486          recalculate_layout();
55487          if(the_main_heatmap) {
55488              the_main_heatmap.resize();
55489              var scale = the_main_heatmap.get_scale();
55490              the_image_header.set_scale(scale.width);
55491          }
55492        } catch (e) {
55493            // do nothing for possible IE errors
55494        }
55495
55496    }
55497
55498    this.init = function() {
55499        do_init();
55500    }
55501
55502    this.set_zoom = function(new_zoom) {
55503        if(the_status_bar)
55504            the_status_bar.set_zoom(new_zoom);
55505    }
55506
55507    this.set_grouping = function(new_grouping) {
55508        if(the_grouping_widget)
55509            the_grouping_widget.set_grouping(new_grouping);
55510    }
55511
55512    this.set_status_label = function(label, border_color) {
55513        the_upper_statusbar.set_status_label(label, border_color ? border_color : 'fff');
55514    }
55515
55516    this.attach_session = function(new_session) {
55517        var current_scale = the_main_heatmap.get_scale();
55518        var current_width = (this.image_data.data_columns * this.image_data.heatmap_format.pixel_width) * current_scale.width;
55519
55520        this.image_data.attach_session(new_session);
55521        the_image_header.column_count = this.image_data.data_columns;
55522        the_image_header.refresh(this.image_data.get_header_url());
55523
55524        var new_scale_width = current_width/(this.image_data.data_columns * this.image_data.heatmap_format.pixel_width);
55525        the_image_header.set_scale(new_scale_width);
55526        the_main_heatmap.set_scale(new_scale_width, current_scale.height);
55527
55528        the_main_heatmap.resize();
55529        update_data(null, true);
55530
55531        heatmap_scale = the_main_heatmap.get_scale();
55532    }
55533
55534    this.show_busy = function(busy) {
55535       this.container.show_busy(busy);
55536    }
55537
55538    this.set_column_sort_icon = function(column_label, sort) {
55539        the_meta_table.set_column_sort_icon(column_label, sort);
55540    }
55541
55542    this.set_selections = function(new_selections) {
55543        the_meta_table.selections = new_selections;
55544        update_data(null, true);
55545    }
55546
55547    this.set_colormap_range = function(lower, upper) {
55548        the_status_bar.set_range(lower, upper);
55549        do_level_change(lower, upper);
55550    };
55551  
55552    this.set_colormap = function(name) {
55553       the_status_bar.set_colormap(name);
55554    };
55555
55556    this.get_visibility = function() {
55557	return heatmap_visible;
55558    };
55559
55560    this.set_visibility = function(visible) {
55561 	heatmap_visible = visible;
55562	var control_mask = HM_CONTROL_ZOOM | HM_CONTROL_RESOLUTION | HM_CONTROL_COLOR_MAP | HM_CONTROL_CLEAR_SELECTION | HM_CONTROL_TOGGLE_BUTTON;
55563	
55564 	if (heatmap_visible) {
55565	    $j(self.container).show();
55566	    
55567 	    // set the hm scale back to what it was before we hid it.  If we don't do this,
55568 	    // the layout code will recalculate based on it's 0 height.
55569	    if (heatmap_previous_scale)
55570 		the_main_heatmap.set_scale(heatmap_previous_scale.width,heatmap_previous_scale.height);
55571	    
55572 	    final_layout();
55573	    
55574	    the_status_bar.set_control_visibility(control_mask,true);
55575 	} else {
55576 	    // keep track of the scale of the heatmap before we resize it to 0
55577 	    heatmap_previous_scale = the_main_heatmap.get_scale();
55578	    
55579	    $j(self.container).hide();
55580
55581	    the_status_bar.set_control_visibility(control_mask,false);
55582 	}
55583    };
55584
55585// private properties
55586    var self = this;
55587
55588    // the html elements that are managed by this controller
55589    var the_heatmap_container = null;
55590    var the_data_table_container = null;
55591    var the_header_container = null;
55592    var the_tiled_window_container = null;
55593    var the_status_row = null;
55594    var the_upper_status_row = null;
55595    var the_download_row = null;
55596
55597    var lower_row = null;
55598    var upper_row = null;
55599
55600    // the javascript components that are managed by this controller
55601    var the_main_heatmap = null;
55602    var the_image_header = null;
55603    var the_meta_table = null;
55604    var the_status_bar = null;
55605    var the_grouping_widget = null;
55606
55607    var the_upper_statusbar = null;
55608	var the_table = null;
55609
55610    // some global state vars
55611    var current_row = -1;
55612    var expanded = true;
55613    var heatmap_aspect_ratio = 1.0;
55614    var heatmap_scale = 1;
55615    var scaffold_complete = false;
55616    var scrolling = false;
55617    var update_image_timer = 0;
55618    var images_loaded = false;
55619    var init_done = false;
55620    var level_range = [0,0];
55621    var heatmap_previous_scale = null; //for keeping track of scale before hiding
55622	var heatmap_visible = true;
55623
55624
55625// private methods
55626
55627    function build_scaffold() {
55628        the_table = new Element('div', {'class':'scaffold_table'});
55629
55630        upper_row = new Element('div', {'class':'upper_scaffold_row'});
55631        the_table.appendChild(upper_row);
55632
55633        the_status_row = new Element('div', {'class':'status_row'});
55634        the_upper_status_row = new Element('div', {'class':'upper_status_row'});
55635
55636        //the_download_row = new Element('div', {'id':'the_download_row', 'class':'download_block'});
55637        the_download_row = new Element('div', {'class':'download_block'});
55638
55639        lower_row = new Element('div', {'class':'lower_scaffold_row'});
55640        the_table.appendChild(the_upper_status_row);
55641        the_table.appendChild(lower_row);
55642        the_table.appendChild(the_status_row);
55643        the_table.appendChild(the_download_row);
55644
55645        the_data_table_container = new Element('div', {'class':'meta_table_cell'});
55646        lower_row.appendChild(the_data_table_container);
55647
55648        the_heatmap_container = new Element('div', {'class':'heatmap_container'});
55649        the_header_container = new Element('div', {'class':'header_image_container'});
55650        the_tiled_window_container = new Element('div', {'class':'tile_window_container'});
55651
55652        self.container.appendChild(the_table);
55653        lower_row.appendChild(the_heatmap_container);
55654        //self.container.appendChild(the_heatmap_container);
55655        the_heatmap_container.appendChild(the_header_container);
55656        the_heatmap_container.appendChild(the_tiled_window_container);
55657
55658        var header_container_width = the_header_container.getWidth() - self.scrollbar_width;
55659        the_header_container.setStyle({width:header_container_width});
55660
55661        scaffold_complete = true;
55662    }
55663
55664
55665
55666    function build_widgets() {
55667//TODO: row_height = 13?  Really?  13?
55668      var row_height = 13;
55669      var column_width = 0; // a column width of 0 indicates that the width
55670                            // should be scaled to fit the container.
55671
55672      the_upper_statusbar = new status_bar(the_upper_status_row, {status_label:self.status_label}, null);
55673
55674      var control_bar_options = {
55675          colormap_set:self.colormap_set,
55676          ramp_url:self.ramp_url,
55677          control_mask:self.control_mask,
55678          //  resolution_set:["Coarse","Averaged Samples","Samples"],
55679          //  control_defaults:{HM_CONTROL_RESOLUTION:"Samples", HM_CONTROL_RESTRICT_DOMAINS:self.default_restriction}
55680          resolution_set:self.resolutions,
55681          control_defaults:{HM_CONTROL_RESOLUTION:self.default_resolution, HM_CONTROL_RESTRICT_DOMAINS:self.default_restriction},
55682          display_donors:self.display_donors,
55683          display_structures:self.display_structures,
55684          show_display_filters:self.show_display_filters,
55685          display_donor_groups:self.display_donor_groups,
55686          display_donor_filter_type:self.display_donor_filter_type,
55687          display_structure_groups:self.display_structure_groups,
55688          display_structure_filter_type:self.display_structure_filter_type,
55689		  toggle_button_visible:(self.user_selection_mode != HM_VIEW_SELECTION_OFF),
55690		  ontology_options:self.ontology_options
55691      };
55692      var control_bar_events = {
55693          on_zoom:do_zoom,
55694          on_zoom_reset:do_zoom_reset,
55695          on_level_change:do_level_change,
55696          on_colormap_change:do_colormap_change,
55697          on_restriction_change:do_restriction_change,
55698          on_resolution_change:do_resolution_change,
55699          on_display_change:do_display_change,
55700          on_view_selection_click:do_view_selection_click,
55701          on_clear_click:do_clear_click,
55702          on_toggle_click:do_toggle_click
55703      };
55704      the_status_bar = new heatmap_status_bar(the_status_row, control_bar_options, control_bar_events);
55705
55706      var data_table_events = {
55707          on_column_click:do_column_click,
55708          on_select_click:do_select_click,
55709          on_row_click:do_row_click,
55710          on_row_over:do_row_over
55711      };
55712      var data_table_options = {
55713          show_selected:self.show_selected,
55714          selections:self.selections
55715      };
55716      the_meta_table = new tiled_data_table(the_data_table_container, self.table_schema, row_height, data_table_events, data_table_options);
55717
55718      
55719      the_image_header = new header_image(the_header_container,
55720                             self.image_data.get_header_url(null),
55721                             self.the_row_data.number_of_columns,    // column count
55722                             column_width,                      // initial scale
55723                             0                                  // initial position
55724                           );
55725
55726      var heatmap_event_options = {on_position_change:do_heatmap_position_change,
55727                                   on_layout_change:do_heatmap_layout_change,
55728                                   on_mouse_over:do_heatmap_mouseover,
55729                                   on_mouse_out:do_heatmap_mouseout,
55730                                   on_mouse_click:do_heatmap_click,
55731                                   on_load:do_tiled_image_load};
55732      the_main_heatmap = new tiled_image_window(the_tiled_window_container,
55733                                                self.image_data,
55734                                                {scale_width:column_width, scale_height:row_height},
55735                                                {drag_content:true, scrollbar_width:self.scrollbar_width},
55736                                                heatmap_event_options);
55737
55738       the_grouping_widget = new grouping_widget(lower_row, 
55739                                                    {groupings:self.groupings, default_grouping:self.default_grouping, tool_tip:self.tool_tip},
55740                                                    {on_grouping_change:do_grouping_change}
55741                                                );
55742
55743      new data_download(the_download_row, self.heatmap_download_service, self.the_row_data, self.image_data, {download_service_data_set:self.download_service_data_set});
55744
55745       heatmap_scale = the_main_heatmap.get_scale();
55746       heatmap_aspect_ratio = heatmap_scale.height/heatmap_scale.width;
55747
55748    }
55749
55750    function do_select_click(sender, selected_id, selected_state) {
55751        if(self.on_select_click)
55752            self.on_select_click(self, selected_id, selected_state);
55753    }
55754
55755    function do_column_click(sender, column_label) {
55756        if(self.on_column_click)
55757            self.on_column_click(self, column_label);
55758    }
55759
55760    function do_tiled_image_load(sender) {
55761        images_loaded = true;
55762
55763        if(self.on_tiled_image_load)
55764            self.on_tiled_image_load(self);
55765
55766        check_complete();
55767    }
55768
55769    function do_grouping_change(new_grouping) {
55770        if(self.on_grouping_change)
55771            self.on_grouping_change(self, new_grouping);
55772    }
55773
55774    function do_restriction_change(restrict) {
55775        if(self.on_restriction_change)
55776            self.on_restriction_change(self, restrict);
55777    }
55778
55779    function do_view_selection_click(sender, mode) {
55780        if(self.on_view_selection_click)
55781            self.on_view_selection_click(sender, mode);
55782    }
55783
55784    function do_clear_click() {
55785        if(self.on_clear_click)
55786            self.on_clear_click(self);
55787    }
55788
55789    function do_toggle_click() {
55790 	self.set_visibility(!heatmap_visible);
55791 		
55792 	if (self.on_visibility_change)
55793 	    self.on_visibility_change(self);
55794    };
55795
55796    function do_colormap_change(sender, name, normalized, lower, upper) {
55797        var opts = self.image_data.get_format_options();
55798
55799        // if(normalized == 'true') {
55800        if(normalized == 'true' || normalized == true) {
55801            opts.normalize = true;
55802            opts.colormap = name;
55803        } else {
55804            delete opts.normalize;
55805            delete opts.colormap;
55806        }
55807        opts.expr_window = lower + "," + upper;
55808        
55809        self.image_data.set_format_options(opts);
55810        the_main_heatmap.refresh();
55811
55812        if(self.on_colormap_change)
55813            self.on_colormap_change(self, name, normalized, lower, upper);
55814    }
55815
55816
55817    // var level_range = [0,0];
55818    function do_level_change(lower, upper) {
55819        level_range= [lower, upper];
55820        update_heatmap_image();
55821
55822        if(self.on_level_change)
55823            self.on_level_change(self, lower, upper);
55824    }
55825
55826    function update_heatmap_image() {
55827        if (update_image_timer == 0) 
55828                update_image_timer = setTimeout(do_update_image, 200);
55829    }
55830
55831    function do_update_image() {
55832        if (update_image_timer != 0)
55833                clearTimeout(update_image_timer);
55834        update_image_timer = 0;
55835
55836        var opts = self.image_data.get_format_options();
55837        opts.expr_window = level_range[0] + "," + level_range[1];
55838        the_main_heatmap.refresh();
55839        self.image_data.set_format_options(opts);
55840    }
55841
55842    function do_zoom(sender, new_zoom) {
55843        if(!the_main_heatmap)
55844            return;
55845        
55846        var zoom_val = new_zoom;
55847        var new_pixel_width;
55848        var new_pixel_height;
55849
55850        // the slider range is -20 to 20
55851        if(new_zoom > 0)
55852            new_zoom = (new_zoom/5) + 1;
55853        else if(new_zoom < 0)
55854            new_zoom = -1/((new_zoom/5)-1);
55855        else
55856            new_zoom = 1;
55857
55858        var physical_width = (self.image_data.data_columns * self.image_data.heatmap_format.pixel_width);
55859        var viewport = the_main_heatmap.container.getDimensions();
55860        var viewport_scaled_width = viewport.width * new_zoom;
55861        var current_scale = the_main_heatmap.get_scale();
55862        var aspect_ratio = current_scale.height/current_scale.width;
55863        
55864        var new_scale = viewport_scaled_width/physical_width;
55865
55866        var new_scale_width = new_scale;
55867        var new_scale_height = new_scale * aspect_ratio;
55868
55869        new_pixel_width = Math.max(0.0, new_scale_width);
55870        new_pixel_height = Math.max(0.0, new_scale_height);
55871
55872        the_main_heatmap.set_scale(new_pixel_width, new_pixel_height);
55873
55874        if(self.on_zoom)
55875            self.on_zoom(self, zoom_val);
55876    }
55877
55878    function do_resolution_change(sender, new_res) {
55879        if(self.on_resolution_change)
55880            self.on_resolution_change(self, new_res);
55881    }
55882
55883    function do_display_change(sender, new_display_donors, new_display_structures, restriction_is_on) {
55884        if(self.on_display_change){
55885            self.on_display_change(self, new_display_donors, new_display_structures, restriction_is_on);
55886
55887        }
55888    }
55889
55890    function do_zoom_reset() {
55891
55892    }
55893
55894    function adjust_container() {
55895       // if requested, adjust the size of the container to fit in the screen
55896       if(!self.fit_to_page)
55897            return;
55898
55899        if(!self.fit_upper_element_id || !self.fit_lower_element_ids)
55900            return;
55901		
55902        var upper_element = $(self.fit_upper_element_id);
55903        var lower_elements = [];
55904        for(var i = 0; i < self.fit_lower_element_ids.length; i++)
55905            lower_elements[i] = $(self.fit_lower_element_ids[i]);
55906
55907        // get the of the upper element
55908        var upper_dims = upper_element.getDimensions();
55909        var upper_pos = upper_element.cumulativeOffset();
55910
55911        var upper_bottom = (upper_pos.top + upper_dims.height);
55912        var top = upper_element.getStyle('marginTop');
55913        var bottom = upper_element.getStyle('marginBottom');
55914        upper_bottom += new Number((top ? top : '0').replace('px',''));
55915        upper_bottom += new Number((bottom ? bottom : '0').replace('px',''));
55916
55917        // get the size of the lower element(s)
55918        var lower_height = 0;
55919        for(var j = 0; j < lower_elements.length; j++) {
55920            lower_height += lower_elements[j].getHeight();
55921            top = lower_elements[j].getStyle('marginTop');
55922            bottom = lower_elements[j].getStyle('marginBottom');
55923            lower_height += new Number((top ? top : '0').replace('px',''));
55924            lower_height += new Number((bottom ? bottom : '0').replace('px',''));
55925        }
55926
55927        // get the size of the browser screen
55928        var screen_dims = self.container.getDimensions();
55929        //var screen_dims = $(document.body).getDimensions();
55930
55931
55932        // figure the maximum height for the container, fitting between the bottom of the
55933        // upper element, leaving enough room for the lower element.
55934        top = self.container.getStyle('marginTop');
55935        bottom = self.container.getStyle('marginBottom');
55936        var container_padding = new Number((top ? top : '0').replace('px','')) + new Number((bottom ? bottom : '0').replace('px',''))
55937
55938        //var max_height = Math.max(screen_dims.height - upper_bottom - lower_height - container_padding - 55, self.min_height);
55939        var max_height = (self.enable_cart_view && (self.user_selection_mode != HM_VIEW_SELECTION_OFF)) ? 
55940			(screen_dims.height - upper_bottom - lower_height)/2 - container_padding - 55 :
55941			screen_dims.height - upper_bottom - lower_height - container_padding - 55;
55942
55943		max_height = Math.max(max_height, self.min_height);
55944
55945//		self.container.setStyle({height:max_height+'px'});
55946//		if (heatmap_visible)
55947			self.container.setStyle({height:max_height+'px'});
55948//		else
55949//]			self.container.setStyle({height:'20px'});
55950    }
55951
55952    function recalculate_layout() {
55953        // Sometimes getDimensions() gets back 0s in IE, causing trouble setting negative width down the road
55954        // Instead of catching the exceptions, we try adjusting the width to 0 in these cases
55955
55956            if(!scaffold_complete)
55957                return;
55958
55959    // TODO:  Really?  6?
55960           var view_padding = 6;
55961           var data_table_visible_width = the_meta_table ? the_meta_table.get_visible_column_width() + view_padding : self.first_column_width;
55962
55963           adjust_container();
55964           // the view is divided into an upper and lower row.  The upper
55965           // row is fixed height and contains the thumbnail and detail info pane.
55966           // The lower row is liquid and contains the main heatmap and data table.
55967           var container_pos = self.container.cumulativeOffset();
55968           var container_dims = self.container.getDimensions();
55969
55970           if(self.heatmap_only) {
55971               upper_row.setStyle({height:view_padding+'px'});
55972           }
55973           var upper_row_dims = upper_row.getDimensions();
55974           var upper_status_dims = the_upper_status_row.getDimensions();
55975           upper_row_dims.height += upper_status_dims.height;
55976
55977           // The lower row should take all the vertical space not occupied by the
55978           // upper row.
55979           var status_row_dims = the_status_row.getDimensions();
55980
55981           // var lower_row_height = container_dims.height - (upper_row_dims.height + status_row_dims.height + view_padding) - view_padding;
55982           var lower_row_height = Math.max(container_dims.height - (upper_row_dims.height + status_row_dims.height + view_padding) - 2*view_padding,0);
55983
55984           lower_row.setStyle({height:lower_row_height+'px'});
55985
55986           // the heatmap container should be the same height as the lower row
55987           // of the scaffold table, and the same width as the container, minus
55988           // the width of the first column of the data table.
55989            var heatmap_height = Math.max(lower_row_height - view_padding,0);
55990           var heatmap_width = (container_dims.width - data_table_visible_width - view_padding > 0 ) ? container_dims.width - data_table_visible_width - view_padding : 0;
55991
55992           var heatmap_left = /*container_pos.left +*/ data_table_visible_width;
55993           var heatmap_top = (container_pos.top + upper_row_dims.height + view_padding); // TODO: Why 5?
55994           //var pos_style = {top:heatmap_top+'px', left:container_pos.left+'px', width:heatmap_width+'px', height:(heatmap_height)+'px'};
55995           var pos_style = {top:heatmap_top+'px', left:heatmap_left+'px', width:heatmap_width+'px', height:(heatmap_height)+'px'};
55996           the_heatmap_container.setStyle(pos_style);
55997           // the sub-compoennts of the heatmap are the tiled image window and
55998           // the header image.  The header image height is set via CSS, the tiled image
55999           // window takes up all the remaining vertical space.
56000
56001           //var tiled_window_height = heatmap_height - the_header_container.getHeight() - self.scrollbar_width;
56002           //var tiled_window_height = Math.max(heatmap_height - the_header_container.getHeight() - self.scrollbar_width,0);
56003           var tiled_window_height = Math.max(heatmap_height - the_header_container.getHeight() - self.scrollbar_width - view_padding,0);
56004           var tiled_window_width = ( heatmap_width - self.scrollbar_width > 0 ) ? heatmap_width - self.scrollbar_width : 0;
56005           the_tiled_window_container.setStyle({height:tiled_window_height+'px',
56006                                                width:tiled_window_width+'px'});
56007
56008
56009            //var header_container_width = the_header_container.getWidth() - self.scrollbar_width;
56010            the_header_container.setStyle({width:tiled_window_width+'px'});
56011
56012           // the data table should be sized & positioned (absolutely?)
56013           //var data_table_pos = {top:heatmap_top+'px', left:'0px', width:(container_dims.width-(2*view_padding)-2)+'px', height:(lower_row_height-self.scrollbar_width)+'px'} ;
56014           var data_table_width = ( container_dims.width-(2*view_padding)-2 > 0 ) ? container_dims.width-(2*view_padding)-2  : 0;
56015
56016            var data_table_height = Math.max(lower_row_height-view_padding,0);
56017           var data_table_pos = {top:heatmap_top+'px', left:'0px', width:data_table_width+'px', height:data_table_height+'px'} ;
56018
56019           the_data_table_container.setStyle(data_table_pos);
56020
56021           if(the_grouping_widget)
56022               the_grouping_widget.reposition();
56023
56024           self.container.rebusy();
56025
56026    }
56027
56028
56029
56030    function do_heatmap_position_change(sender, position) {
56031      the_image_header.set_position(-(position.column));
56032      update_data(position, false);
56033    }
56034
56035    function do_heatmap_layout_change(sender, layout) {
56036
56037      if(the_main_heatmap) {
56038        var scale = the_main_heatmap.get_scale();
56039        the_image_header.set_scale(scale.width);
56040        the_image_header.set_position(-(layout.column));
56041        the_meta_table.set_scale(scale.height);
56042        update_data({column:layout.column, row:layout.row}, true);
56043      }
56044    }
56045
56046    function do_thumbnail_nav_move(sender, position) {
56047        if(scrolling)
56048            return;
56049
56050        trace("thumb nav: " + inspect(position));
56051        the_main_heatmap.navigate_to((position.left/thumb_scale), (position.top/thumb_scale));
56052    }
56053
56054    function refresh_expression_value_on_status_bar(sender, data)
56055    {
56056            var msg = "Expression - z-score: "+data.z_score + ", log2 intensity: " + data.expression_level;
56057            var color = 'fff';
56058            the_upper_statusbar.set_status_real_first(msg, color);
56059            the_upper_statusbar.show_click_marker_symbol();
56060
56061
56062    }
56063
56064    function receive_cell_detail(response) {
56065      var data = response.responseJSON;
56066      if(self.on_cell_click)
56067          self.on_cell_click(self, data);
56068      if(self.show_expression_value_on_status_bar)
56069          refresh_expression_value_on_status_bar(self, data);
56070    }
56071
56072    function update_data(pos, force) {
56073      var position = pos ? pos : the_main_heatmap.get_position();
56074      position.row = Math.ceil(position.row);
56075      position.column = Math.ceil(position.column);
56076
56077      if((position.row != current_row) || force) {
56078        current_row = position.row;
56079        var size = Math.min(self.the_row_data.number_of_rows, Math.ceil(the_main_heatmap.get_data_size().height));
56080
56081        // self.show_busy(true);   // a change made in HBA for the IE8 busy indicator and ajax async
56082        var data = self.the_row_data.get_probe_list(position.row, size);
56083        if(self.show_row_position) {
56084            var upper = Math.min((position.row +  size), self.the_row_data.number_of_rows);
56085            self.set_status_label((position.row + 1) + " - " + upper + " of " + self.the_row_data.number_of_rows);
56086        }
56087        the_meta_table.set_data(data, position.row);
56088        // self.show_busy(false);  // see above comment
56089      }
56090    }
56091
56092    function do_heatmap_click(sender, position) {
56093      self.the_row_data.get_cell_data_detail(position.column, position.row, receive_cell_detail);
56094      the_image_header.set_click_marker_client_x(position.clientX);
56095      the_meta_table.select_row(position.row);
56096    }
56097
56098    function do_row_click(sender, row) {
56099      var data = self.the_row_data.get_cell_data_brief(0, row);
56100      if(self.on_row_click)
56101          self.on_row_click(self, row, data);
56102    }
56103
56104    function do_row_over(sender, row) {
56105      var data = self.the_row_data.get_cell_data_brief(0, row);
56106
56107      var msg = data.column ? data.column.structure.name + ' (' + data.column.structure.abbreviation + ')' : '';
56108      var color = data.column ? data.column.structure.color : '000';
56109      the_upper_statusbar.set_status_wide(msg, color);
56110
56111      if ( !self.hide_donor_age_display )
56112      {
56113          msg = data.column ? ' Age: ' + data.column.donor.age : '';
56114          color = 'fff';
56115          the_upper_statusbar.set_status_narrow(msg, color);
56116      }
56117
56118      msg = data.column ? 'Donor ' + data.column.donor.name  : '';
56119      color = data.column ? data.column.donor.color : '000';
56120      the_upper_statusbar.set_status_first(msg, color);
56121
56122      if(self.on_row_over)
56123          self.on_row_over(self, row, data);
56124    }
56125
56126    function do_heatmap_mouseover(sender, position) {
56127      the_image_header.set_marker_client_x(position.clientX);
56128      the_meta_table.highlight_row(position.row-position.offset_row);
56129
56130      var data = self.the_row_data.get_cell_data_brief(position.column, position.row);
56131
56132      var msg = data.column ? data.column.structure.name + ' (' + data.column.structure.abbreviation + ')' : '';
56133      var color = data.column ? data.column.structure.color : '000';
56134      the_upper_statusbar.set_status_wide(msg, color);
56135
56136      // commented out in ahba
56137      //msg = data.column ? data.column.top_level_structure.abbreviation : '';
56138      //color = data.column ? data.column.top_level_structure.color : 'black';
56139      //the_upper_statusbar.set_status_narrow(msg, color);
56140
56141      // by default, the option is false, and this info will be displayed
56142      // when the ahba heatmap_view is configured, an extra option is passed in
56143      if ( !self.hide_donor_age_display )
56144      {
56145          msg = data.column ? ' Age: ' + data.column.donor.age : '';
56146          color = 'fff';
56147          the_upper_statusbar.set_status_narrow(msg, color);
56148      }
56149
56150      msg = data.column ? 'Donor ' + data.column.donor.name  : '';
56151      color = data.column ? data.column.donor.color : '000';
56152      the_upper_statusbar.set_status_first(msg, color);
56153      
56154      if(self.on_heatmap_mouseover)
56155          self.on_heatmap_mouseover(self, position, data);
56156    }
56157
56158    function do_heatmap_mouseout(sender, position) {
56159      //the_image_header.set_marker_client_x(position.clientX);
56160      the_meta_table.highlight_none();
56161
56162      var data = self.the_row_data.get_cell_data_brief(position.column, position.row);
56163
56164      if(self.on_heatmap_mouseout)
56165          self.on_heatmap_mouseout(self, position, data);
56166    }
56167
56168    function final_layout() {
56169         recalculate_layout();
56170         self.resize();
56171         the_main_heatmap.lock_aspect_ratio();
56172         heatmap_scale = the_main_heatmap.get_scale();
56173         heatmap_aspect_ratio = heatmap_scale.height/heatmap_scale.width;
56174    }
56175
56176    function set_initial_values() {
56177        if(self.initial_zoom)
56178            the_status_bar.set_zoom(self.initial_zoom);
56179	
56180 	self.set_visibility(self.default_visibility);
56181    }
56182
56183    function check_complete() {
56184        if(init_done && images_loaded) 
56185            if(self.on_initialized)
56186                self.on_initialized(self);
56187    }
56188
56189    function do_init() {
56190
56191        try { 
56192             build_scaffold();
56193
56194             recalculate_layout();
56195            build_widgets();
56196            // The layout needs to be
56197            // mostly done in order for the widgets to construct themselves to the right size
56198            // and shape. But then we need to know here what that final size & shape is going
56199            // to be in order to place the individual widgets properly in relation to each other.
56200            // So; the first time the layout is calculated we use some reasonable defaults, then
56201            // once everybody has constructed themselves we go back and nudge everybody in to
56202            // their final positions.
56203            final_layout();
56204            update_data(null, false);
56205            self.index = __heatmap_view_count__++;
56206
56207            set_initial_values();
56208
56209            init_done = true;
56210            check_complete();
56211
56212        } catch(e) {
56213            alert("error initializing the heat map view: " + e.message + "\n\n" + e.stack);
56214        }
56215    }
56216
56217
56218
56219}
56220
56221
56222
56223
56224;
56225  var current_search_term = "";
56226  var tree = null;
56227//  var tree_positioned = false;
56228  var current_search_index = 0;
56229  var structure_nodes = null;
56230  var selected_index = null;
56231  var target_ids = [];
56232  var contrast_ids = [];
56233  var current_diff_input = '';
56234
56235
56236function inspect(obj) {
56237    var s = "";
56238    for(key in obj) {
56239        s += "\nkey: " + key + ", value: " + obj[key];
56240    }
56241    return(s);
56242}
56243
56244var current_input_elt = null;
56245
56246function doSearch(sender) {
56247  hideTree();
56248  searchStrings = current_input_elt.value.split(/\s*;\s*/);
56249  searchStrings.pop();
56250  searchStrings.push(structure_nodes[selected_index].innerHTML);
56251  current_input_elt.value = searchStrings.join(';');
56252}
56253
56254function doTypeAhead(evt) {
56255  var sender = (evt.target) ? evt.target : evt.srcElement;
56256  current_input_elt = sender;
56257
56258  if(evt.keyCode == 13) {
56259    doSearch(sender);
56260    return;
56261  }
56262  // down arrow
56263  if(evt.keyCode == 40) {
56264    doTreeNav(evt)
56265    return;
56266  }
56267  // up arrow
56268  if(evt.keyCode == 38) {
56269    doTreeNav(evt)
56270    return;
56271  }
56272
56273  positionTree(sender);
56274  showTree();
56275  searchStrings = sender.value.split(/\s*;\s*/);
56276  searchString = searchStrings.pop();
56277
56278  search(searchString);
56279}
56280
56281
56282function showTree() {
56283  $('ontology_container').show();
56284}
56285
56286function hideTree() {
56287  $('ontology_container').hide();
56288}
56289
56290function positionTree(input) {
56291  current_diff_input = input.id;
56292
56293  var custom_offset = input.getAttribute('custom_offset_left');
56294  var offset_left = custom_offset ? new Number(custom_offset) : -2;
56295  tree.clonePosition(input, {setHeight:false, setWidth: false, offsetTop:16, offsetLeft:offset_left});
56296
56297  tree_positioned = true;
56298}
56299
56300function doBlur() {
56301  hideTree();
56302}
56303
56304function search(term) {
56305
56306  if(term.length == 0) {
56307    selectNone();
56308    return;
56309  }
56310
56311  var pat = "^"+term;
56312  var reg = new RegExp(pat, 'i');
56313  reg.compile(pat,"i");
56314
56315  for(var i = 0; i < structure_nodes_length; i++) {
56316    if(reg.test(structure_nodes[i].innerHTML)) {
56317      selectStructure(i);
56318      break;
56319    }
56320  }
56321}
56322
56323function moveTo(container, element){
56324  Position.prepare();
56325  container_y = Position.cumulativeOffset($(container))[1]
56326  element_y = Position.cumulativeOffset($(element))[1]
56327  new Effect.Scroll(container, {x:0, y:(element_y-container_y)});
56328  return false;
56329}
56330
56331function selectStructure(index) {
56332  if(selected_index)
56333    structure_nodes[selected_index].removeClassName('selected');
56334
56335  structure_nodes[index].addClassName('selected');
56336  selected_index = index;
56337
56338  moveTo(tree, structure_nodes[index]);
56339}
56340
56341function selectStructureById(id) {
56342  for(var i = 0; i < structure_nodes_length; i++) {
56343    if(structure_nodes[i].id == id) {
56344      selectStructure(i);
56345      break;
56346    }
56347  }
56348}
56349
56350function selectNone() {
56351  if(selected_index)
56352    structure_nodes[selected_index].removeClassName('selected');
56353}
56354
56355function doTreeClick(evt) {
56356  var sender = (evt.target) ? evt.target : evt.srcElement;
56357  selectStructureById(sender.id);
56358  doSearch(sender);
56359}
56360
56361function doTreeNav(evt) {
56362  if(evt.keyCode == 40) {
56363   if(selected_index < structure_nodes_length - 1)
56364     selectStructure(selected_index + 1);
56365  } else if(evt.keyCode == 38) {
56366   if(selected_index > 0)
56367     selectStructure(selected_index - 1);
56368  }
56369}
56370
56371function structure_init(search_box_id) {
56372  var search_box = $(search_box_id);
56373  search_box.observe('keyup', doTypeAhead);
56374}
56375
56376function ontology_container_init() {
56377    tree = $('ontology_container');
56378    tree.observe('click', doTreeClick);
56379    tree.observe('keyup', doTreeNav);
56380    tree.observe('blur', doBlur);
56381
56382    structure_nodes = tree.select('.tnc');
56383    structure_nodes_length = structure_nodes.length;
56384}
56385
56386Effect.Scroll = Class.create();
56387Object.extend(Object.extend(Effect.Scroll.prototype, Effect.Base.prototype), {
56388  initialize: function(element) {
56389    this.element = $(element);
56390    var options = Object.extend({
56391      x:    0,
56392      y:    0,
56393      mode: 'absolute'
56394    } , arguments[1] || {}  );
56395    this.start(options);
56396  },
56397  setup: function() {
56398    if (this.options.continuous && !this.element._ext ) {
56399      this.element.cleanWhitespace();
56400      this.element._ext=true;
56401      this.element.appendChild(this.element.firstChild);
56402    }
56403
56404    this.originalLeft=this.element.scrollLeft;
56405    this.originalTop=this.element.scrollTop;
56406
56407    if(this.options.mode == 'absolute') {
56408      this.options.x -= this.originalLeft;
56409      this.options.y -= this.originalTop;
56410    } else {
56411
56412    }
56413  },
56414  update: function(position) {
56415    this.element.scrollLeft = this.options.x * position + this.originalLeft;
56416    this.element.scrollTop  = this.options.y * position + this.originalTop;
56417  }
56418});
56419// REQUIRED:
56420//  prototype.js, controls.js, slider.js.
56421
56422var __scroller_count = 0;
56423function next_scroller_id() { return(__scroller_count++); }
56424
56425// creates a scrollbar element, unattached to any window content.
56426//
56427// params:
56428//  container: the html ID of the parent node.  Note that the scroller
56429//             has no effect on the parent node as it is scrolled.  The
56430//             parent node is simply the DOM container for the DOM elements
56431//             of the scroller.
56432//  orientation: 'horizontal' or 'vertical'
56433//  options:
56434//   {
56435//      visible: boolean
56436//      initial_position: a float, 0.0 - 1.0.
56437//   }
56438//  event_options:
56439//   {
56440//      on_move: function_reference(sender, location)
56441//                sender: a reference to this scroller
56442//                location: a percentage position, given as a float 0.0 - 1.0.
56443//          Called with each movement of the scroll thumb
56444//
56445//      on_change: function_reference(sender, location)
56446//                sender: a reference to this scroller
56447//                location: a percentage position, given as a float 0.0 - 1.0.
56448//          Called when the scroll thumb is released.
56449//   }
56450function scroller(container, orientation, options, event_options)
56451{
56452// public properties
56453  this.container = $(container);
56454  this.orientation = orientation;
56455  this.initial_position = options ? (options.initial_position ? options.initial_position : 0) : 0;
56456  this.initial_thumb_size = options ? (options.initial_thumb_size ? options.initial_thumb_size : 24) : 24;
56457  this.initial_visiblity = options ? (options.visible ? options.visible : false) : false;
56458  this.id = 'nil';
56459
56460// event hooks
56461  this.on_move = event_options ? (event_options.on_move ? event_options.on_move : null) : null;
56462  this.on_change = event_options ? (event_options.on_change ? event_options.on_change : null) : null;
56463  this.on_button1_click = event_options ? (event_options.on_button1_click ? event_options.on_button1_click : null) : null;
56464  this.on_button2_click = event_options ? (event_options.on_button2_click ? event_options.on_button2_click : null) : null;
56465
56466
56467// public methods
56468  this.set_position = function(new_position) {
56469    do_set_position(new_position);
56470  }
56471
56472  this.get_position = function() {
56473    return(do_get_position());
56474  }
56475
56476  this.show = function(visible) {
56477    set_visibility(visible);
56478  }
56479
56480  this.visible = function() {
56481    return(this.container.visible);
56482  }
56483
56484  // the new size should be a number representing the percentage
56485  // of scrollbar space occupied by the thumb
56486  this.set_thumb_size = function(new_size) {
56487      set_thumb_size(new_size);
56488  }
56489
56490  this.slider = function() {
56491      return(the_slider);
56492  }
56493
56494  this.resize = function() {
56495      do_resize();
56496  }
56497
56498// private properties
56499  var self = this;
56500  var the_slider = null;
56501  var the_track = null;
56502  var the_handle = null;
56503  var the_wrapper = null;
56504  var button1 = null;
56505  var button2 = null;
56506
56507
56508// private methods
56509  function build_scaffold() {
56510      var id = next_scroller_id();
56511      self.id = 'scroller_'+id;
56512
56513      var orient_class = '_scroll_' + self.orientation;
56514      the_wrapper = new Element('div', {id:self.id, 'className':'wrapper'+orient_class});
56515      button1 = new Element('div',{id:'button1_'+self.id, 'className':'scroll_button_1 button'+orient_class });
56516      button2 = new Element('div',{id:'button2_'+self.id, 'className':'scroll_button_2 button'+orient_class});
56517
56518      var track_id = 'track_' + id;
56519      var handle_id = 'handle_' + id;
56520      the_track = new Element('div', {id:track_id, 'class':'scroller_track'});
56521      the_handle = new Element('div', {id:handle_id, 'class':self.orientation+'_scroller_handle'});
56522
56523      self.container.appendChild(the_wrapper);
56524      the_wrapper.appendChild(button1);
56525      the_wrapper.appendChild(the_track);
56526      the_wrapper.appendChild(button2);
56527      the_track.appendChild(the_handle);
56528
56529      var grip = (self.orientation == 'vertical') ? 'ui-icon-grip-dotted-horizontal' : 'ui-icon-grip-dotted-vertical';
56530      $j(the_handle).button({text:false, icons:{secondary:grip}});
56531
56532      var button1_icon = (self.orientation == 'vertical') ? 'ui-icon-triangle-1-n' : 'ui-icon-triangle-1-w';
56533      $j(button1).button({text:false, icons:{secondary:button1_icon}});
56534
56535      var button2_icon = (self.orientation == 'vertical') ? 'ui-icon-triangle-1-s' : 'ui-icon-triangle-1-e';
56536      $j(button2).button({text:false, icons:{secondary:button2_icon}});
56537
56538      //button1.observe('click', do_button1_click);
56539      repeat_button(button1, do_button1_click, 500)
56540      repeat_button(button2, do_button2_click, 500)
56541      //button2.observe('click', do_button2_click);
56542      set_thumb_size(self.initial_thumb_size);
56543  }
56544
56545  function set_thumb_size(new_size) {
56546    if(self.orientation == 'vertical')
56547      the_handle.setStyle({height:new_size+'px'});
56548    else
56549      the_handle.setStyle({width:new_size+'px'});
56550  }
56551
56552  function do_set_position(new_position) {
56553    the_slider.setValue(new_position);
56554  }
56555
56556  function do_get_position() {
56557    return(the_slider.value);
56558  }
56559
56560  function repeat_button(btn, action, initial_speed) {
56561    var t = null;
56562    var speed = initial_speed;
56563    var count = 0;
56564
56565    var repeat = function () {
56566        action();
56567        if(count++ > 5)
56568            speed = initial_speed/10;
56569
56570        t = setTimeout(repeat, speed);
56571    }
56572
56573    btn.observe('mousedown', function() {
56574        repeat();
56575   });
56576    btn.observe('mouseup', function() {
56577        clearTimeout(t);
56578        count = 0;
56579        speed = initial_speed;
56580    });
56581  }
56582
56583  function do_button1_click(evt) {
56584    if(self.on_button1_click)
56585        self.on_button1_click(self);
56586  }
56587
56588  function do_button2_click(evt) {
56589    if(self.on_button2_click)
56590        self.on_button2_click(self);
56591  }
56592
56593  function set_visibility(visible) {
56594    var visibility = visible ? 'block' : 'none';
56595    self.container.setStyle({display:visibility});
56596  }
56597
56598  function do_resize() {
56599    var container_dims = self.container.getDimensions();
56600    var button_dims = null;
56601
56602    var extent = null;
56603    if(self.orientation == 'horizontal') {
56604        button_dims = button2.getDimensions();
56605        extent = container_dims.width - ((2 * button_dims.width) + 3);
56606        the_track.setStyle({width:extent+'px'});
56607    } else {
56608        button_dims = button1.getDimensions();
56609        extent = container_dims.height - ((2 * button_dims.height) + 2);
56610        the_track.setStyle({height:extent+'px'});
56611    }
56612//alert("sizing " + self.orientation + " scrollbar to " + extent);
56613  }
56614
56615  function init() {
56616    build_scaffold();
56617    do_resize();
56618    the_slider = new Control.Slider(the_handle.id, the_track.id,
56619                        {
56620                          axis:self.orientation,
56621                          sliderValue:self.initial_position,
56622                          onSlide:do_move,
56623                          onChange:do_change
56624                        });
56625    set_visibility(self.initial_visiblity);
56626
56627  }
56628
56629  function do_move(val) {
56630    if(self.on_move)
56631      self.on_move(self, val);
56632  }
56633
56634  function do_change(val) {
56635    if(self.on_change)
56636      self.on_change(self, val);
56637  }
56638
56639  init();
56640}
56641;
56642// MRI Viewer Application -- sliderWidgets.js
56643// Copyright (c) 2007-2010 Allen Institute for Brain Science. All Rights Reserved.
56644
56645WindowLevelWidget = Class.create({
56646	
56647	initialize: function(parentNode, colormapUrl, lowerLimit, upperLimit, lowerRange, upperRange, notificationFunc, precision, rangeDisplay)
56648	{
56649		/* Create the following structure
56650		<div>
56651			<div class="windowLevelBox">
56652				<img id="colormapLowerRangeHandle" src="images/heatmap_images/windowLevelHandle.png" width="6px" height="22px" style="position:absolute; z-index:2; margin-top:-3px; margin-left:-2px; left:0px; cursor:col-resize" />
56653				<img id="colormapUpperRangeHandle" src="images/heatmap_images/windowLevelHandle.png" width="6px" height="22px" style="position:absolute; z-index:2; margin-top:-3px; margin-left:-2px; left:0px; cursor:col-resize" />
56654				<img id="colormapLowerRange" src="/colormap?index=0" width="128px" height="16px" style="position:absolute; left:0px" />
56655				<img id="colormapUpperRange" src="/colormap?index=255" width="0px" height="16px" style="position:absolute; left:0px" />
56656				<img id="colormapImage" src="/colormap" width="128px" height="16px" style="position:absolute; top:0px; left:128px; cursor:move" />
56657			</div>
56658			<div id="rangeDisplay">[0.5, 1.0]</div>
56659		</div>
56660		*/
56661		
56662		this.tracking = false;
56663		this.lastEventH = -1;
56664		this.symmetric = false;
56665		this.sourceObj = null;
56666		this.parentNode = parentNode;
56667		this.displayPrecision = precision;
56668		
56669		this.controlWidth = this.parentNode.getWidth();
56670		if (this.controlWidth == 0)
56671			this.controlWidth = 1;
56672		this.notificationFunc = notificationFunc;
56673		
56674		var boxNode = new Element('div', { 'class':'windowLevelBox', title:'Adjust window/level of the color scale' });
56675		this.colormapImage = new Element('img', { 'src':colormapUrl, 'width':'128px', 'height':'16px', 'class':'windowLevelMiddle windowLevelMoveCursor' });
56676		
56677		var sep = colormapUrl.indexOf("?") > 0 ? '&' : '?';
56678		this.lowerRangeImage = new Element('img', { 'src':colormapUrl + sep + "index=0", 'width':'1px', 'height':'16px', 'class':'windowLevelEnds', 'style':'left:0px' });
56679		this.upperRangeImage = new Element('img', { 'src':colormapUrl + sep + "index=255", 'width':'1px', 'height':'16px', 'class':'windowLevelEnds', 'style':'right:0px' });
56680		
56681		this.lowerHandle = new Element('div', { 'class':'windowLevelHandle' });
56682		this.upperHandle = new Element('div', { 'class':'windowLevelHandle' });
56683		
56684		boxNode.appendChild(this.lowerRangeImage);
56685		boxNode.appendChild(this.lowerHandle);
56686		boxNode.appendChild(this.colormapImage);
56687		boxNode.appendChild(this.upperHandle);
56688		boxNode.appendChild(this.upperRangeImage);
56689		parentNode.appendChild(boxNode);
56690		
56691		if (!rangeDisplay)
56692		{
56693			this.rangeDisplay = new Element('div', { 'class':'windowLevelRangeText' });
56694			parentNode.appendChild(this.rangeDisplay);
56695		}
56696		else
56697		{
56698			this.rangeDisplay = rangeDisplay;
56699		}
56700		
56701		this.setLimits(lowerLimit, upperLimit);
56702		this.setRange(lowerRange, upperRange);
56703				
56704		// Register event handlers
56705		this.eventMouseDown = this.onMouseDown.bindAsEventListener(this);
56706		this.eventMouseUp   = this.onMouseUp.bindAsEventListener(this);
56707		this.eventMouseMove = this.onMouseMove.bindAsEventListener(this);
56708		
56709		this.setupMouseListeners(this.colormapImage);
56710		this.setupMouseListeners(this.lowerHandle);
56711		this.setupMouseListeners(this.upperHandle);
56712		
56713		document.observe("mouseup", this.eventMouseUp);
56714		document.observe("mousemove", this.eventMouseMove);
56715	},
56716	
56717	
56718	dispose: function()
56719	{
56720		Event.stopObserving(this.colormapImage, "mousedown", this.eventMouseDown);
56721		Event.stopObserving(this.lowerHandle, "mousedown", this.eventMouseDown);
56722		Event.stopObserving(this.upperHandle, "mousedown", this.eventMouseDown);		
56723		Event.stopObserving(document, "mouseup", this.eventMouseUp);
56724		Event.stopObserving(document, "mousemove", this.eventMouseMove);
56725	},
56726	
56727	
56728	setupMouseListeners: function(element)
56729	{
56730		element.observe("mousedown", this.eventMouseDown);
56731		
56732		element.observe("touchstart", this.eventMouseDown);
56733		element.observe("touchmove", this.eventMouseMove);
56734		element.observe("touchup", this.eventMouseUp);
56735	},
56736	
56737	
56738	setLimits: function(lowerLimit, upperLimit)
56739	{
56740		this.lowerLimit = lowerLimit;
56741		this.upperLimit = upperLimit;
56742		this.scale = this.controlWidth / (this.upperLimit - this.lowerLimit);
56743	},
56744	
56745	
56746	resize: function(frameWidth)
56747	{
56748		// Get range at current control size
56749		var lower = this.getLowerRange();
56750		var upper = this.getUpperRange();
56751		
56752		// this.parentNode.style.width = frameWidth + "px";
56753		var containerWidth = this.parentNode.getWidth();
56754		if (containerWidth > 0)
56755			this.controlWidth = containerWidth;
56756		this.scale = this.controlWidth / (this.upperLimit - this.lowerLimit);
56757		
56758		this.lowerRange = (lower - this.lowerLimit) * this.scale;
56759		this.upperRange = (upper - this.lowerLimit) * this.scale;
56760		this.doLayout();
56761	},
56762	
56763	
56764	onMouseDown: function(event)
56765	{
56766		this.tracking = false;
56767
56768// davidf: this doesn't work because the prototype used in most apps (1.6)
56769// has a bug in IE9 such that isLeftClick always returns false.
56770//		if (!event.touches && !event.isLeftClick())
56771//			return;
56772		
56773		this.sourceObj = event.element();
56774		
56775		this.startRangeLower = this.lowerRange
56776		this.startRangeUpper = this.upperRange;
56777		
56778		var h, v;
56779		if (event.targetTouches)
56780		{
56781			if (event.targetTouches.length > 1)
56782				return;
56783			
56784			this.anchorPos = event.targetTouches[0].pageX;
56785			event.preventDefault();
56786		}
56787		else
56788		{
56789			this.anchorPos = event.pointerX();
56790		}
56791		
56792		this.tracking = true;
56793		event.stop();
56794	},
56795	
56796	
56797	onMouseMove: function(event)
56798	{
56799		if (!this.tracking)
56800			return;
56801		
56802		this.doDrag(event);
56803		event.stop();
56804	},
56805	
56806	
56807	onMouseUp: function(event)
56808	{
56809		if (this.tracking)
56810		{		
56811			this.doDrag(event);
56812			event.stop();
56813		}
56814		
56815		this.tracking = false;
56816		this.sourceObj = null;
56817	},	
56818	
56819	
56820	doDrag: function(event)
56821	{
56822		if (event.touches && event.touches.length > 1)
56823			return;
56824		
56825		var h;
56826		if (event.targetTouches)
56827		{
56828			h = event.targetTouches[0].pageX;
56829			event.preventDefault();
56830		}
56831		else
56832		{
56833			h = event.pointerX();
56834		}
56835		
56836		var dh = h - this.anchorPos;
56837		
56838		if (this.sourceObj == this.lowerHandle)
56839		{
56840			var newValue = this.startRangeLower + dh;	 
56841			if (newValue < 0)
56842				newValue = 0;
56843			if (newValue >= this.upperRange)
56844				newValue = this.upperRange - 1;
56845			
56846			if (newValue != this.lowerRange)
56847			{
56848				this.lowerRange = newValue;
56849				if (this.symmetric)
56850				{
56851					var midPoint = this.controlWidth / 2.0;
56852					this.upperRange = midPoint + (midPoint - this.lowerRange);
56853				}
56854				this.doUpdate();
56855			}
56856		}
56857		else if (this.sourceObj == this.upperHandle)
56858		{
56859			var newValue = this.startRangeUpper + dh;	 
56860			if (newValue <= this.lowerRange)
56861				newValue = this.lowerRange + 1;
56862			if (newValue >= this.controlWidth)
56863				newValue = this.controlWidth;
56864			
56865			if (newValue != this.upperRange)
56866			{
56867				this.upperRange = newValue;
56868				if (this.symmetric)
56869				{
56870					var midPoint = this.controlWidth / 2.0;
56871					this.lowerRange = midPoint - (this.upperRange - midPoint);
56872				}
56873				this.doUpdate();
56874			}
56875		}
56876		else if (this.sourceObj == this.colormapImage)
56877		{
56878			if (this.symmetric)
56879				return;
56880			
56881			if (this.startRangeUpper + dh >= this.controlWidth)
56882				dh = this.controlWidth - this.startRangeUpper;
56883			else if (this.startRangeLower + dh <= 0)
56884				dh = -this.startRangeLower;
56885			
56886			if (this.startRangeUpper + dh != this.upperRange)
56887			{
56888				this.upperRange = this.startRangeUpper + dh;
56889				this.lowerRange = this.startRangeLower + dh;
56890				this.doUpdate();
56891			}
56892		}
56893	},
56894	
56895	
56896	doLayout: function()
56897	{
56898		this.colormapImage.width = Math.floor(this.upperRange - this.lowerRange);
56899		this.colormapImage.style.left = Math.floor(this.lowerRange) + "px";
56900		
56901		this.lowerRangeImage.width = Math.floor(this.lowerRange);
56902		this.upperRangeImage.width = Math.floor(this.controlWidth - this.upperRange);
56903		
56904		this.lowerHandle.style.left = Math.floor(this.lowerRange) + "px";
56905		this.upperHandle.style.left = Math.floor(this.upperRange) + "px";
56906	},	
56907	
56908	
56909	doUpdate: function()
56910	{
56911		this.doLayout();
56912		this.updateText();
56913		this.notificationFunc(this.getLowerRange(), this.getUpperRange());
56914	},	
56915
56916
56917	updateText: function()
56918	{
56919		//this.rangeDisplay.innerHTML = "[" + round(this.getLowerRange(), this.displayPrecision) + ", " + round(this.getUpperRange(), this.displayPrecision) + "]";
56920
56921        // BUGBUG, for testing
56922        this.rangeDisplay.innerHTML = "[" + Math.round(this.getLowerRange()) + ", " + Math.round(this.getUpperRange()) + "]";
56923	},
56924	
56925	
56926	getLowerRange: function()
56927	{
56928		return (this.lowerRange / this.scale) + this.lowerLimit;
56929	},	
56930	
56931	
56932	getUpperRange: function()
56933	{
56934		return (this.upperRange / this.scale) + this.lowerLimit;
56935	},	
56936	
56937	
56938	setRange: function(lower, upper)
56939	{
56940		if (lower < this.lowerLimit)
56941			lower = this.lowerLimit;
56942		if (upper > this.upperLimit)
56943			upper = this.upperLimit;
56944		
56945		this.lowerRange = (lower - this.lowerLimit) * this.scale;
56946		this.upperRange = (upper - this.lowerLimit) * this.scale;
56947		this.doLayout();
56948		this.updateText();
56949	},
56950	
56951	
56952	setColormapImage: function(imageUrl)
56953	{
56954		var sep = imageUrl.indexOf("?") > 0 ? '&' : '?';
56955		this.colormapImage.src = imageUrl;
56956		this.lowerRangeImage.src = imageUrl + sep + "index=0";
56957		this.upperRangeImage.src = imageUrl + sep + "index=255";
56958	},
56959	
56960	
56961	setSymmetric: function(s)
56962	{
56963		if (s)
56964			this.colormapImage.removeClassName('windowLevelMoveCursor');
56965		else
56966			this.colormapImage.addClassName('windowLevelMoveCursor');
56967		
56968		this.symmetric = s;
56969	}
56970		
56971});
56972
56973
56974var __status_count = 0;
56975
56976function status_bar(container, options, event_options) {
56977
56978// public properties
56979    this.container = $(container);
56980    this.initial_label = options ? options.status_label ? options.status_label : '&nbsp;' : '&nbsp;';
56981    
56982// event hooks
56983    this.on_zoom = event_options? (event_options.on_zoom ? event_options.on_zoom : null) : null;
56984    this.on_zoom_reset = event_options? (event_options.on_zoom_reset ? event_options.on_zoom_reset : null) : null;
56985
56986// public methods
56987    this.set_status_wide = function(msg, color) {
56988        the_wide_text_box.update(msg);
56989        the_wide_text_box.setStyle({'borderColor':'#'+color});
56990    }
56991
56992    this.set_status_narrow = function(msg, color) {
56993        the_narrow_text_box.update(msg);
56994        the_narrow_text_box.setStyle({'borderColor':'#'+color});
56995    }
56996
56997    this.set_status_first = function(msg, color) {
56998        the_first_text_box.update(msg);
56999        the_first_text_box.setStyle({'borderColor':'#'+color});
57000    }
57001
57002    this.set_status_real_first = function(msg, color) {
57003        the_real_first_text_box.update(msg);
57004        the_real_first_text_box.setStyle({'borderColor':'#'+color});
57005    }
57006
57007    this.show_click_marker_symbol = function() {
57008        the_click_marker_symbol.show();
57009    }
57010
57011    this.set_status_label = function(msg, color) {
57012        the_label_box.update(msg);
57013        the_label_box.setStyle({'borderColor':'#'+color});
57014    }
57015
57016// private properties
57017    var self = this;
57018
57019    var the_wide_text_box = null;
57020    var the_narrow_text_box = null;
57021    var the_first_text_box = null;
57022    var the_real_first_text_box = null;
57023    var the_label_box = null;
57024    var the_click_marker_symbol = null;
57025
57026
57027// private methods
57028    function build_scaffold() {
57029    // status text
57030        the_label_box= new Element('span', {id:'status_label_text_box_'+__status_count, 'class':'status_text_box status_label'});
57031        self.container.appendChild(the_label_box);
57032        the_label_box.update(self.initial_label);
57033        the_wide_text_box = new Element('span', {id:'status_wide_text_box_'+__status_count, 'class':'status_text_box status_wide_text_box'});
57034        self.container.appendChild(the_wide_text_box);
57035        the_wide_text_box.update('&nbsp;');
57036        the_narrow_text_box = new Element('span', {id:'status_narrow_text_box_'+__status_count, 'class':'status_text_box status_narrow_text_box'});
57037        self.container.appendChild(the_narrow_text_box);
57038        the_narrow_text_box.update('&nbsp;');
57039        the_first_text_box = new Element('span', {id:'status_first_text_box_'+__status_count, 'class':'status_text_box status_first_text_box'});
57040        self.container.appendChild(the_first_text_box);
57041        the_first_text_box.update('&nbsp;');
57042        the_real_first_text_box = new Element('span', {id:'status_real_first_text_box_'+__status_count, 'class':'status_real_first_text_box'});
57043        self.container.appendChild(the_real_first_text_box);
57044        the_real_first_text_box.update('&nbsp;');
57045        the_click_marker_symbol = new Element('img', {id:'hicm_symbol'+__status_count, 'class':'header_marker_symbol'});
57046        the_click_marker_symbol.src = '/assets/heatmap_ui/click_marker_shortened.png';
57047        the_click_marker_symbol.hide();
57048        self.container.appendChild(the_click_marker_symbol);
57049
57050    }
57051
57052    function init() {
57053        build_scaffold();
57054        __status_count++;
57055    }
57056
57057    init();
57058}
57059;
57060var __surface_widget_count__ = 0;
57061
57062function SurfaceWidget(container,options,event_options) {
57063
57064	// public properties
57065	this.container = $(container);
57066	this.frame_width = (options.frame_width != undefined) ? options.frame_width : 200;
57067	this.frame_height = (options.frame_height != undefined) ? options.frame_height : 150;
57068	this.index = (options.index != undefined) ? options.index : -1;
57069	this.colormap_name = (options.colormap_name != undefined) ? options.colormap_name : "redgreen";
57070	this.colormap_range = (options.colormap_range != undefined) ? options.colormap_range : [-3.0,3.0];
57071	this.colormap_normal = (options.colormap_normal != undefined) ? options.colormap_normal : true;
57072	this.host = (options.host != undefined) ? options.host : "";
57073	this.num_rotations = (options.num_rotations != undefined) ? options.num_rotations : 36;
57074	this.image_settings = (options.image_settings != undefined) ? options.image_settings : [];
57075    this.full_resolution = (options.full_resolution != undefined) ? options.full_resolution : false;
57076
57077	this.pick_callback = event_options.pick_callback ? 
57078		event_options.pick_callback : null;
57079
57080	// private properties
57081	var self = this;
57082	
57083	var active = false;
57084	var the_angle = 0;
57085	var the_sample = {};
57086	var the_images = [];
57087	var the_current_surface = "";
57088
57089	// private methods
57090
57091	// initialize surface images and layout the page
57092	function build_scaffold() {
57093
57094		// initialize one surface image for each donor
57095		for (var i=0; i<self.image_settings.length; i++)
57096		    the_images.push(make_image(self.index,i,
57097					       self.frame_width,
57098					       self.frame_height,
57099					       self.image_settings[i].donor,
57100					       self.image_settings[i].probe));
57101		
57102		// lay out the page
57103		for (var i=0; i<the_images.length; i++)
57104			self.container.appendChild(the_images[i].div);
57105
57106	}
57107
57108	// construct a surface image for a donor and probe at a given size
57109    function make_image(widget_index,image_index,width,height,donor,probe) {
57110		var o = {
57111			'div': new Element('div', { 'id' : 'image_'+widget_index+'_'+image_index }),
57112			'donor_id': donor.donor.id,
57113			'probe_id': probe.id,
57114			'param_str': "",
57115			'image' : new Image()
57116		};
57117		o.div.setStyle({
57118		    width : width+'px',
57119		    height : height+'px',
57120		    position: 'absolute',
57121		    left: (image_index * width) + 'px',
57122		    background: "black",
57123		    cursor: "crosshair" });
57124
57125		var t = new Element('p', { 'style' : 'color: gray; text-align: center; margin-top: '+self.frame_height*.45+'px'});
57126		$(t).update('no expression data');
57127
57128		o.text = t;
57129		
57130		o.div.observe("click",on_image_click);
57131		
57132		return o;
57133	}
57134
57135	// callback for pick events
57136	function on_image_click(event)	{
57137
57138		if (event.touches && event.touches.length > 1)
57139			return;
57140		
57141		var h, v;
57142		if (event.targetTouches)
57143		{
57144			h = event.targetTouches[0].pageX;
57145			v = event.targetTouches[0].pageY;
57146			event.preventDefault();
57147		}
57148		else
57149		{
57150			h = event.pointerX();
57151			v = event.pointerY();
57152		}
57153		
57154		// Convert from page to image element coordinates
57155		var imgElement = event.target || event.srcElement;
57156	    
57157	    if (!imgElement)
57158		return;
57159
57160	    var container = imgElement.parentNode;
57161	    
57162		if (!container)
57163			return;
57164		
57165		var nextParent = container;
57166		while (nextParent)
57167		{
57168			h = h - nextParent.offsetLeft;
57169			v = v - nextParent.offsetTop;
57170			
57171			nextParent = nextParent.offsetParent;
57172		}
57173		
57174		var width = container.getWidth();
57175		var angle = the_angle;
57176		var posX = (h+1.0+width*Math.floor(angle)) / (width*self.num_rotations);
57177		var posY = (v+1.0) / container.getHeight();
57178		
57179		// figure out which donor & probe the click came from by checking the div id
57180		var donor_id = 0;
57181		var probe_id = 0;
57182		
57183		for (var i=0; i<the_images.length; i++)
57184		{
57185			if (container.id == the_images[i].div.id) {
57186				donor_id = the_images[i].donor_id;
57187				probe_id = the_images[i].probe_id;
57188			}
57189		}
57190		
57191		// send the surface image query
57192		var params = {
57193			"probe" : probe_id,
57194			"donor" : donor_id,
57195			"selectedPos" : posX+","+posY
57196		};
57197
57198		if (the_current_surface)
57199			params.surface = the_current_surface;
57200
57201		new Ajax.Request(self.host + "/surface/query",
57202						 {
57203							 method:"get",
57204							 parameters: params,
57205							 onSuccess: function(result) { 
57206								 active = true;
57207								 self.set_sample(result.responseJSON);
57208								 if (self.pick_callback) self.pick_callback(result.responseJSON); 
57209								 active = false;
57210							 }
57211						 });
57212	};
57213
57214	// privileged methods
57215	this.set_sample = function(info) {	
57216		if (info.probe) the_sample.probe = info.probe;
57217		if (info.donor) the_sample.donor = info.donor;
57218		if (info.rna_well) the_sample.rna_well = info.rna_well;
57219		if (info.surface) the_sample.surface = info.surface;
57220	}
57221
57222	this.clear_sample = function() {
57223		the_sample = {};
57224	}
57225
57226    this.get_current_well = function() {
57227	return the_sample.rna_well;
57228    }
57229
57230	this.set_current_surface = function(surface) {
57231		the_current_surface = surface;
57232	}
57233
57234	this.is_visible = function() { 
57235		return !(self.container.style.visibility == "hidden"); 
57236	};
57237
57238	this.set_angle = function(val) { 
57239		the_angle = Math.max(0,Math.min(val,self.num_rotations-1));
57240	};
57241
57242    this.set_size = function(width, height) {
57243	this.frame_width = width;
57244	this.frame_height = height;
57245	
57246	for (var i=0; i<the_images.length; i++)
57247	{
57248	    the_images[i].div.setStyle({
57249		width : width+'px',
57250		height : height+'px',
57251		left: (i * width) + 'px'
57252	    });
57253	    
57254	    var img = jQuery(the_images[i].div.childElements()[0]);
57255	    if (img)
57256	    {
57257		var imgWidth = img.width();
57258		var imgHeight = img.height();
57259
57260		if (imgWidth <= 0 || imgHeight <= 0)
57261		    continue;
57262		
57263		var scale_x = this.frame_width / (imgWidth / this.num_rotations);
57264		var scale_y = this.frame_height / imgHeight;
57265
57266		img.css('width', (imgWidth * scale_x)+'px');
57267//		img.css('height', (imgHeight * scale_y)+'px');
57268		
57269		img.css('position','absolute')
57270		    .css('left', -(Math.floor(the_angle) * this.frame_width)+'px');
57271	    }
57272	}
57273    }
57274
57275    this.update_image = function() {
57276		// make sure the widget is visible
57277		if (!self.is_visible())
57278			return;
57279
57280		if (!the_current_surface)
57281			return;
57282
57283		// find out as much as we can about the sample
57284		var sample_well = the_sample.rna_well;
57285		var sample_probe_id = the_sample.probe;
57286		var sample_donor_id = the_sample.donor;
57287		var angle = the_angle;
57288
57289		// update all of the surface images. cached images (unchanged src) should
57290		// just have their cropping frame moved, no service requests necessary.
57291		for (var i=0; i<the_images.length; i++)
57292		{
57293			var image = the_images[i];
57294
57295			// build the new img src
57296			var paramStr = "donor=" + image.donor_id +
57297				"&probe=" + image.probe_id + 
57298				"&exprWindow=" + self.colormap_range;
57299		    
57300			if (the_current_surface)
57301				paramStr += "&surface="+the_current_surface;
57302
57303			if (self.colormap_normal) {
57304				paramStr += "&normalize";
57305				paramStr += "&colormap=" + self.colormap_name;
57306			}
57307
57308		    if (self.full_resolution) {
57309			paramStr += "&fullResolution";
57310		    }
57311			
57312			// if there's a well and it belongs to this image, pass it
57313			if (sample_well && sample_well > 0 && 
57314				sample_donor_id == image.donor_id && 
57315				sample_probe_id == image.probe_id)
57316				paramStr += "&wellId="+sample_well;
57317
57318			// preload the Image, but only if something has changed
57319			if (image.paramStr != paramStr) {
57320			    image.paramStr = paramStr;
57321
57322				// tell the surface image to update it's img source once the 
57323				// Image has finished preloading
57324				image.image.onload = (function() { 
57325
57326				    // Empty the image's container.
57327				    jQuery(this.div).empty();
57328
57329				    // Compute the scale factor between the requested frame size and 
57330				    // the actual image size.
57331				    var imgWidth = this.image.width;
57332				    var imgHeight = this.image.height;
57333
57334				    var scale_x = self.frame_width / (imgWidth / self.num_rotations);
57335				    var scale_y = self.frame_height / imgHeight;
57336
57337				    // Create a new img element.  We could just use the image's img 
57338				    // element, but IE7 is stupid about setting width/height on img's
57339				    // that already have a src.  
57340				    var newImg = jQuery('<img>');
57341
57342				    jQuery(this.div).append(newImg);
57343
57344				    // IE7 can only scale proportionally, at least as far as I can tell.
57345				    // Set the width to the correct size, and height: 100% will take care
57346				    // of the scaling.
57347				    newImg.css('width', (imgWidth * scale_x)+'px');
57348				    newImg.css('height', '100%');
57349
57350				    // Set the src now, after width/height are correct.
57351				    newImg.attr('src', this.image.src);
57352
57353				    // Shift the img to the left, so that the container div crops out the
57354				    // correct thumbnail.
57355				    newImg.css('position','absolute')
57356					.css('left', -(Math.floor(angle) * self.frame_width)+'px');
57357				}).bind(the_images[i]);
57358
57359				// if the server doesn't return an image, make the error msg visible
57360				image.image.onerror = (function(event) {
57361				    jQuery(this.div).empty();
57362				    jQuery(this.div).append(this.text);
57363				}).bind(the_images[i]);
57364			
57365			    image.image.src = self.host + "/surface/show?" + paramStr;
57366			}
57367
57368		    jQuery(image.div).css('overflow','hidden');
57369
57370		    // The img that was created after the image was loaded should be the only
57371		    // child of the container div.
57372		    var img = jQuery(image.div.childElements()[0]);
57373		    if (img)
57374		    {
57375			img.css('position','absolute')
57376			    .css('left', -(Math.floor(angle) * self.frame_width )+'px');
57377		    }
57378		}
57379	};
57380
57381	function initialize() {
57382		self.index = __surface_widget_count__++;
57383		build_scaffold();
57384	};
57385
57386	initialize();
57387};
57388/**
57389Vertigo Tip by www.vertigo-project.com
57390Requires jQuery
57391*/
57392
57393
57394this.vtip = function() {
57395    this.xOffset = -10; // x distance from mouse
57396    this.yOffset = 10; // y distance from mouse
57397
57398    jQuery(".vtip").unbind().hover(
57399        function(e) {
57400            this.t = this.title;
57401            this.title = '';
57402            this.top = (e.pageY + yOffset); this.left = (e.pageX + xOffset);
57403
57404            jQuery('body').append( '<p id="vtip"><img id="vtipArrow" />' + this.t + '</p>' );
57405
57406            jQuery('p#vtip #vtipArrow').attr("src", '/assets/heatmap_ui/vtip_arrow.png');
57407            jQuery('p#vtip').css("top", this.top+"px").css("left", this.left+"px").fadeIn("slow");
57408
57409        },
57410        function() {
57411            this.title = this.t;
57412            jQuery("p#vtip").fadeOut("slow").remove();
57413        }
57414    ).mousemove(
57415        function(e) {
57416            this.top = (e.pageY + yOffset);
57417            this.left = (e.pageX + xOffset);
57418
57419            jQuery("p#vtip").css("top", this.top+"px").css("left", this.left+"px");
57420        }
57421    );
57422
57423};
57424
57425jQuery(document).ready(function(jQuery){vtip();})
57426;
57427// javascript manifest file for this gem, this is the file that is loaded by apps that use this gem.
57428//
57429
57430;
57431AIBS.array = AIBS.array || {}; // namespace
57432
57433
57434/**
57435 * lifted from Google Closure Library
57436 *
57437 * Compares its two arguments for order, using the built in < and >
57438 * operators.
57439 * @param {*} a The first object to be compared.
57440 * @param {*} b The second object to be compared.
57441 * @return {number} A negative number, zero, or a positive number as the first
57442 *     argument is less than, equal to, or greater than the second.
57443 */
57444AIBS.array.defaultCompare = function(a, b) {
57445  return a > b ? 1 : a < b ? -1 : 0;
57446};
57447
57448
57449/**
57450 * adapted from Google Closure Library
57451 * To be extra conservative, does not use native array prototype functions.
57452 *
57453 * Calls a function for each element in an array.
57454 *
57455 * See {@link http://tinyurl.com/developer-mozilla-org-array-foreach}
57456 *
57457 * @param {goog.array.ArrayLike} arr Array or array like object over
57458 *     which to iterate.
57459 * @param {?function(this: T, ...)} f The function to call for every element.
57460 *     This function takes 3 arguments (the element, the index and the array).
57461 *     The return value is ignored. The function is called only for indexes of
57462 *     the array which have assigned values; it is not called for indexes which
57463 *     have been deleted or which have never been assigned values.
57464 * @param {T=} opt_obj The object to be used as the value of 'this'
57465 *     within f.
57466 * @template T
57467 */
57468AIBS.array.forEach = function(arr, f, opt_obj) {
57469  var len = arr.length;  // must be fixed during loop... see docs
57470  var arr2 = AIBS.isString(arr) ? arr.split('') : arr;
57471  for (var i = 0; i < len; i++) {
57472    if (i in arr2) {
57473      f.call(opt_obj, arr2[i], i, arr);
57474    }
57475  }
57476};
57477
57478
57479/**
57480 * Like AIBS.array.forEach but f is called with a slice of the input array.
57481 * Each slice except the last is guaranteed to contain 'n' elements.  The last
57482 * slice is guaranteed to contain at least one element.
57483 * @param arr
57484 * @param n
57485 * @param f
57486 * @param opt_obj
57487 */
57488AIBS.array.forEachN = function(arr, n, f, opt_obj) {
57489  var len = arr.length;  // must be fixed during loop... see docs
57490  var arr2 = AIBS.isString(arr) ? arr.split('') : arr;
57491  for (var i = 0; i < len; i += n) {
57492    if (i in arr2) {
57493      f.call(opt_obj, arr2.slice(i, i + n), i, arr);
57494    }
57495  }
57496};
57497
57498
57499/**
57500 * adapted from Google Closure Library
57501 * To be extra conservative, does not use native array prototype functions.
57502 *
57503 * Calls a function for each element in an array and inserts the result into a
57504 * new array.
57505 *
57506 * See {@link http://tinyurl.com/developer-mozilla-org-array-map}
57507 *
57508 * @param {goog.array.ArrayLike} arr The array over which to iterate.
57509 * @param {Function} f The function to call for every element. This function
57510 *     takes 3 arguments (the element, the index and the array) and should
57511 *     return something. The result will be inserted into a new array.
57512 * @param {Object=} opt_obj The object to be used as the value of 'this'
57513 *     within f.
57514 * @return {!Array} a new array with the results from f.
57515 */
57516AIBS.array.map = function(arr, f, opt_obj) {
57517  var l = arr.length;  // must be fixed during loop... see docs
57518  var res = new Array(l);
57519  var arr2 = AIBS.isString(arr) ? arr.split('') : arr;
57520  for (var i = 0; i < l; i++) {
57521    if (i in arr2) {
57522      res[i] = f.call(opt_obj, arr2[i], i, arr);
57523    }
57524  }
57525  return res;
57526};
57527
57528
57529/**
57530 * lifted from Google Closure Library
57531 *
57532 * Passes every element of an array into a function and accumulates the result.
57533 *
57534 * See {@link http://tinyurl.com/developer-mozilla-org-array-reduce}
57535 *
57536 * For example:
57537 * var a = [1, 2, 3, 4];
57538 * goog.array.reduce(a, function(r, v, i, arr) {return r + v;}, 0);
57539 * returns 10
57540 *
57541 * @param {goog.array.ArrayLike} arr The array over which to iterate.
57542 * @param {Function} f The function to call for every element. This function
57543 *     takes 4 arguments (the function's previous result or the initial value,
57544 *     the value of the current array element, the current array index, and the
57545 *     array itself)
57546 *     function(previousValue, currentValue, index, array).
57547 * @param {*} val The initial value to pass into the function on the first call.
57548 * @param {Object=} opt_obj  The object to be used as the value of 'this'
57549 *     within f.
57550 * @return {*} Result of evaluating f repeatedly across the values of the array.
57551 */
57552AIBS.array.reduce = function(arr, f, val, opt_obj) {
57553  if (arr.reduce) {
57554    if (opt_obj) {
57555      return arr.reduce(AIBS.bind(f, opt_obj), val);
57556    } else {
57557      return arr.reduce(f, val);
57558    }
57559  }
57560  var rval = val;
57561  AIBS.array.forEach(arr, function(val, index) {
57562    rval = f.call(opt_obj, rval, val, index, arr);
57563  });
57564  return rval;
57565};
57566// requires pre-existing AIBS namespace
57567
57568/**
57569 * lifted from Google Closure Library
57570 * Null function used for default values of callbacks, etc.
57571 * @return {void} Nothing.
57572 */
57573
57574AIBS.nullFunction = function() {};
57575
57576
57577/**
57578 * lifted from Google Closure Library
57579 * This is a "fixed" version of the typeof operator.  It differs from the typeof
57580 * operator in such a way that null returns 'null' and arrays return 'array'.
57581 * @param {*} value The value to get the type of.
57582 * @return {string} The name of the type.
57583 */
57584AIBS.typeOf = function(value) {
57585  var s = typeof value;
57586  if (s == 'object') {
57587    if (value) {
vendor: 9,504 bytes, lines 57588-57877
57588      // Check these first, so we can avoid calling Object.prototype.toString if
57589      // possible.
57590      //
57591      // IE improperly marshals tyepof across execution contexts, but a
57592      // cross-context object will still return false for "instanceof Object".
57593      if (value instanceof Array) {
57594        return 'array';
57595      } else if (value instanceof Object) {
57596        return s;
57597      }
57598
57599      // HACK: In order to use an Object prototype method on the arbitrary
57600      //   value, the compiler requires the value be cast to type Object,
57601      //   even though the ECMA spec explicitly allows it.
57602      var className = Object.prototype.toString.call(
57603          /** @type {Object} */ (value));
57604      // In Firefox 3.6, attempting to access iframe window objects' length
57605      // property throws an NS_ERROR_FAILURE, so we need to special-case it
57606      // here.
57607      if (className == '[object Window]') {
57608        return 'object';
57609      }
57610
57611      // We cannot always use constructor == Array or instanceof Array because
57612      // different frames have different Array objects. In IE6, if the iframe
57613      // where the array was created is destroyed, the array loses its
57614      // prototype. Then dereferencing val.splice here throws an exception, so
57615      // we can't use goog.isFunction. Calling typeof directly returns 'unknown'
57616      // so that will work. In this case, this function will return false and
57617      // most array functions will still work because the array is still
57618      // array-like (supports length and []) even though it has lost its
57619      // prototype.
57620      // Mark Miller noticed that Object.prototype.toString
57621      // allows access to the unforgeable [[Class]] property.
57622      //  15.2.4.2 Object.prototype.toString ( )
57623      //  When the toString method is called, the following steps are taken:
57624      //      1. Get the [[Class]] property of this object.
57625      //      2. Compute a string value by concatenating the three strings
57626      //         "[object ", Result(1), and "]".
57627      //      3. Return Result(2).
57628      // and this behavior survives the destruction of the execution context.
57629      if ((className == '[object Array]' ||
57630           // In IE all non value types are wrapped as objects across window
57631           // boundaries (not iframe though) so we have to do object detection
57632           // for this edge case
57633           typeof value.length == 'number' &&
57634           typeof value.splice != 'undefined' &&
57635           typeof value.propertyIsEnumerable != 'undefined' &&
57636           !value.propertyIsEnumerable('splice')
57637
57638          )) {
57639        return 'array';
57640      }
57641      // HACK: There is still an array case that fails.
57642      //     function ArrayImpostor() {}
57643      //     ArrayImpostor.prototype = [];
57644      //     var impostor = new ArrayImpostor;
57645      // this can be fixed by getting rid of the fast path
57646      // (value instanceof Array) and solely relying on
57647      // (value && Object.prototype.toString.vall(value) === '[object Array]')
57648      // but that would require many more function calls and is not warranted
57649      // unless closure code is receiving objects from untrusted sources.
57650
57651      // IE in cross-window calls does not correctly marshal the function type
57652      // (it appears just as an object) so we cannot use just typeof val ==
57653      // 'function'. However, if the object has a call property, it is a
57654      // function.
57655      if ((className == '[object Function]' ||
57656          typeof value.call != 'undefined' &&
57657          typeof value.propertyIsEnumerable != 'undefined' &&
57658          !value.propertyIsEnumerable('call'))) {
57659        return 'function';
57660      }
57661
57662
57663    } else {
57664      return 'null';
57665    }
57666
57667  } else if (s == 'function' && typeof value.call == 'undefined') {
57668    // In Safari typeof nodeList returns 'function', and on Firefox
57669    // typeof behaves similarly for HTML{Applet,Embed,Object}Elements
57670    // and RegExps.  We would like to return object for those and we can
57671    // detect an invalid function by making sure that the function
57672    // object has a call method.
57673    return 'object';
57674  }
57675  return s;
57676};
57677
57678
57679/**
57680 * lifted from Google Closure Library
57681 * Returns true if the specified value is not |undefined|.
57682 * WARNING: Do not use this to test if an object has a property. Use the in
57683 * operator instead.  Additionally, this function assumes that the global
57684 * undefined variable has not been redefined.
57685 * @param {*} val Variable to test.
57686 * @return {boolean} Whether variable is defined.
57687 */
57688AIBS.isDef = function(val) {
57689  return val !== undefined;
57690};
57691
57692
57693/**
57694 * lifted from Google Closure Library
57695 * Returns true if the specified value is |null|
57696 * @param {*} val Variable to test.
57697 * @return {boolean} Whether variable is null.
57698 */
57699AIBS.isNull = function(val) {
57700  return val === null;
57701};
57702
57703
57704/**
57705 * lifted from Google Closure Library
57706 * Returns true if the specified value is defined and not null
57707 * @param {*} val Variable to test.
57708 * @return {boolean} Whether variable is defined and not null.
57709 */
57710AIBS.isDefAndNotNull = function(val) {
57711  // Note that undefined == null.
57712  return val != null;
57713};
57714
57715
57716/**
57717 * lifted from Google Closure Library
57718 * Returns true if the specified value is an array
57719 * @param {*} val Variable to test.
57720 * @return {boolean} Whether variable is an array.
57721 */
57722AIBS.isArray = function(val) {
57723  return AIBS.typeOf(val) == 'array';
57724};
57725
57726
57727/**
57728 * lifted from Google Closure Library
57729 * Returns true if the specified value is an array
57730 * @param {*} val Variable to test.
57731 * @return {boolean} Whether variable is an array.
57732 */
57733AIBS.isArrayLike = function(val) {
57734  var type = AIBS.typeOf(val);
57735  return type == 'array' || type == 'object' && typeof val.length == 'number';
57736};
57737
57738
57739/**
57740 * lifted from Google Closure Library
57741 * Returns true if the object looks like a Date. To qualify as Date-like
57742 * the value needs to be an object and have a getFullYear() function.
57743 * @param {*} val Variable to test.
57744 * @return {boolean} Whether variable is a like a Date.
57745 */
57746AIBS.isDateLike = function(val) {
57747  return AIBS.isObject(val) && typeof val.getFullYear == 'function';
57748};
57749
57750
57751/**
57752 * lifted from Google Closure Library
57753 * Returns true if the specified value is a string
57754 * @param {*} val Variable to test.
57755 * @return {boolean} Whether variable is a string.
57756 */
57757AIBS.isString = function(val) {
57758  return typeof val == 'string';
57759};
57760
57761
57762/**
57763 * lifted from Google Closure Library
57764 * Returns true if the specified value is a boolean
57765 * @param {*} val Variable to test.
57766 * @return {boolean} Whether variable is boolean.
57767 */
57768AIBS.isBoolean = function(val) {
57769  return typeof val == 'boolean';
57770};
57771
57772
57773/**
57774 * lifted from Google Closure Library
57775 * Returns true if the specified value is a number
57776 * @param {*} val Variable to test.
57777 * @return {boolean} Whether variable is a number.
57778 */
57779AIBS.isNumber = function(val) {
57780  return typeof val == 'number';
57781};
57782
57783
57784/**
57785 * lifted from Google Closure Library
57786 *
57787 * Returns true if the specified value is a function
57788 * @param {*} val Variable to test.
57789 * @return {boolean} Whether variable is a function.
57790 */
57791AIBS.isFunction = function(val) {
57792  return AIBS.typeOf(val) == 'function';
57793};
57794
57795
57796/**
57797 * lifted from Google Closure Library
57798 *
57799 * Returns true if the specified value is an object.  This includes arrays
57800 * and functions.
57801 * @param {*} val Variable to test.
57802 * @return {boolean} Whether variable is an object.
57803 */
57804AIBS.isObject = function(val) {
57805  var type = typeof val;
57806  return type == 'object' && val != null || type == 'function';
57807  // return Object(val) === val also works, but is slower, especially if val is
57808  // not an object.
57809};
57810
57811
57812/**
57813 * adatpted from Google Closure Library
57814 * Does not use browser native bind even if it exists.
57815 *
57816 * A pure-JS implementation of goog.bind.
57817 * @param {Function} fn A function to partially apply.
57818 * @param {Object|undefined} selfObj Specifies the object which |this| should
57819 *     point to when the function is run.
57820 * @param {...*} var_args Additional arguments that are partially
57821 *     applied to the function.
57822 * @return {!Function} A partially-applied form of the function bind() was
57823 *     invoked as a method of.
57824 * @private
57825 */
57826AIBS.bind = function(fn, selfObj, var_args) {
57827  if (!fn) {
57828    throw new Error();
57829  }
57830
57831  if (arguments.length > 2) {
57832    var boundArgs = Array.prototype.slice.call(arguments, 2);
57833    return function() {
57834      // Prepend the bound arguments to the current arguments.
57835      var newArgs = Array.prototype.slice.call(arguments);
57836      Array.prototype.unshift.apply(newArgs, boundArgs);
57837      return fn.apply(selfObj, newArgs);
57838    };
57839
57840  } else {
57841    return function() {
57842      return fn.apply(selfObj, arguments);
57843    };
57844  }
57845};
57846
57847
57848/**
57849 * lifted from Google Closure Library
57850 *
57851 * Like bind(), except that a 'this object' is not required. Useful when the
57852 * target function is already bound.
57853 *
57854 * Usage:
57855 * var g = partial(f, arg1, arg2);
57856 * g(arg3, arg4);
57857 *
57858 * @param {Function} fn A function to partially apply.
57859 * @param {...*} var_args Additional arguments that are partially
57860 *     applied to fn.
57861 * @return {!Function} A partially-applied form of the function bind() was
57862 *     invoked as a method of.
57863 */
57864AIBS.partial = function(fn, var_args) {
57865  var args = Array.prototype.slice.call(arguments, 1);
57866  return function() {
57867    // Prepend the bound arguments to the current arguments.
57868    var newArgs = Array.prototype.slice.call(arguments);
57869    newArgs.unshift.apply(newArgs, args);
57870    return fn.apply(this, newArgs);
57871  };
57872};
57873// requires pre-existing AIBS namespace
57874
57875AIBS.dbg = AIBS.dbg || {}; // namespace
57876
57877
57878/** @param {...} msg TODO. */
57879AIBS.dbg.info = function(msg) {
57880  try { window.console.info.apply(window.console, arguments); } catch (ex) {}
57881};
57882
57883
57884/** @param {...} msg TODO. */
57885AIBS.dbg.warn = function(msg) {
57886  try { window.console.warn.apply(window.console, arguments); } catch (ex) {}
57887};
57888
57889
57890/** @param {...} msg TODO. */
57891AIBS.dbg.error = function(msg) {
57892  try { window.console.error.apply(window.console, arguments); } catch (ex) {}
57893};
57894// adapted from slick.model.js
57895
57896
57897AIBS.events = AIBS.events || {}; // namespace
57898
57899/**
57900 * @constructor
57901 */
57902AIBS.events.EventHelper = function() {
57903  /** @private */
57904  this.handlers_ = [];
57905};
57906
57907
57908AIBS.events.EventHelper.prototype.subscribe = function(fn) {
57909  this.handlers_.push(fn);
57910};
57911
57912
57913AIBS.events.EventHelper.prototype.notify = function(args) {
57914  for (var i = 0; i < this.handlers_.length; i++) {
57915    this.handlers_[i].call(this, args);
57916  }
57917};
57918// requires pre-existing AIBS namespace
57919
57920AIBS.object = AIBS.object || {}; // namespace
57921
57922
57923/**
57924 * lifted from Google Closure Library
57925 * Calls a function for each element in an object/map/hash.
57926 *
57927 * @param {Object} obj The object over which to iterate.
57928 * @param {Function} f The function to call for every element. This function
57929 *     takes 3 arguments (the element, the index and the object)
57930 *     and the return value is irrelevant.
57931 * @param {Object=} opt_obj This is used as the 'this' object within f.
57932 */
57933AIBS.object.forEach = function(obj, f, opt_obj) {
57934  for (var key in obj) {
57935    f.call(opt_obj, obj[key], key, obj);
57936  }
57937};
57938
57939
57940/**
57941 * lifted from Google Closure Library
57942 * Get a value from an object multiple levels deep.  This is useful for
57943 * pulling values from deeply nested objects, such as JSON responses.
57944 * Example usage: getValueByKeys(jsonObj, 'foo', 'entries', 3)
57945 *
57946 * @param {!Object} obj An object to get the value from.  Can be array-like.
57947 * @param {...(string|number|!Array.<number|string>)} var_args A number of keys
57948 *     (as strings, or nubmers, for array-like objects).  Can also be
57949 *     specified as a single array of keys.
57950 * @return {*} The resulting value.  If, at any point, the value for a key
57951 *     is undefined, returns undefined.
57952 */
57953AIBS.object.getValueByKeys = function(obj, var_args) {
57954  var isArrayLike = AIBS.isArrayLike(var_args);
57955  var keys = isArrayLike ? var_args : arguments;
57956
57957  // Start with the 2nd parameter for the variable parameters syntax.
57958  for (var i = isArrayLike ? 0 : 1; i < keys.length; i++) {
57959    obj = obj[keys[i]];
57960    if (!AIBS.isDef(obj)) {
57961      break;
57962    }
57963  }
57964
57965  return obj;
57966};
57967AIBS.string = AIBS.string || {}; // namespace
57968
57969/**
57970 * lifted from Google Closure Library
57971 * Trims white spaces to the left and right of a string.
57972 * @param {string} s The string to trim.
57973 * @return {string} A trimmed copy of {@code str}.
57974 */
57975AIBS.string.trim = function(s) {
57976  // Since IE doesn't include non-breaking-space (0xa0) in their \s character
57977  // class (as required by section 7.2 of the ECMAScript spec), we explicitly
57978  // include it in the regexp to enforce consistent cross-browser behavior.
57979  return s.replace(/^[\s\xa0]+|[\s\xa0]+$/g, '');
57980};
57981
57982
57983AIBS.string.stripNonNumerals = function(s) {
57984  return s.replace(/[^0-9]/g, '');
57985};
57986// requires pre-existing AIBS namespace
57987
57988AIBS.util = AIBS.util || {}; // namespace
57989
57990/**
57991 *
57992 * @param {!string} prefix TODO.
57993 * @param {*} o TODO.
57994 * @return {!string} TODO.
57995 */
57996AIBS.util.inspect = function(prefix, o) {
57997  var s = '';
57998  AIBS.object.forEach(o, function(val, key, o) {
57999    s += '\n' + prefix + 'key: ' + key;
58000    if (AIBS.typeOf(val) == 'object' && val !== null & key != 'element') {
58001      s += AIBS.util.inspect(prefix + '    ', val);
58002    } else {
58003      s += ',   value: ' + val;
58004    }
58005  });
58006  return s;
58007};
58008"use strict";
58009/**
58010 * An object encapsulating portions of the Allen Institute Analysis services,
58011 * like Differential search and Correlation search. These services have a different
58012 * workflow than the API data service in that these are session based, and they
58013 * have a different user focus, as they encapsulate calculations performed over the data
58014 * rather than the raw data itself.
58015 * <p>
58016
58017 * </p>
58018 * @class
58019 * @param service_url:   The base url for the analysis services
58020 * @param options:       A hash of the following (work in progress):
58021 * <ul>
58022 * <li>
58023 *     <b>tineout</b>:  A default value after which an ajax call will fail, given in milliseconds.  Default = 10000.
58024 * </li>
58025 * <li>
58026 *     <b>others</b>:  TBD
58027 * </li>
58028 * </ul>
58029 */
58030AIBS.Analysis.DAO = (function($j) {
58031
58032 return (
58033          function(service_url, options)   {
58034
58035        // public properties
58036              this.service_url = service_url;
58037              this.user_options = {
58038                  timeout:10000,
58039                  heartbeat_interval:600000
58040              };
58041              $j.extend(true, this.user_options, options);
58042
58043            /**
58044             * Fetches an object and the specified associations
58045             * @param domain1  {Array<integer>} AKA the Contrast structures(s)
58046             * @param domain2  {Array<integer>} AKA the Target structures(s)
58047             * @param options  An object containing:
58048             * <ul>
58049             * <li>
58050             *    <b>success_callback</b>:    A function to be called when the server responds with HTTP status 200
58051             * </li>
58052             * <li>
58053             *    <b>error_callback</b>:      a function to be called when the server responds with anything other than http status 200.
58054             *          Both callback function should look like this:
58055             *          <pre>
58056             *          fn(response_data, text_status, xhdr)
58057             *              response_data: The server's response to the request, already parsed into the requested format.
58058             *              text_status: a string describing the status
58059             *              xhdr: a superset of the browser's native XMLHttpRequest object. For example, it contains
58060             *                    responseText and responseXML properties, as well as a getResponseHeader()
58061             *          </pre>
58062             * </li>
58063             * <li>
58064             *    <b>timeout</b>:  A number in milliseconds after which the error_callback will be called.
58065             *              NOTE that this value overrides the global timeout value given as part of the DAO
58066             *              initialization.  That value defaults to 10 seconds.
58067             * </li>
58068             * </ul>
58069             */
58070              this.create_differential_session = function(domain1, domain2, options) {
58071
58072                  var settings = {
58073                    domain1:domain1,
58074                    domain2:domain2,
58075                    image_set:'P56',
58076                    format:'json',
58077                    timeout:30000
58078                 }
58079
58080                  if(options)
58081                    $j.extend(settings, options);
58082
58083                  settings.verb = 'POST';
58084                  _attempted_image_set = settings.image_set;
58085                  var url = this.service_url + settings.image_set + '/search/differential_rows';
58086                  return(place_request(settings, url));
58087              }
58088
58089
58090              /**
58091               * Run the correlation query that defines the session.  Returns information about the
58092               * session, which looks like this:
58093               * <pre>
58094               * {
58095                     msg:{
58096                           success: "OK" for success, an error message otherwise.
58097                           code: 0 for success, an error code otherwise.
58098                           rows_available: If the request succeeds, this contains the count of the total rows available in the session
58099                           session_id:  If the request succeeds, the ID of the session connected to this result set.
58100                    }
58101                 }
58102               * </pre>
58103               * @param {Array<integer>}    domain
58104               * @param {integer}           rows
58105               * @param (object)            options
58106               * Options include:
58107               * <ul>
58108               *    <li>
58109               *    image_set: Either 'P56', for all the sections or 'P56Coronal' for just the coronal data. Default = 'P56'
58110               *    </li>
58111               *    <li>
58112               *    sort_order: sorts according to the correlation 'r' value.  Either 'asc' or desc'.  Default = 'desc'.
58113               *    </li>
58114               *    <li>
58115               *    timeout: time (in milliseconds) without response before the request returns an error.  Default = 10000..
58116               *    </li>
58117               *    <li>
58118               *       <b>success_callback</b>:    A function to be called when the server responds with HTTP status 200
58119               *    </li>
58120               *    <li>
58121               *    <b>error_callback</b>:      a function to be called when the server responds with anything other than http status 200.
58122               *          Both callback function should look like this:
58123               *          <pre>
58124               *          fn(response_data, text_status, xhdr)
58125               *              response_data: The server's response to the request, already parsed into the requested format.
58126               *              text_status: a string describing the status
58127               *              xhdr: a superset of the browser's native XMLHttpRequest object. For example, it contains
58128               *                    responseText and responseXML properties, as well as a getResponseHeader()
58129               *          </pre>
58130               *    </li>
58131               * </ul>
58132               */
58133              this.create_correlation_session = function(domain, ages, rows, sort_order, options) {
58134
58135                  var settings = {
58136                    domain:domain,
58137                    ages:ages,
58138                    rows:rows,
58139                    sort_order:'desc',
58140                    timeout:3000
58141                 }
58142                   if(options)
58143                    $j.extend(true, settings, options);
58144
58145                  settings.verb = 'POST';
58146
58147                  var url = this.service_url  + 'search/correlated_rows';
58148                  return(place_request(settings, url));
58149              }
58150
58151	      /**
58152		 Lots of documentation goes here, but there isn't any yet.
58153	       */
58154
58155	      this.create_agea_session = function(seed_point, options) {
58156		  var settings = {
58157		      seed_point: seed_point,
58158		      image_set: '',
58159		      map_age: 'P56',
58160		      seed_age: 'P56'
58161		  }
58162
58163		  if (options)
58164		      $j.extend(true, settings, options);
58165
58166		  settings.verb = 'POST';
58167
58168                  _attempted_image_set = settings.image_set;
58169  //                var url = this.service_url + settings.image_set + '/search/differential_rows';
58170                var url = this.service_url + '/search/differential_rows';
58171
58172                  return(place_request(settings, url));
58173	      }
58174	      
58175              this.sort_correlated_rows = function(session_id, sort_order, options) {
58176
58177                  var settings = {
58178                    sort_order:sort_order,
58179                    timeout:3000
58180                  }
58181
58182                  if(options)
58183                    $j.extend(settings, options);
58184
58185                  settings.verb = 'POST';
58186
58187                  var url = this.service_url + 'search/sort_rows/' + session_id;
58188                  return(place_request(settings, url));
58189              }
58190
58191              /**
58192               * Return some rows for the given session
58193               * @param session_id
58194               * @param options
58195               */
58196              this.get_rows = function(session_id, options) {
58197                  var settings = {
58198                    verb:'GET',
58199                    format:'json',
58200                    timeout:3000,
58201                    limit:100,
58202                    offset:0,
58203                    detail:1
58204                 }
58205
58206                  if(options)
58207                    $j.extend(settings, options);
58208
58209                  var url = this.service_url + 'search/list_rows/' + session_id;
58210                  return(place_request(settings, url));
58211              }
58212
58213              /**
58214               * @private
58215               * @scope AIBS.DATA.API_DAO
58216               */
58217              var _self = this;
58218              var _current_session_id = '';
58219              var _heartbeat_timer = null;
58220              var _current_image_set = '';
58221              var _attempted_image_set = '';
58222
58223        // private methods
58224              function place_request(options, url) {
58225                   // user options are parsed into usable formats
58226                  var req_data = build_request(options);
58227                  var req_options = build_options(options);
58228
58229                  var request = {
58230                    type:req_options.verb,
58231                    url:url,
58232                    data:req_options.verb == 'POST' ? JSON.stringify(req_data) : req_data,
58233                    success: function(response_data, text_status, xhdr) {success_handler(req_options, response_data, text_status, xhdr)},
58234                    error: function(xhdr, text_status, error) {error_handler(options, xhdr, text_status, error)},
58235                    dataType:req_options.format
58236                  }
58237
58238                  if(options.parse_request)
58239                      return(request);
58240
58241                  // the success & error callback functions are
58242                  // wrapped in order to do some housekeeping.
58243                  request.success = function(response_data, text_status, xhdr) {success_handler(req_options, response_data, text_status, xhdr)};
58244                  request.error = function(xhdr, text_status, error) {error_handler(options, xhdr, text_status, error)};
58245                  $j.ajax(request);
58246              }
58247
58248
58249              function start_heartbeat(session_id) {
58250                  _current_session_id = session_id;
58251                  if(_heartbeat_timer)
58252                      clearInterval(_heartbeat_timer);
58253                  _heartbeat_timer = 0;
58254
58255                  _current_image_set = _attempted_image_set;
58256                  _heartbeat_timer = setInterval(beat, _self.user_options.heartbeat_interval);
58257              }
58258
58259              function stop_heartbeat() {
58260                  _current_session_id = '';
58261                  _current_image_set = '';
58262
58263                  if(_heartbeat_timer)
58264                      clearInterval(_heartbeat_timer);
58265
58266                  _heartbeat_timer = 0;
58267              }
58268
58269              function beat() {
58270                  var time = new Date().getTime();
58271                  var url = _self.service_url + _current_image_set + '/search/session_info/' + _current_session_id;
58272
58273                  var request = {
58274                      type:'GET',
58275                      url:url,
58276                      data:null,
58277                      success:function(response_data, text_status, xhdr) { /* NOP */},
58278                      error:function(response_data, text_status, xhdr) { /* NOP */},
58279                      dataType:'json'
58280                  }
58281                  $j.ajax(request);
58282              }
58283
58284              function success_handler(callbacks, response_data, text_status, xhdr) {
58285
58286                  if(!response_data) {
58287                     callbacks.error_callback(response_data, text_status, xhdr);
58288                     return;
58289                  }
58290                  if(response_data.status && response_data.status.id == 0) {
58291                      var response = {
58292                              response:{
58293                                  success:response_data.status.message,
58294                                  code:response_data.status.id,
58295                                  rows_available:response_data.number_of_rows,
58296                                  session_id:response_data.session_id
58297                              }
58298                      };
58299                     callbacks.success_callback(response, text_status, xhdr);
58300                  }
58301                  else { // assume success
58302                      response_data.response = {
58303                          success:"OK",
58304                          code:0
58305                      }
58306                     callbacks.success_callback(response_data, text_status, xhdr);
58307                  }
58308
58309                  if(response_data.session_id) {
58310                     _current_image_set = _attempted_image_set;
58311                     start_heartbeat(response_data.session_id);
58312                  }
58313              }
58314
58315              function error_handler(callbacks, xhdr, text_status, error) {
58316                  if(callbacks) {
58317                     callbacks.error_callback(xhdr, text_status, error);
58318                  }
58319                  stop_heartbeat();
58320              }
58321
58322            /**
58323             * Decoupling from the service parameters & semantics
58324             * @param options
58325             */
58326              function build_request(options) {
58327
58328                  var request = {};
58329                  if(options.limit)
58330                     request.numRows = options.limit;
58331                  if(options.offset >= 0)
58332                     request.startRow = options.offset;
58333                  if(options.domain1)
58334                     request.domain1 = options.domain1;
58335                  if(options.domain2)
58336                     request.domain2 = options.domain2;
58337                  if(options.detail)
58338                     request.showDetail = options.detail;
58339                  if(options.domain)
58340                     request.domain = options.domain;
58341                  if(options.rows)
58342                     request.rows = options.rows;
58343                  if(options.ages)
58344                     request.ages = options.ages;
58345                  if(options.sort_order)
58346                     request.sort_order = options.sort_order;
58347                  if(options.target_domain_threshold)
58348                     request.target_domain_threshold = options.target_domain_threshold;
58349                  if(options.contrast_domain_threshold)
58350                     request.contrast_domain_threshold = options.contrast_domain_threshold;
58351                  if (options.seed_point)
58352                      request.seed_point = options.seed_point;
58353                  if (options.correlation_map_thresholds)
58354                      request.correlation_map_thresholds = options.correlation_map_thresholds;
58355                  if (options.correlation_seed_age_threshold)
58356                      request.correlation_seed_age_threshold = options.correlation_seed_age_threshold;
58357                  if (options.correlation_map_age_threshold)
58358                      request.correlation_map_age_threshold = options.correlation_map_age_threshold;
58359                  if(options.map_age)
58360                      request.map_age = options.map_age;
58361                  if(options.seed_age)
58362                      request.seed_age = options.seed_age;
58363
58364
58365                  return(request);
58366              }
58367
58368              function build_options(options) {
58369
58370                  var request = {};
58371                  request.success_callback = options.success_callback ? options.success_callback : function(){};
58372                  request.error_callback = options.error_callback ? options.error_callback : function(){};
58373                  request.verb = options.verb ? options.verb : 'GET';
58374                  request.format = options.format ? options.format : 'json';
58375                  return(request);
58376              }
58377
58378              function initialize() {
58379
58380
58381                 // HEY! this is wacky. Possibly an interaction with the Prototype library which
58382                 // adds toJSON methods to most objects, including Array, which DOESN'T WORK RIGHT.
58383                 // I don't know how the removal of this method would affect other components that
58384                 // depend on Prototype's Array.toJSON method (that doesn't work) but since it
58385                 // doesn't work anyway, I'm going to go ahead and remove it because it is preventing
58386                 // my JSON-formatted requests from working properly.  Specifically, the Array.toJSON
58387                 // that's causing me trouble is causing arrays to be quoted.
58388                 //delete Array.prototype.toJSON;  // NOTE Array.prototype.toJSON added by prototype lib is needed by the current NHP microarray theme
58389
58390                 // do some ajax config.  Caching, etc.
58391                 // This should reference the user supplied DAO options.
58392                 var settings = {
58393                     timeout:_self.user_options.timeout
58394                 }
58395                 $j.ajaxSetup(settings);
58396
58397              }
58398
58399
58400        // initialize it all up, like.
58401              initialize();
58402
58403          }
58404 );
58405
58406
58407})(jQuery);
58408
58409"use strict";
58410/**
58411 * An object encapsulating portions of the Allen Institute API service
58412 * <p>
58413
58414 * </p>
58415 * @class
58416 * @param service_url:   The base url for the API data service
58417 * @param options:       A hash of the following (work in progress):
58418 * <ul>
58419 * <li>
58420 *     <b>tineout</b>:  A default value after which an ajax call will fail, given in milliseconds.  Default = 3000.  Note that this
58421 * </li>
58422 * <li>
58423 *     <b>others</b>:  TBD
58424 * </li>
58425 * </ul>
58426 */
58427AIBS.DATA.API_DAO = (function($j) {
58428
58429 return (
58430          function(service_url, options)   {
58431
58432        // public properties
58433              this.service_url = service_url;
58434              this.user_options = options ? options : {};
58435
58436            /**
58437             * Fetches an object and the specified associations
58438             * @param type      The name of the data model being queried.
58439             * @param id        The ID of the instance being fetched.
58440             * @param options   An object containing:
58441             * <ul>
58442             * <li>
58443             *    <b>format</b>:             'json' or 'xml'
58444             * </li>
58445             * <li>
58446             *    <b>verb</b>:               'get'  or 'post'
58447             * </li>
58448             * <li>
58449             *    <b>success_callback</b>:    A function to be called when the server responds with HTTP status 200
58450             * </li>
58451             * <li>
58452             *    <b>error_callback</b>:      a function to be called when the server responds with anything other than http status 200.
58453             *          Both callback function should look like this:
58454             *          <pre>
58455             *          fn(response_data, text_status, xhdr)
58456             *              response_data: The server's response to the request, already parsed into the requested format.
58457             *              text_status: a string describing the status
58458             *              xhdr: a superset of the browser's native XMLHttpRequest object. For example, it contains
58459             *                    responseText and responseXML properties, as well as a getResponseHeader()
58460             *          </pre>
58461             * </li>
58462             * <li>
58463             *    <b>timeout</b>:  A number in milliseconds after which the error_callback will be called.
58464             *              NOTE that this value overrides the global timeout value given as part of the DAO
58465             *              initialization.  That value defaults to 10 seconds.
58466             * </li>
58467             * <li>
58468             *    <b>cache</b>:    If true, the result of this request will be cached.  If the same request is made again in this session
58469             *              the cached result will be returned, bypassing the ajax request cycle entirely.
58470             * </li>
58471             * <li>
58472             *    <b>include</b>:  A description of the associations and related criteria.  See the big documentation on the RMA API
58473             * </li>
58474             * <li>
58475             *    <b>exclude</b>:  A comma-delimited list of fields to exclude from the result set.
58476             * </li>
58477             * <li>
58478             *    <b>select</b>:   A comma delimited list of fields to return in the result set.
58479             * </li>
58480             * <li>
58481             *    <b>order</b>:    A a comma-delimited list of fields by which the result set should be ordered.
58482             * </li>
58483             * <li>
58484             *    <b>limit</b>:    The maximum number of rows that should be returned in a result set.
58485             * </li>
58486             * <li>
58487             *    <b>offset</b>:   The offset into the entire result set that should be returned.
58488             * </li>
58489             * <li>
58490             *    <b>query</b>:    A string that, if provided, will be used for a search of the given Type.
58491             * </li>
58492             * </ul>
58493             */
58494              this.fetch_by_id = function(type, id, options) {
58495                  var url = this.service_url + type + '/' + id + '.' + options.format;
58496                  return(place_request(options, url));
58497              }
58498
58499
58500            /**
58501             * Queries the service for the specified type and its specified associations
58502             * @param type      The name of the data model being queried.
58503             * @param options   An object containing:
58504             * <ul>
58505             * <li>
58506             *    <b>format</b>:             'json' or 'xml'
58507             * </li>
58508             * <li>
58509             *    <b>verb</b>:               'get'  or 'post'
58510             * </li>
58511             * <li>
58512             *    <b>success_callback</b>:    A function to be called when the server responds with HTTP status 200
58513             * </li>
58514             * <li>
58515             *    <b>error_callback</b>:      a function to be called when the server responds with anything other than http status 200.
58516             *          Both callback function should look like this:
58517             *          <pre>
58518             *          fn(response_data, text_status, xhdr)
58519             *              response_data: The server's response to the request, already parsed into the requested format.
58520             *              text_status: a string describing the status
58521             *              xhdr: a superset of the browser's native XMLHttpRequest object. For example, it contains
58522             *                    responseText and responseXML properties, as well as a getResponseHeader()
58523             *          </pre>
58524             * </li>
58525             * <li>
58526             *    <b>timeout</b>:  A number in milliseconds after which the error_callback will be called.
58527             *              NOTE that this value overrides the global timeout value given as part of the DAO
58528             *              initialization.  That value defaults to 10 seconds.
58529             * </li>
58530             * <li>
58531             *    <b>cache</b>:    If true, the result of this request will be cached.  If the same request is made again in this session
58532             *              the cached result will be returned, bypassing the ajax request cycle entirely.
58533             * </li>
58534             * <li>
58535             *    <b>include</b>:  A description of the associations and related criteria.  See the big documentation on the RMA API
58536             * </li>
58537             * <li>
58538             *    <b>exclude</b>:  A comma-delimited list of fields to exclude from the result set.
58539             * </li>
58540             * <li>
58541             *    <b>select</b>:   A comma delimited list of fields to return in the result set.
58542             * </li>
58543             * <li>
58544             *    <b>order</b>:    A a comma-delimited list of fields by which the result set should be ordered.
58545             * </li>
58546             * <li>
58547             *    <b>limit</b>:    The maximum number of rows that should be returned in a result set.
58548             * </li>
58549             * <li>
58550             *    <b>offset</b>:   The offset into the entire result set that should be returned.
58551             * </li>
58552             * <li>
58553             *    <b>query</b>:    A string that, if provided, will be used for a search of the given Type.
58554             * </li>
58555             * </ul>
58556             */
58557              this.query = function(type, options) {
58558                  var url = this.service_url + type + '/query' + '.' + options.format;
58559                  return(place_request(options, url));
58560              }
58561
58562
58563            /**
58564             * Queries the service for a description of the specified type, its fields and associations
58565             * @param type      The name of the data model being queried.
58566             * @param options   An object containing:
58567             * <ul>
58568             * <li>
58569             *    <b>format</b>:             'json' or 'xml'
58570             * </li>
58571             * <li>
58572             *    <b>verb</b>:               'get'  or 'post'
58573             * </li>
58574             * <li>
58575             *    <b>success_callback</b>:    A function to be called when the server responds with HTTP status 200
58576             * </li>
58577             * <li>
58578             *    <b>error_callback</b>:      a function to be called when the server responds with anything other than http status 200.
58579             *          Both callback function should look like this:
58580             *          <pre>
58581             *          fn(response_data, text_status, xhdr)
58582             *              response_data: The server's response to the request, already parsed into the requested format.
58583             *              text_status: a string describing the status
58584             *              xhdr: a superset of the browser's native XMLHttpRequest object. For example, it contains
58585             *                    responseText and responseXML properties, as well as a getResponseHeader()
58586             *          </pre>
58587             * </li>
58588             * <li>
58589             *    <b>timeout</b>:  A number in milliseconds after which the error_callback will be called.
58590             *              NOTE that this value overrides the global timeout value given as part of the DAO
58591             *              initialization.  That value defaults to 10 seconds.
58592             * </li>
58593             * <li>
58594             *    <b>cache</b>:    If true, the result of this request will be cached.  If the same request is made again in this session
58595             *              the cached result will be returned, bypassing the ajax request cycle entirely.
58596             * </li>
58597             * </ul>
58598             */
58599               this.describe = function(type, options) {
58600                  var url = this.service_url + type + '/describe' + '.' + options.format;
58601                  return(place_request(options, url));
58602              }
58603
58604
58605            /**
58606             * Enumerates all available types, their fields and associations
58607             * @param options   An object containing:
58608             * <ul>
58609             * <li>
58610             *    <b>format</b>:             'json' or 'xml'
58611             * </li>
58612             * <li>
58613             *    <b>verb</b>:               'get'  or 'post'
58614             * </li>
58615             * <li>
58616             *    <b>success_callback</b>:    A function to be called when the server responds with HTTP status 200
58617             * </li>
58618             * <li>
58619             *    <b>error_callback</b>:      a function to be called when the server responds with anything other than http status 200.
58620             *          Both callback function should look like this:
58621             *          <pre>
58622             *          fn(response_data, text_status, xhdr)
58623             *              response_data: The server's response to the request, already parsed into the requested format.
58624             *              text_status: a string describing the status
58625             *              xhdr: a superset of the browser's native XMLHttpRequest object. For example, it contains
58626             *                    responseText and responseXML properties, as well as a getResponseHeader()
58627             *          </pre>
58628             * </li>
58629             * <li>
58630             *    <b>timeout</b>:  A number in milliseconds after which the error_callback will be called.
58631             *              NOTE that this value overrides the global timeout value given as part of the DAO
58632             *              initialization.  That value defaults to 10 seconds.
58633             * </li>
58634             * <li>
58635             *    <b>cache</b>:    If true, the result of this request will be cached.  If the same request is made again in this session
58636             *              the cached result will be returned, bypassing the ajax request cycle entirely.
58637             * </li>
58638             * </ul>
58639             */
58640              this.enumerate = function(options) {
58641                 var url = this.service_url + 'enumerate' + '.' + options.format;
58642                 return(place_request(options, url));
58643              }
58644
58645              /**
58646               * Queries the service with the supplied customized data, skipping all the request building and response parsing
58647               * @param options   An object containing:
58648               * <ul>
58649               * <li>
58650               *    <b>format</b>:             'json' or 'xml'
58651               * </li>
58652               * <li>
58653               *    <b>verb</b>:               'get'  or 'post'
58654               * </li>
58655               * <li>
58656               *    <b>success_callback</b>:    A function to be called when the server responds with HTTP status 200
58657               * </li>
58658               * <li>
58659               *    <b>error_callback</b>:      a function to be called when the server responds with anything other than http status 200.
58660               *          Both callback function should look like this:
58661               *          <pre>
58662               *          fn(response_data, text_status, xhdr)
58663               *              response_data: The server's response to the request, already parsed into the requested format.
58664               *              text_status: a string describing the status
58665               *              xhdr: a superset of the browser's native XMLHttpRequest object. For example, it contains
58666               *                    responseText and responseXML properties, as well as a getResponseHeader()
58667               *          </pre>
58668               * </li>
58669               * </ul>
58670               * @param url      The url of this request, usually a url on the warehouse server
58671               * @param customized_request_data      The data blob for the jquery.ajax() call's request.data
58672               */
58673              this.customized_request = function(options, url, customized_request_data ) {
58674                  return(place_customized_request(options, url, customized_request_data));
58675              }
58676
58677              /**
58678               * @private
58679               * @scope AIBS.DATA.API_DAO
58680               */
58681              var _self = this;
58682
58683
58684        // private methods
58685              function place_request(options, url) {
58686
58687                   // user options are parsed into usable formats
58688                  var req_data = build_request(options);
58689                  var req_options = build_options(options);
58690
58691                  var request = {
58692         //           cacheResponse:true,
58693                    type:req_options.verb,
58694                    url:url,
58695                    data:req_data,
58696                      // the success & error callback functions are wrapped in order to do some housekeeping.
58697                      success: function(response_data, text_status, xhdr) {success_handler(req_options, response_data, text_status, xhdr)},
58698                    error: function(xhdr, text_status, error) {error_handler(options, xhdr, text_status, error)},
58699                    dataType:req_options.format
58700                  };
58701
58702                  if(options.parse_request)
58703                      return(request);
58704
58705                  $j.ajax(request);
58706              }
58707
58708              function place_customized_request(options, url, customized_req_data) {
58709                   // user options are parsed into usable formats
58710                  //var req_data = build_request(options);
58711                  var req_options = build_options(options);
58712
58713                  var request = {
58714         //           cacheResponse:true,
58715                    type:req_options.verb,
58716                    url:url,
58717                    //data:req_data,
58718                    //success:function(response_data, text_status, xhdr) {success_handler(req_options, response_data, text_status, xhdr)},
58719                    //error:function(xhdr, text_status, error) {error_handler(options, xhdr, text_status, error)},
58720                    success:function(response_data, text_status, xhdr) {options.success_callback(response_data, text_status, xhdr);},
58721                    error:function(xhdr, text_status, error) {options.error_callback(xhdr, text_status, error);},
58722                    dataType:req_options.format
58723                  }
58724
58725                  if(customized_req_data)
58726                      request.data = customized_req_data;
58727
58728                  if(options.parse_request)
58729                      return(request);
58730
58731                  // the success & error callback functions are
58732                  // wrapped in order to do some housekeeping.
58733                  //request.success = function(response_data, text_status, xhdr) {success_handler(req_options, response_data, text_status, xhdr)};
58734                  //request.error = function(xhdr, text_status, error) {error_handler(options, xhdr, text_status, error)};
58735                  $j.ajax(request);
58736              }
58737
58738              function success_handler(callbacks, response_data, text_status, xhdr) {
58739
58740                  if(!response_data || response_data.success == false) {
58741                     callbacks.error_callback(response_data, text_status, xhdr);
58742                     return;
58743                  }
58744
58745        //TODO: handle XML properly
58746                  if(response_data.success == true) {
58747                     if(response_data.msg)
58748                        response_data.msg.response = {success:response_data.success, code:response_data.id, rows_available:response_data.total_rows};
58749                     callbacks.success_callback(response_data.msg, text_status, xhdr);
58750                  }
58751                  else
58752                     // if there is an error that's handled by the server, which then
58753                     // returns an error code & HTTP 200, we have to identify it
58754                     // here and call the user's error callback.
58755                     callbacks.error_callback(response_data, text_status, xhdr);
58756              }
58757
58758              function error_handler(callbacks, xhdr, text_status, error) {
58759                  if(callbacks) {
58760                     callbacks.error_callback(xhdr, text_status, error);
58761                  }
58762              }
58763
58764            /**
58765             * Decoupling from the service parameters & semantics
58766             * @param options
58767             */
58768              function build_request(options) {
58769
58770                  var request = {};
58771                  request.wrap = options.wrap ? options.wrap : 'true';
58772                  if(options.include)
58773                     request.include = options.include;
58774                  if(options.exclude)
58775                     request.except = options.exclude;
58776                  if(options.select)
58777                     request.only = options.select;
58778                  if(options.order)
58779                     request.order = options.order;
58780                  if(options.limit)
58781                     request.numRows = options.limit;
58782                  if(options.offset)
58783                     request.startRow = options.offset;
58784                  if(options.query)
58785                     request.query = options.query;
58786                  if(options.criteria)
58787                     request.criteria = options.criteria;
58788                  if(options.tabular)
58789                     request.tabular = options.tabular;
58790                  if(options.filter)
58791                     request.filter = options.filter;
58792                  return(request);
58793              }
58794
58795              function build_options(options) {
58796
58797                  var request = {};
58798                  request.success_callback = options.success_callback ? options.success_callback : function(){};
58799                  request.error_callback = options.error_callback ? options.error_callback : function(){};
58800                  request.format = options.format ? options.format : 'json';
58801                  request.verb = options.verb ? options.verb : 'GET';
58802
58803                  return(request);
58804              }
58805
58806              function initialize() {
58807                 // do some ajax config.  Caching, etc.
58808                 // This should reference the user supplied DAO options.
58809                 var settings = {
58810                     timeout:_self.user_options ? _self.user_options.timeout ? _self.user_options.timeout : 30000 : 30000
58811                 }
58812                 $j.ajaxSetup(settings);
58813
58814              }
58815
58816        // initialize it all up, like.
58817              initialize();
58818
58819          }
58820 );
58821
58822
58823})(jQuery);
58824"use strict";
58825/**
58826 *
58827 * @param dao
58828 * @param options
58829 */
58830function atlas_factory(dao, options) {
58831
58832    this.dao = dao;
58833    this.user_options= options;
58834    this.settings = {
58835        image_type:'', // used to differentiate P56 Sagittal for Adult, DevMouse atlases
58836        success_callback:null,
58837        error_callback:null
58838    };
58839
58840    $j.extend(true, this.settings, options);
58841
58842
58843    this.get_detail = function(id) {
58844        get_atlas_detail(id);
58845    }
58846
58847    this.get_atlas_data_set_detail = function(data_set_id) {
58848        get_atlas_data_set_detail(data_set_id);
58849    }
58850
58851    // private
58852    var _self = this;
58853
58854    function get_atlas_detail(id) {
58855
58856        var opts = {
58857           success_callback:receive_atlas_detail,
58858           error_callback:receive_atlas_detail_error
58859        };
58860
58861        $j.extend(true, _self.settings, opts);
58862
58863        set_atlas_query_includes(id, _self.settings);
58864        dao.fetch_by_id('Atlas', id, _self.settings);
58865    }
58866
58867    function get_atlas_data_set_detail(data_set_id) {
58868
58869        var opts = {
58870           success_callback:receive_atlas_data_set_detail,
58871           error_callback:receive_atlas_detail_error
58872        };
58873
58874        $j.extend(true, _self.settings, opts);
58875
58876        set_dataset_query_includes(data_set_id, _self.settings);
58877        dao.fetch_by_id('AtlasDataSet', data_set_id, _self.settings);
58878    }
58879
58880    /**
58881     *
58882     * @param atlas_id atlas id
58883     * @param options
58884     */
58885    function set_atlas_query_includes(atlas_id, options) {
58886        var a_id = parseInt(atlas_id);
58887
58888        switch(a_id) {
58889            case 1: // Mouse, P56  Coronal
58890                options.include += '(alternate_images[image_type$li\'Atlas - Adult Mouse\']))';
58891                break;
58892            case 2: // Mouse, P56  Sagittal
58893                options.include += '(alternate_images[image_type$li\'Atlas - Adult Mouse\']))';
58894                break;
58895            case 181275741: // 2013 E11.5
58896            case 181276130: // 2013 E13.5
58897            case 181276151: // 2013 E15.5
58898            case 181276160: // 2013 E18.5
58899            case 181276162: // 2013 P4
58900            case 181276164: // 2013 P14
58901            case 181276165: // 2013 P56
58902                options.include += '(alternate_images[image_type$li\'Atlas - Developing Mouse\']))';
58903                break;
58904            case 112953465: // 2010 E11.5
58905            case 112953466: // 2010 E13.5
58906            case 112953467: // 2010 E15.5
58907            case 112953468: // 2010 E18.5
58908            case 112953469: // 2010 P4
58909            case 112953470: // 2010 P14
58910            case 112953471: // 2010 P56
58911                // no special includes
58912                break;
58913            default:
58914                options.include += ')';
58915
58916        }
58917    }
58918
58919    /**
58920     *
58921     * @param data_set_id dataset id
58922     * @param options
58923     */
58924    function set_dataset_query_includes(data_set_id, options) {
58925        var ds_id = parseInt(data_set_id);
58926
58927        switch(ds_id) {
58928            case 100048576: // Mouse, P56  Coronal
58929                options.include += '(alternate_images[image_type$li\'Atlas - Adult Mouse\'])';
58930                break;
58931            case 100042147: // ABA Mouse, P56  Sagittal *AND* 2013 DevMouse P56 Sagittal
58932                if (options.image_type && options.image_type == AIBS.DATA.p56_sagittal_adult_atlas.image_type)
58933                    options.include += '(alternate_images[image_type$li\'Atlas - Adult Mouse\'])';
58934                else
58935                    options.include += '(alternate_images[image_type$li\'Atlas - Developing Mouse\'])';
58936                break;
58937            case 100020056: // 2013 E11.5
58938            case 100035755: // 2013 E13.5
58939            case 100040861: // 2013 E15.5
58940            case 100034997: // 2013 E18.5
58941            case 100033295: // 2013 P4
58942            case 100016572: // 2013 P14
58943                options.include += '(alternate_images[image_type$li\'Atlas - Developing Mouse\'])';
58944                break;
58945            case 100054043: // 2010 E11.5
58946            case 100054042: // 2010 E13.5
58947            case 100054041: // 2010 E15.5
58948            case 100054040: // 2010 E18.5
58949            case 100054039: // 2010 P4
58950            case 100054038: // 2010 P14
58951            case 100054036: // 2010 P56
58952                // no special includes
58953                break;
58954            default:
58955                //nothing
58956        }
58957    }
58958
58959    function receive_atlas_detail(response_data, text_status, xhdr) {
58960        // the atlas image model is somewhat inside out.  The point of this factory is to
58961        // turn right-side out again
58962
58963        // some atlases are inside out, some are sideways
58964        var atlas_id = parseInt(response_data[0].id);
58965
58966        switch(atlas_id) {
58967            case 1: // Mouse, P56  Coronal
58968                swap_primary_alternate(response_data[0].atlas_data_sets, response_data[0].name);
58969                break;
58970            case 2: // Mouse, P56  Sagittal
58971                swap_primary_alternate(response_data[0].atlas_data_sets, response_data[0].name);
58972                break;
58973            case 4: // Mouse Coronal, v1 (2008)
58974                change_name(response_data[0].atlas_data_sets, response_data[0].name);
58975                break;
58976            case 5: // Mouse Sagittal, v1 (2008)
58977                change_name(response_data[0].atlas_data_sets, response_data[0].name);
58978                break;
58979            case 181275741: // 2013 E11.5
58980            case 181276130: // 2013 E13.5
58981            case 181276151: // 2013 E15.5
58982            case 181276160: // 2013 E18.5
58983            case 181276162: // 2013 P4
58984            case 181276164: // 2013 P14
58985            case 181276165: // 2013 P56
58986                swap_primary_alternate(response_data[0].atlas_data_sets, response_data[0].name);
58987                break;
58988            case 112953465: // 2010 E11.5
58989            case 112953466: // 2010 E13.5
58990            case 112953467: // 2010 E15.5
58991            case 112953468: // 2010 E18.5
58992            case 112953469: // 2010 P4
58993            case 112953470: // 2010 P14
58994            case 112953471: // 2010 P56
58995                change_name(response_data[0].atlas_data_sets, response_data[0].name);
58996            default:
58997                // just return it as is
58998        }
58999        if(_self.user_options.success_callback)
59000            _self.user_options.success_callback(response_data, text_status, xhdr);
59001    }
59002
59003
59004    function receive_atlas_data_set_detail(response_data, text_status, xhdr) {
59005
59006        // the atlas image model is somewhat inside out.  The point of this factory is to
59007        // turn right-side out again
59008
59009        // some atlases are inside out, some are sideways
59010        if(!response_data || !response_data[0]) {
59011            receive_atlas_detail_error({}, 'no data', 'No results found');
59012            return;
59013        }
59014
59015        var data_set_id = parseInt(response_data[0].id);
59016
59017        var name = response_data[0].atlases[0].name;
59018        switch(data_set_id) {
59019            case 100048576: // Mouse, P56  Coronal
59020                swap_primary_alternate(response_data, name);
59021                break;
59022            case 100042147: // Mouse, P56  Sagittal
59023                swap_primary_alternate(response_data, name);
59024                break;
59025            case 100142144: // Mouse Sagittal, v1 (2008)
59026                change_name(response_data, name);
59027                break;
59028            case 100142143: // Mouse Coronal, v1 (2008)
59029                change_name(response_data, name);
59030                break;
59031            case 100020056: // 2013 E11.5
59032            case 100035755: // 2013 E13.5
59033            case 100040861: // 2013 E15.5
59034            case 100034997: // 2013 E18.5
59035            case 100033295: // 2013 P4
59036            case 100016572: // 2013 P14
59037            case 100042147: // 2013 P56
59038                swap_primary_alternate(response_data, name);
59039                break;
59040            case 100054043: // 2010 E11.5
59041            case 100054042: // 2010 E13.5
59042            case 100054041: // 2010 E15.5
59043            case 100054040: // 2010 E18.5
59044            case 100054039: // 2010 P4
59045            case 100054038: // 2010 P14
59046            case 100054036: // 2010 P56
59047                change_name(response_data, name);
59048                break;
59049            default:
59050                // just return it as is
59051        }
59052//alert("munged data set: " + inspect('', response_data));
59053        if(_self.user_options.success_callback)
59054            _self.user_options.success_callback(response_data, text_status, xhdr);
59055    }
59056
59057
59058    // this method alters the given data_sets object by adding a 'section_images array.
59059    // the existing atlas_images array is not altered.
59060    function swap_primary_alternate(data_sets, name) {
59061
59062        // might there be multiple data sets in this atlas?  The schema allows it.
59063        var data_set_count = data_sets.length;
59064        for(var i = 0; i < data_set_count; i++) {
59065
59066            // for this data set, make an additional image array
59067            var section_images = [];
59068            var atlas_images = data_sets[i].atlas_images;
59069            var image_count = atlas_images.length;
59070
59071            // swap the paths for primary & alternate images
59072            for(var j = 0; j < image_count; j++) {
59073                if(atlas_images[j].alternate_images && atlas_images[j].alternate_images.length) {
59074
59075                    // make a copy of this image record
59076                    var new_image = {};
59077                    $j.extend(true, new_image, atlas_images[j]);
59078
59079                    // swap the paths from alternate to primary
59080                    new_image.path = atlas_images[j].alternate_images[0].path;
59081                    new_image.alternate_images[0].path = atlas_images[j].path;
59082
59083                    section_images.push(new_image);
59084                }
59085            }
59086
59087            data_sets[i].section_images = section_images;
59088            data_sets[i].name = name;
59089            data_sets[i].atlas = true;
59090        }
59091    }
59092
59093    function change_name(data_sets, name) {
59094
59095        // might there be multiple data sets in this atlas?  The schema allows it.
59096        var data_set_count = data_sets.length;
59097        for(var i = 0; i < data_set_count; i++) {
59098
59099            // for this data set, make an additional image array
59100            var section_images = [];
59101            var atlas_images = data_sets[i].atlas_images;
59102            var image_count = atlas_images.length;
59103
59104            // copy atlas_images into section_images
59105            for(var j = 0; j < image_count; j++) {
59106                // make a copy of this image record
59107                var new_image = {};
59108                $j.extend(true, new_image, atlas_images[j]);
59109                section_images.push(new_image);
59110            }
59111
59112            data_sets[i].section_images = section_images;
59113            data_sets[i].name = name;
59114            data_sets[i].atlas = true;
59115        }
59116    }
59117
59118    function receive_atlas_detail_error(xhdr, text_status, error) {
59119
59120        if(_self.user_options.error_callback)
59121            _self.user_options.error_callback(xhdr, text_status, error);
59122    }
59123
59124    function inspect(prefix, obj) {
59125        var s = "";
59126        for(var key in obj) {
59127            if((typeof obj[key]) == "function")
59128              s = s;
59129            else if((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
59130                s += "\n" + prefix + "key: " + key + inspect(prefix + "    ", obj[key]);
59131            else
59132                s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
59133        }
59134        return(s);
59135    }
59136}
59137;
59138"use strict";
59139// ABA P56 Sagittal Atlas has SAME dataset id as DevMouse P56 Atlas, but different images. Need this data structure info
59140// to differential between the two atlases. 'extension' is appended to 'data_set_id' to yield '100042147_P56_SAGITTAL_ADULT',
59141// which is used in IVT as the unique index/key to identify each viewer. 'image_type' is used an an url arg in the thumbnails
59142// url in lieu of 'atlas' to differentiate this atlas from the DevMouse atlas. You can change the values for 'extension' and
59143// 'image_type' here, and the values will be picked up and used without further changes.
59144AIBS.DATA.p56_sagittal_adult_atlas = {
59145    'atlas_id': 2,
59146    'data_set_id': 100042147,
59147    'extension': '_P56_SAGITTAL_ADULT',
59148    'image_type': 'atlas_p56_sagittal_adult'
59149};
59150
59151// Used in ish_dao queries to figure out if a data_set_id belongs to an atlas
59152AIBS.DATA.atlas_dataset_lookup = {
59153    '100054043':'2010', // 2010 E11.5
59154    '100054042':'2010', // 2010 E13.5
59155    '100054041':'2010', // 2010 E15.5
59156    '100054040':'2010', // 2010 E18.5
59157    '100054039':'2010', // 2010 P4
59158    '100054038':'2010', // 2010 P14
59159    '100054036':'2010', // 2010 P56
59160
59161    '100020056':'2013', // 2013 E11.5
59162    '100035755':'2013', // 2013 E13.5
59163    '100040861':'2013', // 2013 E15.5
59164    '100034997':'2013', // 2013 E18.5
59165    '100033295':'2013', // 2013 P4
59166    '100016572':'2013', // 2013 P14
59167    '100042147':'2013', // 2013 P56 Sagittal (both atlas_id = 2, atlas_id = 181276165 map to same data_set_id!)
59168    '100048576':'2013'  // 2013 P56 Coronal (not really 2013, it's really older but we use it with 2013 data)
59169};
59170
59171// categorize the atlas based on its id
59172AIBS.DATA.atlas_id_lookup = {
59173    '1': 'ABA',          // ABA Mouse, P56  Coronal
59174    '2': 'ABA',          // ABA Mouse, P56  Sagittal
59175
59176    '112953465': '2010', // 2010 E11.5
59177    '112953466': '2010', // 2010 E13.5
59178    '112953467': '2010', // 2010 E15.5
59179    '112953468': '2010', // 2010 E18.5
59180    '112953469': '2010', // 2010 P4
59181    '112953470': '2010', // 2010 P14
59182    '112953471': '2010',  // 2010 P56
59183
59184    '181275741': '2013', // 2013 E11.5
59185    '181276130': '2013', // 2013 E13.5
59186    '181276151': '2013', // 2013 E15.5
59187    '181276160': '2013', // 2013 E18.5
59188    '181276162': '2013', // 2013 P4
59189    '181276164': '2013', // 2013 P14
59190    '181276165': '2013'  // 2013 P56
59191
59192};
59193
59194// convert dataset id to atlas id. 100042147 deliberately omitted, because it can map to either 2 (ABA P56 Sagittal atlas)
59195// or 181276165 (2013 P56 atlas)
59196AIBS.DATA.dataset_to_atlas = {
59197    100048576:1,         // Mouse, P56  Coronal
59198    100020056:181275741, // 2013 E11.5
59199    100035755:181276130, // 2013 E13.5
59200    100040861:181276151, // 2013 E15.5
59201    100034997:181276160, // 2013 E18.5
59202    100033295:181276162, // 2013 P4
59203    100016572:181276164, // 2013 P14
59204
59205    100054043:112953465, // 2010 E11.5
59206    100054042:112953466, // 2010 E13.5
59207    100054041:112953467, // 2010 E15.5
59208    100054040:112953468, // 2010 E18.5
59209    100054039:112953469, // 2010 P4
59210    100054038:112953470, // 2010 P14
59211    100054036:112953471  // 2010 P56
59212};
59213
59214// determine if a dataset belongs to a Reference Set
59215// {age, theiler, plane, treatment
59216AIBS.DATA.reference_set_id_lookup = {
59217    '100020056': {age:'E11.5', theiler:'TS19', plane:'sagittal', treatment:'HP Yellow'},
59218    '100015815': {age:'E11.5', theiler:'TS19', plane:'coronal', treatment:'HP Yellow'},
59219    '100071965': {age:'E12.5', theiler:'TS20', plane:'sagittal', treatment:'HP Yellow'},
59220    '100073791': {age:'E12.5', theiler:'TS20', plane:'coronal', treatment:'HP Yellow'},
59221    '100035755': {age:'E13.5', theiler:'TS21', plane:'sagittal', treatment:'HP Yellow'},
59222    '100026526': {age:'E13.5', theiler:'TS21', plane:'coronal', treatment:'HP Yellow'},
59223    '100071964': {age:'E14.5', theiler:'TS23', plane:'sagittal', treatment:'HP Yellow'},
59224    '100074513': {age:'E14.5', theiler:'TS23', plane:'coronal', treatment:'HP Yellow'},
59225    '100040861': {age:'E15.5', theiler:'TS24', plane:'sagittal', treatment:'HP Yellow'},
59226    '100020054': {age:'E15.5', theiler:'TS24', plane:'coronal', treatment:'HP Yellow'},
59227    '100072704': {age:'E16.5', theiler:'TS24.5', plane:'sagittal', treatment:'HP Yellow'},
59228    '100075063': {age:'E16.5', theiler:'TS24.5', plane:'coronal', treatment:'HP Yellow'},
59229    '100072703': {age:'E17.5', theiler:'TS25', plane:'sagittal', treatment:'HP Yellow'},
59230    '100075359': {age:'E17.5', theiler:'TS25', plane:'coronal', treatment:'HP Yellow'},
59231    '100034997': {age:'E18.5', theiler:'TS26', plane:'sagittal', treatment:'NISSL'},
59232    '100054035': {age:'E18.5', theiler:'TS26', plane:'coronal', treatment:'NISSL'},
59233    '100073354': {age:'P1', theiler:'', plane:'sagittal', treatment:'NISSL'},
59234    '100102322': {age:'P1', theiler:'', plane:'coronal', treatment:'NISSL'},
59235    '100098646': {age:'P1', theiler:'', plane:'sagittal', treatment:'HP Yellow'},
59236    '100098647': {age:'P1', theiler:'', plane:'coronal', treatment:'HP Yellow'},
59237    '100073355': {age:'P2', theiler:'', plane:'sagittal', treatment:'NISSL'},
59238    '100102321': {age:'P2', theiler:'', plane:'coronal', treatment:'NISSL'},
59239    '100098653': {age:'P2', theiler:'', plane:'coronal', treatment:'HP Yellow'},
59240    '100033295': {age:'P4', theiler:'', plane:'sagittal', treatment:'NISSL'},
59241    '100034998': {age:'P4', theiler:'', plane:'coronal', treatment:'NISSL'},
59242    '100073790': {age:'P7', theiler:'', plane:'sagittal', treatment:'NISSL'},
59243    '100098039': {age:'P7', theiler:'', plane:'coronal', treatment:'NISSL'},
59244    '100016572': {age:'P14', theiler:'', plane:'sagittal', treatment:'NISSL'},
59245    '100014118': {age:'P14', theiler:'', plane:'coronal', treatment:'NISSL'},
59246    '100015421': {age:'P28', theiler:'', plane:'sagittal', treatment:'NISSL'},
59247    '100024848': {age:'P28', theiler:'', plane:'coronal', treatment:'NISSL'},
59248    '100042147': {age:'P56', theiler:'', plane:'sagittal', treatment:'NISSL'},
59249    '100048576': {age:'P56', theiler:'', plane:'coronal', treatment:'NISSL'}
59250};
59251
59252"use strict";
59253/**
59254 * The ISH DAO leverages the AIBS.DATA.api_dao to access the api service.  The methods
59255 * in this object effectively create a set of data views that are common to the ISH-centric
59256 * applications.
59257 * <p>
59258
59259 * </p>
59260 * @class
59261 * @param service_url:   The base url for the API data service
59262 * @param options:       A hash of the following (work in progress):
59263 * <ul>
59264 * <li>
59265 *     <b>tineout</b>:  A default value after which an ajax call will fail, given in milliseconds.  Default = 3000.  Note that this
59266 * </li>
59267 * <li>
59268 *     <b>others</b>:  TBD
59269 * </li>
59270 * </ul>
59271 */
59272AIBS.DATA.ISH_DAO = (function ($j) {
59273
59274    return (
59275        function (service_url, options) {
59276            // private, placed up here for visibility. Other private stuff way below.
59277            var _ish_product_id = '18'; // NHP ISH, also includes Molecular Map (pan gene) info
59278
59279            // public properties
59280            this.service_url = service_url;
59281
59282            this.user_options = {
59283                product: '', // Note: this value is overwritten by application.json's config.default.themes.ish.application.data_sets[]
59284                search_includes: "plane_of_section,specimen(donor(age)),treatments,probes(orientation,gene(gene_aliases)),products[id$in@products@]",
59285                search_excludes: "description,term,termtype,product_id,gene_id,plane_of_section_id," +
59286                    "section_thickness,specimen_id,predicted_sequence_id,forward_sequence_id," +
59287                    "reverse_sequence_id,label,orientation_id,probe_type," +
59288                    "organism_id,chromosome_id,homologene_group_id,created_at,updated_at,gene_tag_type_id"
59289            };
59290
59291            if (options)
59292                $j.extend(true, this.user_options, options);
59293
59294            // public methods
59295            this.gene_typeahead = function (term, options) {
59296
59297                if (!term || term.length < 1)
59298                    throw "Empty term in type-ahead request";
59299
59300                if (!options.exact_match)
59301                    term = term + '*';
59302
59303                options.criteria = "[product_id$in" +  _self.user_options.product + "][term$il'" + term + "']";   // filter with the product info saved in the dao
59304                options.select = "gene_id,term";
59305                options.order = "term";
59306
59307                return(_the_dao.query('GeneLookup', build_option_defaults(options)));
59308            };
59309
59310            this.geneNamesFromClassificationId = function (id, options) {
59311                options.criteria = '[id$eq' + id + ']';
59312                options.include = 'genes';
59313                options.select = 'genes.acronym,genes.name';
59314                options.format = 'json';
59315
59316                return(_the_dao.query('GeneClassification', build_option_defaults(options)));
59317            };
59318
59319            this.ontology_graph = function (structure_graph_id, options) {
59320
59321                options.include = "[structure_graph_id$eq1]";
59322                options.order = "st_order";
59323                options.exclude = "structure_id_path,acronym_path,id,structure_graph_id";
59324
59325                return(_the_dao.query('OntologyNode', build_option_defaults(options)));
59326            };
59327
59328            this.structures_by_set = function (structure_set_id, options) {
59329
59330                options.include = "structure_sets_structures(structure)";
59331                options.order = "structure_sets_structures.structure_order";
59332
59333                return(_the_dao.fetch_by_id('StructureSet', structure_set_id, build_option_defaults(options)));
59334            };
59335
59336            this.structure_set = function (id, options) {
59337
59338                options.include = 'structure_sets_structures';
59339                options.order = "structure_sets_structures.structure_order";
59340                // can't order by structure_order because it is not a field of the type being searched
59341                //options.order = 'structure_order';
59342
59343                return(_the_dao.fetch_by_id('StructureSet', id, build_option_defaults(options)));
59344            };
59345
59346            this.gene_detail = function (id, options) {
59347                options.include = 'organism,chromosome,gene_aliases,data_sets(
59347products[id$in' + _ish_product_id + '])';
59348                return(_the_dao.fetch_by_id('Gene', id, build_option_defaults(options)));
59349            };
59350
59351            this.gene_detail_and_experiments_info = function (id, options) {
59352                options.include = 'organism,chromosome,gene_aliases,data_sets[failed$eqfalse](products[id$in' + _ish_product_id + '],specimen(donor(age)))';
59353                options.order = 'embryonic$desc,days$asc';
59354                return(_the_dao.fetch_by_id('Gene', id, build_option_defaults(options)));
59355            };
59356
59357            this.gene_experiments = function (id, options) {
59358
59359                options.include = 'data_sets(products[id$in' + _ish_product_id + '])';
59360                return(_the_dao.fetch_by_id('Gene', id, build_option_defaults(options)));
59361            };
59362
59363
59364            /**
59365             * Called by ish_ivt_controller.js
59366             *
59367             * @param atlas_id
59368             * @param options
59369             * @returns {*}
59370             */
59371            this.atlas_images = function (atlas_id, options) {
59372                var atlas_type = AIBS.DATA.atlas_id_lookup[atlas_id];
59373                if (atlas_type == '2010')
59374                    options.include = 'atlas_data_sets(atlas_images';
59375                else
59376                    options.include = 'atlas_data_sets(atlas_images[sub_images.annotated$eqtrue]';
59377                options.order = 'section_number';
59378                var opts = build_option_defaults(options);
59379
59380                var atlases = new atlas_factory(_the_dao, opts);
59381                var atlas = atlases.get_detail(atlas_id);
59382                return(atlas);
59383            };
59384
59385            this.atlas_by_structure_graph_id = function (structure_graph_id, options) {
59386
59387                options.include = "atlases";
59388                options.order = "atlases.reference_space_id";   // it appears this will have all the atlases sorted by age
59389
59390                return(_the_dao.fetch_by_id('StructureGraph', structure_graph_id, build_option_defaults(options)));
59391            };
59392
59393            /**
59394             * Gene Classification Search RMA
59395             * Primary search model is SectionDataSet, which is then associated with Gene Classifications, Specimens and Genes.
59396             * Ages span E40, E50, E70, E80, E90, 0 mo, 3 mo, 12 mo, 48 mo
59397             * Sex is M for all timepoints, and also include F for prenatal ages
59398             *
59399             * This query is almost identical to experiment_search_by_gene(), with the addition of gene_classification table.
59400             *
59401             * @param {Number} classification_id GeneClassification id
59402             * @param options
59403             * @returns {*}
59404             */
59405            this.experiment_search_by_gene_classification = function (classification_id, options) {
59406                // override the default search parameters if necessary
59407                if (options)
59408                    $j.extend(true, this.user_options, options);
59409
59410                var rma_only;
59411                if (options.format == 'xml')
59412                    rma_only = ''; // show everything in xml rendering
59413                else {
59414                    rma_only = "[only$eq'data_sets.id,genes.id,genes.acronym,genes.name,genes.organism_id,gene_classifications.id,gene_classifications.name,";
59415                    rma_only += "structures.name,donors.id,donors.sex_full_name,ages.embryonic,ages.name,ages.days,specimens.id']";
59416                }
59417
59418                /* business logic:
59419                 * - never show failed experiments in search results
59420                 */
59421                // treatment id = 1 is for ISH
59422                options.criteria = "rma::criteria,[failed$eqfalse],products[id$eq" + _ish_product_id + "],treatments[id$eq1],";
59423                options.criteria += "rma::include,genes(homologous_genes,gene_classifications[id$eq" + classification_id + "][use$eq'Non-Human Primate ISH gene classifications'])" + ",specimen(structure,donor(age)),";
59424                options.criteria += "rma::options";
59425                options.criteria += rma_only;
59426                options.criteria += "[order$eq'genes.acronym$asc']";
59427                options.order = options.order ? options.order : "";
59428
59429                var all_opts = build_option_defaults(options);
59430
59431                return(_the_dao.query('SectionDataSet', all_opts));
59432            };
59433
59434            /**
59435             * Gene Search
59436             * Primary search model is SectionDataSet, which is then associated with Specimens and Genes.
59437             * Ages span E40, E50, E70, E80, E90, 0 mo, 3 mo, 12 mo, 48 mo
59438             * Sex is M for all timepoints, and also include F for prenatal ages
59439             *
59440             * @param term
59441             * @param options
59442             * @returns {*}
59443             */
59444            this.experiment_search_by_gene = function (term, options) {
59445
59446                var reg_exp_pattern = /(and|or|not|"|\*)/i;
59447                if (term.charAt(0) == '"' || term.charAt(term.length - 1) == '"')
59448                    term = term + '';
59449                else if (options.exact_match)
59450                    term = '"' + term + '"';
59451                else if (!term.match(reg_exp_pattern))   // do not append '*' when special pattern exists
59452                    term = '*' + term + '*';
59453
59454                // 'query' is used for Sphinx indexed text search. 'facets' are used by IS
59454H Advanced Search
59455                var product_name_facet = options.product_name ? (" @product_name " + options.product_name) : "";
59456                options.query = term + product_name_facet;
59457
59458                if (options.facets) {
59459                    options.query += options.facets;
59460                }
59461
59462
59463                var rma_only; // rma::options[only] config
59464                if (options.format == 'xml')
59465                    rma_only = ''; // show everything in xml rendering
59466                else {
59467                    rma_only = "[only$eq'data_sets.id,genes.id,genes.acronym,genes.name,genes.organism_id,structures.name,";
59468                    rma_only += "donors.id,donors.name,donors.sex_full_name,ages.embryonic,ages.name,ages.days,specimens.id']";
59469                }
59470
59471                /* business logic:
59472                 * - never show failed experiments in search results
59473                 */
59474                // treatment id = 1 is for ISH
59475                options.criteria = "rma::criteria,[failed$eqfalse],products[id$eq" + _ish_product_id + "],treatments[id$eq1],";
59476                options.criteria += "rma::include,genes(homologous_genes),specimen(structure,donor(age)),";
59477                options.criteria += "rma::options";
59478                options.criteria += rma_only;
59479                options.order = options.order ? options.order : "genes.acronym$asc";
59480
59481                var all_opts = build_option_defaults(options);
59482                all_opts.timeout = 30000; // for some reason, the default timeout is 10 sec, which is too short for wildcard searches
59483
59484                // Note: could have used SectionDataSet, which has additional associations which are not needed for this project
59485                return(_the_dao.query('SectionDataSet', all_opts));
59486            };
59487
59488            this.inflate_datasets = function (ids, options) {
59489
59490                options.verb = 'POST';
59491                options.include = '[data_sets.id$in' + ids + '],' + this.user_options.search_includes.replace('@products@', _ish_product_id);
59492                options.exclude = this.user_options.search_excludes;
59493
59494                var all_opts = build_option_defaults(options);
59495
59496                return(_the_dao.query('SectionDataSet', all_opts));
59497            };
59498
59499            /**
59500             * Get the dataset and image info associated with each of the data_set_ids in the ids array
59501             * This function is used by the IVT.
59502             *
59503             * @param ids array of data_set_ids
59504             * @param options
59505             * @returns {*}
59506             */
59507            this.experiment_list = function (ids, options) {
59508
59509                options.include = '[data_sets.id$in' + ids + '],section_images(associates,alternate_images),products[id$in' + _ish_product_id + '],plane_of_section,genes,treatments,specimen(structure,donor(age))';
59510                options.order = 'sub_images.section_number$asc';
59511                options.reverse_coronals = false;
59512
59513                var all_opts = build_option_defaults(options);
59514
59515                return(_the_dao.query('SectionDataSet', all_opts));
59516            };
59517
59518            this.experiment_detail = function (id, options) {
59519
59520                options.include = 'genes,plane_of_section,treatments,specimen(donor(age,organism)),probes(orientation,predicted_sequence,forward_primer_sequence,reverse_primer_sequence),products[id$eq' + _ish_product_id + ']';
59521                return(_the_dao.fetch_by_id('SectionDataSet', id, build_option_defaults(options)));
59522            };
59523
59524            // combines details, images & expressions
59525            this.experiment_all = function (id, options) {
59526
59527                if (!options.structure_ids)
59528                    throw "Invalid query; you must specify a set of structure IDs to retireve expression data related to this experiment";
59529
59530                options.include = 'structure_unionizes(structure[id$in' + options.structure_ids + ']),section_images(associates,alternate_images),plane_of_section,treatments,specimen(donor(age,organism)),probes(orientation,predicted_sequence,forward_primer_sequence,reverse_primer_sequence,gene(organism,chromosome)),products[id$eq' + _ish_product_id + ']';
59531                options.order = 'sub_images.section_number$asc';
59532                options.reverse_coronals = false;
59533
59534                return(_the_dao.fetch_by_id('SectionDataSet', id, build_option_defaults(options)));
59535            };
59536
59537            /**
59538             *
59539             * @param id data set id
59540             * @param options
59541             * @returns {*}
59542             */
59543            this.experiment_images = function (id, options) {
59544                options.include = "genes,plane_of_section,treatments,specimen(structure,donor(age)),section_images(associates,alternate_images)";
59545                options.order = 'sub_images.section_number$asc';
59546                options.criteria = '[id$eq' + id + ']';
59547                options.limit = 'all';
59548
59549                return(_the_dao.query('SectionDataSet', build_option_defaults(options)));
59550            };
59551
59552            /**
59553             * Used by dual_view_model for dual view SIV
59554             *
59555             * @param id
59556             * @param options
59557             * @returns {*}
59558             */
59559            this.experiment_images_by_image = function (id, options) {
59560                options.include = "section_data_set(specimen(structure,donor(age)),genes,plane_of_section,section_images(alternate_images,associates))";
59561                options.order = 'sub_images.section_number$asc';
59562                options.criteria = '[id$eq' + id + ']';
59563                options.limit = 'all';
59564
59565                return(_the_dao.query('SectionImage', build_option_defaults(options)));
59566            };
59567
59568            /**
59569             * Called from thumbnails_controller.js
59570             *
59571             * @param data_set_id dataset id
59572             * @param options hash with optional image_type
59573             * @returns {*}
59574             */
59575            this.experiment_specimen = function (data_set_id, options) {
59576
59577                options.include = 'plane_of_section,treatments,specimen(donor(organism,age)),section_images(associates,alternate_images),genes,treatments,probes(orientation)';
59578                options.order = 'sub_images.section_number$asc';
59579                options.reverse_coronals = false;
59580
59581                return(_the_dao.fetch_by_id('SectionDataSet', data_set_id, build_option_defaults(options)));
59582            };
59583
59584            this.experiment_expressions = function (id, options) {
59585
59586                var include = '';
59587                if (options.structure_ids)
59588                    include = 'structure_unionizes(structure[id$in' + options.structure_ids + '])';
59589                else
59590                //TODO: fix this
59591                // this odd construction is due to a flaw in the parser that doesn't deal with the included :structure
59592                // unless there is a where clause referencing it.
59593                    include = 'structure_unionizes(structure)';
59594
59595                options.include = include;
59596                options.select = 'id,name,expression_energy,acronym,color_hex_triplet';
59597
59598                return(_the_dao.fetch_by_id('SectionDataSet', id, build_option_defaults(options)));
59599            };
59600
59601
59602            this.expression_set = function (experiment_ids, structure_ids, options) {
59603
59604                var include = '[id$in' + experiment_ids + '],structure_unionizes(structure[id$in' + structure_ids + '])';
59605
59606                options.include = include;
59607                options.select = 'id,name,expression_energy,acronym,color_hex_triplet';
59608
59609                return(_the_dao.query('SectionDataSet', build_option_defaults(options)));
59610            };
59611
59612            this.specimen_detail = function (id, options) {
59613                options.include = "donor(age,conditions),data_sets(products[id$in" + _ish_product_id + "],genes,treatments),specimen_images,specimen_types,well_known_files,structure";
59614                return(_the_dao.fetch_by_id('Specimen', id, build_option_defaults(options)));
59615            };
59616
59617            this.specimen_children = function (parent_id, options) {
59618
59619                options.criteria = "[parent_id$eq" + parent_id + "]";
59620                options.select = "id,parent_x_coord,parent_y_coord,parent_z_coord";
59621
59622                if (options.has_experiments)
59623                    options.criteria += ",data_sets";
59624
59625                var all_opts = build_option_defaults(options);
59626
59627                return(_the_dao.query('Specimen', all_opts));
59628            };
59629
59630            // get images associated with the given specimen.  Optional provide a treatment type & operator
59631            // for filtering the images.  For example to get only NISSL images use
59632            // options = {treatment:'NISSL'}.  FOr getting all images except NISSLS, use
59633            // options = {treatment:'NISSL', treatment_op:'ne'}.
59634            this.specimen_images_by_treatment = function (id, options) {
59635
59636                //options.include = 'section_data_set(genes,plane_of_section)';
59637                var opts = {
59638                    treatment: null,
59639                    treatment_op: 'eq',
59640                    criteria: "section_data_set(specimen[id$eq" + id + "])",
59641                    order: 'sub_images.section_number$asc',
59642                    limit: 'all'
59643                };
59644                $j.extend(true, opts, options);
59645
59646                if (opts.treatment)
59647                    opts.criteria += ",treatments[name$" + opts.treatment_op + "'" + opts.treatment + "']";
59648
59649                return(_the_dao.query('SectionImage', build_option_defaults(opts)));
59650            };
59651
59652
59653            this.specimen_images_by_donor = function (donor_id, specimen_types, options) {
59654
59655                if (options.section_images)
59656                    options.include = "specimen_types,data_sets(treatments,section_images)";
59657                else
59658                    options.include = 'specimen_images,specimen_types';
59659
59660                //options.include = 'specimen_images,specimen_types';
59661                options.criteria = "[donor_id$eq" + donor_id + "],specimen_types[id$in" + specimen_types + "]";
59662                options.select = 'id,name,path,width,height,x,y,section_number,failed,specimen_id,tier_count,resolution,image_width,image_height,image_type';
59663                options.order = 'sub_images.section_number';
59664
59665                return(_the_dao.query('Specimen', build_option_defaults(options)));
59666            };
59667
59668            this.section_images_by_specimen = function (specimen_id, options) {
59669
59670                options.criteria = 'data_set[specimen_id$eq' + specimen_id + '][width$gt0]';
59671                if (options.treatment && options.treatment.length)
59672                    options.criteria += ",data_set(treatments[name$li'" + options.treatment + "'])";
59673
59674                options.include = "section_data_set(genes),treatments";
59675                options.select = 'id,name,path,width,height,x,y,section_number,failed,specimen_id,tier_count,resolution,image_width,image_height,image_type,acronym';
59676                options.order = 'sub_images.section_number';
59677
59678                return(_the_dao.query('SectionImage', build_option_defaults(options)));
59679            };
59680
59681            this.specimen_images_by_specimen = function (specimen_id, options) {
59682
59683                options.criteria = '[specimen_id$eq' + specimen_id + '][width$gt0]';
59684                if (options.image_type && options.image_type.length)
59685                    options.criteria += "[image_type$li'" + options.image_type + "']";
59686
59687                options.select = 'id,name,path,width,height,x,y,section_number,failed,specimen_id,tier_count,resolution,image_width,image_height,image_type';
59688                options.order = 'sub_images.section_number';
59689
59690                return(_the_dao.query('SubImage', build_option_defaults(options)));
59691            };
59692
59693
59694            /**********************************
59695             *
59696             * functions used by Microarray theme
59697             *
59698             ***********************************/
59699                // get the donor info based on product ids
59700            this.donor_info_by_product_ids = function (term, options) {
59701                if (!term || term.length < 1)
59702                    term = this.user_options.product;
59703
59704                options.include = "products[id$in" + term + "],age,organism";
59705                options.order = "ages.embryonic desc,ages.days,donors.name";
59706
59707                return(_the_dao.query('Donor', build_option_defaults(options)));
59708
59709            };
59710
59711            /*            // look up the list of related probe ids based on a search term, for the microarray app
59712             this.probe_id_by_gene_term = function (term, options) {
59713
59714             if (!term || term.length < 1)
59715             throw "Empty term in probe_id_by_gene_term request";
59716
59717             var product = this.user_options.product;
59718             if (options.product)
59719             product = options.product;
59720
59721             var reg_exp_pattern = /(and|or|not|"|\052)/i;
59722             if (term.charAt(0) == '"' || term.charAt(term.length - 1) == '"')
59723             term = term + '';
59724             else if (options.exact_match)
59725             term = '"' + term + '"';
59726             else if (!term.match(reg_exp_pattern))   // do not append '*' when special pattern exists
59727             term = '*' + term + '*';
59728
59729             //options.include = "gene(gene_lookups[term$il'" + term + "']),products[id$in" + +this.user_options.product+ "]";
59730             //options.select = "id";
59731             //return(_the_dao.query('Probe', build_option_defaults(options)));
59732
59733             // try to use the sphinx index search
59734             // BUGBUG: we should also support probe name search, I think.
59735             options.include = "probes[probe_type$eq'DNA'](products[id$eq" + product + "])";
59736             options.query = term;
59737             options.order = "acronym,probes.name";
59738             options.select = "id";
59739             options.limit = _num_rows; // this maps to the RMA option "numRows" later.
59740             return(_the_dao.query('Gene', build_option_defaults(options)));
59741
59742             }*/
59743
59744/*
59745            // used in NHP microdisection theme
59746            this.affymetrix_probe_id_by_gene_term = function (term, options) {
59747
59748                if (!term || term.length < 1)
59749                    throw "Empty term in probe_id_by_gene_term request";
59750
59751                var product = this.user_options.product;
59752                if (options.product)
59753                    product = options.product;
59754
59755                var reg_exp_pattern = /(and|or|not|"|\052)/i;
59756                if (term.charAt(0) == '"' || term.charAt(term.length - 1) == '"')
59757                    term = term + '';
59758                else if (options.exact_match)
59759                    term = '"' + term + '"';
59760                else if (!term.match(reg_exp_pattern)) {   // do not append '*' when special pattern exists
59761                    term = $j.trim(term);
59762                    term = '*' + term + '*';
59763                }
59764
59765                //options.criteria = "service::text_search[query_string$eq'@(acronym,gene_alias_acronym,name,entrez_id)+" + term + "'][k$eqGene],pi
59765pe::list[xid$eq'id'],model::AffymetrixMacaqueProbeset,rma::criteria,gene_association[gene_association_source_id$eq2][gene_feature_type$eq'AffymetrixMacaqueProbeset'](gene[id$in$xid](organism[name$eq'Macaca mulatta'])),rma::options[order$eq'genes.acronym,affymetrix_macaque_probesets.displayable_id_from_affymetrix'][num_rows$eq'all'][tabular$eq'distinct genes.acronym as gene,affymetrix_macaque_probesets.displayable_id_from_affymetrix as display_id,affymetrix_macaque_probesets.id as probe_id']";
59766                //
59767                // switch to use the sphinx index on AffymetrixMacaqueProbeset model to include human gene results, see IT-7987 and related issues
59768                options.criteria = "service::text_search[query_string$eq'" + term + "'][k$eqAffymetrixMacaqueProbeset],pipe::list[xid$eq'id'],model::AffymetrixMacaqueProbeset,rma::criteria,[id$in$xid],gene_association[gene_association_source_id$eq2][gene_feature_type$eq'AffymetrixMacaqueProbeset'](gene),rma::options[order$eq'genes.acronym,affymetrix_macaque_probesets.displayable_id_from_affymetrix'][num_rows$eq'all'][tabular$eq'distinct genes.acronym as gene,affymetrix_macaque_probesets.displayable_id_from_affymetrix as display_id,affymetrix_macaque_probesets.id as probe_id']";
59769
59770                return(_the_dao.query('AffymetrixMacaqueProbeset', build_option_defaults(options)));
59771
59772
59773            };
59774*/
59775
59776
59777            // used in NHP microdisection theme
59778            this.affymetrix_probe_id_by_gene_term = function (term, options) {
59779
59780                if (!term || term.length < 1)
59781                    throw "Empty term in probe_id_by_gene_term request";
59782
59783                var product = this.user_options.product;
59784                if (options.product)
59785                    product = options.product;
59786
59787                var reg_exp_pattern = /(and|or|not|"|\052)/i;
59788                if (term.charAt(0) == '"' || term.charAt(term.length - 1) == '"')
59789                    term = term + '';
59790                else if (options.exact_match)
59791                    term = '"' + term + '"';
59792                else if (!term.match(reg_exp_pattern)) {   // do not append '*' when special pattern exists
59793                    term = $j.trim(term);
59794                    term = '*' + term + '*';
59795                }
59796
59797                //options.criteria = "service::text_search[query_string$eq'@(acronym,gene_alias_acronym,name,entrez_id)+" + term + "'][k$eqGene],pipe::list[xid$eq'id'],model::AffymetrixMacaqueProbeset,rma::criteria,gene_association[gene_association_source_id$eq2][gene_feature_type$eq'AffymetrixMacaqueProbeset'](gene[id$in$xid](organism[name$eq'Macaca mulatta'])),rma::options[order$eq'genes.acronym,affymetrix_macaque_probesets.displayable_id_from_affymetrix'][num_rows$eq'all'][tabular$eq'distinct genes.acronym as gene,affymetrix_macaque_probesets.displayable_id_from_affymetrix as display_id,affymetrix_macaque_probesets.id as probe_id']";
59798                //
59799                // switch to use the sphinx index on AffymetrixMacaqueProbeset model to include human gene results, see IT-7987 and related issues
59800                options.criteria = "service::text_search[query_string$eq'" + term + "'][k$eqAffymetrixMacaqueProbeset],pipe::list[xid$eq'id'],model::AffymetrixMacaqueProbeset,rma::criteria,[id$in$xid],rma::include,gene_associations(gene),rma::options[order$eq'gene_associations.gene_association_source_id$desc,genes.type$desc,genes.acronym$asc,genes.organism_id$desc,affymetrix_macaque_probesets.displayable_id_from_affymetrix$asc'][num_rows$eq1000]";
59801
59802                return(_the_dao.query('', build_option_defaults(options)));
59803
59804
59805            };
59806
59807
59808            // look up the list of related probe ids based on a search term of gene classification ID, for the microarray app
59809            this.probe_id_by_gene_classification = function (term, options) {
59810
59811                if (!term || term.length < 1)
59812                    throw "Empty term in probe_id_by_gene_classification request";
59813
59814                var product = this.user_options.product;
59815                if (options.product)
59816                    product = options.product;
59817
59818                options.include = "gene_classifications[id$eq" + term + "],probes[probe_type$eq'DNA'](products[id$eq" + product + "])";
59819                options.order = "acronym,probes.name";
59820                options.select = "id";
59821                options.limit = _num_rows; // this maps to the RMA option "numRows" later.
59822                return(_the_dao.query('Gene', build_option_defaults(options)));
59823
59824            };
59825
59826            this.describe = function (type, options) {
59827                return(_the_dao.describe(type, build_option_defaults(options)));
59828            };
59829
59830            this.enumerate = function (options) {
59831                return(_the_dao.enumerate(build_option_defaults(options)));
59832            };
59833
59834            this.customized_request = function (options, url, customized_request_data) {
59835                return(_the_dao.customized_request(options, url, customized_request_data));
59836            };
59837
59838            /**
59839             * @private
59840             * @scope AIBS.DATA.ISH_DAO
59841             */
59842            var _self = this;
59843            var _the_dao = null;
59844            // block repeated requests for the same method before the
59845            // response to the previous call has completed.
59846            var _request_blocks = {
59847                gene_detail: false,
59848                gene_experiments: false,
59849                experiment_detail: false,
59850                experiment_images: false,
59851                experiment_probes: false,
59852                experiment_expressions: false,
59853                structure_set: false
59854            };
59855
59856            var _num_rows = 'all'; // for rma queries
59857
59858            // private methods
59859
59860            /**
59861             * Prepare a string of comma delimited text items for an RMA '$in' clause by surrounding each item in single quotes
59862             *
59863             * @param str input string of comma delimited items
59864             * @returns {string} each item in input string surrounded by single quotes
59865             */
59866            function surround_with_single_quotes(str) {
59867                var quoted = '';
59868
59869                var the_array = str.split(',');
59870                for (var i = 0; i < the_array.length; ++i) {
59871                    quoted += "'" + the_array[i] + "',";
59872                }
59873
59874                // remove trailing comma
59875                quoted = quoted.substring(0, quoted.length - 1);
59876
59877                return quoted;
59878            }
59879
59880            function build_option_defaults(options) {
59881                var opts = {
59882                    include: options.include,
59883                    success_callback: function (response_data, text_status, xhdr) {
59884                        success_handler(options, response_data, text_status, xhdr)
59885                    },
59886                    error_callback: function (xhdr, text_status, error) {
59887                        error_handler(options, xhdr, text_status, error)
59888                    },
59889                    format: options.format ? options.format : 'json',
59890                    verb: options.verb ? options.verb : 'GET'
59891                };
59892
59893                if (options.reverse_coronals)
59894                    opts.success_callback = function (response_data, text_status, xhdr) {
59895                        success_handler_with_reverse(options, response_data, text_status, xhdr)
59896                    };
59897                else
59898                    opts.success_callback = function (response_data, text_status, xhdr) {
59899                        success_handler(options, response_data, text_status, xhdr)
59900                    };
59901
59902                if (options.select)
59903                    opts.select = options.select;
59904
59905                if (options.exclude)
59906                    opts.exclude = options.exclude;
59907
59908                if (options.order)
59909                    opts.order = options.order;
59910
59911                if (options.parse_request)
59912                    opts.parse_request = options.parse_request;
59913
59914                if (options.limit)
59915                    opts.limit = options.limit;
59916
59917                if (options.offset != undefined) {
59918                    opts.offset = options.offset;
59919                }
59920
59921                if (options.query != undefined) {
59922                    opts.query = options.query;
59923                }
59924
59925                if (options.criteria != undefined) {
59926                    opts.criteria = options.criteria;
59927                }
59928
59929                return(opts);
59930            }
59931
59932
59933            function success_handler(callbacks, response_data, text_status, xhdr) {
59934                try {
59935                    callbacks.success_callback(response_data, text_status, xhdr);
59936                }
59937                finally {
59938                    // whatever else happens, after the user callback is complete we're
59939                    // ready for a new request _of the same method_.  NOTE that this means that the user CAN NOT
59940                    // make a new request for this same method in the handler for the previous request.
59941                    // This might be trouble.
59942                    //          _request_blocks[callbacks.block_key] = false;
59943                }
59944            }
59945
59946
59947            function success_handler_with_reverse(callbacks, response_data, text_status, xhdr) {
59948                try {
59949                    var len = response_data.length;
59950                    for (var i = 0; i < len; i++) {
59951                        var data = response_data[i];
59952                        // if this is a section_data_set AND it is coronal (plane_of_section == 1) reverse the images
59953                        if (data.section_images && data.plane_of_section_id && data.plane_of_section_id == 1)
59954                            data.section_images.reverse();
59955                        else if (data.section_data_set && data.section_data_set.section_images && data.section_data_set.plane_of_section_id && data.section_data_set.plane_of_section_id == 1)
59956                            data.section_data_set.section_images.reverse();
59957                    }
59958                    callbacks.success_callback(response_data, text_status, xhdr);
59959                }
59960                finally {
59961                    // whatever else happens, after the user callback is complete we're
59962                    // ready for a new request _of the same method_.  NOTE that this means that the user CAN NOT
59963                    // make a new request for this same method in the handler for the previous request.
59964                    // This might be trouble.
59965                    //          _request_blocks[callbacks.block_key] = false;
59966                }
59967            }
59968
59969            function error_handler(callbacks, xhdr, text_status, error) {
59970                try {
59971                    if (callbacks.error_callback) {
59972                        callbacks.error_callback(xhdr, text_status, error);
59973                    }
59974                }
59975                finally {
59976                    //              _request_blocks[callbacks.block_key] = false;
59977                }
59978            }
59979
59980            function init() {
59981                _the_dao = new AIBS.DATA.API_DAO(_self.service_url, _self.user_options);
59982            }
59983
59984
59985            function inspect(prefix, obj) {
59986                var s = "";
59987                for (var key in obj) {
59988                    if ((typeof obj[key]) == "function")
59989                        s = s;
59990                    else if ((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
59991                        s += "\n" + prefix + "key: " + key + inspect(prefix + "    ", obj[key]);
59992                    else
59993                        s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
59994                }
59995                return(s);
59996            }
59997
59998            // initialization
59999            init();
60000
60001            }
60002        );
60003
60004
60005})(jQuery);
60006"use strict";
60007/**
60008 * The Mapping DAO wraps the spatial mapping portion of the AIBS warehouse service.
60009 * <p>
60010
60011 * </p>
60012 * @class
60013 * @param service_url:   The base url for the spatial mapping data service
60014
60015 */
60016AIBS.DATA.Mapping_DAO = (function($j) {
60017
60018 return (
60019          function(service_url, options) {
60020
60021            // public properties
60022             this.service_url = service_url;
60023
60024             this.settings = {
60025                 // ?
60026             };
60027
60028             if(options)
60029                $j.extend(true, this.settings, options);
60030
60031
60032             // public methods
60033             this.image_to_image = function(image_id, x, y, experiment_ids, options) {
60034
60035                 var query_options = {
60036                     format:'json',
60037                     verb:'GET',
60038                     success_callback:null,
60039                     error_callback:null
60040                 };
60041
60042                 var request = {
60043                     x:x,
60044                     y:y,
60045                     section_data_set_ids:experiment_ids
60046                 };
60047
60048                 $j.extend(true, query_options, options);
60049
60050                 var url = this.service_url + "image_to_image/" + image_id;
60051                 return(place_request(url, request, query_options));
60052             }
60053
60054             this.image_to_atlas = function(image_id, x, y, atlas_id, options) {
60055
60056                 var query_options = {
60057                     format:'json',
60058                     verb:'GET',
60059                     success_callback:null,
60060                     error_callback:null
60061                 };
60062
60063                 var request = {
60064                     x:x,
60065                     y:y,
60066                     atlas_id:atlas_id
60067                 };
60068
60069                 $j.extend(true, query_options, options);
60070
60071                 var url = this.service_url + "image_to_atlas/" + image_id;
60072                 return(place_request(url, request, query_options));
60073             }
60074
60075
60076             this.reference_to_image = function(reference_space_id, x, y, z, experiment_ids, options) {
60077
60078                 var query_options = {
60079                     format:'json',
60080                     verb:'GET',
60081                     success_callback:null,
60082                     error_callback:null
60083                 };
60084
60085                 var request = {
60086                     x:x,
60087                     y:y,
60088                     z:z,
60089                     section_data_set_ids:experiment_ids.join(',')
60090                 };
60091
60092                 $j.extend(true, query_options, options);
60093
60094                 var url = this.service_url + "reference_to_image/" + reference_space_id;
60095                 return(place_request(url, request, query_options));
60096             }
60097
60098              /**
60099               * @private
60100               * @scope AIBS.DATA.Mapping_DAO
60101               */
60102              var _self = this;
60103
60104
60105              // private methods
60106              function place_request(url, query, options) {
60107
60108                  var request = {
60109                    type:options.verb,
60110                    url:url + "." + options.format,
60111                    data:query,
60112                    success: function(response_data, text_status, xhdr) {success_handler(options, response_data, text_status, xhdr)},
60113                    error: function(xhdr, text_status, error) {error_handler(options, xhdr, text_status, error)},
60114                    dataType:options.format
60115                  }
60116
60117                  if(options.parse_request)
60118                      return(request);
60119
60120                  // the success & error callback functions are
60121                  // wrapped in order to do some housekeeping.
60122                  request.success = function(response_data, text_status, xhdr) {success_handler(options, response_data, text_status, xhdr)};
60123                  request.error = function(xhdr, text_status, error) {error_handler(options, xhdr, text_status, error)};
60124                  $j.ajax(request);
60125              }
60126
60127              function success_handler(callbacks, response_data, text_status, xhdr) {
60128                  try {
60129                      if(response_data && response_data.success != undefined && (response_data.success == true || response_data.success == 'true')) {
60130                        callbacks.success_callback(response_data.msg, text_status, xhdr);
60131                      }
60132                      else {
60133                        callbacks.error_callback(xhdr, text_status, response_data);
60134                      }
60135                  }
60136                  finally {
60137
60138                  }
60139              }
60140
60141              function error_handler(callbacks, xhdr, text_status, error) {
60142
60143                  try {
60144                      if(callbacks.error_callback) {
60145                          callbacks.error_callback(xhdr, text_status, error);
60146                      }
60147                  }
60148                  finally {
60149
60150                  }
60151              }
60152
60153    function inspect(prefix, obj) {
60154        var s = "";
60155        for(var key in obj) {
60156            if((typeof obj[key]) == "function")
60157                s = s;
60158            else if((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
60159                s += "\n" + prefix + "key: " + key + inspect(prefix + "    ", obj[key]);
60160            else
60161                s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
60162        }
60163        return(s);
60164    }
60165              function init() {
60166
60167              }
60168
60169              // initialization
60170              init();
60171
60172           }
60173 );
60174
60175
60176})(jQuery);
60177"use strict";
60178// Brain Explorer Download Prompt -- brain_explorer_prompt.js
60179// Copyright (c) 2007-2013 Allen Institute for Brain Science. All Rights Reserved.
60180
60181/**
60182 *
60183 * @param anchor
60184 * @param url
60185 */
60186function brain_explorer_link(anchor, url) {
60187/* 	Uses jQuery's dialog to prompt the user to download brain explorer
60188	if they don't have the setting (or the setting is false).
60189	
60190	To use, create an empty div on the web page (will serve as the prompt) and
60191	a link element that points to the correct location (e.g. aibe://...).  The
60192	prompt hijacks the onclick property of the link element to open the prompt
60193	if a supplied setting is false or unset.  If the user clicks 'yes', the
60194	setting is set and the link is followed.  If the user clicks 'no', the 
60195	supplied download page is opened in a new page.
60196
60197	The prompt is initially disabled and must be enabled via 
60198	prompt.set_link(destination). Disable the	link via prompt.disable(). */
60199
60200	var dialog_name = "brain_explorer_install_dialog";
60201	var container = $j("#" + dialog_name);
60202	if (!container || !container.size())
60203	{
60204		container = document.createElement("div");
60205		container.setAttribute("id", dialog_name);
60206	}
60207
60208	this.dialog_container = $j(container);
60209	this.anchor = $j(anchor);
60210	this.settings = new user_data();
60211// TODO: These next 2 should be global settings
60212	this.setting_name = "brain_explorer_2_installed";
60213	this.download_page = "/static/brainexplorer";
60214	if (url)
60215		this.set_url(url);
60216	else
60217		this.disable();
60218
60219	var _self = this;
60220
60221	this.dialog_container
60222		.html('<p>Brain Explorer is a desktop application for viewing brain anatomy and gene expression in 3D.</p><p>Choose "Download" to proceed to the Brain Explorer download page.</p><p>Choose "View Data" if you have already installed Brain Explorer.</p>')
60223		.dialog({ autoOpen: false,
60224				  modal: true,
60225				  title: "Is Brain Explorer installed?",
60226				  width: "500px",
60227				  buttons: { "Download" : function() { _self.handle_prompt_no(); },
60228							 "View Data": function() { _self.handle_prompt_yes(); } }
60229				});
60230
60231	anchor.click(function() { return _self.check_installed(); });
60232}
60233
60234brain_explorer_link.prototype.set_url = function (destination) 
60235{
60236	this.anchor.attr('href', destination);
60237	this.enabled = true;
60238}
60239
60240brain_explorer_link.prototype.disable = function ()
60241{
60242	this.anchor.attr('href', 'javascript:void(0);');
60243	this.enabled = false;
60244}
60245
60246brain_explorer_link.prototype.check_installed = function ()
60247{
60248	if (!this.enabled)
60249	{
60250		return false;
60251	}
60252	else if (this.settings.get(this.setting_name))
60253	{
60254		return true;
60255	}
60256	else
60257	{
60258		$j(this.dialog_container).dialog('open');
60259		return false;
60260	}
60261}
60262
60263brain_explorer_link.prototype.handle_prompt_yes = function (s)
60264{
60265	this.dialog_container.dialog('close');
60266	this.settings.set(this.setting_name, true);
60267	window.location = this.anchor.attr('href');
60268}
60269
60270brain_explorer_link.prototype.handle_prompt_no = function s()
60271{
60272	this.dialog_container.dialog('close');
60273	window.open(this.download_page);
60274}
60275;
60276
60277
60278
60279
60280function data_download(container, download_service, session_dao, image_data, options) {
60281
60282    this.container = $j(container);
60283    this.download_service = download_service;
60284    this.session_dao = session_dao;
60285    this.image_data = image_data;
60286    this.settings = {
60287        default_start_row:1,
60288        default_num_rows:200,
60289        max_rows:2000
60290    };
60291    $j.extend(true, this.settings, options);
60292
60293
60294    this.show_dialog = function() {
60295
60296        if(_dialog)
60297            _dialog.dialog('open');
60298    }
60299
60300    var _self = this;
60301    var _dialog = null;
60302
60303    var _start_row = _self.settings.default_start_row;
60304    var _num_rows = _self.settings.default_num_rows;
60305    var _normalize = false;
60306    var _resolution = "unknown";
60307    var _group_by = "donors,structures";
60308    var _search_type = "";
60309
60310    var _start_spinner = null;
60311    var _end_spinner = null;
60312
60313
60314    function build_scaffold() {
60315
60316        if(document.getElementById('download_dialog_wrapper'))
60317            return;
60318
60319        var html = '';
60320        html += '<div id="download_dialog_wrapper">';
60321        html += '    <div id="download_dialog_text">Download this data</div>';
60322        html += '</div>';
60323
60324        _self.container.append(html);
60325        $j('#download_dialog_text').bind('click', do_download_click);
60326    }
60327
60328    function build_dialog() {
60329
60330
60331        if(!document.getElementById('download_dialog_container'))  {
60332
60333            var html = '';
60334            html += '<div id="download_dialog_container" class="download_dialog_container">';
60335            html += '<div id="download_dialog" class="download_dialog">';
60336            html += '    <div id="download_dialog_header" class="download_dialog_header">';
60337            html += '      <span id="download_header_text" class="download_header_text">You may download up to 2000 rows at a time.</span>';
60338            html += '    </div>';
60339            html += '      <div id="download_download_input_container" class="download_download_input_container">';
60340            html += '           <span id="download_start_spinner_container" class="download_start_spinner_container">';
60341            html += '               Start row: <input type="text" id="download_start_input" value="1" class="download_spinner_input"/>';
60342            html += '           </span>';
60343            html += '           <span id="download_end_spinner_container" class="download_end_spinner_container">';
60344            html += '               Number of rows: <input type="text" id="download_count_input" value="1" class="download_spinner_input"/>';
60345            html += '           </span>';
60346            html += '      </div>';
60347            html += '</div>';
60348            html += '</div>';
60349
60350            $j(document.body).append(html);
60351        }
60352
60353        if(!_dialog) {
60354            _dialog = $j('#download_dialog').dialog({
60355                autoOpen:false,
60356                minWidth:380,
60357                minHeight:160,
60358                resizable:false,
60359                title:'Data Download',
60360                buttons:[
60361                    {text:"Cancel", click:function() { $j(this).dialog('close');}},
60362                    {text:"Download", click:do_ok}
60363                ]
60364            });
60365        }
60366    }
60367
60368    function request_download() {
60369
60370        var session = _self.session_dao.session;
60371
60372        var url = _self.download_service + '?session_id=' + session.session_id;
60373        url += '&start_row=' + (_start_row-1);
60374        url += '&num_rows=' + _num_rows;
60375        url += '&normalize=' + (_normalize ? 'true' : 'false');
60376        url += '&groupBy=' + _group_by;
60377        url += '&resolution=' + _resolution;
60378        url += "&search_type=" + _search_type;
60379
60380        url += "&url=" + encodeURIComponent(window.location.href);
60381
60382        window.location.href = url;
60383    }
60384
60385    function do_ok() {
60386
60387        var valid = false;
60388        var reason = null;
60389
60390        try {
60391
60392            _start_row = parseInt(document.getElementById('download_start_input').value);
60393            _num_rows = parseInt(document.getElementById('download_count_input').value);
60394            _normalize = _self.image_data.get_format_options().normalize;
60395            _group_by = _self.session_dao.get_session_params().group_by;
60396            _resolution = _self.session_dao.get_session_params().column_set;
60397            _search_type = _self.session_dao.get_session_params().search_type;
60398
60399        } catch(e) {
60400
60401            warn('Both the start row and end row must be valid positive integers.  Please try again.');
60402            return;
60403        }
60404
60405        reason = validate();
60406        if(reason) {
60407            warn(reason);
60408            return;
60409        }
60410
60411        _dialog.dialog('close');
60412        request_download();
60413    }
60414
60415    function warn(msg) {
60416
60417        alert(msg);
60418    }
60419
60420    function validate() {
60421
60422        if(_num_rows < 0 || _start_row < 0)
60423            return('Both the start row and end row must be valid positive integers.  Please try again.');
60424
60425        if(_start_row > _self.session_dao.session.number_of_probes)
60426            return("Your start row value '" + _start_row + "' exceeds the number of rows available.  Please try again.");
60427
60428        if(_start_row - 1 + _num_rows > _self.session_dao.session.number_of_probes)
60429            return("Your indicated range of rows exceeds the total available.  Please try again.");
60430
60431        return null;
60432    }
60433
60434    function do_download_click() {
60435
60436        // if they haven't already been created, create the spinners
60437        build_scaffold();
60438        build_dialog();
60439
60440        if(!_start_spinner ) {
60441            _start_spinner = document.getElementById('download_start_input');
60442            if(_start_spinner)
60443                $j(_start_spinner).spinner({min:1, max:999999});
60444        }
60445
60446        if(!_end_spinner ) {
60447            _end_spinner = document.getElementById('download_count_input');
60448            if(_end_spinner)
60449                $j(_end_spinner).spinner({min:1, max:999999});
60450        }
60451
60452        var available_rows = _self.session_dao.session.number_of_probes;
60453
60454        // set the header text depending on the task
60455        var row_overflow = (available_rows > _self.settings.max_rows);
60456        var row_count = row_overflow ? _self.settings.max_rows : available_rows;
60457        var msg = row_overflow ? "You may download up to 2000 rows at a time." : ("There are " + row_count + " rows available for download.");
60458        document.getElementById('download_header_text').innerHTML = msg;
60459
60460        // set the rows start & row count values to something reasonable for this data set
60461        $j('#download_count_input').spinner("option", "max", row_count);
60462        $j('#download_start_input').spinner("value", 1);
60463        $j('#download_count_input').spinner("value", row_count);
60464
60465        _dialog.dialog('open');
60466    }
60467
60468    function init() {
60469
60470        build_scaffold();
60471        build_dialog();
60472    }
60473
60474    init();
60475}
60476;
60477"use strict";
60478/**
60479 * A tiny widget to place a "Download as XML link on the page.  Methods include
60480 * "set_link", "show" and "hide".
60481 *
60482 * @param container
60483 * @param options
60484 */
60485function data_linker(container, options) {
60486
60487    this.container = $j(container);
60488    this.settings = {
60489        link_text:"This data is also available as XML",
60490        link_title:""
60491    };
60492    $j.extend(true, this.settings, options);
60493
60494
60495    this.set_link = function (href) {
60496
60497        $j('#'+_id).attr('href', href);
60498    };
60499
60500    this.show = function (show) {
60501
60502        if (show)
60503            _linker.show();
60504        else
60505            _linker.hide();
60506    }
60507
60508
60509    var _self = this;
60510    var _id = 'data_linker' + Math.floor(Math.random() * 10001);
60511    var _linker = null;
60512
60513
60514    function build_scaffold() {
60515
60516        var html = "<div class='data_linker' style='float:right;margin-top:-18px'>";
60517        html += "<a id='" + _id + "' href='' target='_blank' title=" + _self.settings.link_title + ">" + _self.settings.link_text + "</a>";
60518        html += "</div>";
60519
60520        _linker = $j(html).appendTo(_self.container);
60521    }
60522
60523    build_scaffold();
60524}
60525;
60526"use strict";
60527/**
60528 * Modal dialog box that can display image filter sliders -- contrast, rgb, or ranged_rgb
60529 * When sliders change value, a user's callback function is invoked with event.filterVals set up as a comma-delimited string of
60530 * filter values appropriate to the current filter. The same callback is used to handle all of the filter types; the types
60531 * are differentiated by event.type.
60532 * This dialog designed to be accessed via a toolbar button on a zap_viewer.
60533 *
60534 * Once the dialog is instantiated on the page, it is alternatively shown/hidden, but not destroyed.
60535 * The contents of the dialog are deleted and re-created each time the dialog is shown.
60536 * This means each time the dialog is shown, you can swap in any of the filter slider types, as well as
60537 * changing their ranges and values. However, you can only show one slider type at any time.
60538 *
60539 * The sliders are created between optional header and footer text.
60540 * The actual dialog HTML lives on the display page, but it is constructed dynamically by this function.
60541 *
60542 * Each slider type is represented by a separate filterWidget instance.
60543 * The dialog box itself is from jQuery UI
60544 *
60545 * Assumption: this is the only instance of a filter_dialog on a given page.
60546 * If there are multiple zap_viewers on a page (like the IVT page), they should share a common filterDialog instance.
60547 * The filterDialog is set up to be modal to avoid dealing with the user interacting with the current zap_viewer (changing viewed image
60548 * type, which may require a different image filter), and with changing focus from one viewer to the next if multiple viewers are on the page.
60549 *
60550 * Usage: once the filterDialog is created, it is instantiated but invisible on the page, and contains no sliders.
60551 * Just prior to showing the dialog, you specify which slider type to display and provide the filter slider settings values.
60552 *
60553 * Example: open the dialog
60554 *   dialogInstance.createFilterSliders(viewer, settings) // 'settings' has both the type, filter values, filter ranges, and options
60555 *   dialogInstance.show()
60556 *
60557 * Example: close the dialog
60558 *   dialogInstance.hide()
60559 *
60560 * @param owner - owner of the dialog, such as current zap viewer or zap wrapper which launched this dialog
60561 * @param container - CSS id of div that will contain the filter_dialog HTML. Can also be jQuery element.
60562 * @param callBacks - hash of optional callbacks for 'onChange', 'onOpen', 'onClose'
60563 *                    You 'onChange' callBack gets an event arg which has event.type which indicate the slider widget type<br/>
60564 *                   and event.filterVal, which is an imageservice-ready filter string reflecting changes to be applied to an image.
60565 * @param zindex - dialog's CSS z-index value.
60566 *
60567 * @return - filterDialog instance
60568 *
60569 *
60570 * settings - settings hash, defining settings for a unique filter slider widget type.<br/>
60571 *                 See below for description of hashes.
60572 *                 You must define settings to match each of the filter slider widget types called out in the 'types' argument.
60573 * --- Settings hash for 'contrast' slider widget (has one slider):
60574 *   {'contrast': {
60575 *      values: number,
60576 *      ranges: [min, max],
60577 *      filter_to_slider: number
60578 *      options: {filter_label:{contrast:label}}
60579 *     }
60580 *   }
60581 *
60582 * where values = initial slider value
60583 * where ranges = lower and upper bounds of the slider
60584 * where filter_to_slider = multiplier used to convert filter value to displayed slider setting
60585 * where filter_label = text label associated with contrast slider
60586 *
60587 * --- Settings hash for 'rgb' filter slider widget (has three sliders):
60588 *
60589 *   {'rgb': {
60590 *      values: {r: number, g: number, b: number},
60591 *      ranges: {r: [min, max], g: [min, max], b: [min, max]},
60592 *      filter_to_slider: number,
60593 *      options: {channel_toggle: {selected:{r:boolean, g:boolean, b:boolean}},
60594 *                slider_labels: {r:label, g:label, b:label}}
60595 *     }
60596 *   }
60597 *
60598 * where values = initial slider values for each slider
60599 * where ranges = lower and upper bounds of each slider
60600 * where filter_to_slider = multiplier used to convert filter value to displayed slider setting
60601 * where options.channel_toggle.selected indicates the selection state of each of the channel toggle checkboxes for each slider
60602 * where filter_label = text labels associated with red, green, blue sliders
60603 *
60604 * --- Settings hash for 'ranged_rgb' filter slider widget (has three sliders with two range slider handles per slider):
60605 *
60606 *   {'ranged_rgb': {
60607 *      values: {r: [low,hi], g: [low,hi], b: [low,hi]},
60608 *      ranges: {r: [min, max], g: [min, max], b: [min, max]},
60609 *      filter_to_slider: number,
60610 *      options: {channel_toggle: {selected:{r:boolean, g:boolean, b:boolean}},
60611 *                algorithm: "",
60612 *                slider_labels: {r:label, g:label, b:label}}
60613 *     }
60614 *   }
60615 *
60616 * where values = initial slider values for each slider, there is a low and high number for each slider, since there are two handles on each slider.
60617 * where ranges = lower and upper bounds of each slider
60618 * where options.channel_toggle.selected indicates the selection state of each of the channel toggle checkboxes for each slider
60619 * where options.algorithm is a placeholder for future callback functions to determine slider calibration
60620 * where filter_to_slider = multiplier used to convert filter value to displayed slider setting
60621 * where options.channel_toggle.selected indicates the selection state of each of the channel toggle checkboxes for each slider
60622 * where filter_label = text labels associated with red, green, blue sliders
60623 */
60624
60625//function filterDialog(viewer, container, callBack, zindex) {
60626function filterDialog(owner, container, callBacks, zindex) {
60627    var _self = this;
60628
60629    var _owner = owner;
60630    var _outerContainer = jQuery(container);
60631//    var _callBack = callBack;
60632
60633    var _contrastSettings;
60634    var _rgbSettings;
60635    var _rangedRgbSettings;
60636    var _sliderInstanceId = 0;
60637    var _contrastContainerName = "contrast_container_" + _sliderInstanceId;
60638    var _rgbContainerName = "rgb_container_" + _sliderInstanceId;
60639    var _rangedRgbContainerName = "ranged_rgb_container_" + _sliderInstanceId;
60640    var _filterWidgetContainer;
60641    var _contrastWidget;
60642    var _rgbWidget;
60643    var _rangedRgbWidget;
60644
60645    var _theDialog;
60646    var _savedOpacity;
60647    var _settings;
60648    var _type;
60649
60650    var _defaultValues;
60651
60652    var _onChange = callBacks['onChange'] == undefined ? undefined : callBacks['onChange'];
60653    var _onOpen = callBacks['onOpen'] == undefined ? undefined : callBacks['onOpen'];
60654    var _onClose = callBacks['onClose'] == undefined ? undefined : callBacks['onClose'];
60655    var _onReset = callBacks['onReset'] == undefined ? undefined : callBacks['onReset'];
60656    var _zindex = zindex !== undefined ? zindex : 20000; // applied during dialog construction, can't be done via CSS
60657
60658    // public
60659    this.setTitle = function(text) {
60660        _theDialog.dialog( "option", "title", text);
60661    }
60662
60663    this.setHeader = function(text) {
60664        _outerContainer.prepend(text);
60665    }
60666
60667    this.setFooter = function(text) {
60668        _outerContainer.append(text);
60669    }
60670
60671    this.show = function() {
60672        _theDialog.dialog("open");
60673    }
60674
60675    this.hide = function() {
60676        _theDialog.dialog("close");
60677    }
60678
60679    /*
60680     * Creates new filter sliders inside body of the filter dialog.
60681     * This removes and replaces the existing filter, as well
60682     * anything else in the body of the dialog, like added text.
60683     *
60684     * @param owner - dialog's owner
60685     * @param filterSettings - hash with same format as filterDialog's 'settings' arg
60686     */
60687    this.createFilterSliders = function(owner, filterSettings) {
60688        _owner = owner;
60689        _outerContainer.empty(); // clear out DOM of filter elements
60690
60691        _contrastWidget = undefined;
60692        _rgbWidget = undefined;
60693        _rangedRgbWidget = undefined;
60694
60695        _contrastSettings = undefined;
60696        _rgbSettings = undefined;
60697        _rangedRgbSettings = undefined;
60698
60699        _settings = filterSettings;
60700
60701        if (_settings["contrast"])
60702            _type = "contrast";
60703        else if (_settings["rgb"])
60704            _type = "rgb"
60705        else if (_settings["ranged_rgb"])
60706            _type = "ranged_rgb"
60707
60708        _filterWidgetContainer = buildContainer(_outerContainer, _type);
60709        parseSettings(_type, _settings);
60710        createFilterWidgets(_type);
60711    }
60712
60713    /*
60714     * Total removal of filterDialog, including all DOM elements created
60715     * Not currently used.
60716     */
60717    this.destroy = function() {
60718        jQuery("body").css("cursor", "auto");
60719        _theDialog.dialog("destroy");
60720        _outerContainer.empty();
60721    }
60722
60723    /*
60724     * Set callbacks to the owner of this dialog. There are three possible callbacks that can be set:
60725     *   onChange - called when the filter values change. An event object is passed along, with these attributes:
60726     *              event.type - 'contrast', 'rgb', or 'ranged_rgb'
60727     *               event.filterVals - a string with filter values. contrast will be "number", rgb will be "num1,num2,num3",
60728     *                            ranged_rgb will be "num1a,num1b,num2a,num2b,num3a,num3b"
60729     *   onOpen - called when the dialog is opened for display
60730     *   onClose - called when the dialog is closed/hidden
60731     *
60732     * @param callBacks - hash of callbacks, can have keys 'onChange', 'onOpen', 'onClose'. All keys are optional.
60733     */
60734    this.setCallBacks = function(callBacks) {
60735        if (callBacks["onChange"]) _onChange = callBacks["onChange"];
60736        if (callBacks["onOpen"]) _onOpen = callBacks["onOpen"];
60737        if (callBacks["onClose"]) _onClose = callBacks["onClose"];
60738        if (callBacks["onReset"]) _onReset = callBacks["onReset"];
60739    }
60740
60741    /*
60742     * Gets the current filter dialog settings, including slider range and values, checkbox states.
60743     * This function is usually called when the filter dialog is close, so that the current settings can
60744     * be saved and restored the next time the dialog is opened.
60745     *
60746     * @return hash of filter settings in the same format as found in application.json in theme.application.filter_settings.
60747     *         slider range and values are in the context of the actual filter.
60748     */
60749    this.getFilterSettings = function() {
60750        var widget;
60751        if (_type == 'contrast')
60752            widget = _contrastWidget;
60753        else if (_type == 'rgb')
60754            widget = _rgbWidget;
60755        else if (_type == 'ranged_rgb')
60756            widget = _rangedRgbWidget;
60757
60758        var curRanges =  widget.getCurrentRanges();
60759        var curValues = widget.getCurrentValues();
60760        var curOptions = widget.getCurrentOptions();
60761        var curFilterToSlider = _settings[_type].filter_to_slider;
60762        var curWidth = _theDialog.dialog("widget").width();
60763
60764        var settings = {};
60765        settings[_type] = {
60766            values: curValues,
60767            ranges: curRanges,
60768            filter_to_slider: curFilterToSlider,
60769            options: curOptions
60770        }
60771
60772        settings["width"] = parseInt(curWidth);
60773        settings["zindex"] =  _zindex;
60774
60775        var filterSettings;
60776        if (_type == 'contrast')
60777            filterSettings = convertContrastSettings(settings, 'toFilter');
60778        else if (_type == 'rgb')
60779            filterSettings = convertRgbSettings(settings, 'toFilter');
60780        else if (_type == 'ranged_rgb')
60781            filterSettings = convertRangedRgbSettings(settings, 'toFilter');
60782
60783        filterSettings.type = _type;
60784        return filterSettings;
60785    }
60786
60787    /*
60788     * Utility function that overrides the filter values in an existing filter settings hash.
60789     *
60790     * @param filterName
60791     * @param filterVals - comma delimited string of filter values
60792     * @param settings - filter settings hash
60793     *
60794     * @return copy of modified settings hash, or an empty hash if there is an error
60795     */
60796    this.overrideFilterValues = function(filterName, filterVals, settings) {
60797        var newSettings = {};
60798
60799        // convert filter name to a slider name
60800        var sliderName = filterName == 'range' ? 'ranged_rgb' : filterName;
60801
60802        // ensure that the settings hash has a key with the sliderName
60803        if (settings[sliderName] == undefined) {
60804            // error condition, do nothing
60805        }
60806        else {
60807            // copy the original hash
60808            jQuery.extend(true, newSettings, settings);
60809
60810            var values = filterVals.split(",");
60811
60812            // insert filterVals into newSettings
60813            if (sliderName == 'contrast') {
60814                // contrast filter value has two values, but the first value never changes (0.5)
60815                newSettings['contrast'].values = values[1];
60816            }
60817            else if (sliderName == 'rgb') {
60818                newSettings['rgb'].values.r = values[0];
60819                newSettings['rgb'].values.g = values[1];
60820                newSettings['rgb'].values.b = values[2];
60821
60822            }
60823            else if (sliderName == 'ranged_rgb') {
60824                newSettings['ranged_rgb'].values.r[0] = values[0];
60825                newSettings['ranged_rgb'].values.r[1] = values[1];
60826                newSettings['ranged_rgb'].values.g[0] = values[2];
60827                newSettings['ranged_rgb'].values.g[1] = values[3];
60828                newSettings['ranged_rgb'].values.b[0] = values[4];
60829                newSettings['ranged_rgb'].values.b[1] = values[5];
60830            }
60831        }
60832
60833        return newSettings;
60834    }
60835
60836    /*
60837     * Utility that returns the CSS z-index value for the dialog.
60838     * This can be used by zap_viewer and other page components that need to juggle z-index values to ensure optimal visiblity
60839     */
60840    this.getMaxZIndex = function() {
60841        return _zindex;
60842    }
60843
60844    // private
60845
60846    /*
60847     * Creates a jQuery UI Dialog, it will be created as a modal
60848     * dialog and will be hidden after creation.
60849     *
60850     * @param containerElement - is a jQuery element object which servers as the container for the sliders and other GUI objects.
60851     * @return - reference to jQuery Dialog
60852     */
60853    function createDialog(containerElement) {
60854        // The actual dialog comprises a <div> that encloses the containerElement div.
60855        // So you can dynamically edit the contents containerElement without affecting the dialog itself
60856        var dialogRef = containerElement.dialog({
60857            modal: true,
60858            draggable: true,
60859            resizable: true,
60860            autoOpen: false,
60861            zIndex: _zindex,
60862            open: doDialogOpen,
60863            close: doDialogClose,
60864            width: 370
60865        });
60866
60867        return dialogRef;
60868    }
60869
60870    /*
60871     * UI housekeeping applied to page, cursors when the dialog is opened
60872     */
60873    function doDialogOpen(event, ui) {
60874        // save existing overlay opacity style, then set it to zero. This is so the underlying viewer image will be WYSIWYG when
60875        // affected by dynamic slider changes.
60876        var overlay = jQuery(".ui-widget-overlay"); // this is a jQuery CSS class
60877        _savedOpacity = overlay.css("opacity"); // will automagically handle the IE nonstandard 'filter' style
60878        overlay.css("opacity", "0");
60879
60880        // immediately change cursor to 'not-allowed'
60881        jQuery("body").css("cursor", "not-allowed");
60882
60883        // change cursors when over/out of the dialog
60884        _theDialog.hover(
60885          function () {
60886            _theDialog.css("cursor", "auto"); // over
60887          },
60888          function () {
60889            jQuery("body").css("cursor", "not-allowed"); // out
60890          }
60891        );
60892
60893        if (_onOpen)
60894            _onOpen(_owner, event);
60895    }
60896
60897    /*
60898     * UI housekeeping applied to page, cursors when the dialog is closed
60899     */
60900    function doDialogClose(event, ui) {
60901        jQuery("body").css("cursor", "auto");
60902
60903       // TODO: figure out why we cannot restore opacity setting
60904       // restore overlay opacity styles. This *not working* for some reason.
60905        jQuery(".ui-widget-overlay").css("opacity", _savedOpacity);
60906
60907        if (_onClose)
60908            _onClose(_owner, event);
60909    }
60910
60911    /*
60912     * Dynamically add filter slider container (divs) as child of the outerContainer.
60913     *
60914     * @param outerContainer
60915     * @param type filter type which needs a container
60916     * @return jQuery element of the new filter slider container 'div', or undefined if type is unknown
60917     */
60918    function buildContainer(outerContainer, type) {
60919        var html = '';
60920        var type;
60921        var containerName;
60922        var containerElement;
60923
60924        if (type == 'contrast') {
60925            containerName = _contrastContainerName;
60926        }
60927        else if (type == 'rgb') {
60928            containerName = _rgbContainerName;
60929        }
60930        else if (type == 'ranged_rgb') {
60931            containerName = _rangedRgbContainerName;
60932        }
60933
60934        if (containerName !== undefined) {
60935            html = '<div id="' + containerName + '" style="display:block"</div>';
60936            jQuery(outerContainer).prepend(html);
60937
60938            containerElement = jQuery("#" + containerName);
60939        }
60940
60941        return containerElement;
60942    }
60943
60944    /*
60945     * Parse out the filter slider settings, and convert filter values to slider display values.
60946     * Will update one of the following vars: _contrastSettings, _rgbSettings, _rangedRgbSettings
60947     *
60948     * @param type slider type
60949     * @param settings filter slider settings
60950     */
60951    function parseSettings(type, settings) {
60952        var direction = "toSlider";
60953
60954        if (type == 'contrast')
60955            _contrastSettings = convertContrastSettings(settings, direction);
60956        else if (type == 'rgb')
60957            _rgbSettings = convertRgbSettings(settings, direction);
60958        else if (type == 'ranged_rgb')
60959            _rangedRgbSettings = convertRangedRgbSettings(settings, direction);
60960
60961    }
60962
60963    /*
60964     * Convert contrast settings from filter --> slider or slider --> filter
60965     *
60966     * contrast settings:
60967     *   {"contrast":{
60968     *         values:number,
60969     *         ranges:[number,number],
60970     *         filter_to_slider:number,
60971     *         options:{channel_toggle:{selected:boolean} }
60972     *     }
60973     *   }
60974     *
60975     * @param settings - settings hash, numerical values are either in filter or slider context
60976     * @param direction - conversion direction, values are "toFilter" or "toSlider"
60977     * @return hash with same keys as 'settings' but whose numeric values are converted
60978     */
60979    function convertContrastSettings(settings, direction) {
60980        var converted = {};
60981
60982        // apply conversions to a copy of the existing settings
60983        jQuery.extend(true, converted, settings);
60984
60985        var multiplier;
60986        if (direction == "toSlider")
60987            multiplier = settings["contrast"].filter_to_slider;
60988        else if (direction == "toFilter")
60989            multiplier = 1/(settings["contrast"].filter_to_slider);
60990
60991        converted["contrast"].values = settings["contrast"].values * multiplier;
60992        converted["contrast"].ranges[0] = settings["contrast"].ranges[0] * multiplier;
60993        converted["contrast"].ranges[1] = settings["contrast"].ranges[1] * multiplier;
60994
60995        return converted;
60996    }
60997
60998    /*
60999     * Convert rgb settings from filter --> slider or slider --> filter
61000     *
61001     * rgb settings structure:
61002     *   {"rgb": {
61003     *      values:{r:number, g:number, b:number},
61004     *      ranges:{r:[number,number], g:[number,number], b:[number,number]},
61005     *      filter_to_slider:number,
61006     *      options:{channel_toggle:{selected:boolean} }
61007     *     }
61008     *   }
61009     *
61010     * @param settings - settings hash, numerical values are either in filter or slider context
61011     * @param direction - conversion direction, values are "toFilter" or "toSlider"
61012     * @return hash with same keys as 'settings' but whose numeric values are converted
61013     */
61014    function convertRgbSettings(settings, direction) {
61015        var converted = {};
61016        jQuery.extend(true, converted, settings);
61017
61018        var multiplier;
61019        if (direction == "toSlider")
61020            multiplier = settings["rgb"].filter_to_slider;
61021        else if (direction == "toFilter")
61022            multiplier = 1/(settings["rgb"].filter_to_slider);
61023
61024        converted["rgb"].values.r = settings["rgb"].values.r * multiplier;
61025        converted["rgb"].values.g = settings["rgb"].values.g * multiplier;
61026        converted["rgb"].values.b = settings["rgb"].values.b * multiplier;
61027
61028        converted["rgb"].ranges.r[0] = settings["rgb"].ranges.r[0] * multiplier;
61029        converted["rgb"].ranges.r[1] = settings["rgb"].ranges.r[1] * multiplier;
61030        converted["rgb"].ranges.g[0] = settings["rgb"].ranges.g[0] * multiplier;
61031        converted["rgb"].ranges.g[1] = settings["rgb"].ranges.g[1] * multiplier;
61032        converted["rgb"].ranges.b[0] = settings["rgb"].ranges.b[0] * multiplier;
61033        converted["rgb"].ranges.b[1] = settings["rgb"].ranges.b[1] * multiplier;
61034
61035        return converted;
61036    }
61037
61038    /*
61039     * Convert ranged_rgb settings from filter --> slider or slider --> filter
61040     *
61041     * ranged_rgb settings structure:
61042     *   {"ranged_rgb": {
61043     *      values:{r:[number,number], g:[number,number], b:[number,number]},
61044     *      ranges:{r:[number,number], g:[number,number], b:[number,number]},
61045     *      filter_to_slider:number,
61046     *      options:{channel_toggle:{selected:{r:boolean, g:boolean, b:boolean}},
61047      *              algorithm:{callback or empty string or undefined}}
61048     *     }
61049     *   }
61050     *
61051     * @param settings - settings hash, numerical values are either in filter or slider context
61052     * @param direction - conversion direction, values are "toFilter" or "toSlider"
61053     * @return hash with same keys as 'settings' but whose numeric values are converted
61054     */
61055    function convertRangedRgbSettings(settings, direction) {
61056        var converted = {};
61057        jQuery.extend(true, converted, settings);
61058
61059        var multiplier;
61060        if (direction == "toSlider")
61061            multiplier = settings["ranged_rgb"].filter_to_slider;
61062        else if (direction == "toFilter")
61063            multiplier = 1/(settings["ranged_rgb"].filter_to_slider);
61064
61065        converted["ranged_rgb"].values.r[0] = Math.round(settings["ranged_rgb"].values.r[0] * multiplier);
61066        converted["ranged_rgb"].values.r[1] = Math.round(settings["ranged_rgb"].values.r[1] * multiplier);
61067        converted["ranged_rgb"].values.g[0] = Math.round(settings["ranged_rgb"].values.g[0] * multiplier);
61068        converted["ranged_rgb"].values.g[1] = Math.round(settings["ranged_rgb"].values.g[1] * multiplier);
61069        converted["ranged_rgb"].values.b[0] = Math.round(settings["ranged_rgb"].values.b[0] * multiplier);
61070        converted["ranged_rgb"].values.b[1] = Math.round(settings["ranged_rgb"].values.b[1] * multiplier);
61071
61072        converted["ranged_rgb"].ranges.r[0] = Math.round(settings["ranged_rgb"].ranges.r[0] * multiplier);
61073        converted["ranged_rgb"].ranges.r[1] = Math.round(settings["ranged_rgb"].ranges.r[1] * multiplier);
61074        converted["ranged_rgb"].ranges.g[0] = Math.round(settings["ranged_rgb"].ranges.g[0] * multiplier);
61075        converted["ranged_rgb"].ranges.g[1] = Math.round(settings["ranged_rgb"].ranges.g[1] * multiplier);
61076        converted["ranged_rgb"].ranges.b[0] = Math.round(settings["ranged_rgb"].ranges.b[0] * multiplier);
61077        converted["ranged_rgb"].ranges.b[1] = Math.round(settings["ranged_rgb"].ranges.b[1] * multiplier);
61078
61079        return converted;
61080    }
61081
61082    /*
61083     * Build filter widgets, based on the existence of the corresponding container.
61084     * In other words, only build widgets if there is already a place for them to live.
61085     * Values and ranges should be in display context of the slider, not of the filter.
61086     *
61087     * @param type filter slider type (contrast, rgb, ranged_rgb)
61088     */
61089    function createFilterWidgets(type) {
61090        var ranges;
61091        var values;
61092        var options;
61093        var imageType = "";
61094
61095        if (type == "contrast") {
61096            ranges = [];
61097            options = {};
61098            values = _contrastSettings["contrast"].values;
61099            ranges = _contrastSettings["contrast"].ranges;
61100            options = _contrastSettings["contrast"].options;
61101            imageType = _owner.get_current_image_type();
61102            _contrastWidget  = new filterWidget("contrast", _filterWidgetContainer, 0, sliderDialogCallBack, ranges, values, options, imageType);
61103        }
61104        if (type == "rgb") {
61105            values = {};
61106            ranges = {};
61107            options = {};
61108            values = _rgbSettings["rgb"].values;
61109            ranges = _rgbSettings["rgb"].ranges;
61110            options = _rgbSettings["rgb"].options;
61111            imageType = _owner.get_current_image_type();
61112            _rgbWidget = new filterWidget("rgb", _filterWidgetContainer, 0, sliderDialogCallBack, ranges, values, options, imageType);
61113        }
61114        if (type == "ranged_rgb") {
61115            values = {};
61116            ranges = {};
61117            options = {};
61118            values = _rangedRgbSettings["ranged_rgb"].values;
61119            ranges = _rangedRgbSettings["ranged_rgb"].ranges;
61120            options = _rangedRgbSettings["ranged_rgb"].options;
61121            imageType = _owner.get_current_image_type();
61122            if (options["algorithm"] == undefined || options["algorithm"] == "") {
61123                options["algorithm"] = pixelMapper; // algorithmic transform object, only used by ranged_rgb filter widget
61124            }
61125
61126            _rangedRgbWidget = new filterWidget("ranged_rgb", _filterWidgetContainer, 0, sliderDialogCallBack, ranges, values, options, imageType);
61127        }
61128    }
61129
61130    /*
61131     * Called whenever sliders change value.
61132     * Attaches an additional properties "owner" and  "filterVals" to the event object passed to the user's onChange c
61132allback.
61133     * 'filterVals' is a comma-delimited string of filter values appropriate for the current filter
61134     */
61135    function sliderDialogCallBack(event, ui) {
61136        var type = event.sliderType;
61137
61138        var sliderValues;
61139        var sliderWidget;
61140        var multiplier;
61141        var filterString;
61142
61143        // determine which slider widget changed values
61144        if (type == 'contrast') {
61145            sliderWidget = _contrastWidget;
61146        }
61147        else if (type == 'rgb') {
61148            sliderWidget = _rgbWidget;
61149        }
61150        else if (type == 'ranged_rgb') {
61151            sliderWidget = _rangedRgbWidget;
61152        }
61153
61154        if (sliderWidget !== undefined) {
61155
61156            // get slider values and convert them to a filter values string
61157            sliderValues = sliderWidget.getSliderValues();
61158            if (type == 'contrast') {
61159                    multiplier = 1/(_contrastSettings["contrast"].filter_to_slider);
61160                    filterString = (sliderValues * multiplier).toString();
61161                }
61162            else if (type == 'rgb') {
61163                multiplier = 1/(_rgbSettings["rgb"].filter_to_slider);
61164                filterString = sliderValues.r * multiplier + ",";
61165                filterString += sliderValues.g * multiplier + ",";
61166                filterString += sliderValues.b * multiplier;
61167            }
61168            else if (type == 'ranged_rgb') {
61169                multiplier = 1/(_rangedRgbSettings["ranged_rgb"].filter_to_slider);
61170                filterString = Math.round(sliderValues.r[0] * multiplier) + "," + Math.round(sliderValues.r[1] * multiplier) + ",";
61171                filterString += Math.round(sliderValues.g[0] * multiplier) + "," + Math.round(sliderValues.g[1] * multiplier) + ",";
61172                filterString += Math.round(sliderValues.b[0] * multiplier) + "," + Math.round(sliderValues.b[1] * multiplier);
61173            }
61174
61175            // send the owner, filter type, and filter string back to the dialog owner
61176            event.owner = _owner;
61177            event.filterVals = filterString;
61178
61179            if(event.type === "reset"){
61180                return _onReset(event, ui);
61181            }
61182            else if (_onChange){
61183
61184                _onChange(event, ui);
61185            }
61186        }
61187    }
61188
61189    /* Pixel value transform functions to map the intensity window slider widget (ranged slider) such that it is linear
61190     * in the 12-bit range and log2 scale from 12 to 16 bits.
61191     *
61192     * Example Usage
61193     *
61194     * Set up a slider widget for 16-bit range
61195     *
61196     * var rangeLimit = [0, 65535];
61197     * slider.setMinimum(pixelMapper.transform(rangeLimit[0]);
61198     * slider.setMaximum(pixelMapper.transform(rangeLimit[1]);
61199     *
61200     * Retrieve values from the slider and map to the image service range parameter
61201     *
61202     * var range = [ Math.round(pixelMapper.inverseTransform(slider.getLowerValue()), 0),
61203     *               Math.round(pixelMapper.inverseTransform(slider.getUpperValue()), 0) ].join();
61204     *
61205     * In the example above, the actual slider range becomes lo,hi = 0,5686,
61206     * but the displayed slider values will be lo,hi = 0, 65535
61207     *
61208     * author: ChrisL
61209     */
61210    var pixelMapper =
61211    {
61212        breakPoint: 4096.0,
61213        scale: 100.0,
61214
61215        // @param v -
61216        transform: function(v) {
61217            if (v < this.breakPoint)
61218                return v;
61219            else
61220                return this.breakPoint + (Math.log(v - this.breakPoint + 1.0) / Math.LN2) * this.scale;
61221        },
61222
61223        inverseTransform: function(v) {
61224            if (v < this.breakPoint)
61225                return v;
61226            else
61227                return Math.pow(2.0, (v - this.breakPoint) / this.scale) + this.breakPoint - 1.0
61228        }
61229    };
61230
61231    function init() {
61232        _theDialog = createDialog(_outerContainer);
61233    }
61234
61235    init();
61236
61237    return _self;
61238}
61239;
61240"use strict";
61241/**
61242 * filter_widget.js
61243 *
61244 * This class creates horizontal sliders used to control image filter settings.
61245 * It uses jQuery and jQuery UI to construct the javascript sliders for one of three different configurations:
61246 *
61247 * 1) Single contrast slider
61248 * 2) Three RGB sliders
61249 * 3) Three ranged RGB sliders
61250 *
61251 * The sliders are displayed horizontally, and take up 100% of the width of their container.
61252 * They are prelabeled as "Contrast:", "Red:", "Green:", "Blue"
61253 *
61254 * There are also two optional checkbox widget types that can be added to the rgb or ranged_rgb slider layouts:
61255 * Channel toggles - checkboxes placed in front of each slider, whose intent is to be able to 'turn off' the adjacent color channel slider by
61256 * returning a value of 0 rather than the normal slider value. This happens when the checkbox is unselected. Toggling these checkboxes does not
61257 * affect the actual slider handle positions, they retain their current settings.
61258 * Expand range - when selected, this checkbox causes the high/low range of the sliders to be expanded. Unselecting the checkbox restores
61259 * a 'normal' range.
61260 *
61261 * Styling should be done via CSS
61262 * The user-supplied listener is called in response to a 'change' event from the sliders. It is up to the user's listener to redraw the
61263 * image in response to slider changes.
61264 *
61265 * All optional widgets (checkboxes) direct their change events to local listener functions, not to the user-supplied listener.
61266 *
61267 * Usage
61268 * ----
61269 * Right after creating a sliderWidget, the user should get the current slider values by calling getSliderValues(), then redrawing
61270 * their image(s) using the filter values derived from the sliders. Whenever the slider values change, whether by direct manipulation of the
61271 * slider handles or by checking/unchecking checkbox buttons, the callback (listener) function is sent a 'change' event. The listener function
61272 * should then call getSliderValues() and update the image(s).
61273 *
61274 * It is possible to have multiple instances of sliderWidget on a page, just make sure the 'ordinal' arg is unique for each instance. This is often
61275 * done when different image types wit different image filters are displayed on a page; the program logic shows/hides the different slider
61276 * containers via CSS.
61277 *
61278 * Error handling
61279 * --------------
61280 * There is very minimal error handling. For the most part, if there is a problem, it will fail silently. There is almost no
61281 * checking for correct format/inclusion of input args.
61282 */
61283
61284/**
61285 * filter_widget
61286 *
61287 * @param type slider type. 'contrast', 'rbg', or 'ranged_rgb'
61288 * @param container DOM element of container that will hold the slider(s)
61289 * @param ordinal unique ordinal number for the filter_widget instance. This is appended to DOM element ids to ensure uniqueness if more than one<br/>
61290 *                sliderWidget is on a page.
61291 * @param listener listener function callback for slider and widget state changes<br/>
61292 *                 Called with jQuery's event args, 'event' and 'ui'. Do not rely on 'ui', sometimes it does not have jQuery property info.
61293 * @param ranges hash of min/max values for each slider.<br/>
61294 *              contrast - [min, max]<br/>
61295 *              rgb, ranged_rgb - {r:[min, max], g:[min, max], b:[min, max]}
61296 * @param values hash of initial values for each slider<br/>
61297 *              contrast - val<br/>
61298 *              rgb - {r:val, g:val, b:val}<br/>
61299 *              ranged_rgb - {r:[lo, hi], g:[lo, hi], b:[lo, hi]}
61300 * @param options
61301 *          Configure the slider label text, if not present, default labels will be used.
61302 *              {slider_labels: {r:label, g:label, b:label, contrast:label}
61303 *          Add slider value toggle checkbox, and initial checkbox state. Defining the option enables it for creation.
61304 *              contrast - {channel_toggle: {selected: true or false}}
61305 *              rgb, ranged_rgb - {channel_toggle: {selected: {r: true or false, g: {true or false}, b: {true or false}}
61306 *          Add expand range checkbox and initial checkbox state. Defining the option enables it for creation.
61307 *              {expand_range: {selected: true or false,<br/>
61308 *                              ranges: {r:[min, max], g:[min, max], b:[min, max]},<br/>
61309 *                              values: {r:[lo, hi], g:[lo, hi], b:[lo, hi]}<br/>
61310 *                             }<br/>
61311 *              }
61312 *          Add slider range and value processing algorithm; this is only used by ranged_rgb slider
61313 *              ranged_rgb - {algorithm:{transform:fnc(rangeLimit), inverseTransform:fnc(sliderValue)}
61314 * @param imageType the type of the current image in display.
61315 */
61316function filterWidget(type, container, ordinal, listener, ranges, values, options, imageType) {
61317    /* ------- private vars --------- */
61318    var _type = type;
61319    var _container = jQuery(container);
61320    var _ordinal = ordinal;
61321    var _listener = listener;
61322    var _ranges = ranges; // save initial range settings
61323    var _values = values; // save initial value settings
61324    var _options = options == undefined? {} : options; // save initial options settings
61325
61326    var _self = this;
61327
61328    var _channelToggleOption = false;
61329    // TODO: _expandRangeOption is no longer used
61330    var _expandRangeOption = false;
61331    var _algorithmOption = false;
61332    var _instanceId = "_" + _ordinal; // append to CSS element ids to ensure unique instance id
61333
61334    var _sliderTypes = ["contrast", "rgb", "ranged_rgb"];
61335
61336    var _imageType = imageType;
61337
61338    // set up default slider labels
61339    var _contrastLabel = "Contrast";
61340    var _redLabel = "Red: ";
61341    var _greenLabel = "Green: ";
61342    var _blueLabel = "Blue: ";
61343    var _expandRangeLabel = "Expand Slider Range";
61344
61345    var _defaultValues;
61346
61347    // color guide descriptions
61348    var _redDescription = "";
61349    var _greenDescription = "";
61350    var _blueDescription = "";
61351    var _contrastDescription = "";
61352
61353    // track state of channel toggle; there are used for their default values, even if this option is not enabled!
61354    var _red_on = true;
61355    var _green_on = true;
61356    var _blue_on = true;
61357
61358    /* ------------------------ public functions ---------------------------- */
61359
61360    /**
61361     * Show/hide expand range checkbox
61362     * This assumes the checkbox already exists.
61363     *
61364     * @param show true = show, false= hide
61365     */
61366    this.showExpandRangeCB = function(show) {
61367        if (_expandRangeOption == true) {
61368            var value = show == true ? "block" : "none";
61369            jQuery("#expand_range_cb" + _instanceId).css("display", value);
61370        }
61371    }
61372
61373    /**
61374     * select/unselect expand range checkbox
61375     * This assumes the checkbox already exists.
61376     * @param select true = select, false = unselect
61377     */
61378    this.selectExpandRangeCB = function(select) {
61379        if (_expandRangeOption == true) {
61380            var state = select == true ? true : false;
61381            jQuery("#expand_range_cb" + _instanceId).attribute("checked", state);
61382        }
61383    }
61384
61385    /**
61386     * Show/hide channel toggle checkboxes for RGB sliders
61387     * This assumes the checkboxes already exist.
61388     *
61389     * @param show true = show, false= hide
61390     */
61391    this.showChannelToggleCB = function(show) {
61392        if (_channelToggleOption == true) {
61393            var value = show == true ? "block" : "none";
61394            jQuery("#red_cb" + _instanceId).css("display", value);
61395            jQuery("#blue_cb" + _instanceId).css("display", value);
61396            jQuery("#green_cb" + _instanceId).css("display", value);
61397        }
61398    }
61399
61400    /**
61401     * select/unselect channel toggle checkboxes
61402     * This assumes the checkboxes already exist.
61403     * @param selectStates hash {red:state, green:state, blue:state} where state is boolean; true = select, false = unselect
61404     *                        You can leave out any key:value pairs that you do not wish to change.
61405     */
61406    this.selectChannelToggleCB = function(selectStates) {
61407        var redState, greenState, blueState;
61408
61409        if (_channelToggleOption == true) {
61410            redState = selectStates["red"];
61411            greenState = selectStates["green"];
61412            blueState = selectStates["blue"];
61413
61414            if (redState !== undefined)
61415                jQuery("#red_cb" + _instanceId).attribute("checked", redState);
61416            if (greenState !== undefined)
61417                jQuery("#green_cb" + _instanceId).attribute("checked", greenState);
61418            if (blueState !== undefined)
61419                jQuery("#blue_cb" + _instanceId).attribute("checked", blueState);
61420        }
61421    }
61422
61423    /**
61424     * Get current slider(s) values.
61425     * This function will return 0 for sliders that have channel toggle buttons
61426     * that are in the 'off' or unselected state.
61427     * Note: if using ranged_rgb, you may need to call 'isExpandRange' to determine if the
61428     * values are for the normal vs. expanded range.
61429     *
61430     * If 'algorithm' option is being used for ranged_rgb, convert the slider
61431     * value to filter (display) values.
61432     *
61433     * @param raw optional arg, if true then return raw slider values, disregarding current setting of channel toggle checkbox.
61434     *            Default is false.
61435     * @return hash; values depend on sliderType<br/>
61436     *         contrast - number<br/>
61437     *         rgb - {r:number, g:number, b:number}
61438     *         ranged_rgb - {r:[lo,hi], g:[lo,hi], b:[lo,hi]}
61439     */
61440    this.getSliderValues = function(raw) {
61441        if (raw == undefined)
61442            raw = false;
61443
61444        var r, g, b;
61445        var sliderValues;
61446
61447        if (_type == 'contrast') {
61448            sliderValues = jQuery("#contrast_slider" + _instanceId).slider("option", "value");
61449        }
61450        else if (_type == 'rgb') {
61451            sliderValues = {};
61452            r = jQuery("#red_slider" + _instanceId).slider("option", "value");
61453            g = jQuery("#green_slider" + _instanceId).slider("option", "value");
61454            b = jQuery("#blue_slider" + _instanceId).slider("option", "value");
61455
61456            // zero out slider values if channel is turned off
61457            if (raw == false) {
61458                if (_red_on == false) r = 0;
61459                if (_green_on == false) g = 0;
61460                if (_blue_on == false) b = 0;
61461            }
61462
61463            sliderValues.r = r;
61464            sliderValues.g = g;
61465            sliderValues.b = b;
61466        }
61467        else if (_type == 'ranged_rgb') {
61468            var lo, hi;
61469            var usingExpandedRange = false;
61470
61471            if (_expandRangeOption == true) {
61472                usingExpandedRange = jQuery("#expand_range_cb" + _instanceId).attr("checked") !== undefined ? true : false;
61473            }
61474            sliderValues = {};
61475
61476            if (_algorithmOption == true) {
61477                var lo, hi;
61478                var tLo, tHi; // 't' = 'transformed'
61479
61480                lo = jQuery("#red_slider" + _instanceId).slider("values", 0);
61481                hi = jQuery("#red_slider" + _instanceId).slider("values", 1);
61482                tLo = Math.round(_options.algorithm.inverseTransform(lo));
61483                tHi = Math.round(_options.algorithm.inverseTransform(hi));
61484                sliderValues.r = [tLo, tHi];
61485
61486                lo = jQuery("#green_slider" + _instanceId).slider("values", 0);
61487                hi = jQuery("#green_slider" + _instanceId).slider("values", 1);
61488                tLo = Math.round(_options.algorithm.inverseTransform(lo));
61489                tHi = Math.round(_options.algorithm.inverseTransform(hi));
61490                sliderValues.g = [tLo, tHi];
61491
61492                lo = jQuery("#blue_slider" + _instanceId).slider("values", 0);
61493                hi = jQuery("#blue_slider" + _instanceId).slider("values", 1);
61494                tLo = Math.round(_options.algorithm.inverseTransform(lo));
61495                tHi = Math.round(_options.algorithm.inverseTransform(hi));
61496                sliderValues.b = [tLo, tHi];
61497
61498            }
61499            else {
61500                lo = jQuery("#red_slider" + _instanceId).slider("values", 0);
61501                hi = jQuery("#red_slider" + _instanceId).slider("values", 1);
61502                sliderValues.r = [lo, hi];
61503
61504                lo = jQuery("#green_slider" + _instanceId).slider("values", 0);
61505                hi = jQuery("#green_slider" + _instanceId).slider("values", 1);
61506                sliderValues.g = [lo, hi];
61507
61508                lo = jQuery("#blue_slider" + _instanceId).slider("values", 0);
61509                hi = jQuery("#blue_slider" + _instanceId).slider("values", 1);
61510                sliderValues.b = [lo, hi];
61511            }
61512
61513            // zero out any slider values if their respective channel is turned off
61514            if (raw == false) {
61515
61516                if (_red_on == false)
61517                    sliderValues.r = [0, 0];
61518
61519                if (_green_on == false)
61520                    sliderValues.g = [0, 0];
61521
61522                if (_blue_on == false)
61523                    sliderValues.b = [0, 0];
61524            }
61525        }
61526        return sliderValues;
61527    }
61528
61529    /**
61530     * Set the slider handle values and ranges.
61531     * You can specify values, ranges, or both.
61532     *
61533     * @param sliderType - 'contrast', 'rgb', or 'ranged_rgb'<br/>
61534     * @param values hash of slider handle values<br/>
61535     *          'contrast' - {values:number}<br/>
61536     *          'rgb' - {values:{r:number, g:number, b:number}}
61537     *          'ranged_rgb' - {values:{r:[lo,hi], g:[lo,hi], b:[lo,hi]}}
61538     * @param ranges hash of slider min/max ranges
61539     *          'contrast' - {ranges: [min, max]}<br/>
61540     *          'rgb' - {ranges: {r:[min, max], g:[min, max], b:[min, max]}
61541     *          'ranged_rgb' - same as 'rgb'
61542     */
61543    this.setSliderValues = function(sliderType, values, ranges) {
61544        if (ranges !== undefined) {
61545            // set ranges
61546            if (sliderType == 'contrast') {
61547                jQuery("#contrast_slider" + _instanceId).slider("option", "min", ranges[0]);
61548                jQuery("#contrast_slider" + _instanceId).slider("option", "max", ranges[1]);
61549            }
61550            else if (sliderType == 'rgb') {
61551                jQuery("#red_slider" + _instanceId).slider("option", "min", ranges.r[0]);
61552                jQuery("#red_slider" + _instanceId).slider("option", "max", ranges.r[1]);
61553                jQuery("#green_slider" + _instanceId).slider("option", "min", ranges.r[0]);
61554                jQuery("#green_slider" + _instanceId).slider("option", "max", ranges.r[1]);
61555                jQuery("#blue_slider" + _instanceId).slider("option", "min", ranges.r[0]);
61556                jQuery("#blue_slider" + _instanceId).slider("option", "max", ranges.r[1]);
61557            }
61558            else if (sliderType == 'ranged_rgb') {
61559                jQuery("#red_slider" + _instanceId).slider("option", "min", ranges.r[0]);
61560                jQuery("#red_slider" + _instanceId).slider("option", "max", ranges.r[1]);
61561                jQuery("#green_slider" + _instanceId).slider("option", "min", ranges.r[0]);
61562                jQuery("#green_slider" + _instanceId).slider("option", "max", ranges.r[1]);
61563                jQuery("#blue_slider" + _instanceId).slider("option", "min", ranges.r[0]);
61564                jQuery("#blue_slider" + _instanceId).slider("option", "max", ranges.r[1]);
61565            }
61566        }
61567
61568        if (values !== undefined ) {
61569            // set values
61570            if (sliderType == 'contrast') {
61571                jQuery("#contrast_slider" + _instanceId).slider("option", "value", values);
61572            }
61573            else if (sliderType == 'rgb') {
61574                jQuery("#red_slider" + _instanceId).slider("option", "value", values.r);
61575                jQuery("#green_slider" + _instanceId).slider("option", "value", values.g);
61576                jQuery("#blue_slider" + _instanceId).slider("option", "value", values.b);
61577            }
61578            else if (sliderType == 'ranged_rgb') {
61579                // value in this case is a two element array, instead of a single value like it is in 'rgb'
61580                jQuery("#red_slider" + _instanceId).slider("option", "values", values.r);
61581                jQuery("#green_slider" + _instanceId).slider("option", "values", values.g);
61582                jQuery("#blue_slider" + _instanceId).slider("option", "values", values.b);
61583            }
61584        }
61585    }
61586
61587    /**
61588     * Update the slider labels that display the numeric slider values
61589     * This is usually called in response to a slider change event.
61590     *
61591     */
61592    this.updateSliderLabels = function() {
61593        var contrast;
61594        var r,g,b;
61595        var rlo,rhi,glo,ghi,blo,bhi;
61596
61597        if (_type == 'contrast') {
61598            values = jQuery("#contrast_slider" + _instanceId).slider("option", "value");
61599            jQuery("#contrast_label" + _instanceId).val(values);
61600        }
61601        else if (_type == 'rgb') {
61602            r = jQuery("#red_slider" + _instanceId).slider("option", "value");
61603            g = jQuery("#green_slider" + _instanceId).slider("option", "value");
61604            b = jQuery("#blue_slider" + _instanceId).slider("option", "value");
61605
61606            jQuery("#red_label" + _instanceId).val(r);
61607            jQuery("#green_label" + _instanceId).val(g);
61608            jQuery("#blue_label" + _instanceId).val(b);
61609        }
61610        else if (_type == 'ranged_rgb') {
61611            rlo = jQuery("#red_slider" + _instanceId).slider("values", 0);
61612            rhi = jQuery("#red_slider" + _instanceId).slider("values", 1);
61613            glo = jQuery("#green_slider" + _instanceId).slider("values", 0);
61614            ghi = jQuery("#green_slider" + _instanceId).slider("values", 1);
61615            blo = jQuery("#blue_slider" + _instanceId).slider("values", 0);
61616            bhi = jQuery("#blue_slider" + _instanceId).slider("values", 1);
61617
61618            if (_algorithmOption == true) {
61619                // inverse transform slider values to display values (which are also the actual filter values)
61620                var lo, hi;
61621                lo = Math.round(_options.algorithm.inverseTransform(rlo));
61622                hi = Math.round(_options.algorithm.inverseTransform(rhi));
61623                jQuery("#red_label" + _instanceId).val(lo + " - " + hi);
61624
61625                lo = Math.round(_options.algorithm.inverseTransform(glo));
61626                hi = Math.round(_options.algorithm.inverseTransform(ghi));
61627                jQuery("#green_label" + _instanceId).val(lo + " - " + hi);
61628
61629                lo = Math.round(_options.algorithm.inverseTransform(blo));
61630                hi = Math.round(_options.algorithm.inverseTransform(bhi));
61631                jQuery("#blue_label" + _instanceId).val(lo + " - " + hi);
61632            }
61633            else {
61634                jQuery("#red_label" + _instanceId).val(rlo + " - " + rhi);
61635                jQuery("#green_label" + _instanceId).val(glo + " - " + ghi);
61636                jQuery("#blue_label" + _instanceId).val(blo + " - " + bhi);
61637            }
61638        }
61639
61640    }
61641
61642    /* ------------------------ private functions ---------------------------- */
61643
61644    function parseOptions(options) {
61645        if (!options) {
61646            return;
61647        }
61648
61649        if (options["channel_toggle"] !== undefined) {
61650            _channelToggleOption = true;
61651        }
61652        if (options["expand_range"] !== undefined) {
61653            _expandRangeOption = true;
61654        }
61655        if (options["algorithm"] !== undefined) {
61656            _algorithmOption = true;
61657        }
61658        if (options["slider_labels"] !== undefined) {
61659            _redLabel = options["slider_labels"].r ? options["slider_labels"].r : _redLabel;
61660            _greenLabel = options["slider_labels"].g ? options["slider_labels"].g : _greenLabel;
61661            _blueLabel = options["slider_labels"].b ? options["slider_labels"].b : _blueLabel;
61662            _contrastLabel = options["slider_labels"].contrast ? options["slider_labels"].contrast : _contrastLabel;
61663        }
61664        else if (options.options){
61665            _contrastLabel = options.options["filter_label"].contrast ? options.options["filter_label"].contrast : _contrastLabel;
61666        }
61667    }
61668
61669    function buildHtml(sliderType) {
61670        if (sliderType == "contrast")
61671            buildContrastHtml();
61672        else if (sliderType == "rgb" || sliderType == "ranged_rgb")
61673            buildRgbHtml(sliderType)
61674    }
61675
61676    /**
61677     * Build HTML for Contrast slider and insert into DOM
61678     * uses _instanceId, _container
61679     */
61680    function buildContrastHtml() {
61681        var html = "";
61682        var inputClass = "slider_input_class";
61683        setColorDescriptions();
61684
61685        html += '<table class="slider_table_class">';
61686        html += '<tbody>';
61687        html += '<tr colspan="2">';
61688        html += '  <td class="slider_cb_class"></td>';
61689        html += '  <td><label for="contrast_label' + _instanceId + '">' + _contrastLabel + '  ' + '</label><input type="text" readonly="true" class="' + inputClass + '" id="contrast_label' + _instanceId + '"/></td>';
61690        html += '  <td><label for="contrast_label_title' + _instanceId + '">' + _contrastDescription + '</label></td>';
61691        html += '</tr>';
61692        html += '<tr>';
61693        if (_channelToggleOption) {
61694            html += '  <td><input id="contrast_cb' + _instanceId + '" class="slider_cb" type="checkbox" title="contrast on/off"/></td>';
61695        }
61696        else {
61697            html += '  <td></td>';
61698        }
61699        html += '  <td colspan="2"><div id="contrast_slider' + _instanceId + '"' + ' class="contrast_slider_class"' + '></div></td>'; // jQuery UI inserts slider HTML into this container
61700        html += '</tr>';
61701        html += '</tbody>';
61702        html += '</table>';
61703        html += '<div style="height: 40px;"><input style="top: 20px;"  id="reset_button" type="button" class="reset_button_class" value="Reset" title="Reset color filters"></div>';
61704
61705        jQuery(_container).prepend(html);
61706    }
61707
61708    /**
61709     * Build HTML for RGB or Ranged RGB sliders and insert into DOM
61710     * This function also adds the HTML for any optional buttons/checkboxes.
61711     * If there are any button/checkbox options built, they are not set to any particular states.
61712     * uses _instanceId, _container, _channelToggleOption, _expandRangeOption (only appropriate for ranged_rgb)
61713     *
61714     * Because of cross-browser background gradient problems, RGB vs. Ranged RGB sliders must be assigned
61715     * different classes so that the appropriate CSS can be applied to the slider backgrounds.
61716     */
61717    function buildRgbHtml(sliderType) {
61718        var redClass, greenClass, blueClass, inputClass;
61719        var html = "";
61720
61721        inputClass = "slider_input_class";
61722
61723        setColorDescriptions();
61724
61725        if (sliderType == "rgb") {
61726            redClass = "red_slider_class";
61727            greenClass = "green_slider_class";
61728            blueClass = "blue_slider_class";
61729        }
61730        else if (sliderType == "ranged_rgb") {
61731            redClass = "ranged_red_slider_class";
61732            greenClass = "ranged_green_slider_class";
61733            blueClass = "ranged_blue_slider_class";
61734        }
61735
61736        html += '<table class="slider_table_class">';
61737        html += '<tbody>';
61738        html += '<tr>';
61739        html += '  <td class="slider_cb_class"></td>';
61740        html += '  <td><label for="red_label' + _instanceId + '">' + _redLabel + '</label><input type="text" readonly="true" class="' + inputClass + '" id="red_label' + _instanceId + '"/></td>';
61741        html += '  <td><label for="red_label_title' + _instanceId + '">' + _redDescription + '</label></td>';
61742        html += '</tr>';
61743        html += '<tr>';
61744        if (_channelToggleOption) {
61745            html += '  <td><input id="red_cb' + _instanceId + '" class="slider_cb" type="checkbox" title="red channel on/off"/></td>';
61746        }
61747        else {
61748            html += '  <td></td>';
61749        }
61750        html += '  <td colspan="2"><div id="red_slider' + _instanceId + '" class="' + redClass + '"></div></td>'; // jQuery UI inserts slider HTML into this container
61751        html += '</tr>';
61752        html += '<tr>';
61753        html += '  <td></td>';
61754        html += '  <td><label for="green_label' + _instanceId + '">' + _greenLabel + '</label><input type="text" readonly="true" class="' + inputClass + '" id="green_label' + _instanceId + '"/></td>';
61755        html += '  <td><label for="green_label_title' + _instanceId + '">' + _greenDescription + '</label></td>';
61756        html += '</tr>';
61757        html += '<tr>';
61758        if (_channelToggleOption) {
61759            html += '  <td><input id="green_cb' + _instanceId + '" class="slider_cb" type="checkbox" title="green channel on/off"></td>';
61760        }
61761        else {
61762            html += '  <td></td>';
61763        }
61764        html += '  <td colspan="2"><div id="green_slider' + _instanceId + '" class="' + greenClass + '"></div></td>'; // jQuery UI inserts slider HTML into this container
61765        html += '</tr>';
61766        html += '<tr>';
61767        html += '  <td></td>';
61768        html += '  <td><label for="blue_label' + _instanceId + '">' + _blueLabel + '</label><input type="text" readonly="true" class="' + inputClass + '" id="blue_label' + _instanceId + '"/></td>';
61769        html += '  <td><label for="blue_label_title' + _instanceId + '">' + _blueDescription + '</label></td>';
61770        html += '</tr>';
61771        html += '<tr>';
61772        if (_channelToggleOption) {
61773            html += '  <td><input id="blue_cb' + _instanceId + '" class="slider_cb" type="checkbox" title="blue channel on/off"/></td>';
61774        }
61775        else {
61776            html += '  <td></td>';
61777        }
61778        html += '  <td colspan="2"><div id="blue_slider' + _instanceId + '" class="' + blueClass + '"></div></td>'; // jQuery UI inserts slider HTML into this container
61779        html += '</tr>';
61780        html += '<tr>';
61781        html += '</tr>';
61782        if (_expandRangeOption) {
61783          html += '<tr>';
61784          html += '  <td colspan="2"><hr></td>';
61785          html += '</tr>';
61786          html += '<tr>';
61787          html += '  <td></td>';
61788          html += '  <td><input id="expand_range_cb' + _instanceId + '" class="slider_cb" type="checkbox"/>' + _expandRangeLabel + '</td>';
61789          html += '</tr>';
61790        }
61791        html += '</tbody>';
61792        html += '</table>';
61793        html += '<div style="height: 40px;"><input id="reset_button" type="button" class="reset_button_class" value="Reset" title="Reset color filters"></div>';
61794
61795        jQuery(_container).prepend(html);
61796    }
61797
61798    function setColorDescriptions( ){
61799
61800        if(_imageType === "NISSL"){
61801            _contrastDescription = _options["slider_labels"].cNI;
61802        }
61803        else if(_imageType === "ACHE"){
61804            _contrastDescription = _options["slider_labels"].cAC;
61805        }
61806        else if(_imageType === "CYOX"){
61807            _contrastDescription = _options["slider_labels"].cCY;
61808        }
61809        else if(_imageType === 'BDA_AAV'){
61810            _redDescription = _options["slider_labels"].rBDA_AAV;
61811            _greenDescription = _options["slider_labels"].gBDA_AAV;
61812            _blueDescription = _options["slider_labels"].bBDA_AAV;
61813        }
61814        else if(_imageType === 'SMI_32'){
61815            _redDescription = _options["slider_labels"].rSMI_32;
61816            _greenDescription = _options["slider_labels"].gSMI_32;
61817            _blueDescription = _options["slider_labels"].bSMI_32;
61818        }
61819        else if(_imageType === 'TWO_PHOTON'){
61820            _redDescription = _options["slider_labels"].rPH;
61821            _greenDescription = _options["slider_labels"].gPH;
61822            _blueDescription = _options["slider_labels"].bPH;
61823        }
61824        else if(_imageType === 'PVALB'){
61825            _redDescription = _options["slider_labels"].rPV;
61826            _greenDescription = _options["slider_labels"].gPV;
61827            _blueDescription = _options["slider_labels"].bPV;
61828        }
61829        else if(_imageType === 'CALB1'){
61830            _redDescription = _options["slider_labels"].rCA;
61831            _greenDescription = _options["slider_labels"].gCA;
61832            _blueDescription = _options["slider_labels"].bCA;
61833        }
61834        else if(_imageType === 'NEUN'){
61835            _redDescription = _options["slider_labels"].rNE;
61836            _greenDescription = _options["slider_labels"].gNE;
61837            _blueDescription = _options["slider_labels"].bNE;
61838        }
61839    }
61840
61841    /**
61842     * Build sliders and insert into DOM
61843     *
61844     * @param sliderType
61845     * @param listener
61846     */
61847    function buildSliders(sliderType, listener) {
61848        if (sliderType == "contrast")
61849            buildContrastSlider(sliderType, listener);
61850        else if (sliderType == "rgb")
61851            buildRgbSliders(sliderType, listener);
61852        else if (sliderType == "ranged_rgb")
61853            buildRangedRgbSliders(sliderType, listener);
61854    }
61855
61856    /**
61857     * Build the contrast slider and sets its label value
61858     * Before calling this function, you need to call buildContrastHtml
61859     * @param sliderType
61860     * @param listener
61861     */
61862    function buildContrastSlider(sliderType, listener) {
61863        var values = getInitialValues(sliderType);
61864        var ranges = getInitialRanges(sliderType);
61865
61866        jQuery("#contrast_label" + _instanceId).val(values["values"]);
61867        jQuery("#contrast_slider" + _instanceId).slider({range: false, min: ranges[0], max: ranges[1], value: values,
61868            change: listener, slide: listener});
61869    }
61870
61871    /**
61872     * Build the RGB sliders and sets their label values.
61873     *
61874     * Before calling this function, you need to call buildRgbHtml
61875     * @param sliderType determines whether to build regular RGB sliders, or ranged RGB sliders
61876     * @param listener callback for slider 'change' events
61877     */
61878    function buildRgbSliders(sliderType, listener) {
61879        var rVals, gVals, bVals;
61880        var rRanges, gRanges, bRanges;
61881
61882        var values = getInitialValues(sliderType);
61883        var ranges = getInitialRanges(sliderType);
61884
61885        rVals = values.r;
61886        gVals = values.g;
61887        bVals = values.b;
61888
61889        rRanges = ranges.r;
61890        gRanges = ranges.g;
61891        bRanges = ranges.b;
61892
61893        jQuery("#red_label" + _instanceId).val(rVals);
61894        jQuery("#red_slider" + _instanceId).slider({range: false, min: rRanges[0], max: rRanges[1],
61895            value: rVals, change: listener, slide: listener});
61896
61897        jQuery("#green_label" + _instanceId).val(gVals);
61898        jQuery("#green_slider" + _instanceId).slider({range: false, min: gRanges[0], max: gRanges[1],
61899            value: gVals, change: listener, slide: listener});
61900
61901        jQuery("#blue_label" + _instanceId).val(bVals);
61902        jQuery("#blue_slider" + _instanceId).slider({range: false, min: bRanges[0], max: bRanges[1],
61903            value: bVals, change: listener, slide: listener});
61904    }
61905
61906    /**
61907     * Build the Ranged RGB sliders and sets their label values.
61908     *
61909     * Before calling this function, you need to call buildRgbHtml
61910     * @param sliderType determines whether to build regular RGB sliders, or ranged RGB sliders
61911     * @param listener callback for slider 'change' events
61912     */
61913    function buildRangedRgbSliders(sliderType, listener) {
61914        var rVals, gVals, bVals;
61915        var rRanges, gRanges, bRanges;
61916
61917        var values = getInitialValues(sliderType);
61918        var ranges = getInitialRanges(sliderType);
61919
61920        rVals = values.r;
61921        gVals = values.g;
61922        bVals = values.b;
61923
61924        rRanges = ranges.r;
61925        gRanges = ranges.g;
61926        bRanges = ranges.b;
61927
61928        jQuery("#red_label" + _instanceId).val(rVals[0] + " - " + rVals[1]);
61929        jQuery("#red_slider" + _instanceId).slider({range: true, min: rRanges[0], max: rRanges[1],
61930            values: rVals, change: listener, slide: listener});
61931
61932        jQuery("#green_label" + _instanceId).val(gVals[0] + " - " + gVals[1]);
61933        jQuery("#green_slider" + _instanceId).slider({range: true, min: gRanges[0], max: gRanges[1],
61934            values: gVals, change: listener, slide: listener});
61935
61936        jQuery("#blue_label" + _instanceId).val(bVals[0] + " - " + bVals[1]);
61937        jQuery("#blue_slider" + _instanceId).slider({range: true, min: bRanges[0], max: bRanges[1],
61938            values: bVals, change: listener, slide: listener});
61939    }
61940
61941    /**
61942     * Get the initial slider value(s).
61943     *
61944     * If the 'expanded range' option is enabled and selected (on) for ranged rgb, then
61945     * the sliders will have the expanded range values and ranges, instead of the normal values.
61946     * Uses _expandedRangeOption, _options
61947     *
61948     * @param sliderType
61949     * @return numeric values.<br/>
61950     *          'contrast' - {value:number}
61951     *          'rgb' - {r:number, g:number, b:number}<br/>
61952     *          'ranged_rgb' - {r:[lo,hi], g:[lo,hi], b:[lo, hi]}
61953     */
61954    function getInitialValues(sliderType) {
61955        var initialValues = {};
61956
61957        if (sliderType == "contrast") {
61958            initialValues = _values;
61959        }
61960        else if (sliderType == "rgb") {
61961            initialValues = _values;
61962        }
61963        else if (sliderType == "ranged_rgb") {
61964            if (_expandRangeOption == true && _options["expand_range"].selected === true) {
61965                // expanded range value
61966                initialValues = _options["expand_range"];
61967            }
61968            else if (_algorithmOption == true) {
61969                // transform the values
61970                var lo, hi;
61971                jQuery.extend(true, initialValues, _values);
61972                lo = initialValues.r[0];
61973                hi = initialValues.r[1];
61974                initialValues.r[0] = _options.algorithm.transform(lo);
61975                initialValues.r[1] = _options.algorithm.transform(hi);
61976
61977                lo = initialValues.g[0];
61978                hi = initialValues.g[1];
61979                initialValues.g[0] = _options.algorithm.transform(lo);
61980                initialValues.g[1] = _options.algorithm.transform(hi);
61981
61982                lo = initialValues.b[0];
61983                hi = initialValues.b[1];
61984                initialValues.b[0] = _options.algorithm.transform(lo);
61985                initialValues.b[1] = _options.algorithm.transform(hi);
61986            }
61987            else {
61988                // normal value
61989                initialValues = _values;
61990            }
61991        }
61992        return initialValues;
61993    }
61994
61995    /*
61996     * Get current slider values.
61997     * This function returns actual slider values, ignoring any effect due to channel toggle checkboxes.
61998     */
61999    this.getCurrentValues = function() {
62000        var raw = true;
62001        var vals = _self.getSliderValues(raw);
62002
62003        return vals;
62004    }
62005
62006    /**
62007     * Get initial min/max range values for each slider.
62008     *
62009     * If the 'expanded range' option is enabled and selected (on) for ranged rgb, then set
62010     * up the sliders using the expanded range values and ranges, instead of the normal values.
62011     *
62012     * If the 'algorithm' option is enabled, transform the lo, hi range values.
62013     *
62014     * Uses _ranges, _expandedRangeOption, _options, _algorithmOption
62015     *
62016     * @param sliderType
62017     * @return Array of range values.<br/>
62018     *         'contrast' - [min, max]<br/>
62019     *         'rgb' - {r:[min, max], g:[min, max], b:[min, max]}
62020     *         'ranged_rgb' = same as 'rgb'
62021     */
62022    function getInitialRanges(sliderType) {
62023        var initialRanges = {};
62024        if (sliderType == "contrast") {
62025            initialRanges = _ranges;
62026        }
62027        else if (sliderType == "rgb") {
62028            initialRanges = _ranges;
62029        }
62030        else if (sliderType == "ranged_rgb") {
62031            if (_options["expand_range"] !== undefined && _options["expand_range"].selected === true) {
62032                // expanded range
62033                initialRanges = _options["expand_range"]["ranges"];
62034            }
62035            else if (_algorithmOption == true) {
62036                var lo, hi;
62037                jQuery.extend(true, initialRanges, _ranges);
62038                lo = initialRanges.r[0];
62039                hi = initialRanges.r[1];
62040                initialRanges.r[0] = _options.algorithm.transform(lo);
62041                initialRanges.r[1] = _options.algorithm.transform(hi);
62042
62043                lo = initialRanges.g[0];
62044                hi = initialRanges.g[1];
62045                initialRanges.g[0] = _options.algorithm.transform(lo);
62046                initialRanges.g[1] = _options.algorithm.transform(hi);
62047
62048                lo = initialRanges.b[0];
62049                hi = initialRanges.b[1];
62050                initialRanges.b[0] = _options.algorithm.transform(lo);
62051                initialRanges.b[1] = _options.algorithm.transform(hi);
62052            }
62053            else {
62054                // normal range
62055                initialRanges = _ranges;
62056            }
62057        }
62058
62059        return initialRanges;
62060    }
62061
62062    /*
62063     * Get current slider ranges
62064     */
62065    this.getCurrentRanges = function() {
62066        var curRanges;
62067
62068        //TODO: couldn't figure out how to access current ranges settings, so for now just return the original range settings.
62069        //in most cases, the range setting won't be changed after initial setting.
62070        curRanges = _ranges;
62071
62072        return curRanges;
62073    }
62074
62075    /**
62076     * Expand the range of the ranged rgb sliders and update the handles.
62077     * It is important to update the handles in order to generate a 'change' event,
62078     * even if the handles retain the same value.
62079     * Uses _options
62080     */
62081    function expandRange() {
62082        var rVals, gVals, bVals;
62083        var rRanges, gRanges, bRanges;
62084
62085        var curValues = _self.getSliderValues();
62086
62087        rVals = curValues.r;
62088        gVals = curValues.g;
62089        bVals = curValues.b;
62090
62091        rRanges = _options["expand_range"]["ranges"].r;
62092        gRanges = _options["expand_range"]["ranges"].g;
62093        bRanges = _options["expand_range"]["ranges"].b;
62094
62095        // increase max range value of RGB sliders.
62096        jQuery("#red_slider" + _instanceId).slider("option", "min", rRanges[0]);
62097        jQuery("#red_slider" + _instanceId).slider("option", "max", rRanges[1]);
62098        jQuery("#red_slider" + _instanceId).slider("option", "values", rVals);
62099
62100        jQuery("#green_slider" + _instanceId).slider("option", "min", gRanges[0]);
62101        jQuery("#green_slider" + _instanceId).slider("option", "max", gRanges[1]);
62102        jQuery("#green_slider" + _instanceId).slider("option", "values", gVals);
62103
62104        jQuery("#blue_slider" + _instanceId).slider("option", "min", bRanges[0]);
62105        jQuery("#blue_slider" + _instanceId).slider("option", "max", bRanges[1]);
62106        jQuery("#blue_slider" + _instanceId).slider("option", "values", bVals);
62107    }
62108
62109    /**
62110     * Reset all sliders to their initial default values and ranges.
62111     * Also resets color checkbox.
62112     *
62113     * @param sliderType
62114     */
62115    function resetSliderDefaults(sliderType) {
62116
62117        if(sliderType === 'rgb' || sliderType === 'ranged_rgb' ){
62118            // reset checkbox
62119            _red_on = true;
62120            _blue_on = true;
62121            _green_on = true;
62122
62123            $j("#red_cb" + _instanceId).prop("checked", _red_on);
62124            $j("#green_cb" + _instanceId).prop("checked", _green_on);
62125            $j("#blue_cb" + _instanceId).prop("checked", _blue_on);
62126
62127            var initialVals =  {};
62128            jQuery.extend(true, initialVals, _defaultValues);
62129
62130            if( sliderType === 'rgb'){
62131                initialVals['rgb'].values.r = initialVals['rgb'].values.r * initialVals['rgb'].filter_to_slider;
62132                initialVals['rgb'].values.g = initialVals['rgb'].values.g * initialVals['rgb'].filter_to_slider;
62133                initialVals['rgb'].values.b = initialVals['rgb'].values.b * 
62133initialVals['rgb'].filter_to_slider;
62134                var ranges = getInitialRanges(sliderType);
62135                _self.setSliderValues(sliderType,initialVals['rgb'].values,ranges);
62136            }
62137
62138            else if(sliderType === 'ranged_rgb'){
62139                var ranges = getInitialRanges(sliderType);
62140                _self.setSliderValues(sliderType, initialVals, ranges);
62141            }
62142        }
62143
62144        else if(sliderType === 'contrast'){
62145            var values = (parseFloat(_defaultValues['contrast'].values) ) * _defaultValues['contrast'].filter_to_slider;
62146            var ranges = getInitialRanges(sliderType);
62147            _self.setSliderValues(sliderType, values, ranges);
62148        }
62149    }
62150
62151    /**
62152     * Initialize the previously-created optional UI widgets (buttons, checkboxes)
62153     * with default setting values, and attach change events to the listener fnc.
62154     * Uses _channelToggleOption, _expandRangeOption
62155     * Uses _options
62156     *
62157     * @param sliderType
62158     */
62159    function setOptionDefaults(sliderType) {
62160
62161        // options are applicable for color sliders
62162        if (sliderType == "rgb" || sliderType == "ranged_rgb") {
62163
62164            $j("#reset_button").bind("click",doReset);
62165
62166            if (_channelToggleOption == true) {
62167                // set local state vars and selection state of the channel toggle checkboxes
62168                _red_on = _options["channel_toggle"].selected.r;
62169                _green_on = _options["channel_toggle"].selected.g;
62170                _blue_on = _options["channel_toggle"].selected.b;
62171
62172                $j("#red_cb" + _instanceId).prop("checked", _red_on);
62173                $j("#green_cb" + _instanceId).prop("checked", _green_on);
62174                jQuery("#blue_cb" + _instanceId).prop("checked", _blue_on);
62175
62176                jQuery("#red_cb" + _instanceId).bind("change", doChannelToggle);
62177                jQuery("#green_cb" + _instanceId).bind("change", doChannelToggle);
62178                jQuery("#blue_cb" + _instanceId).bind("change", doChannelToggle);
62179            }
62180            if (_expandRangeOption == true) {
62181                // set selection state of the Expand Range checkbox, and if the checkbox is
62182                // initially selected, reconfigure the sliders to use the expanded range.
62183                var expand = _options["expand_range"].selected;
62184                jQuery("#expand_range_cb" + _instanceId).attr("checked", expand);
62185                jQuery("#expand_range_cb" + _instanceId).bind("change", doExpandRange);
62186
62187                if (expand == true) {
62188                    // prevent sliders from generating 'change' events while we expand their range.
62189                    // This is hacky, but effective
62190
62191                    jQuery("#red_slider" + _instanceId).slider({change: undefined});
62192                    jQuery("#green_slider" + _instanceId).slider({change: undefined});
62193                    jQuery("#blue_slider" + _instanceId).slider({change: undefined});
62194
62195                    expandRange();
62196
62197                    jQuery("#red_slider" + _instanceId).slider({change: doSliderChange});
62198                    jQuery("#green_slider" + _instanceId).slider({change: doSliderChange});
62199                    jQuery("#blue_slider" + _instanceId).slider({change: doSliderChange});
62200                }
62201            }
62202        }
62203
62204        // options for contrast
62205        if(sliderType == "contrast"){
62206            $j("#reset_button").bind("click",doReset);
62207        }
62208
62209    }
62210
62211    this.getCurrentOptions = function() {
62212        var curOptions = {};
62213
62214        if (_type == "rgb" || _type == "ranged_rgb") {
62215            jQuery.extend(curOptions, _options);
62216
62217            if (_channelToggleOption == true) {
62218                // channel toggle checkboxes may have been selected/unselected
62219                curOptions["channel_toggle"].selected.r = _red_on;
62220                curOptions["channel_toggle"].selected.g = _green_on;
62221                curOptions["channel_toggle"].selected.b = _blue_on;
62222            }
62223
62224            if (_algorithmOption == true) {
62225                // TODO: temporary placeholder logic, assumes application.json sets to ""
62226                curOptions["algorithm"] = "";
62227            }
62228        }
62229
62230        else if(_type == "contrast"){
62231            // need to call this in order to keep the color guide legend in the _options hash
62232            jQuery.extend(curOptions, _options);
62233        }
62234
62235        return curOptions;
62236    }
62237
62238    /* -------------------- listener/event handlers -------------------------------- */
62239
62240    /**
62241     * Intercept slider change events so that the slider labels can be updated automatically.
62242     * After label update, the user's listener function is called to allow normal event processing.
62243     *
62244     * @param event - sets a property "sliderType" on this object to identify the slider 'flavor'
62245     * @param ui
62246     */
62247    function doSliderChange(event, ui) {
62248
62249        _self.updateSliderLabels();
62250
62251        event.sliderType = _type; // identifies the sliderWidget type
62252
62253        if(event.type == "slide"){
62254            // don't do anything in this case (just the updateSliderLabel action already performed above)
62255        }
62256        else if(event.type == "reset"){
62257            return _listener(event, ui);
62258        }
62259        else
62260            _listener(event, ui);
62261    }
62262
62263    /**
62264     * Listener function for the optional channel toggle checkboxes. This is only used for c
62264olor channel sliders (RGB)
62265     * When selected, color channel is on and operates normally.
62266     * When unselected, color channel value will be interpreted as value = 0,
62267     * althought the slider positions will not be affected.
62268     * This logic is implemented in the 'getSliderValues' function.
62269     * Will generate a 'change' event so that the listener can redraw the image.
62270     *
62271     */
62272    function doChannelToggle() {
62273        if (_type == "rgb" || _type == "ranged_rgb") {
62274
62275            _red_on = jQuery("#red_cb" + _instanceId).attr("checked") !== undefined ? true : false;
62276            _green_on = jQuery("#green_cb" + _instanceId).attr("checked") !== undefined ? true : false;
62277            _blue_on = jQuery("#blue_cb" + _instanceId).attr("checked") !== undefined ? true : false;
62278        }
62279
62280        var e = jQuery.Event("change");
62281        // we don't have a jQuery ui object, so just pass in an arbitrary object.
62282        doSliderChange(e, new Object());
62283    }
62284
62285    function doReset() {
62286        var e = jQuery.Event("reset");
62287        var defVals = doSliderChange(e, new Object());
62288        _defaultValues = defVals;
62289        resetSliderDefaults(_type);
62290    }
62291
62292    /**
62293     * Listener function for the optional Expand Range checkbox. This is only used for ranged_rgb sliders.
62294     * When selected, the range is expanded. When unselected, the range is returned to normal
62295     * range. If the handle values exceed the upper bounds of the normal range, then are set
62296     * to the upper bounds value of the normal range.
62297     * TODO: ensure that lower bounds are set correctly; cannot assume that they are fixed at zero!
62298     *
62299     * The slider handle changes will autogenerate a 'change' event
62300     */
62301    function doExpandRange() {
62302        var curValues;
62303
62304        // expand the range slider's range if the checkbox is selected
62305        var expandChecked = this.checked; // 'this' is the event target, the checkbox
62306        if (expandChecked == true) {
62307            expandRange();
62308        }
62309        else {
62310            // contract the range slider's expanded range down to to the normal range if the checkbox is unselected,
62311            // and update the lo/hi slider handle values because there is a good chance that they are out-of-range.
62312            // Note lower bound of range is the same for normal, expanded range.
62313            var handle_lo, handle_hi;
62314            var lowerBound, upperBound;
62315
62316            curValues = jQuery("#red_slider" + _instanceId).slider("option", "values");
62317            handle_lo = curValues[0];
62318            handle_hi = curValues[1];
62319            lowerBound = _ranges.r[0];
62320            upperBound = _ranges.r[1];
62321            handle_lo = handle_lo > upperBound ? upperBound : handle_lo;
62322            handle_hi = handle_hi > upperBound ? upperBound : handle_hi;
62323            jQuery("#red_slider" + _instanceId).slider("option", "min", lowerBound);
62324            jQuery("#red_slider" + _instanceId).slider("option", "max", upperBound);
62325            curValues[0] = handle_lo;
62326            curValues[1] = handle_hi;
62327            jQuery("#red_slider" + _instanceId).slider("option", "values", curValues);
62328
62329            curValues = jQuery("#green_slider" + _instanceId).slider("option", "values");
62330            handle_lo = curValues[0];
62331            handle_hi = curValues[1];
62332            handle_lo = handle_lo > upperBound ? upperBound : handle_lo;
62333            handle_hi = handle_hi > upperBound ? upperBound : handle_hi;
62334            jQuery("#green_slider" + _instanceId).slider("option", "min", lowerBound);
62335            jQuery("#green_slider" + _instanceId).slider("option", "max", upperBound);
62336            curValues[0] = handle_lo;
62337            curValues[1] = handle_hi;
62338            jQuery("#green_slider" + _instanceId).slider("option", "values", curValues);
62339
62340            curValues = jQuery("#blue_slider" + _instanceId).slider("option", "values");
62341            handle_lo = curValues[0];
62342            handle_hi = curValues[1];
62343            handle_lo = handle_lo > upperBound ? upperBound : handle_lo;
62344            handle_hi = handle_hi > upperBound ? upperBound : handle_hi;
62345            jQuery("#blue_slider" + _instanceId).slider("option", "min", lowerBound);
62346            jQuery("#blue_slider" + _instanceId).slider("option", "max", upperBound);
62347            curValues[0] = handle_lo;
62348            curValues[1] = handle_hi;
62349            jQuery("#blue_slider" + _instanceId).slider("option", "values", curValues);
62350        }
62351    }
62352
62353    /* ------------------------ initialize the function ---------------------- */
62354
62355    function init() {
62356        // ensure all required input args are present and valid
62357        // TODO: this needs to be completed
62358        if (jQuery.inArray(_type, _sliderTypes) == -1) {
62359            return false;
62360        }
62361
62362        parseOptions(_options);
62363        buildHtml(_type);
62364        buildSliders(_type, doSliderChange);
62365        setOptionDefaults(_type);
62366        _self.updateSliderLabels();
62367    }
62368
62369    init();
62370}
62371;
62372"use strict";
62373function busy_panel(busy_container) {
62374
62375    this.container = busy_container;
62376
62377    this.show_busy = function(is_busy) {
62378
62379        do_show_busy(is_busy, true);
62380    };
62381
62382    this.show_disabled = function(is_disabled) {
62383
62384        do_show_busy(is_disabled, false);
62385    };
62386
62387    var _self = this;
62388    var _events_setup = false;
62389    var _busy_indicator = {};
62390    var BUSY = true;
62391
62392
62393    function position_busy_indicator() {
62394
62395         if(!_busy_indicator.panel)
62396            setup_busy_indicator();
62397
62398         var pos = _self.container.offset();
62399         var container_width = _self.container.outerWidth();
62400         var container_height = _self.container.outerHeight();
62401
62402         _busy_indicator.panel.css({
62403
62404             position:'absolute',
62405	         background: "none",//"url('/assets/aiclient/ajax-loader.gif') no-repeat center center",
62406             top:pos.top,
62407             left:pos.left,
62408             'background-color':'#FFF',
62409             opacity:0.7,
62410             width:container_width,
62411             height:container_height
62412         });
62413    }
62414
62415    function setup_busy_indicator() {
62416
62417         var html = "<div id='busy_panel2'></div>";
62418         _busy_indicator.panel = $j(html);
62419         _busy_indicator.panel.appendTo(_self.container);
62420    }
62421
62422    function do_show_busy(busy, busy_or_disabled) {
62423
62424        if(!_busy_indicator.panel) {
62425            setup_busy_indicator();
62426        }
62427
62428        if(busy_or_disabled == BUSY) {
62429            _busy_indicator.panel.css({"background-image": "url('/assets/aiclient/ajax-loader.gif')"});
62430        }
62431        else {
62432            _busy_indicator.panel.css({"background-image": "0 !important", "background-color":"#fff"});
62433        }
62434
62435        if(busy) {
62436
62437            position_busy_indicator();
62438            _busy_indicator.panel.show();
62439        }
62440        else {
62441
62442            _busy_indicator.panel.hide();
62443        }
62444    }
62445
62446    function setup_events() {
62447
62448        if(_events_setup)
62449            return;
62450
62451        $j(window).bind('resize', function() { position_busy_indicator()});
62452        _events_setup = true;
62453    }
62454
62455    function init() {
62456
62457        position_busy_indicator();
62458        setup_events();
62459    }
62460
62461    init();
62462}
62463;
62464"use strict";
62465AIBS = AIBS || {};
62466AIBS.UI = AIBS.UI || {};
62467/**
62468 * @class
62469 *
62470 *
62471 *
62472 *
62473 * @param container          <pre>The ID or element that is the container for this component_container.  This component_container
62474 *                                will attempt to fill the given container.</pre>
62475 *
62476 * @param layout             <pre>A description of how the components contained in this component_container should be arranged.
62477 *                                This uses the same format as AIBS.UI.info_widget, see details on the layout parameter there.</pre>
62478 * @param components         <pre>A description and settings for each component, details TBD.</pre>
62479 *
62480 * @param options            <pre>A hash containing optional information.  Possible values are:
62481 *
62482 *                           on_ready:          A callback function to be called when the experiment have been fetched. This
62483 *                                              callback should accept a single argument, which is the experiment data.
62484 *
62485 *                           on_error:          A callback function to be called if an error occurs fetching data.
62486 *
62487 *                           set_component_properties: A callback invoked after the HTML layout has been created but before
62488 *                                              any components have been constructed.  The intended use is for setting run-time
62489 *                                              properties that aren't available as part of the component configuration that's
62490 *                                              passed in as part of the options.component_config hash.
62491 *
62492 *                           data_map:          A description of which fields in the data object should be passed to
62493 *                                              which component, details TBD.
62494 *
62495 *                           table_class: A class name to be added to the table.
62496 *
62497 *                           row_class: A class name to be added to each row.
62498 *
62499 *                           cell_class: A class name to be added to each cell.
62500 *
62501 *    The info_widget table is loaded with class names to facilitate styling.  Adding a table class is usually all
62502 *    that's necessary to target any cell in an info_widget table.
62503 *                           </pre>
62504 */
62505AIBS.UI.component_container = (function () {
62506
62507    return(
62508        function (container, options) {
62509
62510            this.container = $j(container);
62511            this.settings = {
62512                on_ready:null,
62513                on_error:null,
62514                set_component_properties:null,
62515                layout_config:{
62516                    layout:null,
62517                    data_map:[],
62518                    decorators:null,
62519                    table_class:null,
62520                    row_class:null,
62521                    cell_class:null
62522                },
62523                component_config:{}
62524            };
62525            $j.extend(true, this.settings, options);
62526
62527
62528            /**
62529             * The get_state method is called on all contained components.  They are aggregated
62530             * and returned as a hash, keyed by component name.
62531             * @return {*}
62532             */
62533            this.get_state = function () {
62534
62535                return(do_get_state());
62536            };
62537
62538            /**
62539             * The set_state method is called on all contained components.
62540             * @param state
62541             */
62542            this.set_state = function (state) {
62543
62544                do_set_state(state);
62545            };
62546
62547            /**
62548             * The reset method is called on all contained components.  Generally this means
62549             * that the component is reset to its default state.
62550             */
62551            this.clear = function () {
62552
62553                do_clear();
62554            };
62555
62556            /**
62557             * The enable method is called on all contained components
62558             *
62559             * @param enabled
62560             */
62561            this.enable = function(enabled) {
62562
62563                do_enable(enabled);
62564            };
62565
62566            /**
62567             * As in the CSS declarations for display:none & visible:hidden,
62568             * the display & visible methods on a component container (or component)
62569             * mean different things.  display(false) means the component should
62570             * not be included in layout.  visible(false) means that the component
62571             * still takes up space in the page layout, but is not rendered.
62572             *
62573             * @param displayed
62574             */
62575            this.display = function(displayed) {
62576
62577                do_display(displayed);
62578            };
62579
62580            /**
62581             * As in the CSS declarations for display:none & visible:hidden,
62582             * the display & visible methods on a component container (or component)
62583             * mean different things.  display(false) means the component should
62584             * not be included in layout.  visible(false) means that the component
62585             * still takes up space in the page layout, but is not rendered.
62586             *
62587             * @param vis
62588             */
62589            this.visible = function(vis) {
62590
62591                do_visible(vis);
62592            };
62593
62594            /**
62595             * The component container and all that it contains are removed from the DOM.
62596             *
62597             */
62598            this.destroy = function() {
62599
62600                do_destroy();
62601            };
62602
62603            /**
62604             *
62605             * @private
62606             */
62607            var _id = "_cc_" + Math.floor(Math.random() * 10001);
62608            var _self = this;
62609            var _state = null;
62610            var _the_view = null;
62611            var _layout_widget = null;
62612
62613
62614            function create_view() {
62615
62616                var settings = {
62617                    widget_config:_self.settings.component_config,
62618                    on_ready:_self.settings.on_ready,
62619                    delegates:{
62620                        layout_delegate: _self.settings.layout ? build_layout : null
62621                    }
62622                };
62623
62624                if(_self.settings.set_component_properties)
62625                    _self.settings.set_component_properties(_self.settings.component_config);
62626
62627                _the_view = new AIBS.Application.view(_self.container, settings);
62628            }
62629
62630            function build_layout(layout_container, component_config, view_id) {
62631
62632                var layout_options = {
62633                    table_class:_self.settings.layout_config.table_class,
62634                    row_class:_self.settings.layout_config.row_class,
62635                    cell_class:_self.settings.layout_config.cell_class
62636                };
62637
62638                _layout_widget = new AIBS.UI.info_widget(
62639                        layout_container,
62640                        _self.settings.layout_config.layout,
62641                        _self.settings.layout_config.data_map,
62642                        _self.settings.layout_config.decorators,
62643                        layout_options);
62644
62645                // each component_config must be given a container_id value that matches
62646                // the ID of the table cell in which it is supposed to appear.
62647                for(var name in component_config) {
62648
62649                    var component = component_config[name];
62650                    var cell_id = _id + view_id + component.column + "_" + component.row;
62651                    var cell = _layout_widget.get_cell(component.row, component.column);
62652                    $j(cell).attr('id', cell_id);
62653                    component.container_id = cell_id;
62654                }
62655            }
62656
62657            function do_enable(enabled) {
62658
62659                invoke_widget_method('enable', enabled);
62660            }
62661
62662            function do_destroy() {
62663
62664                _the_view.destroy();
62665                if(_layout_widget)
62666                    $j('#_iw_t_'+_layout_widget.id).remove();
62667            }
62668
62669            function do_clear() {
62670
62671                invoke_widget_method('clear');
62672            }
62673
62674            function do_visible(vis) {
62675
62676                $j('#_iw_t_'+_layout_widget.id).css({visibility:vis ? 'visible' : 'hidden'});
62677            }
62678
62679            function do_display(displayed) {
62680
62681                $j('#_iw_t_'+_layout_widget.id).css({display:displayed ? 'block' : 'none'});
62682            }
62683
62684            function do_set_state(new_state) {
62685
62686                _state = new_state;
62687                invoke_widget_method('set_state', _state);
62688            }
62689
62690            function do_get_state() {
62691
62692                if(!_the_view)
62693                    return({});
62694
62695                _state = {};
62696                var widgets = _the_view.get_widgets();
62697                for(var name in widgets) {
62698
62699                    var component = widgets[name].instance;
62700                    if(component && component.get_state)
62701                         _state[name] = component.get_state();
62702                }
62703
62704                return(_state);
62705            }
62706
62707            function invoke_widget_method(method_name, param) {
62708
62709                if(!_the_view)
62710                    return;
62711
62712                _state = {};
62713                var widgets = _the_view.get_widgets();
62714                for(var name in widgets) {
62715
62716                    var component = widgets[name].instance;
62717                    if(component && typeof(component[method_name]) == 'function')
62718                        component[method_name](param);
62719                }
62720            }
62721
62722            function init() {
62723
62724                create_view();
62725            }
62726
62727            init();
62728        });
62729})();
62730
62731
62732"use strict";
62733function gene_neuroblast_widget(container, options) {
62734
62735    this.container = $j(container);
62736    this.settings = {
62737        on_search:null,
62738        on_error:null,
62739        on_search_ready:null,
62740        "select_width":"200px",
62741        "ages":[],
62742        "structures":[]
62743    };
62744    $j.extend(true, this.settings, options);
62745
62746
62747    this.get_state = function () {
62748
62749        var state = {
62750            search_type:"neuroblast",
62751            search_term:get_search_state()
62752        };
62753
62754        return(get_search_state());
62755    };
62756
62757    this.set_state = function (state) {
62758
62759        set_search_state(state);
62760    };
62761
62762    this.clear = function () {
62763
62764        _gene_label.text('');
62765    };
62766
62767
62768    /**
62769     *
62770     * @private
62771     */
62772    var _id = "_nb_" + Math.floor(Math.random() * 10001);
62773    var _self = this;
62774    var _state = null;
62775    var _structure_select = null;
62776    var _age_select = null;
62777    var _search_button = null;
62778    var _gene_label = null;
62779
62780
62781    function build_scaffold() {
62782
62783        var html = "<table class='neuroblast_table'>";
62784        html += "<tr><td>Correlation Search</td><td id='neuroblast_gene_label'></td></tr>";
62785        html += "<tr><td><select id='structures" + _id + "' class='neuroblast_structure_select' multiple></select></td>";
62786        html += "<td rowspan='2'><div id='search" + _id + "'>Search</div></td></tr>";
62787        html += "<tr><td><select id='ages" + _id + "' class='neuroblast_age_select' multiple></select></td></tr>";
62788        html += "</table>";
62789
62790        _self.container.append(html);
62791        _structure_select = $j("#structures" + _id);
62792        _age_select = $j("#ages" + _id);
62793        _search_button = $j('#search'+_id);
62794        _gene_label = $j('#neuroblast_gene_label');
62795    }
62796
62797    function build_components() {
62798
62799        for(var i = 0; i < _self.settings.ages.length; i++) {
62800            var age = _self.settings.ages[i];
62801            var options = _age_select[0].options;
62802            options[options.length] = new Option(age.label, age.id);
62803            if(age.selected)
62804                options[options.length-1].selected = 'selected';
62805        }
62806
62807        for(var i = 0; i < _self.settings.structures.length; i++) {
62808            var structure = _self.settings.structures[i];
62809            var options = _structure_select[0].options;
62810            options[options.length] = new Option(structure.label, structure.id);
62811            if(structure.selected)
62812                options[options.length-1].selected = 'selected';
62813        }
62814
62815        _structure_select.dropdownchecklist({
62816                    firstItemChecksAll:true,
62817                    icon: {
62818                        placement: 'right',
62819                        toOpen: 'ui-icon-triangle-1-s',
62820                        toClose: 'ui-icon-triangle-1-w'
62821                    },
62822                    zIndex: 200,
62823                    width:_self.settings.select_width
62824                });
62825                //_structure_select.dropdownchecklist('disable');
62826
62827        _age_select.dropdownchecklist({
62828                    firstItemChecksAll:true,
62829                    icon: {
62830                        placement: 'right',
62831                        toOpen: 'ui-icon-triangle-1-s',
62832                        toClose: 'ui-icon-triangle-1-w'
62833                    },
62834                    zIndex: 200,
62835                    width:_self.settings.select_width
62836                });
62837                //_age_select.dropdownchecklist('disable');
62838
62839        _search_button
62840            .button()
62841            .bind('click', do_search);
62842    }
62843
62844    function get_search_state() {
62845
62846        if(!_state)
62847            return(null);
62848
62849       //_search_options.domain, _search_options.rows, _search_options.sort_order, _search_options.image_set
62850        var state = {
62851
62852            search_type:'neuroblast',
62853            search_term:{
62854                domain:_structure_select.val().filter(function(item) { return(item != 'All'); }),       // structure acronym or id
62855                ages:_age_select.val().filter(function(item) { return(item != 'All')} ),                // age labels
62856                rows:[_state.gene_id],                                                                  // the gene ID
62857                gene_id:_state.gene_id,
62858                gene_acronym:_state.gene_acronym
62859            }
62860        };
62861
62862        return(state);
62863    }
62864
62865    function set_search_state(new_state) {
62866
62867        if(!new_state)
62868            return;
62869
62870        if(new_state.gene_acronym)
62871            _gene_label.text("Gene: " + new_state.gene_acronym);
62872
62873        if(new_state.domain) {
62874
62875            var structures = new_state.domain.split(',');
62876            _structure_select.val(structures);
62877            _structure_select.dropdownchecklist('refresh');
62878        }
62879
62880        if(new_state.ages) {
62881
62882            var ages = new_state.ages.split(',');
62883            _age_select.val(ages);
62884            _age_select.dropdownchecklist('refresh');
62885        }
62886
62887        _state = new_state;
62888
62889        if(_self.settings.on_search_ready)
62890            _self.settings.on_search_ready();
62891    }
62892
62893    function do_search() {
62894
62895        if(_self.settings.on_search)
62896            _self.settings.on_search(get_search_state());
62897    }
62898
62899    function init() {
62900
62901        build_scaffold();
62902        build_components();
62903    }
62904
62905    init();
62906}
62907;
62908"use strict";
62909/**
62910 *
62911 * @param container
62912 * @param options
62913 */
62914function gene_preview_container(container, options) {
62915
62916    this.container = $j(container);
62917    this.settings = {
62918        on_search_ready:null,
62919        on_error:null,
62920        on_search:null,
62921        urls:null,
62922        set_component_properties:set_widget_properties,
62923        layout_config:{
62924            layout:null,
62925            data_map:[],
62926            decorators:null,
62927            table_class:null,
62928            row_class:null,
62929            cell_class:null
62930        },
62931        component_config:{}
62932    };
62933    $j.extend(true, this.settings, options);
62934
62935
62936    // state will have a gene_id, and might also have neuroblast search state.
62937    this.set_state = function(new_state) {
62938
62939        this.clear();
62940
62941        _state = new_state;
62942        if(_state.id || _state.gene_id)
62943            do_new_gene(_state.id || _state.gene_id);
62944
62945    };
62946
62947    this.get_state = function() {
62948
62949        var widget_state = _widget_container.get_state();
62950        return(widget_state['neuroblast_widget']);
62951    };
62952
62953    this.clear = function() {
62954
62955        if(_widget_container)
62956            _widget_container.clear();
62957
62958        _busy_panel.show_disabled(true);
62959    };
62960
62961    var _self = this;
62962    var _urls = _self.settings.urls;
62963    var _state = null;
62964    var _the_model = null;
62965    var _busy_panel = null;
62966    var _widget_container = null;
62967
62968
62969    function set_widget_properties(widgets) {
62970
62971        for (var name in widgets) {
62972
62973            switch (name) {
62974
62975                case "related_data_widget":
62976                    widgets[name].settings.related_data_url = _self.settings.urls.related_data;
62977                    break;
62978                case "thumbnail_set_widget":
62979                    widgets[name].settings.thumbnail_url = _self.settings.urls.threed_thumbnail;
62980                    widgets[name].settings.detail_url = _self.settings.urls.experiment;
62981                    widgets[name].settings.image_view_url = _self.settings.urls.ivt;
62982                    break;
62983                case "neuroblast_widget":
62984                    widgets[name].settings.on_search_ready = _self.settings.on_search_ready;
62985                    widgets[name].settings.on_error = _self.settings.on_error;
62986                    widgets[name].settings.on_search = _self.settings.on_search;
62987                    break;
62988                default:
62989                    ;
62990            }
62991        }
62992    }
62993
62994
62995    function build_components() {
62996
62997        _widget_container = new AIBS.UI.component_container(_self.container, _self.settings);
62998        _busy_panel = new busy_panel(_self.container);
62999    }
63000
63001    function do_new_gene(gene_id) {
63002
63003        _busy_panel.show_busy(true);
63004        _the_model = new AIBS.DATA.gene_preview_model(_urls.data_service,
63005                                                    gene_id,
63006                                                    {
63007                                                        on_ready:do_data_ready,
63008                                                        on_error:do_data_error,
63009                                                        metadata_delegate:manage_metadata
63010                                                    });
63011    }
63012
63013    function manage_metadata(raw_data) {
63014
63015        if(raw_data.response)
63016            delete raw_data.response;
63017
63018        var len = raw_data.length;
63019        for(var i = 0; i < len; i++) {
63020
63021            // for some reason the 'delegate' column is not actually a boolean; it is
63022            // a char either 't' or 'f'.  turn it into a bool here.
63023            raw_data[i].delegate = (raw_data[i].delegate == 't');
63024
63025            // add a field to mark which of the experiments should be displayed as thumbnails.
63026            // if it's a delegated experiment AND its included in the thumbnail sizes AND it has a valid
63027            // storage directory, mark the 'thumbnail' field true.
63028            raw_data[i].thumbnail = (raw_data[i].delegate
63029                                    &&
63030                                    (raw_data[i].storage_directory !== null)
63031                                    &&
63032                                    (_self.settings.component_config.thumbnail_set_widget.settings.thumbnail_sizes[raw_data[i].label] !== undefined))
63033        }
63034
63035        return(raw_data);
63036    }
63037
63038    function do_data_ready(data) {
63039
63040        var glom = {
63041
63042            gene_id:_state.id || _state.gene_id,
63043            gene_acronym:_state.acronym ? _state.acronym :
63044                            _state['genes.acronym'] ? _state['genes.acronym'] : '',
63045            experiments:data
63046        };
63047        $j.extend(true, glom, _state);
63048
63049        _busy_panel.show_busy(false);
63050        _widget_container.set_state(glom);
63051    }
63052
63053    function do_data_error(err) {
63054
63055        _busy_panel.show_busy(false);
63056
63057        if(_self.settings.on_error)
63058            _self.settings.on_error(err);
63059    }
63060
63061    function init() {
63062
63063        build_components();
63064        _busy_panel.show_disabled(true);
63065    }
63066
63067    init();
63068}
63069
63070
63071function placeholder_widget(container, settings) {
63072
63073    $j(container)
63074    .css({height:(settings.height ? settings.height : 20) +'px'})
63075        .html(settings.name ? settings.name : '');
63076}
63077
63078;
63079"use strict";
63080AIBS = AIBS || {};
63081AIBS.DATA = AIBS.DATA || {};
63082
63083/**
63084 * @class
63085 *
63086 * The AIBS.DATA.gene_preview_model provides access to information about a single gene.  It is
63087 * designed to provide all the necessary (non-image) information to work with the gene-centric
63088 * search result preview component used in the Developing Mouse project.
63089 *
63090 * This object requires the AIBS.DATA.API_DAO and will throw at instantiation if it is not present.
63091 *
63092 *
63093 * @param data_service_url   <pre>A string that is the base path of the RMA API, for example http://api.brain-map.org/api/v2/data.</pre>
63094 * @param gene_id            <pre>The ID of the gene (NCBI Gene) for which data is to be fetched.</pre>
63095 * @param options            <pre>A hash containing optional information.  Possible values are:
63096 *
63097 *                           on_ready:          A callback function to be called when the experiment have been fetched. This
63098 *                                              callback should accept a single argument, which is the experiment data.
63099 *
63100 *                           on_error:          A callback function to be called if an error occurs fetching data.
63101 *
63102 *                           metadata_delegate: If provided, the metadata delegate will be called _after_ the default metadata
63103 *                                              routine is run.  This delegate is called with one parameter, which is the raw data
63104 *                                              returned by the RMA call.
63105 *
63106 *                                              Typically the delegate will add or update fields on the given gene
63107 *                                              and return the same object it received.
63108 *
63109 *                           </pre>
63110 */
63111AIBS.DATA.gene_preview_model = (function () {
63112
63113    return(
63114        function (data_service_url, gene_id, options) {
63115
63116            this.data_service = data_service_url;
63117            this.gene_id = gene_id;
63118            this.settings = {
63119                on_ready:null,
63120                on_error:null,
63121                "product_ids":"3",
63122                metadata_delegate:null
63123            };
63124            $j.extend(true, this.settings, options);
63125
63126
63127            /**
63128             * returns the gene data previously fetched
63129             * @return {*}
63130             */
63131            this.gene_data = function () {
63132
63133                return(_the_data);
63134            };
63135
63136            /**
63137             * Fetch data for the given gene_id.  WHen the fetch completes either
63138             * on_ready or on_error will be called.  The newly fetched data can
63139             * then be accessed via the gene_data() method.
63140             *
63141             * @param gene_id
63142             */
63143            this.fetch_gene_data = function (gene_id) {
63144
63145                this.gene_id = gene_id;
63146                fetch_data(this.gene_id);
63147            }
63148
63149
63150            /**
63151             *
63152             * @private
63153             */
63154            var _self = this;
63155            var _the_data = null;
63156            var _the_dao = null;    // the API_DAO instantiated internally
63157
63158
63159            function fetch_data(gene_id) {
63160
63161                var opts = {
63162                    success_callback:receive_data,
63163                    error_callback:show_data_error
63164                };
63165                gene_data(gene_id, opts);
63166            }
63167
63168
63169            function receive_data(response_data, text_status, xhdr) {
63170
63171                _the_data = massage_data(response_data);
63172
63173                if (_self.settings.on_ready)
63174                    _self.settings.on_ready(_the_data);
63175            }
63176
63177
63178            function massage_data(raw_data) {
63179
63180                // if the caller wants to do their own metadata calculation, do it here.
63181                if (_self.settings.metadata_delegate)
63182                    raw_data = _self.settings.metadata_delegate(raw_data);
63183
63184                return(raw_data);
63185            }
63186
63187
63188            function show_data_error(msg) {
63189
63190                var err_msg = 'Your search could not be completed';
63191
63192                if (msg) {
63193                    if (msg.msg)
63194                        err_msg += ' (' + msg.msg + ')';
63195                    else
63196                        err_msg += ' (' + inspect('', msg) + ')';
63197                }
63198                else
63199                    err_msg += '.';
63200
63201                if (_self.settings.on_error)
63202                    _self.settings.on_error(err_msg);
63203            }
63204
63205
63206            /* The actual data access bits here-- */
63207            function gene_data(id, options) {
63208
63209                options.criteria = "products[id$in'" + _self.settings.product_ids + "'][failed$eqfalse],genes[id$eq" + id + "],"
63210                if (options.ages)
63211                    options.criteria += "specimen(donor(age[name$in" + options.ages + ")),";
63212                else
63213                    options.criteria += "specimen(donor(age)),";
63214                options.criteria += "rma::options[tabular$eq'data_sets.id,data_sets.delegate,data_sets.storage_directory,ages.name as label'],rma::options[order$eq'ages.embryonic desc,ages.days']";
63215
63216                options.format = 'json';
63217                options.verb = 'GET';
63218
63219                return(_the_dao.query('SectionDataSet', options));
63220            }
63221
63222            function init() {
63223
63224                if (!AIBS.DATA.API_DAO)
63225                    throw "The gene preview model requires AIBS.DATA.API_DAO, which is not present.";
63226
63227                _the_dao = new AIBS.DATA.API_DAO(_self.data_service, {});
63228
63229                if (_self.gene_id)
63230                    fetch_data(_self.gene_id);
63231            }
63232
63233            /*  initialize the instance */
63234            init();
63235
63236        });
63237})();
63238
63239
63240
63241"use strict";
63242/**
63243 * Just a wrapper around the existing AIBS.UI.related_data_widget class that makes it compliant with a
63244 * standard Component interface.
63245 *
63246 * @param container
63247 * @param settings
63248 * @param view
63249 * @return {AIBS.UI.related_data_widget}
63250 */
63251function related_gene_data_widget(container, options, view) {
63252
63253    this.container = container;
63254    this.view = view;
63255    this.settings = {
63256        related_data_url:null
63257    };
63258    $j.extend(true, this.settings, options);
63259
63260    /**
63261     * The new_state param is expected to contain a 'gene' hash with at least the field 'id'
63262     * which should be the aibs id of the gene for which related data is wanted.
63263     *
63264     * @param new_state
63265     */
63266    this.set_state = function(new_state) {
63267
63268        if(!_the_widget)
63269            return;
63270
63271        var data = {
63272            id:new_state.gene ? new_state.gene.id : new_state.gene_id ? new_state.gene_id : new_state.id ? new_state.id : 0
63273        };
63274
63275        _the_widget.set_data(data);
63276    };
63277
63278    this.clear = function() {
63279
63280
63281    };
63282
63283    var _self = this;
63284    var _the_widget = null;
63285
63286    function build() {
63287
63288        var gene_version = _self.settings.related_data_url.replace('related/matches', 'related/gene/matches');
63289        _the_widget = new AIBS.UI.related_data_widget(container, gene_version + '?project=350&id=');
63290
63291        // The header for this widget is persistently a dark blue, via the ui-widget-header class.
63292        // The bit below seems to be the only way to override that.
63293        $j('#_iw_t_'+_the_widget.id()).find('td._c_0_0').removeClass('ui-widget-header').css({backgroundColor:"#efefef"});
63294    }
63295
63296    build();
63297}
63298;
63299"use strict";
63300/**
63301 *
63302 * @param container
63303 * @param options
63304 */
63305function threed_thumbnail_set(container, options) {
63306
63307    this.container = $j(container);
63308    this.settings = {
63309        "thumbnail_url":null,
63310        "detail_url":null,
63311        "image_view_url":null,
63312        "slider_height":0,
63313        "num_slider_images":36,
63314        "thumbnail_sizes":{},
63315        "rows":0,
63316        "columns":0,
63317        "control_coordinates":{
63318            "column":0,
63319            "row":0,
63320            "colspan":0,
63321            "rowspan":0
63322        },
63323        "initial_state":{
63324            "data":[],
63325            "positions":[
63326                {
63327                   "row":0,
63328                   "column":0,
63329                   "position":0
63330                }
63331            ]
63332        }
63333    };
63334    $j.extend(true, this.settings, options);
63335
63336
63337    this.get_state = function () {
63338
63339        return(_state);
63340    };
63341
63342    this.set_state = function (state) {
63343
63344        _state.data = state.experiments;
63345        create_thumbnails(_state.data);
63346        build_image_set_button(null);
63347    };
63348
63349    this.clear = function () {
63350
63351        for(var c in _controls) {
63352
63353            _controls[c].thumbnail.clear();
63354        }
63355    };
63356
63357
63358    /**
63359     * the new_settings param can contain anything that's :
63360     *
63361     *
63362     * @param new_settings
63363     */
63364    this.rebuild = function(new_settings) {
63365
63366
63367    }
63368
63369
63370    this.enable = function(enabled) {
63371
63372
63373    };
63374
63375    this.display = function(displayed) {
63376
63377
63378    };
63379
63380    this.visible = function(vis) {
63381
63382
63383    };
63384
63385    this.destroy = function() {
63386
63387
63388    };
63389
63390    /**
63391     *
63392     * @private
63393     */
63394    var _id = "_nb_" + Math.floor(Math.random() * 10001);
63395    var _self = this;
63396    var _table = null;
63397    var _state = _self.settings.initial_state;
63398    var _controls = {};
63399    var _the_slider = null;
63400    var _slider_index = null;
63401    var _control_cell = null;
63402    var _images_link = null;
63403
63404
63405    function build_scaffold() {
63406
63407        var html = "<table class='_threed_thumbnail_set'>";
63408        var coords = _self.settings.control_coordinates;
63409
63410        for(var i = 0; i < _self.settings.rows; i++) {
63411
63412            html += "<tr>";
63413
63414            for(var j = 0; j < _self.settings.columns; j++) {
63415
63416                html += "<td";
63417                // if this is the cell where we're putting the controls, we have some work o do...
63418                if(i == coords.row && j == coords.column) {
63419
63420                    html +=  " id='control_cell" + _id + "'";
63421                    html += " class='threed_set_controls'";
63422
63423                    if(coords.colspan && coords.colspan != 1) {
63424                        html += " colspan='" + coords.colspan + "'";
63425                        j += coords.colspan - 1;
63426                    }
63427
63428                    if(coords.rowspan && coords.rowspan != 1) {
63429                        html += " rowspan='" + coords.rowspan + "'";
63430                        i += coords.rowspan - 1;
63431                    }
63432                }
63433                html += ">";
63434                html += "</td>";
63435            }
63436            html += "</tr>";
63437        }
63438        html += "</table>";
63439
63440        _table = $j(html).appendTo(_self.container);
63441        _control_cell = $j('#control_cell' + _id);
63442    }
63443
63444    function build_controls() {
63445
63446        build_slider(_control_cell);
63447        build_image_set_button(_control_cell);
63448    }
63449
63450    function create_thumbnails(data) {
63451
63452        if(!data)
63453            return;
63454
63455        var thumbnail_options = {
63456            project_cell_height:100,                            // height in pixels of the container
63457            slider_height:_self.settings.slider_height,         // height in pixels reserved for the slider
63458            num_slider_images:_self.settings.num_slider_images, // number of images in an aba  mini thumbnail
63459            thumbnail_dims:[],                                  // dimensions of a single frame of an aba mini thumbnail filmstrip
63460            animate_duration:0,
63461            include_slider:false
63462        };
63463
63464        // not all of the experiments given will be used for thumbnails, filter here.
63465        var thumbs = data.filter(function(exp) { return( exp.thumbnail); });
63466
63467        for(var i = 0; i < thumbs.length; i++) {
63468
63469            var r = Math.floor(i / _self.settings.columns);
63470            var c = i % _self.settings.columns;
63471            var cell = $j(_table[0].rows[r].cells[c]);
63472
63473            // the thumbnails are all different widths
63474            thumbnail_options.thumbnail_dims = _self.settings.thumbnail_sizes[thumbs[i].label];
63475
63476            var control_set = _controls[r+"_"+c];
63477            if(!control_set) {
63478
63479                control_set = {
63480                    thumbnail:new AIBS.UI.threed_thumbnail_widget(cell, thumbs[i].id, _self.settings.thumbnail_url, thumbnail_options),
63481                    // put a label in the lower left corner of the thumbnail.  This should be the age with a link to the experiment.
63482                    label:new age_label(cell, thumbs[i].label, thumbs[i].id)
63483                };
63484                _controls[r+"_"+c] = control_set;
63485            } else {
63486
63487                control_set.thumbnail.set_experiment(thumbs[i].id);
63488                control_set.label.set_label(thumbs[i].label, thumbs[i].id)
63489            }
63490        }
63491    }
63492
63493    function age_label(container, label, id) {
63494
63495
63496        this.set_label = function(new_label, new_id) {
63497
63498            _label.find('a')
63499                .attr('href', _self.settings.detail_url + "/" + new_id)
63500                .text(new_label);
63501        };
63502
63503        var _label = null;
63504
63505        function build() {
63506
63507            var html = "<div class='age_label_container'>"
63508            html += "<a href='" + _self.settings.detail_url + "/" + id + "' target='_blank'>" + label + "</a>";
63509            html += "</div>";
63510
63511            _label = $j(html).appendTo(container);
63512        }
63513
63514        build();
63515    }
63516
63517
63518    function build_slider(container) {
63519
63520        var slider_height = _self.settings.slider_height;
63521        var slider_width = container.width() - 20;
63522
63523        var slider_container = $j("<div class='slider_container'></div>").appendTo(container);
63524        _the_slider = $j("<div class='_threed_slider'>")
63525            .css({
63526                width: slider_width+"px",
63527                height: slider_height+"px"
63528            })
63529            .appendTo(slider_container);
63530
63531        _the_slider.slider({
63532          min: 0,
63533          max: _self.settings.num_slider_images-1,
63534          value: _slider_index,
63535          slide: do_slide,
63536          animate:0
63537        });
63538
63539        $j("<div class='slider_label_container'>Rotate Thumbnails</div>").appendTo(slider_container);
63540    }
63541
63542    function build_image_set_button(container) {
63543
63544        var id_list = "";
63545        if(_state.data) {
63546
63547            var ids = _state.data.map(function(d) { return(d.id); });
63548            if(ids)
63549                id_list = ids.join(',');
63550        }
63551
63552        if(!_images_link) {
63553            var cont = $j("<div class='image_button_container'></div>").appendTo(container);
63554            _images_link = $j("<a href='" + _self.settings.image_view_url + "?id=" + id_list + "' target='_blank'>See All Images for this Gene</a>")
63555                                        .appendTo(cont)
63556                                        //.button()
63557                                        //.bind('click', show_images);
63558        } else {
63559
63560            _images_link.attr('href', _self.settings.image_view_url + "?id=" + id_list);
63561        }
63562    }
63563
63564
63565    function do_slide(event, ui) {
63566
63567        _slider_index = ui.value;
63568        _table.find('img').each(function() {
63569            var img_width = $j(this).width() / _self.settings.num_slider_images;
63570            $j(this).css({ left:(-_slider_index * img_width)+"px" });
63571        });
63572    }
63573
63574    function rebuild() {
63575
63576        _table.remove();
63577        _controls = {};
63578
63579        build_scaffold();
63580        build_controls();
63581    }
63582
63583    function init() {
63584
63585        build_scaffold();
63586        build_controls();
63587        if(_state.data)
63588            create_thumbnails(_state.data);
63589    }
63590
63591    init();
63592}
63593;
63594"use strict";
63595AIBS.UI.ish_gene_heatmap = (function($j) {
63596
63597 return (
63598
63599        function(container, gene_id, options) {
63600
63601        // public properties
63602            this.container = $j(container);
63603
63604
63605        // public methods
63606
63607
63608
63609        // private properties
63610            var _self = this;
63611            var _gene_id = gene_id;
63612            var _info_widget = null;
63613            var _canvas, _ctx, _img_for_canvas;
63614            var _img_div_container = null;
63615            var _canvas_container = null;
63616            var _tooltip_timer = null;
63617            var _heatmap_tooltip_container = null;
63618            var _saved_tooltip_x, _saved_tooltip_y, _img_div_offset_x, _img_div_offset_y, _img_div_offset;
63619
63620            var _age_points = options.age_points;
63621            var _display_structures = options.structures;
63622            var _urls = options.urls;
63623
63624            var _x_pixels_per_structure = 50;
63625            var _y_pixels_per_age = 30;
63626
63627            var _x_pixels_offset = 25;
63628            var _y_pixels_offset = 15;
63629
63630            var _x_pixels_expression_area = 575;   // the heatmap image will be stretched to this size
63631            var _y_pixels_expression_area = 225;
63632
63633            var _mousemove_refresh_delay = 300;   // in ms
63634
63635            function replace_img_with_canvas() {
63636
63637                if(_img_div_container.length == 0)    // do nothing if not found
63638                     return;
63639
63640                // the pre-generated heatmap images are 115 x 75
63641                // stretch the image to the canvas size defined
63642                var canvas_html = "<canvas id='heatmap_canvas' width='" + _x_pixels_expression_area + "' height='" +
63643                                             _y_pixels_expression_area + "'>";
63644                _img_div_container.append(canvas_html);
63645
63646                var target_img = $j("img#heatmap_img");
63647                target_img.hide();
63648
63649                _canvas = document.getElementById('heatmap_canvas');
63650                if(_canvas && _canvas.getContext) {
63651                    _ctx = _canvas.getContext('2d');
63652                    _ctx.imageSmoothingEnabled = false;
63653                    _ctx.webkitImageSmoothingEnabled = false;
63654                    _ctx.mozImageSmoothingEnabled = false;
63655                    _ctx.drawImage(target_img[0], 0, 0, _x_pixels_expression_area, _y_pixels_expression_area);
63656                                    }
63657                else {
63658
63659                }
63660
63661                return;
63662
63663            }
63664
63665            function hide_tooltip() {
63666                if (_tooltip_timer) {
63667                  clearTimeout(_tooltip_timer);
63668                }
63669
63670                _heatmap_tooltip_container.fadeOut("fast");
63671
63672            }
63673
63674            function show_tooltip(event) {
63675
63676                  // many mousemove events may happen with the same x, y
63677                  if(_saved_tooltip_x == event.pageX && _saved_tooltip_y == event.pageY ) {
63678                      return;
63679                  }
63680
63681                  hide_tooltip();
63682
63683                  _tooltip_timer = setTimeout(function() {
63684
63685                      // use jquery offset instead of the prototype cumulativeOffset
63686                      var imgPos = jQuery(event.currentTarget).offset();
63687                      //var mousePos = [ event.pageX - imgPos.left, event.pageY - imgPos.top ];
63688                      var display_x = event.pageX - imgPos.left;
63689                      var display_y = event.pageY - imgPos.top;
63690
63691                      // do not show the tool tip if the mouse position is out of the heatmap expression area
63692                      if(display_x < 0 || display_x >= _x_pixels_expression_area || display_y < 0 || display_y >= _y_pixels_expression_area)
63693                          return;
63694
63695                      var age_index = Math.floor((display_y - _y_pixels_offset) / _y_pixels_per_age);
63696                      var structure_index = Math.floor((display_x - _x_pixels_offset) / _x_pixels_per_structure);
63697
63698                      if(age_index < 0 || age_index > _age_points.length - 1 || structure_index < 0 || structure_index > _display_structures.length - 1 ) {
63699                          //hide_tooltip();
63700                          return;
63701                      }
63702
63703
63704                      var age_string = _age_points[age_index].atlas_name;
63705                      var structure_string = _display_structures[structure_index].name;
63706
63707                      _heatmap_tooltip_container.css({ "left": event.pageX+"px", "top": event.pageY+"px"});
63708
63709                      //_heatmap_tooltip_container.html(display_x+","+display_y+" : " + age_string + " - " + structure_string);
63710                      //_heatmap_tooltip_container.html( age_string + " - " + structure_string );
63711                      _heatmap_tooltip_container.html( "View " + structure_string + " in the " + age_string );
63712
63713                      _heatmap_tooltip_container.fadeIn("fast");
63714
63715
63716                   }, _mousemove_refresh_delay);
63717
63718                    _saved_tooltip_x = event.pageX;
63719                    _saved_tooltip_y = event.pageY;
63720            }
63721
63722            function show_atlas_page(event) {
63723
63724                  // use jquery offset instead of the prototype cumulativeOffset
63725                  var imgPos = jQuery(event.currentTarget).offset();
63726
63727                  var display_x = event.pageX - imgPos.left;
63728                  var display_y = event.pageY - imgPos.top;
63729
63730                  // do nothing if the mouse position is out of the heatmap expression area
63731                  if(display_x < 0 || display_x >= _x_pixels_expression_area || display_y < 0 || display_y >= _y_pixels_expression_area)
63732                      return;
63733
63734                  var age_index = Math.floor((display_y - _y_pixels_offset) / _y_pixels_per_age);
63735                  var structure_index = Math.floor((display_x - _x_pixels_offset) / _x_pixels_per_structure);
63736
63737                  if(age_index < 0 || age_index > _age_points.length - 1 || structure_index < 0 || structure_index > _display_structures.length - 1 )
63738                      return;
63739
63740                  var atlas_id = _age_points[age_index].atlas_id;
63741                  var structure_id = _display_structures[structure_index].id;
63742
63743                  var reference_atlas_url = _urls.atlas_viewer_service + "/#atlas=" + atlas_id + "&structure=" +structure_id;
63744                  window.open(reference_atlas_url);
63745
63746            }
63747
63748
63749            function browser() {
63750
63751                var ua = navigator.userAgent.toString();
63752
63753                if(/Chrome/g.exec(ua) && !/MSIE/g.exec(ua))   // in case IE10 will have "Chrome" in its user agent string
63754                    return('Chrome');
63755
63756                if(/WebKit/g.exec(ua))
63757                    return('WebKit');
63758
63759                if(/Firefox/g.exec(ua))
63760                    return('Firefox');
63761
63762                if(/MSIE/g.exec(ua))
63763                    return('MSIE');
63764
63765                return('Unknown');
63766            }
63767
63768
63769            function post_image_load() {
63770
63771                _img_div_container = $j("div#heatmap_img_div");
63772
63773                _img_div_offset = _img_div_container.offset();
63774                _img_div_offset_x = _img_div_offset.left;
63775                _img_div_offset_y = _img_div_offset.top;
63776
63777                _heatmap_tooltip_container = $j("div#HeatmapTooltip");
63778                _heatmap_tooltip_container.hide();
63779
63780                var browser_detected;
63781                browser_detected = browser();
63782                if(browser_detected == 'Chrome' || browser_detected == 'WebKit') {
63783                    //alert(browser_detected + ' browser detected');
63784                    replace_img_with_canvas();
63785                }
63786
63787                // hook up mouse events
63788                _img_div_container.mousemove(show_tooltip);
63789                _img_div_container.mouseout(hide_tooltip);
63790                _img_div_container.click(show_atlas_page);
63791
63792            }
63793
63794            function build_table(){
63795                var container_table_html = "<div class='gene_heatmap_widget _gene_widget'><div class='ui-widget-header'>Expression Summary</div><table id='gene_heatmap_table'>" +
63796                    "<tr class='row_for_one_age'><td class='first_column' rowspan='10'><b>Age</b></td><td class='center_text' colspan='13'><span><b>Anatomic Region</b></span></td>" +
63797                    "<td class='expression_legend_column' rowspan='10'><table><tr  class='row_padding'><td></td></tr><tr class='row_padding'><td></td></tr><tr><td>3.5</td></tr>" +
63798                               "<tr><td><img height='140' width='20' src='/expressionSummaries/colormap.png'></td></tr><tr><td>-1.5</td></tr><tr><td>log(expression)</td></tr></table></td></tr>"  +
63799                    "<tr><td class='table_column_age'></td><td class='column_padding'></td>" +
63800                                "<td class='column_for_one_structure'>RSP</td><td class='column_for_one_structure'>Tel</td><td class='column_for_one_structure'>PedHy</td>" +
63801                                "<td class='column_for_one_structure'>p3</td><td class='column_for_one_structure'>
63801p2</td><td class='column_for_one_structure'>p1</td>" +
63802                                "<td class='column_for_one_structure'>M</td><td class='column_for_one_structure'>PPH</td><td class='column_for_one_structure'>PH</td>" +
63803                                "<td class='column_for_one_structure'>PMH</td><td class='column_for_one_structure'>MH</td>" +
63804                    "</tr>" +
63805                    "<tr class='row_padding'><td class='table_column_age'><td class='img_holder' colspan='12' rowspan='8'>" +
63806                                                                           "<div id='heatmap_img_div'><img id='heatmap_img' height='225' width='575' src='"+ "/expressionSummaries/"+ _gene_id + ".png'>" + "</div></td>" +
63807                    "<tr class='row_for_one_age'><td class='table_column_age'>E11.5</td></tr>" +
63808                    "<tr class='row_for_one_age'><td class='table_column_age'>E13.5</td></tr>" +
63809                    "<tr class='row_for_one_age'><td class='table_column_age'>E15.5</td></tr>" +
63810                    "<tr class='row_for_one_age'><td class='table_column_age'>E18.5</td></tr>" +
63811                    "<tr class='row_for_one_age'><td class='table_column_age'>P4</td></tr>" +
63812                    "<tr class='row_for_one_age'><td class='table_column_age'>P14</td></tr>" +
63813                    "<tr class='row_for_one_age'><td class='table_column_age'>P28</td></tr>" +
63814                    "</table></div><div id='HeatmapTooltip'>Brain</div>";
63815
63816                _self.container.append(container_table_html);
63817
63818                var img = document.getElementById('heatmap_img');
63819                img.onload = post_image_load;
63820
63821            }
63822
63823
63824            function init() {
63825               build_table();
63826
63827            }
63828
63829            init();
63830        }
63831 );
63832
63833})(jQuery);
63834"use strict";
63835/**
63836 * Copyright(c) The Allen Institute for Brain Science 2011
63837 *
63838 *
63839 * Tim Dolbeare
63840 */
63841
63842/**
63843 * A widget for displaying the fields of a data object in a table.
63844 * <p>
63845 * The info widget class encapsulates the concept of using a table to show the fields of some data object, for
63846 * example, if you need to show the data contained in a Probe object, you could use an info_widget to manage the
63847 * task of laying out, constructing, labeling & populating a table with the fields of the Probe object.
63848 * </p><p>
63849 * This is a basic component used in composing more useful widgets.  The info_widget doesn't do anything
63850 * useful on its own.  You have to provide it a description of the layout you want to use as well as a map
63851 * of which fields should be displayed in which table cells, and optionally a map of 'decorator' functions used to do
63852 * any formatting necessary for specific fields.
63853 * </p>
63854 *
63855 * @class
63856 * @param container     The DOM element or jQuery object that contains the info_widget. <p></p>
63857 * @param layout        A description of how the rows and columns of the underlying HTML table should be constructed.
63858 * For example:
63859  <pre>
63860  var layout = {
63861            columns:4,
63862            rows:5,
63863            spans:[
63864               {row:0, column:0, column_span:4, row_span:1},
63865               {row:4, column:0, column_span:4, row_span:1}
63866            ]
63867        };
63868  </pre>
63869  The example layout above would result in a table like this:
63870  <style>
63871  .ex_table {border:1px solid #000; width:500px; border-collapse:collapse}
63872  .ex_table td {border:1px solid #000; height:14px;}
63873  </style>
63874  <table class="ex_table">
63875        <tbody>
63876                <tr>
63877                        <td rowspan="1" colspan="4"></td>
63878                </tr>
63879                <tr>
63880                        <td></td>
63881                        <td></td>
63882                        <td></td>
63883                        <td></td>
63884                </tr>
63885                <tr>
63886                        <td class="_iw_td _co _c_0 _c_0_2"></td>
63887                        <td class="_iw_td _ce _c_1 _c_1_2"></td>
63888                        <td class="_iw_td _co _c_2 _c_2_2"></td>
63889                        <td class="_iw_td _ce _c_3 _c_3_2"></td>
63890                </tr>
63891                <tr class="_iw_tr _re _r_3">
63892                        <td class="_iw_td _co _c_0 _c_0_3"></td>
63893                        <td class="_iw_td _ce _c_1 _c_1_3"></td>
63894                        <td class="_iw_td _co _c_2 _c_2_3"></td>
63895                        <td class="_iw_td _ce _c_3 _c_3_3"></td>
63896                </tr>
63897                <tr class="_iw_tr _ro _r_4">
63898                        <td rowspan="1" colspan="4" class="_iw_td _co _c_0 _c_0_4"></td>
63899                </tr>
63900        </tbody>
63901</table>
63902<p></p>
63903 *
63904 * @param data_map The data_map describes which fields in the data object should be displayed in
63905 * which cells of the table.  For example, mapping the data in a Probe object to the table layed out
63906 * in the example above could look like this: <p></p>
63907 * <pre>
63908        var data_map = [
63909            {field:'name', cell:[0,0]},
63910            {field:'gene.acronym', cell:[1,1]},
63911            {field:'orientation.name', cell:[1,2]},
63912            {field:'forward_primer_sequence.sequence_data', cell:[3,1]},
63913            {field:'reverse_primer_sequence.sequence_data', cell:[3,2]},
63914            {field:'predicted_sequence.sequence_length', cell:[3,3]}
63915        ];
63916   </pre>
63917   Note that field names can be qualified.  Given a Probe object that has an associated Gene object, you
63918   can display the gene name by referencing the field 'gene.name'.  The depth of qualification is only
63919   limited by the associations of the given data object.  Also note the the field names are
63920   given as strings.
63921   <p></p>
63922 * @param decorators  The decorator map can be used to map functions that do any special
63923   formatting required to a specific cell.  The decorator map looks like this:
63924   <pre>
63925        var decorators = {
63926            '0,0':add_title,
63927            '0,4':wrap_sequence
63928        };
63929   </pre>
63930   Decorator functions have a prototype like so:
63931   <pre>
63932      function my_decorator(container, data) : returns String;
63933   </pre>
63934   where 'container' is the TD element referenced in the decorator map and 'data' is the field to be displayed.
63935   Note that 'data' is often a string, but can be an array or any other object represented by a field in the
63936   data object passed to the info_widget in the set_data(data) method. The decorator should return a string,
63937   which may be any text or valid HTML.  Below is an example that labels the 0,0 cell of our Probe info table
63938   with the Probe.name if the name field is not null, or just the word 'Probe' if it is null:
63939   <pre>
63940        function add_title(container, data) {
63941           if(data)
63942             return("Probe " + data);
63943
63944           return("Probe");
63945        }
63946   </pre>
63947 * @param options
63948 *    <ul>
63949 *      <li>
63950 *         redecorate: if true, any decorator functions will be called any time the info_widget is resized.  Default = false.
63951 *      </li>
63952 *      <li>
63953 *         table_class: A class name to be added to the table.
63954 *      </li>
63955 *      <li>
63956 *         row_class: A class name to be added to each row.
63957 *      </li>
63958 *      <li>
63959 *         cell_class: A class name to be added to each cell.
63960 *      </li>
63961 *    </ul>
63962 *    <p></p>
63963 *    For example:
63964 *    <pre>
63965         var options = {
63966              table_class:'_probe_widget',
63967              redecorate:true
63968         };
63969 *    </pre>
63970 *    The info_widget table is loaded with class names to facilitate styling.  Adding a table class is usually all
63971 *    that's necessary to target any cell in an info_widget table.  Below is the HTML that would be
63972 *    produced by the example parameters and options above:<p></p>
63973&lt;table&nbsp;class=&quot;_iw_t&nbsp;_probe_widget&quot;&nbsp;id=&quot;_iw_t_0&quot;&gt;
63974<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;tbody&gt;
63975<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;tr&nbsp;class=&quot;_iw_tr&nbsp;_ro&nbsp;_r_0&quot;&gt;
63976<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;rowspan=&quot;1&quot;&nbsp;colspan=&quot;4&quot;&nbsp;class=&quot;_iw_td&nbsp;_co&nbsp;_c_0&nbsp;_c_0_0&quot;&gt;&lt;/td&gt;
63977<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/tr&gt;
63978<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;tr&nbsp;class=&quot;_iw_tr&nbsp;_re&nbsp;_r_1&quot;&gt;
63979<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;class=&quot;_iw_td&nbsp;_co&nbsp;_c_0&nbsp;_c_0_1&quot;&gt;&lt;/td&gt;
63980<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;class=&quot;_iw_td&nbsp;_ce&nbsp;_c_1&nbsp;_c_1_1&quot;&gt;&lt;/td&gt;
63981<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;class=&quot;_iw_td&nbsp;_co&nbsp;_c_2&nbsp;_c_2_1&quot;&gt;&lt;/td&gt;
63982<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;class=&quot;_iw_td&nbsp;_ce&nbsp;_c_3&nbsp;_c_3_1&quot;&gt;&lt;/td&gt;
63983<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/tr&gt;
63984<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;tr&nbsp;class=&quot;_iw_tr&nbsp;_ro&nbsp;_r_2&quot;&gt;
63985<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;class=&quot;_iw_td&nbsp;_co&nbsp;_c_0&nbsp;_c_0_2&quot;&gt;&lt;/td&gt;
63986<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;class=&quot;_iw_td&nbsp;_ce&nbsp;_c_1&nbsp;_c_1_2&quot;&gt;&lt;/td&gt;
63987<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;class=&quot;_iw_td&nbsp;_co&nbsp;_c_2&nbsp;_c_2_2&quot;&gt;&lt;/td&gt;
63988<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;class=&quot;_iw_td&nbsp;_ce&nbsp;_c_3&nbsp;_c_3_2&quot;&gt;&lt;/td&gt;
63989<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/tr&gt;
63990<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;tr&nbsp;class=&quot;_iw_tr&nbsp;_re&nbsp;_r_3&quot;&gt;
63991<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;class=&quot;_iw_td&nbsp;_co&nbsp;_c_0&nbsp;_c_0_3&quot;&gt;&lt;/td&gt;
63992<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;class=&quot;_iw_td&nbsp;_ce&nbsp;_c_1&nbsp;_c_1_3&quot;&gt;&lt;/td&gt;
63993<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;class=&quot;_iw_td&nbsp;_co&nbsp;_c_2&nbsp;_c_2_3&quot;&gt;&lt;/td&gt;
63994<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;class=&quot;_iw_td&nbsp;_ce&nbsp;_c_3&nbsp;_c_3_3&quot;&gt;&lt;/td&gt;
63995<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/tr&gt;
63996<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;tr&nbsp;class=&quot;_iw_tr&nbsp;_ro&nbsp;_r_4&quot;&gt;
63997<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;td&nbsp;rowspan=&quot;1&quot;&nbsp;colspan=&quot;4&quot;&nbsp;class=&quot;_iw_td&nbsp;_co&nbsp;_c_0&nbsp;_c_0_4&quot;&gt;&lt;/td&gt;
63998<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/tr&gt;
63999<br>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&lt;/tbody&gt;
64000<br>&lt;/table&gt;
64001<p></p>
64002Both rows and cells have classes defined; columns have a class; there are also unique classes for even and odd on both
64003rows and columns; each row has an indexed class, as does each cell.  To style just the second cell of the third row
64004of our Probe table on a page that has multiple info_widgets, you could use this:
64005<pre>
64006        ._probe_widget _c_1_2 { ...my style... }
64007</pre>
64008<p></p>
64009To do the same for all cells in the second column, you could use this:
64010<pre>
64011        ._probe_widget _c_1 { ...my style... }
64012</pre>
64013<p></p>
64014
64015 */
64016AIBS.UI.info_widget = (function($j) {
64017
64018
64019 AIBS.UI._INFO_WIDGET_INDEX = {widget_count:0};
64020
64021 return (
64022
64023    function(container, layout, data_map, decorators, options) {
64024
64025
64026            this.container = container;
64027            this.layout = layout;
64028            this.data_map = data_map;
64029            this.decorators = decorators;
64030            this.options = options;
64031            this.id = null;
64032
64033
64034            /**
64035             * Sets the content at the given column,row
64036             * @param row       {integer}
64037             * @param column    {integer}
64038             * @param content   {string} The content to be appended to the TD at the given column,row.
64039             */
64040            this.set_content = function(column, row, content) {
64041                var cell = _the_table.context.rows[row].cells[column];
64042                cell.innerHTML = content;
64043            }
64044
64045            /**
64046             * Sets the data used to populate the info_widget.
64047             * @param data The data object to be displayed
64048             */
64049            this.set_data = function(data) {
64050                _the_data = data;
64051                var len = this.data_map.length;
64052
64053                for(var i = 0; i < len; i++) {
64054                    var map = this.data_map[i];
64055                    var cell = _the_table.context.rows[map.cell[1]].cells[map.cell[0]];
64056                    var decorator = this.decorators[map.cell[0] + ',' + map.cell[1]]
64057                    var content = ""
64058                    try {
64059                        content = decorator ? decorator(cell, eval('_the_data.' + map.field)) : eval('_the_data.' + map.field);
64060                    } catch(e) {
64061                        // swallowing this.  The eval tried to find a non-existent field
64062                    }
64063                    if(content)
64064                        cell.innerHTML = content;
64065                }
64066            }
64067
64068            /**
64069             * Gets the DOM element (a TD) at the given column, row.
64070             * @param column
64071             * @param row
64072             * @return The TD at the given column,row.
64073             */
64074            this.get_cell = function(row, column) {
64075                return( _the_table.context.rows[row].cells[column] );
64076            }
64077
64078
64079        // private properties
64080            var _self = this;
64081            var _table_class = '_iw_t ' + (_self.options ? _self.options.table_class ? _self.options.table_class : '' : '');
64082            var _row_class = '_iw_tr' + (_self.options ? _self.options.row_class ? _self.options.row_class : '' : '');
64083            var _cell_class = '_iw_td' + (_self.options ? _self.options.cell_class ? _self.options.cell_class : '' : '');
64084            var _redecorate = (_self.options ? _self.options.redecorate ? _self.options.redecorate : false : false);
64085            var _span_map = null;
64086            var _the_table = null;
64087            var _the_data = null;
64088
64089        // private methods
64090        /**
64091         * @private
64092         * @scope AIBS.UI.info_widget
64093         */
64094
64095
64096        /**
64097         * Builds the html table based on user-supplied layout
64098         * @param container
64099         * @param layout
64100         * @param map
64101         */
64102           function build_scaffold(container, layout, map) {
64103
64104               var id = '_iw_t_' + _self.id;
64105               var html = "<table id='" + id + "' class='" + _table_class + "'>";
64106               for(var row = 0; row < layout.rows; row++) {
64107                   var row_classes = _row_class + (row%2 ? ' _re' : ' _ro') + (' _r_'+row);
64108                   html += "<tr class='" + row_classes + "'>";
64109
64110                   for(var column = 0; column < layout.columns; column++) {
64111                       // consult the map to properly set the rowspan & colspan attributes.
64112                       // where spans have been defined, TDs should not be created.
64113                       var cell_classes = _cell_class + (column%2 ? ' _ce' : ' _co') + (' _c_'+column) + (' _c_'+column+'_'+row);
64114                       if(_self.options.jquery_theme && column+row == 0)
64115                            cell_classes += ' ui-widget-header';
64116
64117                       var flag = map[column][row];
64118                       switch(flag) {
64119                           case 1:
64120                             html += "<td class='" + cell_classes + "'>";
64121                             break;
64122                           case 0:
64123                             break;
64124                           default: // if the map does not contain a 0 or 1 it is expected to contain a span object.
64125                             html += "<td class='" + cell_classes;
64126                             html += "' colspan='" + flag.column_span;
64127                             html += "' rowspan='" + flag.row_span + "'>";
64128                       }
64129                       html += "</td>";
64130                   }
64131                   html += "</tr>";
64132               }
64133               html += "</table>";
64134               var e = $j(html);
64135               e.appendTo(_self.container);
64136               _the_table = $j(e[0]);
64137
64138           }
64139
64140           /**
64141            * Returns a dense row x column array,  Where the array contains
64142            * a 0 if no td is necessary, a 1 where a td is necessary, and where
64143            * rowspan & colspans are defined, an anon object like so:
64144            * {row_span:3, column_span:1}
64145            * @param layout The user-supplied table layout
64146           */
64147           function build_span_map(layout) {
64148
64149               // create a dense 2d array...
64150               var map = {};
64151               for(var x = 0; x < layout.columns; x++) {
64152                   map[x] = {};
64153                   for( var y = 0; y < layout.rows; y++) {
64154                       map[x][y] = 1;
64155                   }
64156               }
64157
64158               // and populate it with flags describing the row & column spans
64159               var len= layout.spans.length;
64160               for(var i = 0; i < len; i++) {
64161                  var span = layout.spans[i];
64162
64163                  var row_start = span.row;
64164                  var col_start = span.column;
64165                  var row_stop = span.row + span.row_span;
64166                  var col_stop = span.column + span.column_span;
64167
64168                  for(var row = row_start;
64168 row < row_stop; row++) {
64169                      for(var col = col_start; col < col_stop; col++) {
64170                          map[col][row] = 0;
64171                      }
64172                  }
64173                  map[span.column][span.row] = {row_span:span.row_span, column_span:span.column_span};
64174               }
64175               return(map);
64176           }
64177
64178            /**
64179            *  The data is re-set and any decorator functions are called again.
64180            */
64181           function redecorate() {
64182              _self.set_data(_the_data);
64183           }
64184
64185        /**
64186         * Builds the private data structures, the HTML table, and binds
64187         * to the window 'resize' event.
64188         */
64189           function init() {
64190               _self.id = AIBS.UI._INFO_WIDGET_INDEX.widget_count++;
64191               _span_map = build_span_map(_self.layout);
64192               build_scaffold(_self.container, _self.layout, _span_map);
64193               if(_redecorate)
64194                _self.container.bind('resize', redecorate);
64195           }
64196
64197           init();
64198
64199    }
64200
64201 );
64202
64203
64204})(jQuery);
64205
64206
64207
64208"use strict";
64209/**
64210 * The ISH Experiment widget class.
64211 * <p></p>
64212 * The ish_experiment_widget widget presents some of the information contained in the data
64213 * object returned by a AIBS.DATA.ISH_DAO.experiment_detail() object.
64214 * <p></p>
64215 * This is a wrapper around the info_widget.
64216 * @class
64217 * @param container     The DOM element or jQuery object that contains the info_widget. <p></p>
64218 */
64219AIBS.UI.ish_experiment_widget = (function($j) {
64220
64221 return (
64222
64223    function(container) {
64224
64225    // public properties
64226        this.container = $j(container);
64227
64228        /**
64229         * Set the data to be displayed.
64230         * @param data  The return value from AIBS.DATA.ISH_DAO.experiment_detail()
64231         */
64232        this.set_data = function(data) {
64233            _info_widget.set_data(data);
64234        }
64235
64236        this.set_title = function(title) {
64237
64238            _info_widget.set_content(0, 0, title);
64239        }
64240
64241        /**
64242         * A unique ID for this widget. This is not an HTML ID, it is an integer value scoped to
64243         * AIBS.UI.
64244         */
64245        this.id = function() {
64246            if(_info_widget)
64247                return(_info_widget.id);
64248
64249            return(null);
64250        }
64251
64252
64253        /**
64254         * @private
64255         * @scope AIBS.UI.ish_experiment_widget
64256         */
64257        var _self = this;
64258        var _info_widget = null;
64259
64260
64261        /**
64262         * The layout, data_map & decorators are defined for this info widget.
64263         */
64264        function build_table() {
64265
64266            // describe the table
64267            var layout = {
64268                columns:4,
64269                rows:3,
64270                spans:[
64271                   {row:0, column:0, column_span:4, row_span:1},
64272                   {row:2, column:1, column_span:3, row_span:1}
64273                ]
64274            };
64275            // describe how data should be mapped to cells in the table
64276            var data_map = [
64277                {field:'name', cell:[0,0]},
64278                {field:'genes[0].acronym', cell:[1,1]},
64279                {field:'plane_of_section.name', cell:[3,1]},
64280                {field:'treatments', cell:[1,2]}
64281            ];
64282            // which cells require a callback to massage the data presentation before
64283            // placing it in the table.
64284            var decorators = {
64285                '0,0':add_title,
64286                '1,2':list_treatments
64287            };
64288
64289            var options = {
64290                table_class:'_experiment_widget ui-widget-content',
64291                jquery_theme:true
64292            };
64293            // build the widget
64294            _info_widget = new AIBS.UI.info_widget(_self.container, layout, data_map, decorators, options);
64295            // set the static field labels
64296            set_labels(_info_widget);
64297        }
64298
64299        /**
64300         * set the static labels for the Experiment fields
64301         * @param widget
64302         */
64303        function set_labels(widget) {
64304            _info_widget.set_content(0,0, "Experiment");
64305            _info_widget.set_content(0,1, "Gene");
64306            _info_widget.set_content(2,1, "Plane of Section");
64307            _info_widget.set_content(0,2, "Treatments");
64308        }
64309
64310        /**
64311         * A decorator function that sets a title in the top-most cell
64312         *
64313         */
64314        function add_title(container, data) {
64315           if(data)
64316             return("Experiment " + data);
64317
64318           return("Experiment");
64319        }
64320
64321        /**
64322         * A decorator function that creates a comma-delimited list out of an array of treatment names.
64323         * @param container
64324         * @param data
64325         */
64326        function list_treatments(container, data) {
64327           if(!data)
64328             return("");
64329
64330           var s = "";
64331           for(var i = 0; i < data.length; i++) {
64332               if(i)
64333                 s += ", ";
64334               s += data[i].name
64335           }
64336           return(s);
64337        }
64338
64339        function init() {
64340           build_table();
64341        }
64342
64343        init();
64344    }
64345 );
64346
64347})(jQuery);
64348"use strict";
64349/**
64350 *
64351 * Creates instance of AIBS.UI.info_widget
64352 *
64353 * @param container
64354 * @param options
64355 */
64356function ish_experiment_specimen_widget(container, options) {
64357
64358// public properties
64359    this.container = $j(container);
64360
64361    // these settings are appropriate for ABA, probably mouse in general
64362    this.settings = {
64363        jquery_theme:true,
64364        layout:{
64365            columns:4,
64366            rows:6,
64367            spans:[
64368                {row:0, column:0, column_span:4, row_span:1}
64369            ]
64370        },
64371        data_map:{
64372            "genes[0].acronym":[1,1],
64373            "genes[0].name":[3,1],
64374            "plane_of_section.name":[1,2],
64375            "treatments":[3,2],
64376            "probes[0].probe_type":[1,3],
64377            "probes[0].orientation.name":[3,3],
64378            "specimen.donor.organism.name":[1,4],
64379            "specimen.donor.strain":[3,4],
64380            "specimen.donor.sex":[1,5],
64381            "specimen.donor.age.days":[3,5]
64382        },
64383        labels:{
64384            "Experiment - Specimen":[0,0],
64385            "Gene Symbol":[0,1],
64386            "Gene Name":[2,1],
64387            "Plane of Section":[0,2],
64388            "Treatments":[2,2],
64389            "Probe Type":[0,3],
64390            "Orientation":[2,3],
64391            "Organism":[0,4],
64392            "Strain":[2,4],
64393            "Sex":[0,5],
64394            "Age":[2,5]
64395        },
64396        decorators:{
64397            "0,0":{
64398                "name":"add_title",
64399                "options":{}
64400            },
64401            "3,2":{
64402                "name":"list_treatments",
64403                "options":{}
64404            },
64405            "3,5":{
64406                "name":"show_age_units",
64407                "options":{}
64408            }
64409        },
64410        info_opts:{
64411            table_class:'_experiment_specimen_widget'
64412        }
64413    };
64414    $j.extend(true, this.settings, options);
64415
64416// public methods
64417
64418    // data is expected to be the graph of goodies returned by the ish_dao.experiment_specimen method
64419    this.set_data = function(data) {
64420        _info_widget.set_data(data);
64421    }
64422
64423    this.id = function() {
64424        if(_info_widget)
64425            return(_info_widget.id);
64426
64427        return(null);
64428    }
64429
64430
64431// private properties
64432    var _self = this;
64433    var _info_widget = null;
64434
64435
64436    function inspect(prefix, obj) {
64437        var s = "";
64438        for(var key in obj) {
64439            if((typeof obj[key]) == "function")
64440                s = s;
64441            else if((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
64442                s += "\n" + prefix + "key: " + key + inspect(prefix + "    ", obj[key]);
64443            else
64444                s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
64445        }
64446        return(s);
64447    }
64448
64449    function build_table() {
64450
64451        _self.settings.info_opts.decorator_options = {};
64452        for(var key in _self.settings.decorators) {
64453            var dec_options = _self.settings.decorators[key].options;
64454            _self.settings.decorators[key] = eval(_self.settings.decorators[key].name);
64455            _self.settings.info_opts.decorator_options[key] = dec_options;
64456        }
64457
64458        _info_widget = new AIBS.UI.info_widget(
64459            _self.container,
64460            _self.settings.layout,
64461            build_data_map(_self.settings.data_map),
64462            _self.settings.decorators,
64463            _self.settings.info_opts
64464        );
64465        // set the static field labels
64466        set_labels(_info_widget);
64467    }
64468
64469    function set_labels(widget) {
64470
64471        for(var key in _self.settings.labels) {
64472            var coords = _self.settings.labels[key];
64473            _info_widget.set_content(coords[0], coords[1], key);
64474        }
64475    }
64476
64477    function build_data_map(map) {
64478
64479        var map_array = [];
64480        for(var key in map) {
64481            map_array.push({
64482                field:key,
64483                cell:map[key]
64484            })
64485        }
64486        return(map_array);
64487    }
64488
64489    function add_title(container, data) {
64490        if(data)
64491            return("Experiment " + data);
64492
64493        return("Experiment");
64494    }
64495
64496    function show_age_units(container, data) {
64497        if(data)
64498            return(data + " days");
64499
64500        return("");
64501    }
64502
64503    function list_treatments(container, data) {
64504        if(!data)
64505            return("");
64506
64507        var s = "";
64508        for(var i = 0; i < data.length; i++) {
64509            if(i)
64510                s += ", ";
64511            s += data[i].name
64512        }
64513        return(s);
64514    }
64515
64516    function init() {
64517        build_table();
64518    }
64519
64520    init();
64521}
64522;
64523/**
64524 * This file is intended to encapsulate model data for ISH image filters, and is intended to by used by
64525 * several different page components, like IVT, thumbnails, SIV. Ideally, this might be located in a
64526 * different directory, such as assets/javascripts/models.
64527 *
64528 * See ish_image_types.js for ISH image types
64529 */
64530
64531function ish_filter_types() {
64532    var _filters = [
64533        {
64534            filterName:'contrast',
64535            filterVals:'0.5,0.5,0,256,4'
64536        },
64537        {
64538            filterName:'colormap',
64539            filterVals:'0.5,1,0,256,4'
64540        }
64541    ];
64542
64543    var _image_to_filter = {
64544        'ish':'contrast',
64545        'expression':'colormap',
64546        'nissl':'contrast',
64547        'ache':'contrast',
64548        'ihc:parvalbumin':'contrast'
64549    };
64550
64551    this.get_all_filters = function() {
64552        return _filters;
64553    };
64554
64555    /**
64556     * Get all the filters associated with an array of image types
64557     *
64558     * @param {array} image_types
64559     * @returns {object} hashes with key = image_type, value = {filterName:name, filterVals:string_of_values}
64560     */
64561    this.get_image_filters = function(image_types) {
64562        var image_filters = {};
64563        var filter_name;
64564        var filter;
64565        var type;
64566
64567        for (var i = 0, len = image_types.length; i < len; ++i) {
64568            type = image_types[i];
64569            filter_name = _image_to_filter[type];
64570            for (var j = 0, count = _filters.length; j < count; ++j) {
64571                if (_filters[j].filterName == filter_name) {
64572                    image_filters[type] = _filters[j];
64573                }
64574            }
64575        }
64576
64577        return image_filters;
64578    };
64579
64580    /**
64581     * Gets the filter name and default value
64582     *
64583     * @param {string} image_type 'ish', 'expression', 'nissl', 'ache', or 'ihc:parvalbumin'
64584     * @returns {object} hash of filter_name:default_filter_value or undefined if image_type not found
64585     */
64586    this.get_filter = function(image_type) {
64587        var filter;
64588        var filter_name = _filters[image_type];
64589
64590        if (filter_name != undefined) {
64591            for (var i = 0, len = _filters.length; i < len; ++i) {
64592                if (_filters[i].filterName == filter_name) {
64593                    filter = _filters[i];
64594                    break;
64595                }
64596            }
64597        }
64598
64599        return filter;
64600    };
64601}
64602;
64603"use strict";
64604/**
64605 * The ISH Gene widget class.
64606 * <p></p>
64607 * The ish_gene_info widget presents the information contained in the data
64608 * object returned by a AIBS.DATA.ISH_DAO.experiment_detail().probes[0].gene object.
64609 * <p></p>
64610 * This is a wrapper around the info_widget.
64611 * @class
64612 * @param container     The DOM element or jQuery object that contains the info_widget. <p></p>
64613 */
64614AIBS.UI.ish_gene_widget = (function($j) {
64615
64616 return (
64617
64618        function(container) {
64619
64620        // public properties
64621            this.container = $j(container);
64622
64623        // public methods
64624            /**
64625             * Set the Gene data to be displayed
64626             * @param data  AIBS.DATA.ISH_DAO.experiment_detail().probes[0].gene
64627             */
64628            this.set_data = function(data) {
64629                _info_widget.set_data(data);
64630            }
64631
64632         /**
64633          * A unique ID for this widget. This is not an HTML ID, it is an integer value scoped to
64634          * AIBS.UI.
64635          */
64636            this.id = function() {
64637                if(_info_widget)
64638                    return(_info_widget.id);
64639
64640                return(null);
64641            }
64642
64643
64644        // private properties
64645            /**
64646             * @private
64647             * @scope AIBS.UI.ish_gene_widget
64648             */
64649            var _self = this;
64650            var _info_widget = null;
64651
64652         /**
64653          * The layout, data_map & decorators are defined for this info widget.
64654          */
64655            function build_table() {
64656
64657                // describe the table
64658                var layout = {
64659                    columns:4,
64660                    rows:4,
64661                    spans:[
64662                       {row:0, column:0, column_span:4, row_span:1},
64663                       {row:1, column:1, column_span:3, row_span:1}
64664                    ]
64665                };
64666                // describe how data should be mapped to cells in the table
64667                var data_map = [
64668                    {field:'acronym', cell:[0,0]},
64669                    {field:'name', cell:[1,1]},
64670                    {field:'gene_aliases', cell:[1,2]},
64671                    {field:'entrez_id', cell:[3,2]},
64672                    {field:'organism.name', cell:[1,3]},
64673                    {field:'chromosome.name', cell:[3,3]}
64674                ];
64675                // which cells require a callback to massage the data presentation before
64676                // placing it in the table.
64677                var decorators = {
64678                    '0,0':add_title,
64679                    '1,2':list_aliases
64680                };
64681
64682                var options = {
64683                    table_class:'_gene_widget',
64684                    jquery_theme:true
64685                };
64686                // build the widget
64687                _info_widget = new AIBS.UI.info_widget(_self.container, layout, data_map, decorators, options);
64688                // set the static field labels
64689                set_labels(_info_widget);
64690            }
64691
64692         /**
64693          * The static field labels are set
64694          * @param widget The underlying info_widget
64695          */
64696            function set_labels(widget) {
64697                _info_widget.set_content(0,0, "Gene");
64698                _info_widget.set_content(0,1, "Name");
64699                _info_widget.set_content(0,2, "Aliases");
64700                _info_widget.set_content(0,3, "Organism");
64701                _info_widget.set_content(2,2, "Entrez ID");
64702                _info_widget.set_content(2,3, "Chromosome");
64703            }
64704
64705         /**
64706          *
64707          * @param container The table cell that holds the title
64708          * @param data The data in the container
64709          * @returns The title string.
64710          */
64711            function add_title(container, data) {
64712               if(data)
64713                 return("Gene " + data);
64714
64715               return("Gene");
64716            }
64717
64718
64719            /**
64720             * A decorator function that creates a comma-delimited list out of an array of alias acronyms.
64721             * @param container
64722             * @param data
64723             */
64724            function list_aliases(container, data) {
64725                if(!data)
64726                    return("");
64727
64728                var s = "";
64729                for(var i = 0; i < data.length; i++) {
64730                    if(i)
64731                        s += ", ";
64732                    s += data[i].acronym
64733                }
64734                var w = $j(container).width();
64735                var d = "<div class='_pw_list' style='width:" + w + "px;' title='" + s + "'>" + s + "</div>";
64736                return(d);
64737            }
64738
64739         /**
64740          * builds the table.
64741          */
64742            function init() {
64743               build_table();
64744            }
64745
64746            init();
64747        }
64748 );
64749
64750})(jQuery);
64751/**
64752 * This file is intended to encapsulate model data for ISH image filters, and is intended to by used by
64753 * several different page components, like IVT, thumbnails, SIV. Ideally, this might be located in a
64754 * different directory, such as assets/javascripts/models.
64755 *
64756 * This object provides custom delegate functions related to ISH image determination to aiclient's image_set_model,
64757 * as well as custom functions called directly to to get image type information.
64758 */
64759
64760function ish_image_types() {
64761    /**
64762     * Gets the primary image type of the dataset by examining the treatment data in the experiment.
64763     *
64764     * Use as the "determine_primary_image_type" delegate for aiclient's image_set_model
64765     *
64766     * @param {number} experiment experiment (dataset) object
64767     * @returns {string} 'atlas', 'ish', 'nissl', 'hp_yellow' or '' if unable to determine primary type
64768     */
64769    this.get_primary_image_type = function(experiment) {
64770        var primary_image_type = '';
64771
64772        var atlas = (experiment.atlas != undefined && experiment.atlas == true);
64773        var treatment = experiment.treatments ? (experiment.treatments.length > 0 ? experiment.treatments[0].name : undefined) : undefined;
64774
64775        if (atlas)
64776            primary_image_type = 'atlas';
64777        else {
64778            switch (treatment) {
64779                case 'NISSL':
64780                    primary_image_type = 'nissl';
64781                    break;
64782                case 'ISH':
64783                    primary_image_type = 'ish'
64784                    break
64785                case 'AchE':
64786                    primary_image_type = 'ache'
64787                    break;
64788                case 'IHC:Parvalbumin':
64789                    primary_image_type = 'ihc:parvalbumin'
64790                    break;
64791                default:
64792                    primary_image_type = '';
64793                    break;
64794            }
64795        }
64796
64797        return primary_image_type;
64798    };
64799
64800    /**
64801     * Traverse the array of image metadata, and determine what image types are present for display in menus.
64802     * The return values are either the display values (used in a menu UI) or the actual image type value.
64803     * See the 'forDisplay' argument description.
64804     *
64805     * @param {string} primary_image_type name of primary image for the dataset (lowercase value)
64806     * @param {array} images
64807     * @param [boolean} forDisplay set true to get display values of image types, set false to get actual image type. Defaults to false.
64808     * @returns {array} elements are one or more of 'Atlas', 'ISH', 'Expression', 'NISSL', 'AchE', 'Parvalbumin', either mixed or lower case.
64809     */
64810    // the_data, image, relation ('alternate', 'associate')
64811    this.get_image_types = function(primary_image_type, images, forDisplay) {
64812        var display = forDisplay ? (forDisplay == true ? true : false) : false;
64813
64814        var image_types_array = [];
64815        var image_types = {};
64816
64817        switch (primary_image_type) {
64818            case 'atlas':
64819                image_types.Atlas = true;
64820                break;
64821            case 'ish':
64822                image_types.ISH = true;
64823                break;
64824            case 'nissl':
64825                image_types.NISSL = true;
64826                break;
64827            case 'ache':
64828                image_types.AchE = true;
64829                break;
64830            case 'ihc:parvalbumin':
64831                image_types['IHC:Parvalbumin'] = true;
64832                break;
64833            default:
64834                image_types.ISH = true;
64835                break;
64836        }
64837
64838        // Determine if any secondary image types are present; these will be 'Expression' or 'NISSL'
64839        var expression_image;
64840        var len = images.length;
64841        for (var i = 0; i < len; ++i) {
64842            expression_image = images[i].alternate_images ? (images[i].alternate_images.length ? images[i].alternate_images[0] : {path:''}) : {path:''};
64843            if (expression_image.path != '' && primary_image_type != 'atlas') {
64844                // Edge case: eliminate 'false positive' expression images when image_type == 'Atlas - Developing Mouse' or 'Atlas - Adult Mouse'
64845                // which occurs when the dataset is from the 'Additional Reference Data' of the DevMouse Reference Atlas theme
64846                image_types.Expression = expression_image.image_type.indexOf('Atlas') == -1 ? true : false;
64847            }
64848
64849            // for ISH zap viewers we assume that the one-and-only associate is a nissl
64850            if (images[i].associates && images[i].associates.length == 1) {
64851                image_types.NISSL = true;
64852            }
64853        }
64854
64855        // copy valid image_type hash keys into an array
64856        for (var key in image_types) {
64857            if (image_types.hasOwnProperty(key)) {
64858                if (image_types[key] == true)
64859                    image_types_array.push(key);
64860            }
64861        }
64862
64863        // convert display value to actual value
64864        if (display == false) {
64865            for (i = 0, len = image_types_array.length; i < len; ++i) {
64866                image_types_array[i] = image_types_array[i].toLowerCase();
64867            }
64868        }
64869
64870        return image_types_array;
64871    };
64872}
64873;
64874"use strict";
64875/**
64876 * The ISH Probe widget class.
64877 * <p></p>
64878 * The ish_probe_widget widget presents some of the information contained in the data
64879 * object returned by a AIBS.DATA.ISH_DAO.experiment_detail().probes[0] object.
64880 * <p></p>
64881 * This is a wrapper around the info_widget.
64882 * @class
64883 * @param container     The DOM element or jQuery object that contains the info_widget. <p></p>
64884 */
64885AIBS.UI.ish_probe_widget = (function($j) {
64886
64887 return (
64888
64889        function(container) {
64890
64891        // public properties
64892            this.container = $j(container);
64893
64894        /**
64895         * Set the data to be displayed.
64896         * @param data  AIBS.DATA.ISH_DAO.experiment_detail().probes[0]
64897         */
64898        this.set_data = function(data) {
64899            _info_widget.set_data(data);
64900        }
64901
64902        /**
64903         * A unique ID for this widget. This is not an HTML ID, it is an integer value scoped to
64904         * AIBS.UI.
64905         */
64906        this.id = function() {
64907            if(_info_widget)
64908                return(_info_widget.id);
64909
64910            return(null);
64911        }
64912
64913
64914        /**
64915         * @private
64916         * @scope AIBS.UI.ish_probe_widget
64917         */
64918            var _self = this;
64919            var _info_widget = null;
64920            var _tester_div_id = '_piw_test_size_tester';
64921
64922
64923            function build_table() {
64924
64925                // describe the table
64926                var layout = {
64927                    columns:4,
64928                    rows:5,
64929                    spans:[
64930                       {row:0, column:0, column_span:4, row_span:1},
64931                       {row:4, column:0, column_span:4, row_span:1}
64932                    ]
64933                };
64934                // describe how data should be mapped to cells in the table
64935                var data_map = [
64936                    {field:'name', cell:[0,0]},
64937
64938                    {field:'probe_type', cell:[1,1]},
64939                    {field:'orientation.name', cell:[3,1]},
64940
64941                    {field:'ncbi_accession_number', cell:[1,2]},
64942                    {field:'forward_primer_sequence.sequence_data', cell:[3,2]},
64943
64944                    {field:'gi', cell:[1,3]},
64945                    {field:'reverse_primer_sequence.sequence_data', cell:[3,3]},
64946
64947                    {field:'predicted_sequence.sequence_data', cell:[0,4]}
64948                ];
64949                // which cells require a callback to massage the data
64950                // presentation before placing it in the table.
64951                var decorators = {
64952                    '0,0':add_title,
64953                    '0,4':wrap_sequence
64954                };
64955
64956                // NOTE we're telling it to call the decorator
64957                // functions every time it redraws due to resize, etc.
64958                var options = {
64959                    table_class:'_probe_widget',
64960                    redecorate:true,
64961                    jquery_theme:true
64962                };
64963
64964                // build the widget.
64965                _info_widget = new AIBS.UI.info_widget(_self.container, layout, data_map, decorators, options);
64966
64967                // set the static field labels
64968                set_probe_labels(_info_widget);
64969            }
64970
64971
64972            function build_text_tester() {
64973                var tester_div = $j("<div id='" + _tester_div_id + "'></div>");
64974                tester_div.css({
64975                    'position':'absolute',
64976                    'visibility':'hidden',
64977                    'height':'auto',
64978                    'width':'auto'
64979                 });
64980                tester_div.appendTo(document.body);
64981            }
64982
64983            function set_probe_labels(widget) {
64984                _info_widget.set_content(0,0, "Probe");
64985
64986                _info_widget.set_content(0,1, "Type");
64987                _info_widget.set_content(2,1, "Orientation");
64988
64989                _info_widget.set_content(0,2, "NCBI Accession");
64990                _info_widget.set_content(2,2, "Forward Primer");
64991
64992                _info_widget.set_content(0,3, "GI");
64993                _info_widget.set_content(2,3, "Reverse primer");
64994
64995                _info_widget.set_content(0,4, "Sequence");
64996            }
64997
64998            // the sequence data is a very long unbroken string of G,T,C & A.
64999            // this decorator method breaks it up to fit into its container.
65000            function wrap_sequence(container, data) {
65001 /*
65002               // the sequence string is placed into a hidden
65003               // div so we can measure it and get the correct size, regardless
65004               // of what font or other attributes are being used to display it.
65005
65006               // set the font size on the tester div to
65007               // whatever it will be in our actual container
65008               var font_size = $j(container).css('fontSize');
65009               var test_div = $j('#'+_tester_div_id);
65010               test_div.css({fontSize:font_size});
65011
65012               // place the content in the the test div and measure it
65013               test_div.html(data);
65014               var text_pixel_width = $j('#'+_tester_div_id).width();
65015               // get rid of the content in the tester div; even though it's
65016               // hidden, long content will cause scroll bars to appear
65017               // in the browser.
65018               test_div.html("");
65019
65020               // figure out how to break the long sequence string so it will fit
65021               // in the container.
65022               var data_chars = data.length;
65023               var char_width = text_pixel_width / data_chars;
65024
65025               var container_pixel_width = $j(_self.container).width() - 20;
65026               var line_char_count = Math.ceil(container_pixel_width/char_width);
65027               var pieces = Math.ceil((data_chars >= line_char_count) ? data_chars/line_char_count : 1);
65028
65029               // Add a label and some line breaks to the sequence string.
65030               var new_string = "<b>Sequence:</b><br/>";
65031               for(var i = 0; i < pieces-1; i++) {
65032                   new_string += data.substr(i*line_char_count,line_char_count) + "<br>";
65033               }
65034               new_string += data.substr(i*line_char_count);
65035               return(new_string);
65036
65037*/
65038                return("<b>Sequence: </b>" + data);
65039            }
65040
65041            // decorator to add the label "Probe" to the probe name in the title bar
65042            function add_title(container, data) {
65043               if(data)
65044                 return("Probe " + data);
65045
65046               return("Probe");
65047            }
65048
65049            function init() {
65050               build_table();
65051               build_text_tester();
65052            }
65053
65054            init();
65055        }
65056 );
65057
65058
65059})(jQuery);
65060
65061"use strict";
65062/**
65063 * The ISH Specimen widget class.
65064 * <p></p>
65065 * The ish_specimen_widget widget presents some of the information contained in the data
65066 * object returned by a AIBS.DATA.ISH_DAO.experiment_detail().specimen object.
65067 * <p></p>
65068 * This is a wrapper around the info_widget.
65069 * @class
65070 * @param container     The DOM element or jQuery object that contains the info_widget. <p></p>
65071 */
65072AIBS.UI.ish_specimen_widget = (function($j) {
65073
65074 return (
65075        function(container) {
65076
65077        // public properties
65078            this.container = $j(container);
65079
65080        /**
65081         * Set the data to be displayed.
65082         * @param data  AIBS.DATA.ISH_DAO.experiment_detail().specimen
65083         */
65084        this.set_data = function(data) {
65085            _info_widget.set_data(data);
65086        }
65087
65088        /**
65089         * A unique ID for this widget. This is not an HTML ID, it is an integer value scoped to
65090         * AIBS.UI.
65091         */
65092        this.id = function() {
65093            if(_info_widget)
65094                return(_info_widget.id);
65095
65096            return(null);
65097        }
65098
65099
65100        /**
65101         * @private
65102         * @scope AIBS.UI.ish_specimen_widget
65103         */
65104            var _self = this;
65105            var _info_widget = null;
65106
65107
65108            function build_table() {
65109
65110                // describe the table
65111                var layout = {
65112                    columns:4,
65113                    rows:3,
65114                    spans:[
65115                       {row:0, column:0, column_span:4, row_span:1}
65116                    ]
65117                };
65118                // describe how data should be mapped to cells in the table
65119                var data_map = [
65120                    {field:'name', cell:[0,0]},
65121                    {field:'donor.organism.name', cell:[1,1]},
65122                    {field:'donor.age.days', cell:[1,2]},
65123                    {field:'donor.strain', cell:[3,1]},
65124                    {field:'donor.sex', cell:[3,2]}
65125                ];
65126                // which cells require a callback to massage the data presentation before
65127                // placing it in the table.
65128                var decorators = {
65129                    '0,0':add_title
65130                };
65131
65132                var options = {
65133                    table_class:'_specimen_widget',
65134                    redecorate:true,
65135                    jquery_theme:true
65136                };
65137
65138                // build the widget
65139                _info_widget = new AIBS.UI.info_widget(_self.container, layout, data_map, decorators, options);
65140                // set the static field labels
65141                set_labels(_info_widget);
65142            }
65143
65144            function set_labels(widget) {
65145                _info_widget.set_content(0,0, "Specimen");
65146                _info_widget.set_content(0,1, "Organism");
65147                _info_widget.set_content(2,1, "Strain");
65148                _info_widget.set_content(2,2, "Sex");
65149                _info_widget.set_content(0,2, "Age");
65150            }
65151
65152
65153            function add_title(container, data) {
65154               if(data)
65155                 return("Specimen " + data);
65156
65157               return("Specimen");
65158            }
65159
65160            function init() {
65161               build_table();
65162            }
65163
65164            init();
65165        }
65166  );
65167
65168
65169})(jQuery);
65170"use strict";
65171/**
65172 * The ISH Specimen widget class.
65173 * <p></p>
65174 * The ish_specimen_widget widget presents some of the information contained in the data
65175 * object returned by a AIBS.DATA.ISH_DAO.experiment_detail().specimen object.
65176 * <p></p>
65177 * This is a wrapper around the info_widget.
65178 * @class
65179 * @param container     The DOM element or jQuery object that contains the info_widget. <p></p>
65180 */
65181AIBS.UI.ish_specimen_widget_narrow = (function($j) {
65182
65183 return (
65184        function(container) {
65185
65186        // public properties
65187            this.container = $j(container);
65188
65189        /**
65190         * Set the data to be displayed.
65191         * @param data  AIBS.DATA.ISH_DAO.experiment_detail().specimen
65192         */
65193        this.set_data = function(data) {
65194            _info_widget.set_data(data);
65195        }
65196
65197        /**
65198         * A unique ID for this widget. This is not an HTML ID, it is an integer value scoped to
65199         * AIBS.UI.
65200         */
65201        this.id = function() {
65202            if(_info_widget)
65203                return(_info_widget.id);
65204
65205            return(null);
65206        }
65207
65208
65209        /**
65210         * @private
65211         * @scope AIBS.UI.ish_specimen_widget
65212         */
65213            var _self = this;
65214            var _info_widget = null;
65215
65216
65217            function build_table() {
65218
65219                // describe the table
65220                var layout = {
65221                    columns:2,
65222                    rows:5,
65223                    spans:[
65224                       {row:0, column:0, column_span:2, row_span:1}
65225                    ]
65226                };
65227                // describe how data should be mapped to cells in the table
65228                var data_map = [
65229                    {field:'name', cell:[0,0]},
65230                    {field:'donor.organism.name', cell:[1,1]},
65231                    {field:'donor.strain', cell:[1,2]},
65232                    {field:'donor.age.days', cell:[1,3]},
65233                    {field:'donor.sex', cell:[1,4]}
65234                ];
65235                // which cells require a callback to massage the data presentation before
65236                // placing it in the table.
65237                var decorators = {
65238                    '0,0':add_title
65239                };
65240
65241                var options = {
65242                    table_class:'_specimen_widget',
65243                    redecorate:true,
65244                    jquery_theme:true
65245                };
65246
65247                // build the widget
65248                _info_widget = new AIBS.UI.info_widget(_self.container, layout, data_map, decorators, options);
65249                // set the static field labels
65250                set_labels(_info_widget);
65251            }
65252
65253            function set_labels(widget) {
65254                _info_widget.set_content(0,0, "Specimen");
65255                _info_widget.set_content(0,1, "Organism");
65256                _info_widget.set_content(0,2, "Strain");
65257                _info_widget.set_content(0,3, "Age");
65258                _info_widget.set_content(0,4, "Sex");
65259            }
65260
65261            function add_title(container, data) {
65262               if(data)
65263                 return("Specimen " + data);
65264
65265               return("Specimen");
65266            }
65267
65268            function init() {
65269               build_table();
65270            }
65271
65272            init();
65273        }
65274  );
65275
65276
65277})(jQuery);
65278"use strict";
65279/**
65280 * The ISH Specimen widget class.
65281 * <p></p>
65282 * The ish_specimen_widget_narrow_for_nhp presents a subset of the information contained in the data
65283 * object returned by a AIBS.DATA.ISH_DAO.experiment_detail().specimen object.
65284 * <p></p>
65285 * This is a wrapper around the info_widget.
65286 * @class
65287 * @param container     The DOM element or jQuery object that contains the info_widget. <p></p>
65288 */
65289AIBS.UI.ish_specimen_widget_narrow_for_nhp = (function ($j) {
65290
65291    return (
65292        function (container, options) {
65293
65294            // public properties
65295            this.container = $j(container);
65296
65297            // these settings are appropriate for NHP
65298            this.settings = {
65299                jquery_theme: true,
65300                "layout": {
65301                    "columns": 2,
65302                    "rows": 7,
65303                    "spans": [
65304                        {"row": 0, "column": 0, "column_span": 2, "row_span": 1}
65305                    ]
65306                },
65307                "data_map": {
65308                    "name": [0, 0],
65309                    "id": [1, 1],
65310                    "donor.organism.name": [1, 2],
65311                    "donor.age.name": [1, 3],
65312                    "donor.sex": [1, 4],
65313                    "structure.name": [1, 5],
65314                    "hemisphere": [1, 6]
65315                },
65316                "labels": {
65317                    "Specimen": [0, 0],
65318                    "ID": [0, 1],
65319                    "Organism": [0, 2],
65320                    "Age": [0, 3],
65321                    "Sex": [0, 4],
65322                    "Tissue Location": [0, 5],
65323                    "Hemisphere": [0, 6]
65324                },
65325                "decorators": {
65326                    "0,0": {
65327                        "name": "AIBS.UI.decorators.add_specimen_title",
65328                        "options": {}
65329                    },
65330                    "1,1": {
65331                        "name": "AIBS.UI.decorators.specimen_link_decorator",
65332                        "options": {
65333                            "path": "/ish/specimen/show/"
65334                        }
65335                    }
65336                },
65337                info_opts: {
65338                    table_class: '_specimen_widget',
65339                    redecorate: true,
65340                    jquery_theme: true
65341                }
65342            };
65343            $j.extend(true, this.settings, options);
65344
65345            // public methods
65346            /**
65347             * Set the data to be displayed.
65348             * @param data  AIBS.DATA.ISH_DAO.experiment_detail().specimen
65349             */
65350            this.set_data = function (data) {
65351                _info_widget.set_data(data);
65352            };
65353
65354            /**
65355             * A unique ID for this widget. This is not an HTML ID, it is an integer value scoped to
65356             * AIBS.UI.
65357             */
65358            this.id = function () {
65359                if (_info_widget)
65360                    return(_info_widget.id);
65361
65362                return(null);
65363            };
65364
65365
65366            // private properties
65367
65368            var _self = this;
65369            var _info_widget = null;
65370
65371
65372            function build_table() {
65373
65374                _self.settings.info_opts.decorator_options = {};
65375                for (var key in _self.settings.decorators) {
65376                    var dec_options = _self.settings.decorators[key].options;
65377                    _self.settings.decorators[key] = eval(_self.settings.decorators[key].name);
65378                    _self.settings.info_opts.decorator_options[key] = dec_options;
65379                }
65380
65381                // build the widget
65382                _info_widget = new AIBS.UI.info_widget(
65383                    _self.container,
65384                    _self.settings.layout,
65385                    build_data_map(_self.settings.data_map),
65386                    _self.settings.decorators,
65387                    _self.settings.info_opts
65388                );
65389
65390                // set the static field labels
65391                set_labels(_info_widget);
65392            }
65393
65394            function set_labels(widget) {
65395
65396                for (var key in _self.settings.labels) {
65397                    var coords = _self.settings.labels[key];
65398                    _info_widget.set_content(coords[0], coords[1], key);
65399                }
65400            }
65401
65402            function build_data_map(map) {
65403
65404                var map_array = [];
65405                for (var key in map) {
65406                    map_array.push({
65407                        field: key,
65408                        cell: map[key]
65409                    })
65410                }
65411                return(map_array);
65412            }
65413
65414            function init() {
65415                build_table();
65416            }
65417
65418            init();
65419        }
65420        );
65421
65422})(jQuery);
65423"use strict";
65424/**
65425 * The ISH Specimen widget class.
65426 * <p></p>
65427 * The ish_specimen_widget widget presents some of the information contained in the data
65428 * object returned by a AIBS.DATA.ISH_DAO.experiment_detail() object.
65429 * <p></p>
65430 * This is a wrapper around the info_widget.
65431 * @class
65432 * @param container     The DOM element or jQuery object that contains the info_widget. <p></p>
65433 */
65434AIBS.UI.related_data_widget = (function($j) {
65435
65436 return (
65437        function(container, related_url) {
65438
65439        // public properties
65440            this.container = $j(container);
65441            this.related_url = related_url;
65442
65443        /**
65444         * Set the data to be displayed.
65445         * @param data  AIBS.DATA.ISH_DAO.experiment_detail()
65446         */
65447        this.set_data = function(data) {
65448            _info_widget.set_data(data);
65449        }
65450
65451        /**
65452         * A unique ID for this widget. This is not an HTML ID, it is an integer value scoped to
65453         * AIBS.UI.
65454         */
65455        this.id = function() {
65456            if(_info_widget)
65457                return(_info_widget.id);
65458
65459            return(null);
65460        }
65461
65462
65463        /**
65464         * @private
65465         * @scope AIBS.UI.ish_specimen_widget
65466         */
65467            var _self = this;
65468            var _info_widget = null;
65469
65470
65471            function build_table() {
65472
65473                // describe the table
65474                var layout = {
65475                    columns:1,
65476                    rows:2,
65477                    spans:[]
65478                };
65479                // describe how data should be mapped to cells in the table
65480                var data_map = [
65481                    {field:'id', cell:[0,1]} // experiment id
65482                ];
65483
65484                var decorators = {
65485                    '0,1':make_related_link
65486                };
65487
65488                var options = {
65489                    table_class:'_related_data',
65490                    jquery_theme:true
65491                };
65492
65493                // build the widget
65494                _info_widget = new AIBS.UI.info_widget(_self.container, layout, data_map, decorators, options);
65495                // set the static field labels
65496                set_labels(_info_widget);
65497            }
65498
65499            function set_labels(widget) {
65500                _info_widget.set_content(0,0, "Related Institute Data");
65501            }
65502
65503            function make_related_link(container, data) {
65504                if(!data)
65505                    return("");
65506
65507                var img_path =  _self.related_url + data;
65508                var link_path = img_path.replace('.jpg', '.html');
65509
65510                var img_link = "<a href='" + link_path + "' class='_related_data_img_link'><img src='" + img_path + "'/></a>";
65511                return(img_link);
65512            }
65513
65514            function init() {
65515               build_table();
65516            }
65517
65518            init();
65519        }
65520  );
65521
65522
65523})(jQuery);
65524/**
65525 * The section info widget class.
65526 * <p></p>
65527 * The section_info_narrow widget presents some of the information contained in the data
65528 * object returned by a AIBS.DATA.ISH_DAO.experiment_detail() object.
65529 * <p></p>
65530 * This is a wrapper around the info_widget.
65531 * @class
65532 * @param container     The DOM element or jQuery object that contains the info_widget. <p></p>
65533 */
65534
65535AIBS.UI.section_info_narrow = (function($j) {
65536
65537 return (
65538         function(container, options) {
65539
65540        // public properties
65541            this.container = $j(container);
65542/*
65543            var layout = {
65544                columns:2,
65545                rows:6,
65546                spans:[
65547                   {row:0, column:0, column_span:2, row_span:1}
65548                ]
65549            };
65550            // describe how data should be mapped to cells in the table
65551            var data_map = [
65552                {field:'gene_acronym', cell:[1,1]},
65553                {field:'experiment_id', cell:[1,2]},
65554                {field:'section_number', cell:[1,3]},
65555                {field:'treatments', cell:[1,4]},
65556                {field:'study', cell:[1,5]}
65557            ];
65558
65559            _info_widget.set_content(0,0, "Section Information");
65560            _info_widget.set_content(0,1, "Gene");
65561            _info_widget.set_content(0,2, "Experiment");
65562            _info_widget.set_content(0,3, "Section Number");
65563            _info_widget.set_content(0,4, "Treatments");
65564            _info_widget.set_content(0,5, "Study");
65565
65566
65567            // which cells require a callback to massage the data presentation before
65568            // placing it in the table.
65569            var decorators = {
65570                '1,4':list_treatments
65571            };
65572
65573            var options = {
65574                table_class:'_section_info_widget ui-widget-content',
65575                jquery_theme:true
65576            };
65577 */
65578
65579
65580            // these settings are appropriate for ABA, probably mouse in general
65581            this.settings = {
65582                jquery_theme:true,
65583                "layout":{
65584                    "columns":2,
65585                    "rows":6,
65586                    "spans":[
65587                       {"row":0, "column":0, "column_span":2, "row_span":1}
65588                    ]
65589                },
65590                "data_map":{
65591                    "gene":[1,1],
65592                    "experiment_id":[1,2],
65593                    "section_number":[1,3],
65594                    "treatments":[1,4],
65595                    "study":[1,5]
65596                },
65597                "labels":{
65598                    "Section Information":[0,0],
65599                    "Gene":[0,1],
65600                    "Experiment":[0,2],
65601                    "Section Number":[0,3],
65602                    "Treatments":[0,4],
65603                    "Study":[0,5]
65604                },
65605                "decorators":{
65606                     "1,4":{
65607                        "name":"AIBS.UI.decorators.list_treatments",
65608                        "options":{}
65609                     }
65610                },
65611                "info_opts":{
65612                    "table_class":"_section_info_widget ui-widget-content",
65613                    "jquery_theme":true
65614                }
65615            };
65616            $j.extend(true, this.settings, options);
65617
65618
65619        // public methods
65620            // data is expected to be the graph of goodies returned by the ish_dao.experiment_specimen method
65621            this.set_data = function(data) {
65622                _info_widget.set_data(data);
65623            }
65624
65625            this.id = function() {
65626                if(_info_widget)
65627                    return(_info_widget.id);
65628
65629                return(null);
65630            }
65631
65632
65633        // private properties
65634            var _self = this;
65635            var _info_widget = null;
65636
65637
65638            function build_table() {
65639
65640                _self.settings.info_opts.decorator_options = {};
65641                for(var key in _self.settings.decorators) {
65642                    var dec_options = _self.settings.decorators[key].options;
65643                   _self.settings.decorators[key] = eval(_self.settings.decorators[key].name);
65644                   _self.settings.info_opts.decorator_options[key] = dec_options;
65645                }
65646
65647                _info_widget = new AIBS.UI.info_widget(
65648                    _self.container,
65649                    _self.settings.layout,
65650                    build_data_map(_self.settings.data_map),
65651                    _self.settings.decorators,
65652                    _self.settings.info_opts
65653                );
65654                // set the static field labels
65655                set_labels(_info_widget);
65656            }
65657
65658            function set_labels(widget) {
65659
65660                for(var key in _self.settings.labels) {
65661                    var coords = _self.settings.labels[key];
65662                    _info_widget.set_content(coords[0], coords[1], key);
65663                }
65664            }
65665
65666            function build_data_map(map) {
65667
65668                var map_array = [];
65669                for(var key in map) {
65670                    map_array.push({
65671                        field:key,
65672                        cell:map[key]
65673                    })
65674                }
65675                return(map_array);
65676            }
65677
65678            function init() {
65679               build_table();
65680            }
65681
65682            init();
65683    }
65684  );
65685
65686})(jQuery);
65687
65688/**
65689 * The ISH Specimen donor widget class.
65690 * <p></p>
65691 * The ish_specimen_donor_widget widget presents a subset of the information contained in the data
65692 * object returned by a AIBS.DATA.ISH_DAO.specimen_detail().specimen object.
65693 * <p></p>
65694 * This is a wrapper around the info_widget.
65695 * @class
65696 * @param container     The DOM element or jQuery object that contains the info_widget. <p></p>
65697 */
65698
65699AIBS.UI.ish_specimen_donor_widget = (function($j) {
65700
65701    return (
65702        function(container, options) {
65703
65704            // public properties
65705            this.container = $j(container);
65706            this.settings = {
65707                strain_label:'Race'
65708            };
65709            $j.extend(true, this.settings, options);
65710
65711            /**
65712             * Set the data to be displayed.
65713             * @param data  AIBS.DATA.ISH_DAO.experiment_detail().specimen
65714             */
65715            this.set_data = function(data) {
65716                _info_widget.set_data(data);
65717            };
65718
65719            /**
65720             * A unique ID for this widget. This is not an HTML ID, it is an integer value scoped to
65721             * AIBS.UI.
65722             */
65723            this.id = function() {
65724                if(_info_widget)
65725                    return(_info_widget.id);
65726
65727                return(null);
65728            };
65729
65730
65731            /**
65732             * @private
65733             * @scope AIBS.UI.ish_specimen_widget
65734             */
65735            var _self = this;
65736            var _info_widget = null;
65737
65738
65739            function build_table() {
65740                // describe the table
65741                var layout = {
65742                    columns:4,
65743                    rows:3,
65744                    spans:[
65745                        {row:0, column:0, column_span:4, row_span:1},
65746                    ]
65747                };
65748                // describe how data should be mapped to cells in the table
65749                var data_map = [
65750                    {field:'name', cell:[0,0]},
65751                    {field:'donor.age.name', cell:[1,1]},
65752                    {field:'donor.sex', cell:[3,1]},
65753                    {field:'structure.name', cell:[1,2]},
65754                    {field:'hemisphere', cell:[3,2]}
65755                ];
65756                // which cells require a callback to massage the data presentation before
65757                // placing it in the table.
65758                var decorators = {
65759                    '0,0':add_title
65760                };
65761
65762                var options = {
65763                    table_class:'_specimen_donor_widget',
65764                    redecorate:true,
65765                    jquery_theme:true
65766                };
65767
65768                // build the widget
65769                _info_widget = new AIBS.UI.info_widget(_self.container, layout, data_map, decorators, options);
65770                // set the static field labels
65771                set_labels(_info_widget);
65772            }
65773
65774            function set_labels(widget) {
65775                _info_widget.set_content(0,0, "Specimen");
65776                _info_widget.set_content(0,1, "Age");
65777                _info_widget.set_content(2,1, "Sex");
65778                _info_widget.set_content(0,2, "Tissue Location");
65779                _info_widget.set_content(2,2, "Hemisphere");
65780            }
65781
65782
65783            function add_title(container, data) {
65784                if(data)
65785                    return("Specimen " + data);
65786
65787                return("Specimen");
65788            }
65789
65790            function show_conditions(container, data) {
65791
65792                if(!data)
65793                    return("");
65794
65795                var s = "";
65796                for(var i = 0; i < data.length; i++) {
65797                    if(i)
65798                        s += ", ";
65799                    s += data[i].name
65800                }
65801                var w = $j(container).width();
65802                var d = "<div class='_pw_list' style='width:90%;' title='" + s + "'>" + s + "</div>";
65803                return(d);
65804            }
65805
65806            function init() {
65807                build_table();
65808            }
65809
65810            init();
65811        }
65812        );
65813
65814
65815})(jQuery);
65816"use strict";
65817/**
65818 * Note: this file is a direct parallel to HBA's app/assests/javascripts/pages/siv/all_dual_graphics.js file.
65819 * The primary difference between the two files is that this file hides function and variable scope within
65820 * the single global var, ish_zap_viewer_globals, whereas the HBA file exposes all variables and functions into
65821 * the global scope.
65822 *
65823 */
65824var ish_zap_viewer_globals = {
65825
65826// some legacy codes
65827
65828    // vars
65829    contrastFilter:"contrast",
65830    colorMapFilter:"colormap",
65831    filterName:this.contrastFilter,
65832    sliderDivName:"track",
65833    redraw_delay : 50, // msc
65834    on_get_atlas_id : null, // intialized in setupGraphics
65835    atlas_viewer_service : null, // initialized in setupGraphics
65836
65837    // Note: the dao data is from structure_graph_id = 8, "Non-Human Primate Brain Atlas", which has ontology_id = 4
65838    // Use these warehouse SQL query to get data:
65839    //   select structure_id, acronym from ontology_nodes where structure_graph_id = 8 order by structure_id; -- structure_labels
65840    //   select structure_id, name from ontology_nodes where structure_graph_id = 8 order by structure_id; -- structure_names
65841    //   select structure_id, '0x'||color from ontology_nodes where structure_graph_id = 8 order by structure_id; -- structure_colors
65842    dao:{
65843        structure_labels:"3999,Br,4000,MFC,4001,BN,4002,Amy,4003,HF,4004,Ocx,9999,rCG,10000,rCG1,10001,rCG2,10002,rCG3,10003,rCG4,10004,rCG5,10005,rCG6,10007,rCGwm,10008,mOF,10009,mOF1,10010,mOF2,10011,mOF3,10012,mOF4,10013,mOF5,10014,mOF6,10016,mOFwm,10017,cOF,10018,cOF1,10019,cOF2,10020,cOF3,10021,cOF4,10022,cOF5,10023,cOF6,10025,cOFwm,10026,V1,10027,V1-1,10028,V1-2,10029,V1-3,10030,V1-4,10031,V1-4A,10032,V1-4B,10033,V14Ca,10034,V14Cb,10035,V1-5,10036,V1-6,10038,V1wm,10039,V2,10040,V2-1,10041,V2-2,10042,V2-3,10043,V2-4,10048,V2_L5,10049,V2-6,10051,V2wm,10053,ProS,10054,S,10055,PrS,10058,CA1,10059,CA1or,10060,CA1py,10061,CA1ra,10062,CA1lm,10063,CA2,10064,CA2or,10065,CA2py,10066,CA2ra,10067,CA2lm,10068,CA3,10069,CA3or,10070,CA3py,10071,CA3sl,10072,CA3ra,10073,CA3lm,10074,CA4,10075,DG,10076,DGmo,10077,DGgr,10078,DGsg,10079,DGpf,10080,STR,10081,dSTR,10082,Ca,10086,Pu,10091,vSTR,10092,NAC,10093,NACc,10094,NACs,10095,IsCj,10096,Tu,10097,GP,10098,GPi,10099,GPe,10102,ic,10103,AA,10106,B,10107,Bi,10108,Bd,10109,Bv,10110,AB,10111,ABd,10112,ABv,10116,L,10117,Ldl,10118,Ldm,10119,Lvl,10120,Lvm,10121,CE,10123,CEl,10124,CEm,10125,Me,10128,PL,10132,INA,10134,rPAC,10135,AHA,10136,AHAmc,10137,AHApc,10139,IMG,10140,APir,10141,BNM,12984,dlPF,12986,dlPF2,12987,dlPF3,12988,dlPF4,12989,dlPF5,12990,dlPF6,128011350,Tel,128011354,Cx,128011368,dlPFsg,128011372,dlPFmz,128011378,dlPFcp,128011614,rCGmz,128011624,rCGcpo,128011638,rCGcpi,128011646,rCGsp,128011650,rCGiz,128011654,rCGsz,128011658,rCGszo,128011662,rCGszi,128011666,rCGvz,128011670,rCGvzo,128011674,rCGvzi,128011776,S1mz,128011788,S1cpo,128011796,S1-2,128011800,S1-3,128011804,S1-4,128011808,S1-cpi,128011812,S1-5,128011816,S1-6,128011820,S1sp,128011824,S1iz,128011828,S1sz,128011832,S1szo,128011836,S1szi,128011840,S1vz,128011844,S1vzo,128011848,S1vzi,128011876,LEC,128011900,LEC2,128011936,MEC,128011960,MEC2,128012080,PC,128012084,MPC,128012120,MPC5,128012144,LPC,128012220,DGmz,128012226,DGgra,128012232,DGsga,128012238,DGpfa,128012244,Hip,128012250,CA1mz,128012258,CA1cp,128012264,CA1sp,128012270,CA1iz,128012274,CA1szi,128012278,CA1vz,128012284,CA2mz,128012292,CA2cp,128012298,CA2sp,128012318,CA3mz,128012326,CA3cp,128012332,CA3sp,128012354,SCx,128012368,Scp,128012372,Spy,128012400,PrSr,128012404,PrSc,128012418,V1sg,128012422,V1mz,128012428,V1cp,128012432,V1cpo,128012436,V1-2/3,128012458,V1cpi,128012466,V1sp,128012470,V1icd,128012474,V1iz,128012478,V1sz,128012482,V1szo,
65843128012486,V1szi,128012490,V1vz,128012494,V1vzo,128012498,V1vzi,128012584,RMS,128012588,RMSvz,128012596,CxN,128012616,CM,128012620,PAC,128012634,cPAC,128012638,cPACd,128012678,BLN,128012818,GE,128012822,LGE,128012834,MGE,128012846,CGE,128012920,NACsm,128012926,IsCjm,128012960,Cla,128012976,BF,128013010,Die,128013014,Thal,128013018,DTh,128013070,LGN,128013074,DLG,128013078,DLGmc,128013091,DLGpc,128013113,DLGkc,128013147,ETh,159658800,rCGofz,159658804,rCGifz,159658808,S1ofz,159658816,S1ifz,159658820,DMS,159658824,Hem,159658836,V1ofz,159658840,V1tmz,159658844,V1ifz,159658852,LGEcx,294021746,NCx,294021766,SG,294021774,MZ,294021786,L1,294021800,L2,294021818,L2/3,294021824,L3,294021842,CPo,294021852,L4,294021878,L5,294021896,L6,294021914,CP,294021920,CPi,294021932,SP,294021942,IZ,294021954,TMZ,294021960,OFZ,294021970,SZ,294021980,SZo,294021990,IFZ,294022000,SZi,294022010,VZo,294022020,VZi,294022030,VZ,294022044,WM,294022158,ECx",
65844        structure_names:{3999:"brain",4000:"medial frontal cortex (areas 24, 25 and 32)",4001:"basal nuclei (basal ganglia)",4002:"amygdaloid complex",4003:"hippocampal cortex (hippocampal formation)",4004:"occipital cortex",9999:"rostral cingulate cortex (areas 24 and 32)",10000:"layer I of rostral cingulate cortex",10001:"layer II of rostral cingulate cortex",10002:"layer III of rostral cingulate cortex",10003:"granular layer IV of rostral cingulate cortex",10004:"layer V of rostral cingulate cortex",10005:"layer VI of rostral cingulate cortex",10007:"white matter of rostral cingulate cortex",10008:"medial orbital frontal cortex (area 14)",10009:"layer I of medial orbitofrontal cortex",10010:"layer II of medial orbitofrontal cortex",10011:"layer III of medial orbitofrontal cortex",10012:"granular layer IV of medial orbitofrontal cortex",10013:"layer V of medial orbitofrontal cortex",10014:"layer VI of medial orbitofrontal cortex",10016:"white matter of medial orbitofrontal cortex",10017:"caudal orbital frontal cortex (area 13)",10018:"layer I of caudal orbitofrontal cortex",10019:"layer II of caudal orbitofrontal cortex",10020:"layer III of caudal orbitofrontal cortex",10021:"granular layer IV of caudal orbitofrontal cortex",10022:"layer V of caudal orbitofrontal cortex",10023:"layer VI of caudal orbitofrontal cortex",10025:"white matter of caudal orbitofrontal cortex",10026:"primary visual cortex (V1, striate cortex, area 17)",10027:"layer I of V1",10028:"layer II of V1",10029:"layer III of V1",10030:"granular layer IV of V1",10031:"layer IVA of V1",10032:"layer IVB of V1",10033:"layer IVCa of V1",10034:"layer IVCb of V1",10035:"layer V of V1",10036:"layer VI of V1",10038:"white matter of V1",10039:"parastriate cortex (V2, area 18)",10040:"layer I of V2",10041:"layer II of V2",10042:"layer III of V2",10043:"granular layer IV of V2",10048:"layer V of V2",10049:"layer VI of V2",10051:"white matter of V2",10053:"prosubiculum",10054:"subiculum",10055:"presubicular cortex (presubiculum)",10058:"CA1 region",10059:"stratum oriens of CA1",10060:"stratum pyramidale of CA1",10061:"stratum radiatum of CA1",10062:"stratum lacunosum-moleculare of CA1",10063:"CA2 region",10064:"stratum oriens of CA2",10065:"stratum pyramidale of CA2",10066:"stratum radiatum of CA2",10067:"stratum lacunosum-moleculare of CA2",10068:"CA3 region",10069:"stratum oriens of CA3",10070:"stratum pyramidale of CA3",10071:"hippocampal stratum lucidum of CA3",10072:"stratum radiatum of CA3",10073:"stratum lacunosum-moleculare of CA3",10074:"CA4 region",10075:"dentate gyrus (dentate cortex)",10076:"molecular layer of dentate gyrus (cortex)",10077:"granular layer of dentate gyrus (cortex)",10078:"subgranular zone of dentate gyrus (cortex)",10079:"polyform layer of dentate gyrus (cortex)",10080:"striatum",10081:"dorsal striatum",10082:"caudate nucleus",10086:"putamen",10091:"ventral striatum",10092:"nucleus accumbens",10093:"core of nucleus accumbens",10094:"shell of nucleus accumbens",10095:"islands of Calleja",10096:"olfactory tubercle",10097:"globus pallidus",10098:"internal segment of globus pallidus",10099:"external segment of globus pallidus",10102:"internal 
65844capsule",10103:"anterior amygdaloid area",10106:"basal nucleus (basolateral nucleus)",10107:"intermediate division of basal nucleus",10108:"dorsal (magnocellular) division of basal nucleus",10109:"ventral (parvocellular) division of basal nucleus",10110:"accessory basal nucleus (basomedial nucleus)",10111:"dorsal (magnocellular) division of accessory basal nucleus",10112:"ventral (parvocellular) division of accessory basal nucleus",10116:"lateral nucleus",10117:"dorsal division of lateral nucleus, lateral part",10118:"dorsal division of lateral nucleus, medial part",10119:"ventral division of lateral nucleus, lateral part",10120:"ventral division of lateral nucleus, medial part",10121:"central amygdaloid nucleus",10123:"lateral division of central nucleus",10124:"medial division of central nucleus",10125:"medial nucleus",10128:"paralaminar nucleus",10132:"intercalated nucleus of amygdala",10134:"rostral periamygdaloid cortex (rPAC)",10135:"amygdalohippocampal area",10136:"amygdalohippocampal area, magnocellualr part",10137:"amygdalohippocampal area, parvocelluar part",10139:"amygdaloid intramedullary gray",10140:"amygdalopiriform transition area",10141:"basal nucleus of Meynert",12984:"dorsolateral prefrontal cortex (areas 9 and 46)",12986:"layer II of dorsolateral prefrontal cortex",12987:"layer III of dorsolateral prefrontal cortex",12988:"granular layer IV of dorsolateral prefrontal cortex",12989:"layer V of dorsolateral prefrontal cortex",12990:"layer VI of dorsolateral prefrontal cortex",128011350:"telencephalon",128011354:"cerebral cortex",128011368:"subpial granular zone of dorsolateral prefrontal cortex",128011372:"marginal zone of dorsolateral prefrontal cortex",128011378:"cortical plate of dorsolateral prefrontal cortex",128011614:"marginal zone of rostral cingulate cortex",128011624:"outer cortical plate of rostral cingulate cortex",128011638:"inner cortical plate (infragranular layer) of rostral cingulate cortex",128011646:"subplate zone of rostral cingulate cortex",128011650:"intermediate zone of rostral cingulate cortex",128011654:"subventricular zone of rostral cingulate cortex",128011658:"outer subventricular zone of rostral cingulate cortex",128011662:"inner subventricular zone of rostral cingulate cortex",128011666:"ventr
65844icular zone of rostral cingulate cortex",128011670:"outer ventricular zone of rostral cingulate cortex",128011674:"inner ventricular zone of rostral cingulate cortex",128011776:"marginal zone of S1",128011788:"outer cortical plate of S1",128011796:"layer II of S1",128011800:"layer III of S1",128011804:"granular layer IV of S1",128011808:"inner cortical plate (infragranular layer) of S1",128011812:"layer V of S1",128011816:"layer VI of S1",128011820:"subplate zone of S1",128011824:"intermediate zone of S1",128011828:"subventricular zone of S1",128011832:"outer subventricular zone of S1",128011836:"inner subventricular zone of S1",128011840:"ventricular zone of S1",128011844:"outer ventricular zone of S1",128011848:"inner ventricular zone of S1",128011876:"lateral (anterior) entorhinal cortex (LEC)",128011900:"layer II of LEC",128011936:"medial (posterior) entorhinal cortex (MEC)",128011960:"layer II of MEC",128012080:"perirhinal cortex",128012084:"medial perirhinal cortex (area 35)",128012120:"layer V of perirhinal cortex (area 35)",128012144:"lateral perirhinal cortex (area 36)",128012220:"marginal zone of dentate gyrus (cortex)",128012226:"granular layer anlage of dentate gyrus (cortex)",128012232:"subgranular layer anlage of dentate gyrus (cortex)",128012238:"polyform layer anlage of dentate gyrus (cortex)",128012244:"hippocampal proper",128012250:"marginal zone of CA1",128012258:"hippocampal plate of CA1",128012264:"hippocampal subplate of CA1",128012270:"hippocampal intermediate zone of CA1",128012274:"hippocampal subventricular zone of CA1",128012278:"hippocampal ventricular zone of CA1",128012284:"marginal zone of CA2",128012292:"hippocampal plate of CA2",128012298:"hippocampal subplate of CA2",128012318:"marginal zone of CA3",128012326:"hippocampal plate of CA3",128012332:"hippocampal subplate of CA3",128012354:"subicular cortex",128012368:"hippocampal plate of subiculum",128012372:"pyramidal layer of subiculum",128012400:"rostral (anterior) presubiculum",128012404:"caudal (posterior) presubiculum",128012418:"subpial granular zone of V1",128012422:"marginal zone of V1",128012428:"cortical plate of V1",128012432:"outer cortical plate of V1",128012436:"supragranular layer of V1",128012458:"inner cortical plate (infragranular layer) of V1",128012466:"subplate zone of V1",128012470:"intermediate cell dense zone of V1",128012474:"intermediate zone of V1",128012478:"subventricular zone of V1",128012482:"outer subventricular zone of V1",128012486:"inner subventricular zone of V1",128012490:"ventricular zone of V1",128012494:"outer ventricular zone of V1",128012498:"inner ventricular zone of V1",128012584:"rostral migratory stream (RMS)",128012588:"ventricular portion of RMS",128012596:"cerebral nuclei",128012616:"corticomedial amygdaloid nucleus",128012620:"periamygdaloid cortex (cortical amygdaloid nucleus)",128012634:"caudal periamygdaloid cortex (cPAC)",128012638:"caudal periamygdaloid cortex, dorsal part",128012678:"basolateral nuclear complex",128012818:"ganglionic eminence (VZ in basal nuclei)",128012822:"lateral ganglionic eminence",128012834:"medial ganglionic eminence",128012846:"caudal ganglionic eminence",128012920:"shell of nucleus accumbens, medial part",128012926:"main island of Calleja",128012960:"claustrum",128012976:"basal forebrain",128013010:"diencephalon",128013014:"thalamus",128013018:"dorsal thalamus",128013070:"lateral geniculate nucleus",128013074:"dorsal lateral geniculate nucleus",128013078:"dorsal lateral geniculate nucleus, magnocellular layers",128013091:"dorsal lateral geniculate nucleus, parvocellular layers",128013113:"dorsal lateral geniculate nucleus, koniocellular layers",128013147:"epithalamus",159658800:"outer fiber (plexiform) zone of rostral cingulate cortex",159658804:"inner fiber (plexiform) zone of rostral cingulate cortex",159658808:"outer fiber (plexiform) zone of S1",159658816:"inner fiber (plexiform) zone of S1",159658820:"dentate migratory stream",159658824:"cortical hem",159658836:"outer fiber (plexiform) zone of V1",159658840:"transitory migratory zone of V1",159658844:"inner fiber (plexiform) zone of V1",159658852:"lateral ganglionic eminence-cortex border",294021746:"neocortex",294021766:"subpial granular zone",294021774:"marginal zone",294021786:"layer I",294021800:"layer II",294021818:"layer II/III",294021824:"layer III",294021842:"cortical plate (outer)",294021852:"layer IV",294021878:"layer V",294021896:"layer VI",294021914:"cortical plate",294021920:"cortical plate (inner)",294021932:"subplate",294021942:"intermediate zone",294021954:"transitory migratory zone",294021960:"outer fiber zone",294021970:"subventricular zone",294021980:"subventricular zone (outer)",294021990:"inner fiber zone",294022000:"subventricular zone (inner)",294022010:"ventr
65844icular zone (outer)",294022020:"ventricular zone (inner)",294022030:"ventricular zone",294022044:"white matter",294022158:"entorhinal cortex"},
65845        structure_colors:"3999,0xE0E0E0,4000,0xE26880,4001,0xE2EACA,4002,0xC9E2B1,4003,0xBFB5D5,4004,0xE7ACA1,9999,0xE26880,10000,0xF2E5BB,10001,0xC2A335,10002,0xB8A26D,10003,0xFFE168,10004,0xEC813B,10005,0xED8F56,10007,0xE0E0DE,10008,0xB8A26D,10009,0xF2E5BB,10010,0xC2A335,10011,0xB8A26D,10012,0xFFE168,10013,0xEC813B,10014,0xED8F56,10016,0xE0E0DE,10017,0xCAB781,10018,0xF2E5BB,10019,0xC2A335,10020,0xB8A26D,10021,0xFFE168,10022,0xEC813B,10023,0xED8F56,10025,0xE0E0DE,10026,0xC13331,10027,0xF2E5BB,10028,0xC2A335,10029,0xB8A26D,10030,0xFFE168,10031,0xFFE168,10032,0xFFE168,10033,0xFFE168,10034,0xFFE168,10035,0xEC813B,10036,0xED8F56,10038,0xE0E0DE,10039,0xD14D46,10040,0xF2E5BB,10041,0xC2A335,10042,0xB8A26D,10043,0xFFE168,10048,0xEC813B,10049,0xED8F56,10051,0xE0E0DE,10053,0xAA9DC6,10054,0x9988BB,10055,0xC1A68F,10058,0xAA9DC6,10059,0x8A7AB1,10060,0x694F8C,10061,0xE6E6EC,10062,0xD1CEE2,10063,0x9988BB,10064,0x8A7AB1,10065,0x5D457F,10066,0xE6E6EC,10067,0xD1CEE2,10068,0x9988BB,10069,0x8A7AB1,10070,0x614884,10071,0xE6E6EC,10072,0xE6E6EC,10073,0xD1CEE2,10074,0xAA9DC6,10075,0x8A7AB1,10076,0xE6E6EC,10077,0x543E74,10078,0x725398,10079,0xAA9DC6,10080,0xC1CEA7,10081,0xC1CEA7,10082,0x99A677,10086,0xA8B485,10091,0xC1CEA7,10092,0x91A06E,10093,0x91A06E,10094,0x899662,10095,0x858536,10096,0x9988BB,10097,0xD7E0BF,10098,0xD0D9B2,10099,0xD7E0BF,10102,0xF2F1F0,10103,0xC0DD9D,10106,0x89BA46,10107,0x7EA541,10108,0x779E3F,10109,0x82B146,10110,0x9CCC66,10111,0x7EA541,10112,0x89BA46,10116,0xA6D17A,10117,0x92C851,10118,0x92C851,10119,0x89BA46,10120,0x89BA46,10121,0xAFD58A,10123,0xAFD58A,10124,0xAFD58A,10125,0x9CCC66,10128,0x92C851,10132,0x72983F,10134,0x89BA46,10135,0x89BA46,10136,0x72983F,10137,0x82B146,10139,0x89BA46,10140,0x74A046,10141,0x8F8E42,12984,0xD4B235,12986,0xC2A335,12987,0xB8A26D,12988,0xFFE168,12989,0xEC813B,12990,0xED8F56,128011350,0xEBD6D0,128011354,0xE9CCC2,128011368,0x806D2C,128011372,0xEEE0A8,128011378,0xE49C33,128011614,0
65845xEEE0A8,128011624,0xEDCF37,128011638,0xE49C33,128011646,0xD38E32,128011650,0xEBBFD0,128011654,0xD670A0,128011658,0xDA80A8,128011662,0xD55C92,128011666,0xBA2E2E,128011670,0xD14D46,128011674,0xCE3D39,128011776,0xEEE0A8,128011788,0xEDCF37,128011796,0xC2A335,128011800,0xB8A26D,128011804,0xFFE168,128011808,0xE49C33,128011812,0xEC813B,128011816,0xED8F56,128011820,0xD38E32,128011824,0xEBBFD0,128011828,0xD670A0,128011832,0xDA80A8,128011836,0xD55C92,128011840,0xBA2E2E,128011844,0xD14D46,128011848,0xCE3D39,128011876,0xCBB7A8,128011900,0xCBB7A8,128011936,0xCBB7A8,128011960,0xCBB7A8,128012080,0xB67BB4,128012084,0xB67BB4,128012120,0xB67BB4,128012144,0xB67BB4,128012220,0xE6E6EC,128012226,0x543E74,128012232,0x725398,128012238,0xAA9DC6,128012244,0xBFB5D5,128012250,0xD1CEE2,128012258,0x5D457F,128012264,0xD1CEE2,128012270,0xE6E6EC,128012274,0x674881,128012278,0x5D457F,128012284,0xD1CEE2,128012292,0x5D457F,128012298,0xD1CEE2,128012318,0xD1CEE2,128012326,0x5D457F,128012332,0xD1CEE2,128012354,0x9988BB,128012368,0xF2F1F0,128012372,0x614884,128012400,0xC1A68F,128012404,0xC1A68F,128012418,0x806D2C,128012422,0xEEE0A8,128012428,0xFDD92E,128012432,0xEDCF37,128012436,0xD4B235,128012458,0xE49C33,128012466,0xD38E32,128012470,0xEBBFD0,128012474,0xEBBFD0,128012478,0xD670A0,128012482,0xDA80A8,128012486,0xD55C92,128012490,0xBA2E2E,128012494,0xD14D46,128012498,0xCE3D39,128012584,0xA78D8D,128012588,0x937B7C,128012596,0xD7E9C3,128012616,0xA6D17A,128012620,0x9CCC66,128012634,0x89BA46,128012638,0x9CCC66,128012678,0xA6D17A,128012818,0x81696B,128012822,0x937B7C,128012834,0x8C7373,128012846,0x81696B,128012920,0x899662,128012926,0x858536,128012960,0xAADBCE,128012976,0xDEDDAF,128013010,0xE4CCE3,128013014,0xE4CCE3,128013018,0xCC9FC9,128013070,0x8C4685,128013074,0x8C4685,128013078,0x773671,128013091,0x8C4685,128013113,0xE4CDE4,128013147,0xCC9FC9,159658800,0xE196B7,159658804,0xE3A8C2,159658808,0xE196B7,159658816,0xE3A8C2,159658820,0x50487F,159658824,0x937B7C,159658836,0xE196B7,159658840,0xF0D5E1,159658844,0xE3A8C2,159658852,0x937B7C,294021746,0xE9CCC2,294021766,0x806D2C,294021774,0xEEE0A8,294021786,0xF2E5BB,294021800,0xC2A335,294021818,0xD4B235,294021824,0xB8A26D,294021842,0xEDCF37,294021852,0xFFE168,294021878,0xEC813B,294021896,0xED8F56,294021914,0xFDD92E,294021920,0xE49C33,294021932,0xD38E32,294021942,0xEBBFD0,294021954,0xF0D5E1,294021960,0xE196B7,294021970,0xD670A0,294021980,0xDA80A8,294021990,0xE3A8C2,294022000,0xD55C92,294022010,0xD14D46,294022020,0xCE3D39,294022030,0xBA2E2E,294022044,0xE0E0DE,294022158,0xD2BFB2"
65846    },
65847    structureInfo:"",
65848    self:ish_zap_viewer_globals,
65849    resizeTimer:0,
65850
65851    // state var used by doStructureClick()
65852    isPanning: false,
65853
65854    // overriding zap viewer's key handlers
65855    keyDownEvent:function (evt) {
65856        var target = evt.target;
65857        var series_id = target.id.replace(/\D*/, ''); // strip non-decimal chars from id to grab ImageSeriesId
65858
65859        var viewer_object = _zapviewers[series_id];
65860
65861        if (viewer_object) {
65862            var images = viewer_object.getImages();
65863            var image = viewer_object.getImageInfo();
65864            var pos = viewer_object.getPosition();
65865            var keyCode = evt.memo.keyCode;
65866
65867            switch (keyCode) {
65868                case 84: // "t" = advance all viewers to next image
65869                    hbaMoveAll(true);
65870                    break;
65871                case 87: // "w" = reverse all viewers to previous image
65872                    hbaMoveAll(false);
65873                    break;
65874                /* case 83: // "s" = synch
65875                 doSync(viewer_object);
65876                 break; */
65877                default: // pass unprocessed key events back to Zap Viewer, such as the zoom keys and arrow keys
65878                    evt.keyCode = keyCode;
65879                    var viewer = evt.memo.viewer;
65880                    viewer.doKey(evt);
65881                    break;
65882            }
65883        }
65884    },
65885
65886    // move to next/previous image in all zap viewers on page
65887    // isNext - next if true, previous if false
65888    hbaMoveAll:function (isNext) {
65889        for (var key in _zapviewers) {
65890            var viewer = _zapviewers[key];
65891            var images = viewer.getImages();
65892            var image = viewer.getImageInfo();
65893            var pos = viewer.getPosition();
65894            var targetImage;
65895
65896            if (isNext) {
65897                targetImage = getNextImage(image.id, images);
65898            }
65899            else {
65900                targetImage = getPreviousImage(image.id, images);
65901            }
65902
65903            if (targetImage != null) {
65904                hbaMoveToImage(viewer, targetImage.id, image.tier, pos);
65905            }
65906        }
65907    },
65908
65909    getNextImage:function (currentImageId, images) {
65910        var isNextImage = false;
65911        var nextImage;
65912
65913        for (var key in images) {
65914            if (isNextImage) {
65915                nextImage = images[key];
65916                break;
65917            }
65918
65919            if (images[key].id == currentImageId) {
65920                isNextImage = true;
65921            }
65922        }
65923        return nextImage;
65924    },
65925
65926    getPreviousImage:function (currentImageId, images) {
65927        var prevImage;
65928
65929        for (var key in images) {
65930            if (images[key].id == currentImageId) {
65931                break;
65932            }
65933            else {
65934                prevImage = images[key];
65935            }
65936        }
65937        return prevImage;
65938    },
65939
65940    hbaMoveToImage:function (viewer, image_id, image_tier, position) {
65941        viewer.select(image_id);
65942        viewer.moveToImage(image_id);
65943        var info = viewer.getImageInfo();
65944        var newTier;
65945        // edge case, moving from an image at a higher tier than exists in the new image
65946        if (image_tier > (info.num_tiers - 1)) {
65947            newTier = info.num_tiers - 1;
65948        }
65949        else {
65950            newTier = image_tier;
65951        }
65952        viewer.setPosition('normal', newTier, position.normal.x, position.normal.y);
65953
65954        var evt = new Object();
65955        var memo = new Object();
65956        memo.position = position;
65957        memo.viewer = viewer;
65958        memo.image = viewer.getImageInfo();
65959        evt.memo = memo;
65960        viewer.scalebar.updateScaleBar(evt);
65961
65962        // synch pan, zoom of vector graphics to underlying image
65963        var zapimg = viewer.zapimg;
65964        if (zapimg.enable_graphics && zapimg.graphics !== null && zapimg.graphics.graphics_enabled) {
65965            zapimg.updateGraphicsPosition();
65966        }
65967    },
65968
65969
65970    setPanel:function (viewer) {
65971        var panel = null;
65972
65973        // this can get called by an onImageChange event that is issued before viewer is fully instantiated
65974        if (typeof viewer.getPanel == 'function') {
65975            panel = viewer.getPanel('info_panel');
65976        }
65977
65978        var image = viewer.getImageInfo();
65979
65980        if (panel != null && image != null) {
65981            var image = viewer.getImageInfo();
65982            var panelHTML = "<table class='viewer_key' cell-spacing='0' cell-padding='0'><tr>";
65983
65984            if (viewer.series_data.failed == 'true') {
65985                panelHTML += "<td width='90px'><span class='warning'>&nbsp;FAILED QC&nbsp;</span></td>";
65986            }
65987            if ((viewer.series_data.age != undefined) && (viewer.imgView != "NISSL")) {
65988                panelHTML += "<td width='90px'>Age: <span class='info'>" + viewer.series_data.age + "</span></td>";
65989            }
65990            if (image.section_number !== undefined) {
65991                if (viewer.imgView === "NISSL" && (image.nearest_nissl_sti !== undefined && image.nearest_nissl_sti !== "")) {
65992                    panelHTML += "<td width='110px'>Tissue Index: <span class='info'>" + image.nearest_nissl_sti + "</span></td>";
65993                }
65994                else {
65995                    panelHTML += "<td width='110px'>Tissue Index: <span class='info'>" + image.section_number + "</span></td>";
65996                }
65997            }
65998            if (viewer.series_data.structure_name !== undefined) {
65999                panelHTML += "<td>Location: <span class='info'>" + viewer.series_data.structure_name + "</span></td></tr>";
66000            }
66001            // hotspot info from Nissl graphic layer, SIV Zap only
66002            if (viewer.show_structure_name == true && ish_zap_viewer_globals.structureInfo !== undefined && ish_zap_viewer_globals.structureInfo !== "") {
66003                panelHTML += "<tr><td colspan='4'>Structure Name: <span class='info'>" + ish_zap_viewer_globals.structureInfo + "</span></td>";
66004            }
66005            panelHTML += "</tr></table>";
66006            panel.foreground.innerHTML = panelHTML;
66007        }
66008    },
66009
66010
66011    setupGraphics:function (viewer, options) {
66012        if (options !== undefined) {
66013            ish_zap_viewer_globals.on_get_atlas_id = options.on_get_atlas_id;
66014            ish_zap_viewer_globals.atlas_viewer_service = options.atlas_viewer_service;
66015        }
66016
66017        if (viewer.graphics_enabled == false) {
66018            // if we've tried to enable graphics, but graphics are not enabled,
66019            // let the user know why.  Probably they don't have flash/up-to-date version
66020            viewer.addPanel('no_flash', 'panel_class');
66021            var panel = viewer.getPanel('no_flash');
66022            var flash_warning = viewer.zapimg.graphics.failure_message;
66023            panel.foreground.innerHTML = flash_warning;
66024        }
66025
66026        viewer.zapimg.graphics.addEventListener('STRUCTURE_CLICK', ish_zap_viewer_globals.doStructureClick);
66027        viewer.zapimg.graphics.addEventListener('MOUSE_CLICK', ish_zap_viewer_globals.doStructureClick);
66028        viewer.zapimg.graphics.addEventListener('PAN_DRAG', ish_zap_viewer_globals.doStructureClick);
66029
66030        viewer.zapimg.graphics.addEventListener('STRUCTURE_MOUSE_OVER', ish_zap_viewer_globals.doStructureOver);
66031        viewer.zapimg.graphics.addEventListener('STRUCTURE_MOUSE_OUT', ish_zap_viewer_globals.doStructureOut);
66032
66033        viewer.showing_colors = true;
66034        viewer.showing_shapes = true;
66035        viewer.showing_text = false;
66036        ish_zap_viewer_globals.showText(viewer, viewer.showing_text);
66037        ish_zap_viewer_globals.showOutlines(viewer, viewer.showing_shapes);
66038        ish_zap_viewer_globals.showColors(viewer, viewer.showing_colors);
66039    },
66040
66041    /**
66042     * Construct an url to the Atlas Viewer and open a new window for display
66043     *
66044     * Because there is a bug and it isn't possible to get a STRUCTURE_MOUSE_OUT event, the best we can do is ensure
66045     * that mouse dragging won't cause the unintended launch of Atlas Viewer.
66046     *
66047     * Fixed event sequence for panning: PAN_START_DRAG --> PAN_DRAG(multiple) --> PAN_END_DRAG --> MOUSE_CLICK | STRUCTURE_CLICK
66048     *
66049     * @param id
66050     * @param val
66051     * @param json
66052     * @param evt
66053     */
66054    doStructureClick: function(id, val, json, evt) {
66055        if (evt == 'PAN_DRAG')
66056            ish_zap_viewer_globals.isPanning = true;
66057
66058        if (evt == 'STRUCTURE_CLICK') {
66059            if (ish_zap_viewer_globals.isPanning == true) {
66060                // don't process bogus STRUCTURE_CLICK
66061                ish_zap_viewer_globals.isPanning = false;
66062            }
66063            else {
66064                // launch Atlas Viewer
66065                var value;
66066                eval("value = " + val); // string representation of a hash
66067                var structure_id = value.structure_id;
66068                var atlas_id = ish_zap_viewer_globals.on_get_atlas_id();
66069                var atlas_viewer_service = ish_zap_viewer_globals.atlas_viewer_service;
66070
66071                var window_name = "hba_atlas_viewer";
66072                window.open(atlas_viewer_service + "?atlas=" + atlas_id + "&structure=" + structure_id, window_name, "devhuman_atlas_window", "resizable=1");
66073            }
66074        }
66075
66076        if (evt == 'MOUSE_CLICK') {
66077            ish_zap_viewer_globals.isPanning = false;
66078        }
66079    },
66080
66081    doStructureOver:function (id, val, structure, evt) {
66082        ish_zap_viewer_globals.showStructureInfo(id, structure);
66083    },
66084
66085    doStructureOut:function (id, val, json, evt) {
66086        ish_zap_viewer_globals.showStructureInfo(id, null);
66087    },
66088
66089    showStructureInfo:function (id, structure) {
66090        ish_zap_viewer_globals.structureInfo = "";
66091        if (structure) {
66092            ish_zap_viewer_globals.structureInfo = ish_zap_viewer_globals.dao.structure_names[structure.structure_id];
66093        }
66094        var viewer = ish_zap_viewer_globals.findViewer(id);
66095        ish_zap_viewer_globals.setPanel(viewer);
66096    },
66097
66098    // find zap viewer corresponding to the zap graphics object id
66099    findViewer:function (zapgraphicsId) {
66100        var numericId = zapgraphicsId.match(/\d+/); // looks like "zapimg100102590"
66101        var viewer = _zapviewers[numericId];
66102        return(viewer);
66103    },
66104
66105
66106    inspect:function (prefix, obj) {
66107        var s = "";
66108        for (var key in obj) {
66109            if ((typeof obj[key]) == "function")
66110                s = s;
66111            else if ((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
66112                s += "\n" + prefix + "key: " + key;// + inspect(prefix + "    ", obj[key]);
66113            else
66114                s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
66115        }
66116        return(s);
66117    },
66118
66119    // listener for onImageChange
66120    imageChangeEvent : function(viewer, image) {
66121        // update display metatdata via panel
66122        ish_zap_viewer_globals.setPanel(viewer);
66123
66124        if (viewer.graphics_enabled) {
66125            ish_zap_viewer_globals.showText(viewer, viewer.showing_text);
66126            ish_zap_viewer_globals.showOutlines(viewer, viewer.showing_shapes);
66127            ish_zap_viewer_globals.showColors(viewer, viewer.showing_colors);
66128        }
66129    },
66130
66131    onSelectEvent:function (evt) {
66132
66133    },
66134
66135    showOutlines:function (viewer, show) {
66136        if (!show)
66137            return;
66138
66139        if (viewer.zapimg.graphics !== null && viewer.zapimg.graphics.graphics_enabled && viewer.zapimg.graphics.canvas !== null) {
66140            viewer.zapimg.graphics.canvas.executeToolMethod("showShapes", show);
66141            viewer.zapimg.graphics.redraw();
66142        }
66143        return(show);
66144    },
66145
66146    showText:function (viewer, show) {
66147        if (viewer.graphics_enabled && viewer.zapimg.graphics.canvas !== null) {
66148            if (!show) {
66149                viewer.zapimg.graphics.executeToolMethod("removeStructureLabels");
66150                viewer.zapimg.graphics.clear();
66151            } else
66152                viewer.zapimg.graphics.executeToolMethod("applyStructureLabels", ish_zap_viewer_globals.dao.structure_labels);
66153
66154            viewer.zapimg.graphics.redraw();
66155        }
66156        return(show);
66157    },
66158
66159    showColors:function (viewer, show) {
66160        if (viewer.zapimg.graphics !== null && viewer.zapimg.graphics.graphics_enabled && viewer.zapimg.graphics.canvas !== null) {
66161            if (!show) {
66162                viewer.zapimg.graphics.executeToolMethod("removeStructureColors");
66163                viewer.zapimg.graphics.clear();
66164            } else {
66165                viewer.zapimg.graphics.executeToolMethod("applyStructureColors", ish_zap_viewer_globals.dao.structure_colors);
66166            }
66167            viewer.zapimg.graphics.redraw();
66168        }
66169        return(show);
66170    }
66171};
66172
66173"use strict";
66174/**
66175 * Wrapper function enclosing zapviewer()
66176 *
66177 * NOTE: this app's variant of ish_zap_wrapper has a large chunk of code added to support the structure pulldown
66178 * menu, which is present when viewing Atlases. When a structure is selected, the viewer zooms in to the area
66179 * of interest.
66180 *
66181 * @param {string|Object} container_id - CSS id or jQuery element
66182 * @param {Object} services
66183 * @param {string} services.image_service
66184 * @param {string} services.tile_service
66185 * @param {string} services.info_service
66186 * @param {string} mode - unused argument
66187 * @param {Object} tools
66188 * @param {Object} options
66189 * @param {Object} options.atlas
66190 * @param {string} options.atlas_id
66191 * @param {number} options.length
66192 * @param {Object} options.event_options
66193 * @param {Object} options.event_options.on_load
66194 * @param {Object} options.event_options.on_before_image_type_change
66195 * @param {Object} options.event_options.on_after_image_type_change
66196 * @param {Object} options.event_options.on_before_siv
66197 * @param {Object} options.event_options.on_after_siv
66198 * @param {Object} options.event_options.on_before_contact_sheet
66199 * @param {Object} options.event_options.on_after_contact_sheet
66200 * @param {Object} options.event_options.on_before_legend
66201 * @param {Object} options.event_options.on_after_legend
66202 * @param {Object} options.event_options.on_before_contrast
66203 * @param {Object} options.event_options.on_after_contrast
66204 * @param {Object} options.event_options.on_before_info
66205 * @param {Object} options.event_options.on_after_info
66206 * @param {Object} options.event_options.on_before_info
66207 * @param {Object} options.event_options.on_before_help
66208 * @param {Object} options.event_options.on_after_help
66209 * @param {Object} options.event_options.on_before_download
66210 * @param {Object} options.event_options.on_after_download
66211 * @param {Object} options.event_options.on_before_sync
66212 * @param {Object} options.event_options.on_after_sync
66213 * @param {Object} options.event_options.on_before_sync_check
66214 * @param {Object} options.event_options.on_after_sync_check
66215 * @param {Object} options.event_options.on_before_nissl_only
66216 * @param {Object} options.event_options.on_after_nissl_only
66217 * @param {Object} options.event_options.on_before_all_stains
66218 * @param {Object} options.event_options.on_after_all_stains
66219 * @param {Object} options.event_options.on_before_show_text
66220 * @param {Object} options.event_options.on_after_show_text
66221 * @param {Object} options.event_options.on_before_show_color
66222 * @param {Object} options.event_options.on_after_show_color
66223 * @param {Object} options.event_options.on_before_show_outline
66224 * @param {Object} options.event_options.on_after_show_outline
66225 * @param {Object} options.event_options.on_before_navigation
66226 * @param {Object} options.event_options.on_after_navigation
66227 * @param {Object} options.event_options.on_before_blockface
66228 * @param {Object} options.event_options.on_after_blockface
66229 * @param {Object} options.event_options.on_before_color_filters
66230 * @param {Object} options.event_options.on_after_color_filters
66231 * @param {Object} options.event_options.on_image_set_load
66232 * @param {Object} options.event_options.on_before_close
66233 * @param {Object} options.defer_init
66234 * @param {Object} options.strip_tier
66235 * @param {Object} options.include_failed
66236 * @param {Object} options.sync_strip_image_type
66237 * @param {Object} options.sync_strip_image_filter
66238 * @param {Object} options._apply_styles
66239 * @param {Object} options.image_types
66240 * @param {Object} options.initial_image_type
66241 * @param {Object} options.daos
66242 * @param {Object} options.debugger
66243 * @param {Object} options._error_manager
66244 * @param {Object} options.title
66245 * @param {Object} options.graphics_options
66246 * @param {Object} options.zap_options
66247 * @param {Object} options.onKeyBeforeFncs
66248 * @param {Object} options.sb_create
66249 * @param {Object} options.onImageChangeFncs
66250 */
66251function ish_zap_wrapper(container_id, services, mode, tools, options) {
66252// public properties
66253    this.container_id = container_id instanceof jQuery ? container_id.attr('id') : container_id;
66254    this.container = $j('#' + container_id);
66255    this.services = services;
66256    this.mode = mode;
66257    this.tools = tools;
66258    this.options = options;
66259    this.atlas = options.atlas;
66260    this.atlas_id = options.atlas_id;
66261
66262// public methods
66263    this.set_images = function (images) {
66264
66265        do_set_images(images);
66266        scalebar_in_viewport();
66267    };
66268
66269    this.get_image_types = function() {
66270        var select = $j('#_zap_tool_image_select_' + _the_viewer.id)[0];
66271        if(!select)
66272            return('atlas');
66273
66274        var types = "";
66275        for(var i = 0; i < select.options.length; i++) {
66276            if(i)
66277                types += ",";
66278            types += select.options[i].value;
66279        }
66280
66281        return(types);
66282    };
66283
66284    /**
66285     * Sets all the menu options in the image types menu
66286     * @param image_types
66287     */
66288    this.set_image_types_menu = function(image_types) {
66289        do_set_image_types_menu(image_types);
66290    };
66291
66292    /**
66293     * Set the current selection from the image types menu.
66294     * This does not generate a change event.
66295     *
66296     * @param image_type
66297     */
66298    this.set_image_types_selection = function(image_type) {
66299        do_set_image_types_selection(image_type);
66300    }
66301
66302    this.clear_images = function () {
66303        _the_viewer.strip.clear();
66304    };
66305
66306    this.get_image_info = function () {
66307        var image = _the_viewer.getImageInfo();
66308        var pos = _the_viewer.getImagePosition();
66309        return({image:image, position:pos} );
66310    };
66311
66312    // Current view info (for download)
66313    this.get_download_info = function(){
66314        var info = _the_viewer.getViewInfo();
66315
66316        return({
66317            image:info
66318        })
66319    };
66320
66321    this.get_images = function () {
66322        return(_the_viewer.getImages());
66323    };
66324
66325    /**
66326     * Get centerpoint of viewport image
66327     *
66328     * @returns {Object} hash with keys x, y in full resolution image coords
66329     */
66330    this.get_center_point = function () {
66331        return(get_center_point());
66332    };
66333
66334    this.get_current_image = function() {
66335        return (_the_viewer.getCurrentImage());
66336    };
66337
66338    this.update_scale_bar = function() {
66339        update_scale_bar(_the_viewer);
66340    };
66341
66342    /**
66343     * Select an image from the image strip thumbnails, scrolling that image into view, and displaying that image
66344     * in the main viewer area. The scalebar is also updated to correspond with the image in the main viewer area.
66345     *
66346     * @param {number} id image id
66347     * @param {boolean} retainPanZoom if true, current x, y and zoom values (if any) from the previous display
66348      */
66349    this.select_image = function(id, retainPanZoom) {
66350        _the_viewer.moveToImage(id);
66351        _the_viewer.select(id, retainPanZoom);
66352        update_scale_bar(_the_viewer);
66353    };
66354
66355    /**
66356     * Zooms and pans the current image, then updates the scale bar
66357     *
66358     * @param coord_system
66359     * @param tier
66360     * @param left
66361     * @param top
66362     */
66363    this.set_image_position = function (coord_system, tier, left, top) {
66364        _the_viewer.setPosition(coord_system, tier, left, top);
66365        update_scale_bar(_the_viewer);
66366    };
66367
66368    /**
66369     *
66370     * @param image_id
66371     * @param center_x
66372     * @param center_y
66373     * @param {Object} options
66374     * @param {number} [options.absolute_tier] - target tier
66375     * @param {number} [options.tier_delta] - tier delta
66376     * @param {Object} options.ready - completion callback
66377     */
66378    this.move_select_and_center = function (image_id, center_x, center_y, options) {
66379        var tier_delta = 0; // used for relative tier changes from current image tier. Not applicable.
66380
66381        select_image_by_center(image_id, center_x, center_y, tier_delta, options);
66382    };
66383
66384    this.resize = function () {
66385        if (_the_viewer) {
66386            _the_viewer.resize();
66387            scalebar_in_viewport();
66388        }
66389    };
66390
66391    this.get_viewer = function () {
66392        return(_the_viewer);
66393    };
66394
66395    this.init = function () {
66396        init();
66397    };
66398
66399    this.destroy = function () {
66400        unbind_toolbar_handlers();
66401        _the_viewer = null;
66402    };
66403
66404    this.get_current_image_type = function() {
66405        return(_current_image_type);
66406    };
66407
66408    this.get_filter = function () {
66409        if (!_the_viewer)
66410            return;
66411
66412        return(_the_viewer.getFilter());
66413    };
66414
66415    /**
66416     * Pass in the filter data for each image type of the viewer. The filter values will be the default values,
66417     * this should be overridden with the actual current value.
66418     *
66419     * @param image_filters hashes of image_type:{filterName:name, filterVals:filter_value}
66420     */
66421    this.set_image_filters = function(image_filters) {
66422        _image_filters = image_filters;
66423    };
66424
66425    /**
66426     * This is called after the viewer is created and after the images are loaded by
66427     * the external caller.
66428     *
66429     * Business logic:
66430     * In general, filters should only be applied to the primary image, and not the imagestrip thumbnails.
66431     * The only exception is for Expression images, which require that a filter be applied to all its images to covert
66432     * its native grayscale images to color.
66433     *
66434     * @param {string} name filter name
66435     * @param {string}values filter values, comma-delimited
66436     * @param {boolean} apply_imagestrip optional arg, if true then filter is applied to both primary viewed image and imagestrip (thumbnail) images
66437     * @return {*}
66438     */
66439    this.set_filter = function(name, values, apply_imagestrip) {
66440        apply_imagestrip = apply_imagestrip == undefined ? false : apply_imagestrip;
66441        apply_imagestrip = _current_image_type == 'expression' ? true : apply_imagestrip;
66442        _the_viewer.setFilter(name, values, apply_imagestrip);
66443
66444        // update local filter store values
66445        var filter = _the_viewer.getFilter();
66446        update_image_filters(filter);
66447    };
66448
66449    this.position_contrast_slider = function() {
66450        // expression images do not use the contrast filter/slider
66451        if (_current_image_type != 'expression') {
66452            position_contrast_slider();
66453        }
66454    };
66455
66456    this.get_sync_check = function () {
66457        return(_sync_checkbox.is(':checked'));
66458    };
66459
66460    this.set_sync_check = function (checked) {
66461        _sync_checkbox.prop('checked', checked);
66462    };
66463
66464    this.add_event_listener = function (event_name, callback) {
66465        add_zap_listener(event_name, callback);
66466    };
66467
66468    this.set_title = function (new_title) {
66469        if (!_the_viewer)
66470            return;
66471
66472        _the_viewer.setTitle(new_title);
66473    };
66474
66475
66476    this.set_tool_state = function (tool_name, enabled) {
66477
66478        var tool = $j('#_zap_tool_' + tool_name + '_' + _the_viewer.id);
66479        if (enabled) {
66480            tool.removeClass('_tb_disabled');
66481            tool.bind('click', _tools[tool_name]);
66482        }
66483        else {
66484            tool.addClass('_tb_disabled');
66485            tool.unbind('click', _tools[tool_name]);
66486        }
66487    };
66488// event hooks
66489    // lotsa hooks
66490
66491    // let's make these all before-and-after, with the before having
66492    // the option to cancel the default event handling, so the caller
66493    // can substitute their own delegate if necessary.
66494
66495    // all event handler prototypes should look like this:
66496    // handler(this (the ish_zap_wrapper instance), event_data (includes event_name & event-specific data)
66497    //
66498    // for each event type there is an on_before_xx event and an on_after_xx event.
66499    // on_before_event handlers should return true or false.  If true, the default handler is also called;
66500    // if false the default handler is not called.
66501
66502    this.on_load = options.event_options ? options.event_options.on_load : null;
66503
66504    this.on_before_image_type_change = options.event_options ? options.event_options.on_before_image_type_change : null;
66505    this.on_after_image_type_change = options.event_options ? options.event_options.on_after_image_type_change : null;
66506
66507    this.on_before_structure_change = options.event_options ? options.event_options.on_before_structure_change : null;
66508    this.on_after_structure_change = options.event_options ? options.event_options.on_before_structure_change : null;
66509
66510    this.on_before_siv = options.event_options ? options.event_options.on_before_siv : null;
66511    this.on_after_siv = options.event_options ? options.event_options.on_after_siv : null;
66512
66513    this.on_before_contact_sheet = options.event_options ? options.event_options.on_before_contact_sheet : null;
66514    this.on_after_contact_sheet = options.event_options ? options.event_options.on_after_contact_sheet : null;
66515
66516    this.on_before_legend = options.event_options ? options.event_options.on_before_legend : null;
66517    this.on_after_legend = options.event_options ? options.event_options.on_after_legend : null;
66518
66519    this.on_before_contrast = options.event_options ? options.event_options.on_before_contrast : null;
66520    this.on_after_contrast = options.event_options ? options.event_options.on_after_contrast : null;
66521
66522    this.on_before_info = options.event_options ? options.event_options.on_before_info : null;
66523    this.on_after_info = options.event_options ? options.event_options.on_after_info : null;
66524
66525    this.on_before_help = options.event_options ? options.event_options.on_before_help : null;
66526    this.on_after_help = options.event_options ? options.event_options.on_after_help : null;
66527
66528    this.on_before_download = options.event_options ? options.event_options.on_before_download : null;
66529    this.on_after_download = options.event_options ? options.event_options.on_after_download : null;
66530
66531    this.on_before_sync = options.event_options ? options.event_options.on_before_sync : null;
66532    this.on_after_sync = options.event_options ? options.event_options.on_after_sync : null;
66533
66534    this.on_before_sync_check = options.event_options ? options.event_options.on_before_sync_check : null;
66535    this.on_after_sync_check = options.event_options ? options.event_options.on_after_sync_check : null;
66536
66537    this.on_before_nissl_only = options.event_options ? options.event_options.on_before_nissl_only : null;
66538    this.on_after_nissl_only = options.event_options ? options.event_options.on_after_nissl_only : null;
66539
66540    this.on_before_all_stains = options.event_options ? options.event_options.on_before_all_stains : null;
66541    this.on_after_all_stains = options.event_options ? options.event_options.on_after_all_stains : null;
66542
66543    this.on_before_show_text = options.event_options ? options.event_options.on_before_show_text : null;
66544    this.on_after_show_text = options.event_options ? options.event_options.on_after_show_text : null;
66545
66546    this.on_before_show_color = options.event_options ? options.event_options.on_before_show_color : null;
66547    this.on_after_show_color = options.event_options ? options.event_options.on_after_show_color : null;
66548
66549    this.on_before_show_outline = options.event_options ? options.event_options.on_before_show_outline : null;
66550    this.on_after_show_outline = options.event_options ? options.event_options.on_after_show_outline : null;
66551
66552    this.on_before_navigation = options.event_options ? options.event_options.on_before_navigation : null;
66553    this.on_after_navigation = options.event_options ? options.event_options.on_after_navigation : null;
66554
66555    this.on_before_blockface = options.event_options ? options.event_options.on_before_blockface : null;
66556    this.on_after_blockface = options.event_options ? options.event_options.on_after_blockface : null;
66557
66558    this.on_before_color_filters = options.event_options ? options.event_options.on_before_color_filters : null;
66559    this.on_after_color_filters = options.event_options ? options.event_options.on_after_color_filters : null;
66560
66561    this.on_image_set_load = options.event_options ? options.event_options.on_image_set_load : null;
66562
66563    // the close event is an exception because, you know.
66564    this.on_before_close = options.event_options ? options.event_options.on_before_close : null;
66565
66566    $j('#zaptitletext' + container_id).css({'width':'25px', 'display':'inline-block', 'float':'left'});
66567// private properties
66568
66569    var _self = this;
66570    var _the_viewer = null;
66571    var _defer_init = _self.options ? _self.options.defer_init ? _self.options.defer_init : false : false;
66572    var _strip_tier = _self.options ? _self.options.strip_tier ? _self.options.strip_tier : 0 : 0;
66573    var _include_failed = _self.options ? _self.options.include_failed ? _self.options.include_failed : false : false;
66574    var _sync_strip_image_type = _self.options ? _self.options.sync_strip_image_type ? _self.options.sync_strip_image_type : false : false;
66575    var _sync_strip_image_filter = _self.options ? _self.options.sync_strip_image_filter ? _self.options.sync_strip_image_filter : false : false;
66576//TODO: well, this needs a lot of documentation.  IE will not honor styles applied in any way other than programaticaly.
66577    var _default_styles = [
66578        {selector:'#zaptitletext' + container_id, style:{'width':'25px', 'display':'inline-block', 'float':'left'}}
66579    ];
66580    var _apply_styles = _self.options ? _self.options._apply_styles ? _self.options._apply_styles : _default_styles : _default_styles;
66581    var _image_types = _self.options ? _self.options.image_types ? _self.options.image_types : "" : "";
66582    var _initial_image_type = _self.options ? (_self.options.initial_image_type ? _self.options.initial_image_type.toLowerCase() : 'ish') : 'ish';
66583    var _position_queue = null;
66584    var _positioning = false;
66585    var _sync_checkbox = null;
66586    var _contrast_slider = null;
66587    var _filter_cache = {};
66588    var _the_daos = options.daos;
66589
66590    var _current_image_type = _initial_image_type;
66591
66592    // populated by call to this.set_image_filters(), the hash will have entries like image_type:{filterName:name, filterVals:filter_value}
66593    var _image_filters = {};
66594
66595    var _tools = {
66596        siv:do_siv,
66597        info:do_info,
66598        help:do_help,
66599        legend:do_legend,
66600        download:do_download,
66601        sync:do_sync,
66602        sync_check:do_sync_check,
66603        close:do_close,
66604        image_select:do_image_select,
66605        contrast:do_contrast,
66606        nissl_only:do_nissl_only,
66607        all_stains:do_all_stains,
66608        show_text:do_show_text,
66609        show_color:do_show_color,
66610        show_outline:do_show_outline,
66611        contact_sheet:do_contact_sheet,
66612        navigation:do_navigation,
66613        blockface:do_blockface,
66614        color_filters:do_color_filters
66615    };
66616
66617
66618// TODO: these make sense if they are implemented throughout the app.
66619    var _debugger = options ? options._debugger ? options._debugger : null : null;
66620    var _error_manager = options ? options._error_manager ? options._error_manager : null : null;
66621
66622
66623// private methods
66624
66625    /**
66626     * Update the filter values of all _image_filters entries which have the same filter name as the input arg
66627     *
66628     * @param filter hash {filterName:name, filterVals:values}
66629     */
66630    function update_image_filters(filter) {
66631        for (var f in _image_filters) {
66632            if (_image_filters[f].filterName == filter.filterName) {
66633                _image_filters[f].filterVals = filter.filterVals;
66634            }
66635        }
66636    }
66637
66638    function scalebar_in_viewport() {
66639
66640        if (!_the_viewer)
66641            return;
66642
66643        var id = _the_viewer.id;
66644        var img = $j("#zapimg" + id);
66645        var sb = $j("#scalebar" + id);
66646        var sbHeight = sb.height();
66647        var imgHeight = img.height();
66648        var newX = 0;
66649        var newY = imgHeight - sbHeight;
66650        sb.css({'top':newY + "px", 'left':newX + "px"});
66651    }
66652
66653    function add_zap_listener(event_name, callback) {
66654        // !!!!!!!!!!!!!!! Craziness alert !!!!!!!!!!!!!!!!!!
66655        // the zap viewer is a Prototype library widget.  the following statement uses a Prototype library
66656        // method to capture the viewer creation event and bring it back into the jquery world:
66657        $(_self.container_id).observe(event_name, function (event) {
66658            callback(event.memo.viewer);
66659        });
66660        // !!!!!!!!!!! end this particular craziness !!!!!!!!!!!!!!!!
66661    }
66662
66663    function container_error(container, msg) {
66664        // it would be nice to have one central
66665        // place for handling UI error reporting...
66666        if (_error_manager) {
66667            _error_manager.ui_report(_self, container, msg);
66668            return;
66669        }
66670
66671        var err_html = "<div class='err_notice ui-state-error'>" + msg + "</div>";
66672        container.html(err_html);
66673    }
66674
66675    function show_image_error(msg) {
66676        var cont = $j(".zapimg") ? $j(".zapimg") : this.container;
66677        var err_msg = 'Images can not be displayed';
66678        if (msg && msg.length)
66679            err_msg += ': <BR/><BR/>(' + msg + ')';
66680        else
66681            err_msg += '.';
66682
66683        container_error(cont, err_msg);
66684    }
66685
66686    function build_viewer() {
66687
66688        var zap_options = {
66689            defer_init:_self.defer_init,
66690            src_key:'path',
66691            strip_tier:_self.strip_tier,
66692            precalculated_path:'true'
66693        };
66694
66695        if (_self.options.title)
66696            zap_options.title = _self.options.title;
66697
66698        $j.extend(true, zap_options, _self.options.zap_options);
66699
66700        // update the scalebar resolution when the image changes, if the scalebar is present
66701        if (zap_options.sb_create && zap_options.sb_create == 'true') {
66702            zap_options.onImageChangeFncs = zap_options.onImageChangeFncs ? zap_options.onImageChangeFncs + ',' + 'zap_functions.update_scalebar' : 'zap_functions.update_scalebar';
66703            $j('#' + _self.container_id).bind('resize', scalebar_in_viewport);
66704        }
66705
66706
66707        // !!!!!!!!!!!!!!! Craziness alert !!!!!!!!!!!!!!!!!!
66708        // the zap viewer is a Prototype library widget.  the following statement uses a Prototype library
66709        // method to capture the viewer creation event and bring it back into the jquery world:
66710        $(container_id).observe("zapviewer:viewercreated", function (event) {
66711            viewer_created(event.memo.viewer);
66712        });
66713        // !!!!!!!!!!! end this particular craziness !!!!!!!!!!!!!!!!
66714
66715        _the_viewer = new zapviewer(
66716            container_id,
66717            _self.services.image_service,
66718            _self.services.tile_service,
66719            _self.services.info_service,
66720            zap_options
66721        );
66722
66723        //attach_zap_event('imagechange', do_image_change);
66724
66725        if (_self.options.jquery_theme) {
66726            $j('#zapviewer' + container_id).addClass('ui-widget-content');
66727            // $j('#zaptitle'+container_id).addClass('ui-widget-header');
66728        }
66729
66730        apply_styles();
66731    }
66732
66733    function attach_zap_event(event_name, callback) {
66734
66735        // !!!!!!!!!!!!!!! Craziness alert !!!!!!!!!!!!!!!!!!
66736        // the zap viewer is a Prototype library widget.  the following statement uses a Prototype library
66737        // method to capture the viewer creation event and bring it back into the jquery world:
66738        $(container_id).observe("zapviewer:" + event_name, function (event) {
66739            callback(event.memo.viewer, event_name);
66740        });
66741        // !!!!!!!!!!! end this particular craziness !!!!!!!!!!!!!!!!
66742    }
66743
66744    /**
66745     * Callback from zapviewer after successful creation. This is asynchronous.
66746     *
66747     * @param viewer
66748     */
66749    function viewer_created(viewer) {
66750
66751        if (_self.on_load)
66752            _self.on_load(viewer);
66753    }
66754
66755
66756    function do_image_change(viewer) {
66757
66758
66759    }
66760
66761    /**
66762     * Update the scalebar to reflect the zoom level of the current image
66763     */
66764    function update_scale_bar(viewer) {
66765        var img_info = viewer.getImageInfo();
66766        if (img_info != null)
66767            viewer.scalebar.autoUpdateScaleBar(viewer, img_info);
66768    }
66769
66770    function apply_styles() {
66771        if (_apply_styles) {
66772            for (var i = 0; i < _apply_styles.length; i++) {
66773                var style = _apply_styles[i];
66774                $j(style.selector).css(style.style);
66775            }
66776        }
66777    }
66778
66779    function add_toolbar() {
66780        // set any custom event handlers
66781        set_toolbar_user_handlers();
66782
66783        // build the html for the tool controls and hand them to the viewer
66784        // to be added to the DOM
66785        create_toolbar_controls();
66786
66787        // find the newly created toolbar controls and attach handlers
66788        bind_toolbar_handlers();
66789    }
66790
66791    /**
66792     * Assuming the current filter values are appropriate for the contrast filter,
66793     * set the contrast slider's thumb to correspond to the filter value.
66794     */
66795    function position_contrast_slider() {
66796        var filter = _the_viewer.getFilter();
66797        var valArray = filter.filterVals.split(',');
66798        var contrastVal = valArray[1];
66799        if (isNaN(contrastVal) == false) {
66800            var sliderVal = parseInt(100 * (parseFloat(contrastVal) + .0001));
66801            _contrast_slider.slider("value", sliderVal);
66802        }
66803    }
66804
66805    function set_toolbar_user_handlers() {
66806        var len = _self.tools.length;
66807        for (var i = 0; i < len; i++)
66808            if (_self.tools[i].callback)
66809                _tools[_self.tools[i].name] = _self.tools[i].callback;
66810    }
66811
66812    function bind_toolbar_handlers() {
66813        // bind handlers to the control events
66814        // there are default handlers for all, but the user may have over-ridden them
66815        var viewer_id = _the_viewer.id;
66816        var len = _self.tools.length;
66817        for (var i = 0; i < len; i++) {
66818            var html = "";
66819            switch (_self.tools[i].name) {
66820                case 'siv':
66821                    $j('#_zap_tool_siv_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66822                    break;
66823                case 'info':
66824                    $j('#_zap_tool_info_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66825                    break;
66826                case 'help':
66827                    $j('#_zap_tool_help_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66828                    break;
66829                case 'download':
66830                    $j('#_zap_tool_download_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66831                    break;
66832                case 'sync':
66833                    $j('#_zap_tool_sync_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66834                    break;
66835                case 'legend':
66836                    $j('#_zap_tool_legend_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66837                    break;
66838                case 'close':
66839                    $j('#_zap_tool_close_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66840                    break;
66841                case 'image_select':
66842                    $j('#_zap_tool_image_select_' + viewer_id).bind('change', _tools[_self.tools[i].name]);
66843                    break;
66844                case 'contact_sheet':
66845                    $j('#_zap_tool_contact_sheet_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66846                    break;
66847                case 'nissl_only':
66848                    $j('#_zap_tool_nissl_only_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66849                    break;
66850                case 'all_stains':
66851                    $j('#_zap_tool_all_stains_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66852                    break;
66853
66854                case 'show_text':
66855                    $j('#_zap_tool_show_text_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66856                    break;
66857                case 'show_color':
66858                    $j('#_zap_tool_show_color_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66859                    break;
66860                case 'show_outline':
66861                    $j('#_zap_tool_show_outline_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66862                    break;
66863
66864                case 'navigation':
66865                    $j('#_zap_tool_navigation_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66866                    break;
66867                case 'blockface':
66868                    $j('#_zap_tool_blockface_' + viewer_id).bind('click', _tools[_self.tools[i].name]);
66869                    break;
66870                case 'color_filters':
66871                    $j('#_zap_tool_color_filters_'+ viewer_id).bind('click', _tools[_self.tools[i].name]);
66872                    break;
66873
66874                case 'contrast':
66875                    _contrast_slider = $j('#_zap_tool_contrast_'+ viewer_id);
66876                    _contrast_slider.slider({value:50});
66877                    _contrast_slider.bind('slidechange', _tools[_self.tools[i].name]);
66878                    break;
66879                case 'sync_check':
66880                    _sync_checkbox = $j('#_zap_tool_sync_check_' + viewer_id);
66881                    _sync_checkbox.bind('click', _tools[_self.tools[i].name]);
66882                    break;
66883                default:
66884            }
66885        }
66886    }
66887
66888    function unbind_toolbar_handlers() {
66889        // bind handlers to the control events
66890        // there are default handlers for all, but the user may have over-ridden them
66891        var viewer_id = _the_viewer.id;
66892        var len = _self.tools.length;
66893        for (var i = 0; i < len; i++) {
66894            var html = "";
66895            switch (_self.tools[i].name) {
66896                case 'siv':
66897                    $j('#_zap_tool_siv_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66898                    break;
66899                case 'info':
66900                    $j('#_zap_tool_info_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66901                    break;
66902                case 'help':
66903                    $j('#_zap_tool_help_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66904                    break;
66905                case 'sync':
66906                    $j('#_zap_tool_sync_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66907                    break;
66908                case 'legend':
66909                    $j('#_zap_tool_legend_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66910                    break;
66911                case 'close':
66912                    $j('#_zap_tool_close_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66913                    break;
66914                case 'image_select':
66915                    $j('#_zap_tool_image_select_' + viewer_id).unbind('change', _tools[_self.tools[i].name]);
66916                    break;
66917                case 'contact_sheet':
66918                    $j('#_zap_tool_contact_sheet_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66919                    break;
66920                case 'nissl_only':
66921                    $j('#_zap_tool_nissl_only_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66922                    break;
66923                case 'all_stains':
66924                    $j('#_zap_tool_all_stains_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66925                    break;
66926
66927                case 'show_text':
66928                    $j('#_zap_tool_show_text_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66929                    break;
66930                case 'show_color':
66931                    $j('#_zap_tool_show_color_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66932                    break;
66933                case 'show_outline':
66934                    $j('#_zap_tool_show_outline_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66935                    break;
66936
66937                case 'navigation':
66938                    $j('#_zap_tool_navigation_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66939                    break;
66940                case 'blockface':
66941                    $j('#_zap_tool_blockface_' + viewer_id).unbind('click', _tools[_self.tools[i].name]);
66942                    break;
66943                case 'color_filters':
66944                    $j('#_zap_tool_color_filters_'+ viewer_id).unbind('click', _tools[_self.tools[i].name]);
66945                    break;
66946
66947                case 'contrast':
66948                    _contrast_slider = $j('#_zap_tool_contrast_'+ viewer_id);
66949                    _contrast_slider.slider({value:50});
66950                    _contrast_slider.unbind('slidechange', _tools[_self.tools[i].name]);
66951                    break;
66952                case 'sync_check':
66953                    _sync_checkbox.unbind('click', _tools[_self.tools[i].name]);
66954                    break;
66955                default:
66956            }
66957        }
66958    }
66959
66960    function create_toolbar_controls() {
66961        // create the tool buttons
66962        var viewer_id = _the_viewer.id;
66963        var len = _self.tools.length;
66964        var html = "";
66965        for (var i = 0; i < len; i++) {
66966            switch (_self.tools[i].name) {
66967                case 'siv':
66968                    html += "<img id='_zap_tool_siv_" + viewer_id + "' title='Launch a high resolution viewer in a 
66968new window' class='viewControlButton' src='/assets/showInSIV.png' />";
66969                    break;
66970                case 'info':
66971                    html += "<img id='_zap_tool_info_" + viewer_id + "' title='See details for this gene and image series in a new browser window' class='viewControlButton' src='/assets/info.png' />";
66972                    break;
66973                case 'help':
66974                    html += "<img id='_zap_tool_help_" + viewer_id + "' title='Help - How to use this viewer' class='viewControlButton' src='/assets/ZapViewerHelp.png' />";
66975                    break;
66976                case 'download':
66977                    html += "<img id='_zap_tool_download_" + viewer_id + "' title='Download current image as a jpeg file' class='viewControlButton' src='/assets/download.png' />";
66978                    break;
66979                case 'sync':
66980                    html += "<img id='_zap_tool_sync_" + viewer_id + "' title='Synchronize other viewers on this page to this image' class='viewControlButton' src='/assets/syncToMe2.png' />";
66981                    break;
66982                case 'legend':
66983                    html += "<img id='_zap_tool_legend_" + viewer_id + "' title='See the Interactive Allen Reference Atlas in a new browser window' class='viewControlButton' src='/assets/key.png' />";
66984                    break;
66985                case 'close':
66986                    html += "<img id='_zap_tool_close_" + viewer_id + "' title='Remove this viewer from the page' class='viewControlButton' src='/assets/exit.png' />";
66987                    break;
66988                case 'contact_sheet':
66989                    html += "<img id='_zap_tool_contact_sheet_" + viewer_id + "' title='See all the images in a contact sheet layout' class='viewControlButton' src='/assets/thumbSets.png' />";
66990                    break;
66991                case 'nissl_only':
66992                    html += "<img id='_zap_tool_nissl_only_" + viewer_id + "' title='Show only Nissl images from this specimen' class='viewControlButton toggle_button group_button' src='/assets/nisslOnly.png' />";
66993                    break;
66994                case 'all_stains':
66995                    html += "<img id='_zap_tool_all_stains_" + viewer_id + "' title='Show all images from this specimen' class='viewControlButton toggle_button group_button' src='/assets/allStains.png' />";
66996                    break;
66997
66998                case 'show_text':
66999                    html += "<img id='_zap_tool_show_text_" + viewer_id + "' title='Toggle text on/off' class='viewControlButton toggle_button' src='/assets/structureLabels.png' />";
67000                    break;
67001                case 'show_color':
67002                    html += "<img id='_zap_tool_show_color_" + viewer_id + "' title='Toggle colors on/off' class='viewControlButton toggle_button' src='/assets/structureColors.png' />";
67003                    break;
67004                case 'show_outline':
67005                    html += "<img id='_zap_tool_show_outline_" + viewer_id + "' title='Toggle outlines on/off' class='viewControlButton toggle_button' src='/assets/outline.png' />";
67006                    break;
67007
67008                case 'navigation':
67009                    html += "<img id='_zap_tool_navigation_" + viewer_id + "' title='Show navigation images' class='viewControlButton toggle_button group_button' src='/assets/navigation.png' />";
67010                    break;
67011                case 'blockface':
67012                    html += "<img id='_zap_tool_blockface_" + viewer_id + "' title='Show blockface images' class='viewControlButton toggle_button group_button' src='/assets/blockface.png' />";
67013                    break;
67014                case 'color_filters':
67015                    html += "<img id='_zap_tool_color_filters_" + viewer_id + "' title='Adjust image filters' class='viewControlButton toggle_button group_button' src='/assets/filterSliders.png' />";
67016                    break;
67017
67018                case 'image_select':
67019                    html +=  build_image_select(viewer_id, _initial_image_type);
67020
67021                    break;
67022
67023                case 'sync_check':
67024                    html += "<div class='zap_sync_check'>"
67025                    html += "<input type='checkbox' name='" + (options.sync_check.name ? options.sync_check.name : 'sync_check') + " " + (options.sync_check.checked ? 'checked' : '');
67026                    html += " class='sync_checkbox' id='_zap_tool_sync_check_" + viewer_id + "'/>";
67027                    html += "<div class='zap_sync_check_label'>" + (options.sync_check.label ? options.sync_check.label : 'Sync') + "</div></div>";
67028                    break;
67029                case 'contrast':
67030                    html += "<div id='_zap_tool_contrast_" + viewer_id + "' class='zap_contrast_slider' title='Adjust image contrast'></div>";
67031                    break;
67032                default:
67033            }
67034        }
67035        // add the tools to the viewer
67036        _the_viewer.setControls(html);
67037
67038        // edge case: if initial display image is Expression, hide the contrast slider tool if it is present in the DOM
67039        if (_initial_image_type == 'expression') {
67040            hide_contrast(true);
67041        }
67042    }
67043
67044    /**
67045     * Construct the HTML for the drop down image select menu, and setting the default selection
67046     *
67047     * @param viewer_id
67048     * @param default_selection value of default selection
67049     * @return {String}
67050     */
67051    function build_image_select(viewer_id, default_selection) {
67052        var html = "";
67053
67054        var len = _image_types.length;
67055        if (len > 0) {
67056            html = '<select class="viewControlSelect" id="_zap_tool_image_select_' + viewer_id + '">';
67057            var select_value;
67058            var selected = '';
67059            for (var i = 0; i < len; i++) {
67060                select_value = _image_types[i].toLowerCase();
67061                if (select_value == default_selection)
67062                    selected = ' selected="selected"';
67063                else
67064                    selected = '';
67065                html += '   <option value="' + select_value + '"' + selected + '>' + _image_types[i] + '</option>';
67066            }
67067            html += '</select>';
67068        }
67069        else {
67070            html += "<select class='viewControlSelect' id='_zap_tool_image_select_" + viewer_id + "'>";
67071            html += "   <option value='ish'>ISH</option>";
67072            html += "   <option value='expression'>Expression</option>";
67073            html += "   <option value='nissl'>Nissl</option>";
67074            html += "</select>";
67075        }
67076
67077        return(html);
67078    }
67079
67080    /**
67081     * Set an item as selected from the image type menu. This does not trigger a selection event.
67082     *
67083     * @param {string} image_type value of the menu item, which is simply the image type in lowercase
67084     */
67085    function do_set_image_types_selection(image_type) {
67086        var type = image_type.toLowerCase();
67087        $j('#_zap_tool_image_select_' + _the_viewer.id).val(type);
67088    }
67089
67090    /**
67091     * Sets the contents of the image types menu. The input arg consists of the display names of the menu,
67092     * the menu value is set to be the lowercased value of the display name.
67093     *
67094     * @param {Object[]} image_types
67095     */
67096    function do_set_image_types_menu(image_types) {
67097
67098        var select_menu = $j("#_zap_tool_image_select_" + _the_viewer.id);
67099        if(!select_menu || !select_menu[0].options)
67100            return;
67101
67102        select_menu.children().remove().end();
67103        for (var i = 0, len = image_types.length; i < len; ++i) {
67104            select_menu.append('<option selected value="' + sanitize_image_type(image_types[i]) + '">' + image_types[i] + '</option>');
67105        }
67106    }
67107
67108    function sanitize_image_type(image_type) {
67109        return image_type.toLowerCase().replace(/\s+/g, "_");
67110    }
67111
67112    function do_set_images(images) {
67113        if (!images) {
67114            show_image_error();
67115            return;
67116        }
67117
67118        var target_thumbnail_size = $j('#' + _self.container_id).height() * 0.24;
67119        var len = images.length;
67120        var first_id = null;
67121        for (var i = 0; i < len; i++) {
67122
67123            // don't show failed images unless the caller has specified that we should in the options hash
67124            if ((images[i].failed == false || images[i].failed == 'false') || _include_failed) {
67125                if (!first_id)
67126                    first_id = images[i].id;
67127
67128                // NOTE num_tiers should be a field of the image object
67129//TODO: this is broken because the tier_count field is not properly populated.  This estimation should work for most
67130                var num_tiers = images[i].tier_count;
67131
67132                var num_tiers = 0;
67133                if (images[i].tier_count && images[i].tier_count > 0)
67134                    num_tiers = images[i].tier_count;
67135                else {
67136                    num_tiers = Math.ceil(((Math.log(Math.max(images[i].image_width, images[i].image_height) / 256.0) ) / Math.log(2))) + 1;
67137                }
67138
67139                // NOTE target_tier should be a configurable options.  It is the tier
67140                // at which the thumbnails should be generated.
67141                // NOTE target_tier should be a configurable options.  It is the tier
67142                // at which the thumbnails should be generated. Now being generated as the
67143                // smallest tier that produces an image with largest size not more than the target_thumbnail_size.
67144                // The target size is calculated to be 24% of the height of the container for this viewer.
67145                _strip_tier = find_strip_tier(images[i], target_thumbnail_size);
67146                var target_tier = _strip_tier;
67147                var scale = Math.pow(2, (num_tiers - 1) - _strip_tier);
67148
67149                var expression_image = images[i].alternate_images ? images[i].alternate_images.length ? images[i].alternate_images[0] : {path:''} : {path:''};
67150
67151                images[i].ish_id = images[i].id;
67152                images[i].precalculated_path = 'true';
67153                var path = (images[i].path && images[i].path.indexOf("&path=") >= 0) ? images[i].path : _self.services.image_service + "&path=" + images[i].path + "&zoom=" + target_tier + "&top=" + images[i].y + "&left=" + images[i].x + "&width=" 
67153+ images[i].width / scale + "&height=" + images[i].height / scale;
67154                images[i].path = path;
67155                images[i].expression_path = _self.services.image_service + "&path=" + expression_image.path + "&zoom=" + target_tier + "&top=" + images[i].y + "&left=" + images[i].x + "&width=" + images[i].width / scale + "&height=" + images[i].height / scale;
67156                images[i].src_key = 'path';
67157                images[i].tier = _strip_tier;
67158                images[i].subimage_path = {top:images[i].y, left:images[i].x, width:images[i].width, height:images[i].height};
67159                images[i].subimage_expression_path = {top:images[i].y, left:images[i].x, width:images[i].width, height:images[i].height};
67160
67161                // for ISH zap viewers we assume that the one-and-only associate is a nissl
67162                if (images[i].associates && images[i].associates.length == 1) {
67163                    var nissl = images[i].associates[0];
67164                    var nissl_strip_tier = find_strip_tier(nissl, target_thumbnail_size);
67165                    var nissl_scale = Math.pow(2, (nissl.tier_count - 1) - nissl_strip_tier);
67166                    images[i].nissl_id = nissl.id;
67167                    images[i].nissl_path = _self.services.image_service + "&path=" + nissl.path + "&zoom=" + nissl_strip_tier + "&top=" + nissl.y + "&left=" + nissl.x + "&width=" + nissl.width / nissl_scale + "&height=" + nissl.height / nissl_scale;
67168                    images[i].subimage_nissl_path = {top:nissl.y, left:nissl.x, width:nissl.width, height:nissl.height};
67169                }
67170
67171                _the_viewer.addImage(images[i]);
67172            }
67173        }
67174        _the_viewer.select(first_id);
67175    }
67176
67177
67178    function find_strip_tier(image, target_size) {
67179
67180        // trying to find the lowest tier at which the image is >= 128 pixels wide/height
67181        var max = Math.max(parseInt(image.width), parseInt(image.height));
67182        var target = target_size / ( max / Math.pow(2, (image.tier_count - 1)));
67183        var target_tier = Math.max(0, Math.ceil(Math.log(target) / Math.log(2)));
67184
67185        return(target_tier);
67186    }
67187
67188    function do_siv(e) {
67189        var image_pos = _the_viewer.getImagePosition();
67190        var image_info = _the_viewer.getImageInfo();
67191        var proceed = true;
67192        if (_self.on_before_siv)
67193            proceed = _self.on_before_siv(_self, {event_name:'before_siv', image_info:image_info, image_position:image_pos});
67194
67195        if (!proceed)
67196            return;
67197
67198        if (_self.on_after_siv)
67199            _self.on_after_siv(_self, {event_name:'after_siv', image_info:image_info, image_position:image_pos});
67200    }
67201
67202    function do_info(e) {
67203        var proceed = true;
67204        if (_self.on_before_info)
67205            proceed = _self.on_before_info(_self, {event_name:'before_info', value:null});
67206
67207        if (!proceed)
67208            return;
67209
67210        if (_self.on_after_info)
67211            _self.on_after_info(_self, {event_name:'after_info', value:null});
67212    }
67213
67214    function do_sync_check(e) {
67215        var proceed = true;
67216        if (_self.on_before_sync_check)
67217            proceed = _self.on_before_sync_check(_self, {event_name:'before_sync_check', value:null});
67218
67219        if (!proceed)
67220            return;
67221
67222        if (_self.on_after_sync_check)
67223            _self.on_after_sync_check(_self, {event_name:'after_sync_check', value:null});
67224    }
67225
67226    function do_help(e) {
67227        var proceed = true;
67228        if (_self.on_before_help)
67229            proceed = _self.on_before_help(_self, {event_name:'before_help', value:null});
67230
67231        if (!proceed)
67232            return;
67233
67234        if (_self.on_after_help)
67235            _self.on_after_help(_self, {event_name:'after_help', value:null});
67236    }
67237
67238    function do_download(e) {
67239        var proceed = true;
67240        if (_self.on_before_download)
67241            proceed = _self.on_before_download(_self, {event_name:'before_download', value:null});
67242
67243        if (!proceed)
67244            return;
67245
67246        if (_self.on_after_download)
67247            _self.on_after_download(_self, {event_name:'after_download', value:null});
67248    }
67249
67250    function do_nissl_only(e) {
67251        var proceed = true;
67252        if (_self.on_before_nissl_only)
67253            proceed = _self.on_before_nissl_only(_self, {event_name:'before_nissl_only', value:null});
67254
67255        if (!proceed)
67256            return;
67257
67258        if (_self.on_after_nissl_only)
67259            _self.on_after_nissl_only(_self, {event_name:'after_nissl_only', value:null});
67260    }
67261
67262    function do_all_stains(e) {
67263        var proceed = true;
67264        if (_self.on_before_all_stains)
67265            proceed = _self.on_before_all_stains(_self, {event_name:'before_all_stains', value:null});
67266
67267        if (!proceed)
67268            return;
67269
67270        if (_self.on_after_all_stains)
67271            _self.on_after_all_stains(_self, {event_name:'after_all_stains', value:null});
67272    }
67273
67274    function do_show_text(e) {
67275        var proceed = true;
67276        if (_self.on_before_show_text)
67277            proceed = _self.on_before_show_text(_self, {event_name:'before_show_text', value:null});
67278
67279        if (!proceed)
67280            return;
67281
67282        if (_self.on_after_show_text)
67283            _self.on_after_show_text(_self, {event_name:'after_show_text', value:null});
67284    }
67285
67286    function do_show_color(e) {
67287        var proceed = true;
67288        if (_self.on_before_show_color)
67289            proceed = _self.on_before_show_color(_self, {event_name:'before_show_color', value:null});
67290
67291        if (!proceed)
67292            return;
67293
67294        if (_self.on_after_show_color)
67295            _self.on_after_show_color(_self, {event_name:'after_show_color', value:null});
67296    }
67297
67298    function do_show_outline(e) {
67299        var proceed = true;
67300        if (_self.on_before_show_outline)
67301            proceed = _self.on_before_show_outline(_self, {event_name:'before_show_outline', value:null});
67302
67303        if (!proceed)
67304            return;
67305
67306        if (_self.on_after_show_outline)
67307            _self.on_after_show_outline(_self, {event_name:'after_show_outline', value:null});
67308    }
67309
67310    function do_navigation(e) {
67311        var proceed = true;
67312        if (_self.on_before_navigation)
67313            proceed = _self.on_before_navigation(_self, {event_name:'before_navigation', value:null});
67314
67315        if (!proceed)
67316            return;
67317
67318        if (_self.on_after_navigation)
67319            _self.on_after_navigation(_self, {event_name:'after_navigation', value:null});
67320    }
67321
67322    function do_blockface(e) {
67323        var proceed = true;
67324        if (_self.on_before_blockface)
67325            proceed = _self.on_before_blockface(_self, {event_name:'before_blockface', value:null});
67326
67327        if (!proceed)
67328            return;
67329
67330        if (_self.on_after_blockface)
67331            _self.on_after_blockface(_self, {event_name:'after_blockface', value:null});
67332    }
67333
67334    function do_color_filters(e) {
67335
67336        var proceed = true;
67337        if(_self.on_before_color_filters)
67338            proceed = _self.on_before_color_filters( _self, {event_name:'before_color_filters', value:null} );
67339
67340        if(!proceed)
67341            return;
67342
67343        do_color_filter_click(_self, null);
67344
67345        if(_self.on_after_color_filters)
67346            _self.on_after_color_filters( _self, {event_name:'after_color_filters', value:null} );
67347    }
67348
67349    function do_legend(e) {
67350        var image_info = _the_viewer.getImagePosition();
67351
67352        var proceed = true;
67353        if (_self.on_before_legend)
67354            proceed = _self.on_before_legend(_self, {event_name:'before_legend', value:image_info});
67355
67356        if (!proceed)
67357            return;
67358
67359        if (_self.on_after_legend)
67360            _self.on_after_legend(_self, {event_name:'after_legend', value:image_info});
67361    }
67362
67363    function do_sync(e) {
67364        var image_info = _the_viewer.getImagePosition();
67365
67366        var proceed = true;
67367        if (_self.on_before_sync)
67368            proceed = _self.on_before_sync(_self, {event_name:'before_sync', value:image_info});
67369
67370        if (!proceed)
67371            return;
67372
67373        if (_self.on_after_sync)
67374            _self.on_after_sync(_self, {event_name:'after_sync', value:image_info});
67375    }
67376
67377    function do_close(e) {
67378        var image_info = _the_viewer.getImagePosition();
67379
67380        var proceed = true;
67381        if (_self.on_before_close)
67382            _self.on_before_close(_self, {event_name:'close', value:image_info});
67383
67384        if (!proceed)
67385            return;
67386
67387        // what to do here?  unlink the DOM elements?
67388    }
67389
67390    function manage_graphics_image_type_change() {
67391
67392        if (!_the_viewer.graphics_enabled)
67393            return;
67394
67395        // update the id field of the zap viewers images to point to the correct thing
67396
67397        // TODO: current display default always shows initial display image type in the imagestr
67397ip.
67398        // You don't want to alter the imagestrip's array of image.id values, because keyboard nav
67399        // uses the DOM <img> tag id attribute values to retrieve ids which it then matches against
67400        // the imagestrip's image[id] to get next/previous/first/last image objects. Should change
67401        // aizap gem's keyboard nav code to get away from using DOM values.
67402//        var id_field = (_current_image_type == 'nissl') ? 'nissl_id' : 'ish_id';
67403//        var images = _the_viewer.strip.getImages();
67404//        for (var key in images) images[key].id = images[key][id_field];
67405
67406// TODO: hack alert.  This shouldn't be necessary but there seems to be a bug in the flash renderer that causes the last graphics doc to persist.  This hides all the layers
67407        var group_list = _the_viewer.zapimg.graphics.getGroupList('json');
67408        if (group_list && group_list.length) {
67409            var groups = eval(group_list);
67410            for (var i = 0; i < groups.length; i++) {
67411                _the_viewer.zapimg.graphics.setGroupVisibility(groups[i].name, false);
67412            }
67413        }
67414    }
67415
67416    /**
67417     * Event handler for image selection menu (class=viewControlSelect)
67418     * @param e
67419     */
67420    function do_image_select(e) {
67421
67422        // the value of the select option after the change
67423        var value = $j(this).val();
67424        var proceed = true;
67425
67426        if (_self.on_before_image_type_change)
67427            proceed = _self.on_before_image_type_change(_self, {event_name:'before_image_type_change', value:value});
67428
67429        if (!proceed)
67430            return;
67431
67432        var new_image_type = value;
67433        var new_filter = _image_filters[new_image_type];
67434        _the_viewer.setFilter(new_filter.filterName, new_filter.filterVals, _sync_strip_image_filter);
67435
67436        switch(new_image_type) {
67437            case 'ish':
67438                hide_contrast(false);
67439                _the_viewer.setSrcKey('path');
67440                if (_sync_strip_image_type)
67441                    _the_viewer.strip.setSrcKey('path');
67442                break;
67443            case 'expression':
67444                hide_contrast(true);
67445                _the_viewer.setSrcKey('expression_path');
67446                if (_sync_strip_image_type) {
67447                    _the_viewer.strip.setSrcKey('expression_path');
67448                    // always apply the filter to thumbnails otherwise they appear grayscale
67449                    _the_viewer.strip.setFilter(new_filter.filterName, new_filter.filterVals);
67450                }
67451                break;
67452            case 'nissl':
67453                hide_contrast(false);
67454                _the_viewer.setSrcKey('nissl_path');
67455                if (_sync_strip_image_type)
67456                    _the_viewer.strip.setSrcKey('nissl_path');
67457                break;
67458        }
67459
67460        manage_graphics_image_type_change();
67461
67462        _current_image_type = new_image_type;
67463
67464        if (_self.on_after_image_type_change)
67465            _self.on_after_image_type_change(_self, {event_name:'after_image_type_change', value:value});
67466
67467    }
67468
67469    function do_contact_sheet(e) {
67470        var proceed = true;
67471        if (_self.on_before_contact_sheet)
67472            proceed = _self.on_before_contact_sheet(_self, {event_name:'before_contact_sheet', value:null});
67473
67474        if (!proceed)
67475            return;
67476
67477        if (_self.on_after_contact_sheet)
67478            _self.on_after_contact_sheet(_self, {event_name:'after_contact_sheet', value:null});
67479    }
67480
67481    /**
67482     *
67483     * @param {Object} e - jQuery event
67484     * @param {Object} ui - jquery ui
67485     */
67486    function do_contrast(e, ui) {
67487        var val = ui.value / 100;
67488
67489        var proceed = true;
67490        if (_self.on_before_contrast)
67491            proceed = _self.on_before_contrast(_self, {event_name:'before_contrast', value:val});
67492
67493        if (!proceed)
67494            return;
67495
67496        _the_viewer.setFilter('contrast', '0.5,' + (val - .0001).toFixed(4) + ',0,255,4', _sync_strip_image_filter);
67497
67498        // update local copy of the appropriate filter
67499        var f = _the_viewer.getFilter();
67500        update_image_filters(f);
67501
67502        if (_self.on_after_contrast)
67503            _self.on_after_contrast(_self, {event_name:'after_contrast', value:val});
67504    }
67505
67506    function hide_contrast(hide) {
67507
67508        if(hide) {
67509            $j('#_zap_tool_contrast_' + _the_viewer.id).hide();
67510            $j('#_zap_tool_color_filters_' + _the_viewer.id).hide();
67511        }
67512        else {
67513            $j('#_zap_tool_contrast_' + _the_viewer.id).show();
67514            $j('#_zap_tool_color_filters_' + _the_viewer.id).show();
67515        }
67516    }
67517
67518    /**
67519     *
67520     * @param {number} image_id
67521     * @param {number} center_x
67522     * @param {number} center_y
67523     * @param {number} tier_delta tier difference from displayed image tier
67524     * @param {Object} options
67525     * @param {number} [options.absolute_tier] (optional argument) tier to use, regardless of tier_delta or current display tier
67526     * @param {Object} options.image_selected - callback function
67527     */
67528    function select_image_by_center(image_id, center_x, center_y, tier_delta, options) {
67529
67530        if (_positioning)
67531            return;
67532
67533        _positioning = true;
67534        // it takes some time for the selected image to load,
67535        // so wait for it to be available before trying to set the position.
67536
67537        // we're taking over the onImageChange event handler, so keep a handle
67538        // to any existing handler so it can be restored and called when we're done with it.
67539        var old_changer = _the_viewer.onImageChange;
67540        _the_viewer.onImageChange = do_queued_position;
67541        var tier_value = options.absolute_tier ? options.absolute_tier : 0; // use if defined, otherwise set to 0
67542        _position_queue = {
67543            tier_delta:tier_delta,
67544            x:center_x,
67545            y:center_y,
67546            tier:tier_value,
67547            old_handler:old_changer,
67548            callback: options.ready
67549        };
67550
67551        _self.select_image(image_id, false);
67552    }
67553
67554    /**
67555     *
67556     * @param evt Prototype library callback arg
67557     */
67558    function do_queued_position(evt) {
67559
67560        if (!_position_queue || !_positioning)
67561            return;
67562
67563        // what the user gave us was a tier_delta, so figure out what the new tier should be
67564        var image_info = _self.get_image_info();
67565        if (_position_queue.tier >= 0 && _position_queue.tier_delta == 0) {
67566            // use absolute tier number, which is existing value of _position_queue.tier
67567        }
67568        else {
67569            // change tier number relative to current display tier
67570            _position_queue.tier = image_info.position.image.tier + _position_queue.tier_delta;
67571        }
67572
67573        // we are setting the center point of the image, while setPosition
67574        // expects left, top values.  Calculate top,left that will put
67575        // our x,y in the center of the image.
67576        var top_left = get_top_left(_position_queue);
67577
67578        _the_viewer.setPosition('image', _position_queue.tier, parseFloat(top_left.x), parseFloat(top_left.y));
67579
67580        // restore original handler
67581        if (_position_queue.old_handler)
67582            _the_viewer.onImageChange = _position_queue.old_handler;
67583
67584        // now call the old handler to finish remaining tasks
67585        if (_position_queue.old_handler)
67586            _the_viewer.onImageChange(evt);
67587
67588        // now call the ready callback
67589        if (_position_queue.callback) {
67590            _position_queue.callback();
67591        }
67592
67593        _position_queue = null;
67594        _positioning = false;
67595    }
67596
67597
67598    /**
67599     * Find the zoomify tier number (0 to n) that will cause a rectangle to be maximally displayed in the view,
67600     * also described as 'fit-to-view'.
67601     * In our tier notation, 'n' corresponds to the tier of the full-resolution image.
67602     * Assume that the rectangle dimensions are larger than the view.
67603     *
67604     * @param tier_count total number of zoomify tiers in the current image
67605     * @param rect rectangle dimensions scaled to full resolution image. Hash has keys 'width', 'height'
67606     * @param view view dimensions scaled to full resolution image. Hash has keys 'width', 'height'
67607     * @return tier number
67608     */
67609    function fit_rect_to_view(tier_count, rect, view) {
67610        var fit_tier;
67611
67612        var rw = rect.width;
67613        var rh = rect.height;
67614        var vw = view.width;
67615        var vh = view.height;
67616
67617        // calculate how many 2X reductions of the rectangle dimensions are needed
67618        // to fit within the view dimensions
67619        var width_reductions = Math.ceil((Math.log(rect.width / view.width)) / Math.log(2));
67620        width_reductions = width_reductions < 0 ? 0 : width_reductions;
67621        var height_reductions = Math.ceil((Math.log(rect.height / view.height)) / Math.log(2));
67622        height_reductions = height_reductions < 0 ? 0 : height_reductions;
67623
67624        // use the larger of the reductions in the width, height dimensions to ensure no cropping
67625        var reductions = (width_reductions - height_reductions) > 0 ? width_reductions : height_reductions;
67626
67627        fit_tier = tier_count - 1 - reductions;
67628        fit_tier = fit_tier < 0 ? 0 : fit_tier;
67629
67630        return fit_tier;
67631    }
67632
67633    function get_top_left(position) {
67634
67635        var image_info = _self.get_image_info();
67636        var scale = Math.pow(2, (image_info.image.tier_count - 1) - position.tier);
67637        var view = $j('#zapimg' + _self.container_id);
67638
67639        var viewer_width = view.width();
67640        var viewer_height = view.height();
67641
67642        var top_left = {
67643            x:(position.x) - ((viewer_width * scale) / 2),
67644            y:(position.y) - ((viewer_height * scale) / 2)
67645        };
67646
67647        return(top_left);
67648    }
67649
67650    /**
67651     * Get centerpoint of viewport image
67652     *
67653     * @returns {Object} hash with keys x, y in full resolution image coords
67654     */
67655    function get_center_point() {
67656
67657        var image_info = _self.get_image_info();
67658        var scale = Math.pow(2, (image_info.image.tier_count - 1) - image_info.position.image.tier);
67659
67660        var view = $j('#' + _self.container_id).find('.zapimg');
67661        var viewer_width = view.width();
67662        var viewer_height = view.height();
67663
67664        var center = {
67665            x:((viewer_width * scale) / 2) + image_info.position.image.x,
67666            y:((viewer_height * scale) / 2) + image_info.position.image.y
67667        };
67668        return(center);
67669    }
67670
67671    /**
67672     * Zoom viewport into the Atlas structure, selecting the image whose section that is the closest to the center
67673     * of the structure. This is the intent of the function, although to enact this operation, a series of
67674     * seemingly unrelated calls need to be made.
67675     *
67676     * @param atlas_id DevHuman atlas id
67677     * @param structure_id
67678     */
67679    function zoom_atlas_structure(atlas_id, structure_id) {
67680        // get the bounding box data so we can select the correct image/structure
67681        get_structure_bounding_boxes(structure_id, atlas_id, receive_bounding_boxes);
67682    }
67683
67684    /**
67685     * Return bounding boxes for this atlas and the given structure_id.
67686     *
67687     * @param structure_id    An ID from the set of structures associated with this atlas.
67688     * @param atlas_id        ID of current atlas.
67689     * @param callback        Called when the data is fetched successfully.  The first argument
67690     *                        to this callback will be an array of BoundingBox records.
67691     *                        NOTE that if an error occurs during the request the model's
67692     *                        settings.on_error callback will be used.
67693     */
67694    function get_structure_bounding_boxes(structure_id, atlas_id, callback) {
67695        _the_daos.data.fetch_structure_bounding_boxes(structure_id, atlas_id, {
67696            success_callback:callback,
67697            error_callback:on_error
67698        });
67699    }
67700
67701    function receive_bounding_boxes(data) {
67702        if (!data)
67703            return;
67704
67705        var viewer_images = _self.get_images();
67706
67707        // this set is ordered by section number, so get the box at the mid-point
67708        var mid_box = data[Math.floor(data.length / 2)];
67709
67710        // find viewer image closest to mid-box, using section_number
67711        var closest_image = find_closest_image(viewer_images, mid_box.section_number);
67712
67713        var tier_count = closest_image.tier_count;
67714        var image_view = $j('#zapimg' + _self.container_id);
67715        var view = {width:image_view.width(), height:image_view.height()};
67716        var target_tier = fit_rect_to_view(tier_count, mid_box, view);
67717
67718        // select and display the image in the viewer, centered over the structure bounding box and zoomed in
67719        var xCenter = mid_box.x + mid_box.width / 2;
67720        var yCenter = mid_box.y + mid_box.height / 2;
67721//        console.log("tier_count = " + tier_count + ", target_tier = " + target_tier);
67722
67723        var tier_delta = 0; // used for relative tier changes from current image tier. Not applicable.
67724        select_image_by_center(closest_image.id, xCenter, yCenter, tier_delta, target_tier);
67725    }
67726
67727    /**
67728     * Find image record that is closest to the input section_number.
67729     *
67730     * @param images hash of hashes with values = image records
67731     * @param section_number target section number
67732     * @return {*}
67733     */
67734    function find_closest_image(images, section_number) {
67735        var closest_image = undefined;
67736        var snum;
67737
67738        // create sorted array of section_numbers from the images hash
67739        var snum_array = [];
67740        for (var key in images) {
67741            if (images.hasOwnProperty(key)) {
67742                snum = images[key].section_number;
67743                snum_array.push(snum);
67744            }
67745        }
67746        snum_array.sort(function (a, b) {
67747            return a - b
67748        }); // numeric sort, ascending order
67749
67750        // find image section number closest to target section number
67751        var closest_section_number = find_closest_number(snum_array, section_number);
67752
67753        // now find image object that has the closest_section_number
67754        for (key in images) {
67755            if (images.hasOwnProperty(key)) {
67756                if (closest_section_number == images[key].section_number) {
67757                    closest_image = images[key];
67758                    break;
67759                }
67760            }
67761        }
67762
67763        return closest_image;
67764    }
67765
67766    /**
67767     * Find number in array that is closest to the target number.
67768     *
67769     * @param numbers array of numbers, sorted in ascending order
67770     * @param target number to find in array
67771     * @return array value that is closest to target number
67772     */
67773    function find_closest_number(numbers, target) {
67774        var closest_number;
67775
67776        var len = numbers.length;
67777        var previous_number = -1;
67778        var current_number;
67779        for (var i = 0; i < len; ++i) {
67780            current_number = numbers[i];
67781            if (target == current_number) {
67782                // exact match
67783                closest_number = current_number;
67784                break;
67785            }
67786            else if (target < current_number && target > previous_number) {
67787                // edge case, target is less than all values in numbers array, so pick 1st array number
67788                if (previous_number == -1) {
67789                    closest_number = current_number;
67790                    break;
67791                }
67792                else {
67793                    // target value is between array numbers, so default to the closest array number
67794                    var hi_diff = current_number - target;
67795                    var lo_diff = target - previous_number;
67796                    if (hi_diff == lo_diff) {
67797                        closest_number = current_number; // arbitrary decision
67798                    }
67799                    else if (hi_diff > lo_diff) {
67800                        closest_number = previous_number;
67801                    }
67802                    else {
67803                        closest_number = current_number;
67804                    }
67805                    break;
67806                }
67807            }
67808            previous_number = current_number;
67809        }
67810
67811        if (closest_number == undefined) {
67812            // target larger than any of the array numbers, pick the last number in array.
67813            closest_number = numbers[len - 1];
67814        }
67815
67816        return closest_number;
67817    }
67818
67819    function on_error(msg) {
67820        alert(msg);
67821    }
67822
67823    function init() {
67824        build_viewer();
67825        add_toolbar();
67826    }
67827
67828    if (!_defer_init)
67829        init();
67830
67831}
67832
67833var zap_functions = {
67834
67835    update_scalebar:function (evt) {
67836
67837        var viewer = evt.memo.viewer;
67838        var image = viewer.getImageInfo();
67839
67840        if (!viewer || !image || !image.resolution)
67841            return;
67842
67843        var res = image.resolution < 0 ? 1 : image.resolution;
67844        viewer.setResolution(res);
67845    }
67846};
67847"use strict";
67848/**
67849 * Represent manual annotation data as a two axis (x-y) graphical chart, showing age on the x axis and intensity and
67850 * density on the y axis
67851 *
67852 * @param container
67853 * @param data
67854 * @param options
67855 */
67856function manual_annotation_chart(container, data, options) {
67857    this.data = data;
67858    this.container = jQuery(container);
67859    this.settings = {
67860        color_map: {
67861            "Cannot Annotate": "#ddd",
67862            Undetected: "#ffff00",
67863            Low: "#ff8000",
67864            Medium: "#ff0000",
67865            High: "#800000"
67866        },
67867        hotmetal_color_map: {
67868            "Cannot Annotate": "#000",
67869            Undetected: "#a50000",
67870            Low: "#ff5000",
67871            Medium: "#fffa00",
67872            High: "#fffffb"
67873        },
67874        redtemp_color_map: {
67875            "Cannot Annotate": "#000",
67876            Undetected: "#590000",
67877            Low: "#b60b00",
67878            Medium: "#ff8400",
67879            High: "#fffdfb"
67880        },
67881        byr_color_map: {
67882            "Cannot Annotate": "#ddd",
67883            Undetected: "#000000",
67884            Low: "#0000ff",
67885            Medium: "#ffff00",
67886            High: "#ff0000"
67887        },
67888        blacktored_color_map: {
67889            "Cannot Annotate": "#ddd",
67890            Undetected: "#000000",
67891            Low: "#3F0000",
67892            Medium: "#7F0000",
67893            High: "#BD0000"
67894        },
67895        height: '100px',                 // or any valid css height descriptor
67896        chart_type: 'heatmap',           // or 'bars'
67897        age_label_position: 'bottom',    // or 'top' or 'none'
67898        measure_label_position: 'left',    // or 'right' or 'none'
67899        render_delegate: null
67900    };
67901    jQuery.extend(true, this.settings, options);
67902
67903    this.set_type = function (new_type) {
67904        this.settings.chart_type = new_type;
67905        render_chart();
67906    };
67907
67908    var _self = this;
67909    var _structures = [];
67910    var _ages = [];
67911    var _table = null;
67912    var _browser = browser();
67913
67914
67915    function build_scaffold() {
67916        var rows = _self.settings.include_pattern ? 6 : 5;
67917        var columns = (_structures.length * _ages.length) + 2;
67918        var age_cell_width = _structures.length;
67919        var header_row_cell_count = _ages.length + 2;
67920        var html = "<table class='anno_chart_table'>";
67921
67922        // the first header row is for the top age labels
67923        html += "<tr class='anno_chart_top_header'>";
67924        for (var i = 0; i < header_row_cell_count; i++) {
67925            if (i == 0 || i == header_row_cell_count - 1) {
67926                html += i == 0 ? "<th class='anno_measure_label_left'></th>" : "<th class='anno_measure_label_right'></th>";
67927            } else {
67928                html += "<th colspan='" + age_cell_width + "'>";
67929                html += _ages[i - 1];
67930                html += "</th>";
67931            }
67932        }
67933        html += "</tr>";
67934
67935        // the next chunk of rows is for each of the data cells + 1 cell on either end for labels
67936        for (var r = 0; r < rows - 2; r++) {
67937            html += "<tr>";
67938            for (var c = 0; c < columns; c++) {
67939                // this is a label cell
67940                if (c == 0 || c == columns - 1) {
67941                    html += "<td class='anno_row_label";
67942                    switch (r) {
67943                        case 0:
67944                            html += c == 0 ? " anno_measure_label_left" : " anno_measure_label_right";
67945                            html += " anno_struct_strip_cell'>";  // adding a class name to the first row of interior cells
67946                            break;
67947                        case 1:
67948                            html += c == 0 ? " anno_measure_label_left" : " anno_measure_label_right";
67949                            html += "'>I";   // intensity
67950                            break;
67951                        case 2:
67952                            html += c == 0 ? " anno_measure_label_left" : " anno_measure_label_right";
67953                            html += "'>D";
67954                            break;
67955                        case 3:
67956                            html += c == 0 ? " anno_measure_label_left" : " anno_measure_label_right";
67957                            html += "'>P";
67958                            break;
67959                        default:
67960                            html += "'>";
67961                    }
67962                    html += "</td>";
67963                }
67964                else { // this is an interior cell that will hold a value
67965                    html += "<td class='";
67966                    switch (r) {
67967                        case 0:
67968                            html += " anno_struct_strip_cell'>";  // adding a class name to the first row of interior cells
67969                            break;
67970                        default:
67971                            html += "anno_cell'>";
67972                    }
67973                    html += "</td>";
67974                }
67975            }
67976            html += "</tr>";
67977        }
67978
67979        // the last header row is for the bottom age labels
67980        html += "<tr class='anno_chart_bottom_header'>";
67981        for (var i = 0; i < header_row_cell_count; i++) {
67982            if (i == 0 || i == header_row_cell_count - 1) {
67983                html += i == 0 ? "<th class='anno_measure_label_left'></th>" : "<th class='anno_measure_label_right'></th>";
67984            } else {
67985                html += "<th  foo='goo' colspan='" + age_cell_width + "'>";
67986                if (_self.settings.age_label_link) {
67987                    html += "<a class='age_label_link' href='' title='" + _self.settings.age_label_link_title + "'>";
67988                    html += _ages[i - 1];
67989                    html += "</a>";
67990                } else {
67991                    html += _ages[i - 1];
67992                }
67993                html += "<input type='checkbox' class='_experiment_select_check' experiment.id=''/>";
67994                html += "</th>";
67995            }
67996        }
67997        html += "</tr>";
67998
67999        _self.container.empty();
68000        _table = jQuery(html).appendTo(_self.container);
68001    }
68002
68003    function set_chart_options() {
68004        if (_self.settings.measure_label_position == 'left')
68005            _table.find('.anno_measure_label_right').css({display: 'none'});
68006        else if (_self.settings.measure_label_position == 'right')
68007            _table.find('.anno_measure_label_left').css({display: 'none'});
68008        if (_self.settings.measure_label_position == 'none') {
68009            _table.find('.anno_measure_label_left').css({display: 'none'});
68010            _table.find('.anno_measure_label_right').css({display: 'none'});
68011        }
68012
68013        if (_self.settings.age_label_position == 'bottom') {
68014            _table.find('.anno_chart_top_header').css({display: 'none'});
68015        }
68016        else if (_self.settings.age_label_position == 'top')
68017            _table.find('.anno_chart_bottom_header').css({display: 'none'});
68018        if (_self.settings.age_label_position == 'none') {
68019            _table.find('.anno_chart_top_header').css({display: 'none'});
68020            _table.find('.anno_chart_bottom_header').css({display: 'none'});
68021        }
68022    }
68023
68024    function render_chart() {
68025        if (_self.settings.render_delegate) {
68026            _self.settings.render_delegate(data, _table);
68027            return;
68028        }
68029
68030        if (_self.settings.chart_type == 'bars') {
68031            _table.find('.anno_struct_strip_cell').css({display: 'none'});
68032            render_bars();
68033        }
68034        else
68035            render_heatmap();
68036    }
68037
68038    function render_heatmap() {
68039        // color the header with the structure colors
68040        var cells = _table[0].rows[1].cells;
68041        var struct_count = _structures.length;
68042        for (var a = 0; a < _ages.length; a++)
68043            for (var s = 0; s < struct_count; s++)
68044                jQuery(cells[(a * struct_count) + 1 + s]).css({backgroundColor: '#' + _structures[s].color_hex_triplet});
68045
68046
68047        // color the interior cells based on the color_map
68048        // The intensity row is index 2, density = 3
68049        var intensity_cells = _table[0].rows[2].cells;
68050        var density_cells = _table[0].rows[3].cells;
68051        for (a = 0; a < _ages.length; a++) {
68052            for (s = 0; s < struct_count; s++) {
68053                var anno = _self.data.data_sets[a].manual_annotations[s];
68054                jQuery(intensity_cells[(a * struct_count) + 1 + s]).css({backgroundColor: _self.settings.color_map[anno.intensity_call]});
68055                jQuery(density_cells[(a * struct_count) + 1 + s]).css({backgroundColor: _self.settings.color_map[anno.density_call]});
68056            }
68057        }
68058
68059        // add hrefs to the age labels
68060        if (_self.settings.age_label_link) {
68061            var label_row = _self.settings.include_pattern ? 5 : 4;
68062            var cells = _table[0].rows[label_row].cells;
68063
68064            for (var a = 0; a < _ages.length; a++) {
68065
68066                var anno = _self.data.data_sets[a];
68067                jQuery(cells[a + 1])
68068                    .find('.age_label_link')
68069                    .attr('href', (_self.settings.age_label_link + anno.id));
68070
68071                jQuery(cells[a + 1])
68072                    .find('._experiment_select_check')
68073                    .attr('experiment.id', anno.id);
68074            }
68075        }
68076
68077        if (_self.settings.include_pattern)
68078            render_pattern();
68079    }
68080
68081
68082    function render_bars() {
68083
68084        // color the interior cells based on the color_map
68085        // The intensity row is index 2, density = 3
68086        var intensity_cells = _table[0].rows[2].cells;
68087        var density_cells = _table[0].rows[3].cells;
68088        var struct_count = _structures.length;
68089        for (var a = 0; a < _ages.length; a++) {
68090            for (var s = 0; s < struct_count; s++) {
68091                var anno = _self.data.data_sets[a].manual_annotations[s];
68092                jQuery(intensity_cells[(a * struct_count) + 1 + s]).css({backgroundColor: _self.settings.color_map[anno.intensity_call]});
68093                jQuery(density_cells[(a * struct_count) + 1 + s]).css({backgroundColor: _self.settings.color_map[anno.density_call]});
68094            }
68095        }
68096
68097        render_pattern(true);
68098    }
68099
68100    function new_bar(anno) {
68101        // apparently this has not been implemented
68102    }
68103
68104    function render_pattern(structure_colored) {
68105        // for the patter call we need to represent one of
68106        // Undetected, Regional, Gradient or Full.
68107        // do these by
68108        // Undetected: background is the same color as density Undetected.
68109        // Regional: Undetected background with two regions at High color
68110        // Gradient: css gradient from Undetected to High
68111        // Full: High color.
68112        var pattern_cells = _table[0].rows[4].cells;
68113        var cell = jQuery(pattern_cells[1]);
68114        var height = (cell.height() / 2);
68115        var width = (cell.width());
68116
68117        var struct_count = _structures.length;
68118        for (var a = 0; a < _ages.length; a++) {
68119            for (var s = 0; s < struct_count; s++) {
68120                var anno = _self.data.data_sets[a].manual_annotations[s];
68121                var style = {};
68122
68123                switch (anno.pattern_call) {
68124                    case "Undetected":
68125                        style.backgroundColor = _self.settings.color_map["Undetected"];
68126                        break;
68127                    case "Regional":
68128                        style.backgroundColor = _self.settings.color_map["Undetected"];
68129                        // build a couple of little boxes and put them in the cell
68130                        cell = jQuery(pattern_cells[(a * struct_count) + 1 + s]);
68131                        var html = "<div class='region_wrapper'><div class='anno_region_box' ";
68132                        html += "style='position:absolute;top:0;left:0;";
68133                        html += "background-color:" + _self.settings.color_map["High"] + ";";
68134                        html += "width:" + width + "px;";
68135                        html += "height:" + height + "px;display:inline-block'></div>";
68136
68137                        html += "<div class='anno_region_box' ";
68138                        html += "style='position:absolute;top:" + height + "px;left:" + width + "px;";
68139                        html += "background-color:" + _self.settings.color_map["High"] + ";";
68140                        html += "width:" + width + "px;";
68141                        html += "height:" + height + "px;display:inline-block'></div></div>";
68142
68143                        cell.append(html);
68144                        break;
68145                    case "Gradient":
68146                        var start_color = _self.settings.color_map["Undetected"];
68147                        var stop_color = _self.settings.color_map["High"];
68148                        //TODO: this is browser specific.  I don't know of any other way to do this:
68149                        switch (_browser) {
68150                            case "WebKit":
68151                                style.background = "-webkit-linear-gradient(left top, " + start_color + ", " + stop_color + ")";
68152                                break;
68153                            case "Firefox":
68154                                style.background = "linear-gradient(to right bottom, " + start_color + ", " + stop_color + ")";
68155                                break;
68156                            case "MSIE":
68157                                style.filter = "progid:DXImageTransform.Microsoft.gradient(startColorstr='" + start_color + "', endColorstr='" + stop_color + "')";
68158                                break;
68159                            default:
68160                                style.backgroundColor = start_color;
68161                        }
68162                        break;
68163                    case "Full":
68164                        style.backgroundColor = _self.settings.color_map["High"];
68165                        break;
68166                    default:
68167                        style.backgroundColor = _self.settings.color_map["Undetected"];
68168                }
68169                jQuery(pattern_cells[(a * struct_count) + 1 + s]).css(style);
68170            }
68171        }
68172
68173    }
68174
68175
68176    function browser() {
68177        var ua = navigator.userAgent.toString();
68178
68179        if (/WebKit/g.exec(ua))
68180            return('WebKit');
68181
68182        if (/Firefox/g.exec(ua))
68183            return('Firefox');
68184
68185        if (/MSIE/g.exec(ua))
68186            return('MSIE');
68187
68188        return('Unknown');
68189    }
68190
68191    function build_chart() {
68192        build_scaffold();
68193        set_chart_options();
68194        render_chart();
68195    }
68196
68197    function munge_data() {
68198        var sample = _self.data.data_sets;
68199        if (!sample)
68200            return(false);
68201
68202        var age_count = sample.length;
68203
68204        for (var i = 0; i < age_count; i++) {
68205            _ages[i] = sample[i].specimen.donor.age.name;
68206        }
68207
68208        sample = sample[0].manual_annotations;
68209        var structure_count = sample.length;
68210        for (i = 0; i < structure_count; i++) {
68211            _structures[i] = sample[i].structure;
68212        }
68213
68214        return(true);
68215    }
68216
68217    function set_color_map() {
68218        try {
68219            var url = window.location.href;
68220            var qstring = url.split('?')[1];
68221            var pairs = qstring.split('&');
68222            var params = {};
68223            for (var i = 0; i < pairs.length; i++) {
68224
68225                var pair_vals = pairs[i].split('=');
68226                if (pair_vals && pair_vals.length == 2)
68227                    params[pair_vals[0]] = pair_vals[1];
68228            }
68229
68230            if (params['colormap']) {
68231                if (_self.settings[params['colormap'] + '_color_map']) {
68232                    _self.settings.color_map = _self.settings[params['colormap'] + '_color_map'];
68233                 }
68234            }
68235        } catch (e) {
68236            trace("failed parsing debug options: " + e);
68237        }
68238
68239    }
68240
68241    function init() {
68242        if (munge_data()) {
68243            build_chart();
68244        }
68245    }
68246
68247    init();
68248}
68249;
68250/**
68251 * @file multi_column_sort_widget.js
68252 * @class MultiColumnSortWidget
68253 *
68254 * This widget provides a UI which allows a user to specify the column sort order for one or more columns in a
68255 * SlickGrid table. The user defines the content of a single menu, and how many duplicate copies of the menu are to be
68256 * displayed in a horizontal row. Each menu is accompanied by a checkbox that is used to define sorting
68257 * direction -- if checked then the sort is ascending order, otherwise it is descending order. Requires use of jQuery
68258 * javascript library.
68259 *
68260 * When the widget's 'Sort' button is pressed, the column sort info is collected from the menus and checkboxes, and
68261 * delivered to a callback function defined by the user. It is the user's responsibility to perform the sorting.
68262 *
68263 * Assumptions
68264 * - menus are always defined so that the 1st item is blank (empty string)
68265 *
68266 * You can CSS style this widget based on the <div> container class, 'mcsort', <select> class 'mcsort_menu',
68267 * <button> class 'mcsort_button'
68268 *
68269 * Example of menu configuration
68270 * -----------------------------
68271 * Usually the menu configuration is read out of the public/application.json file. Here is a short example, defining
68272 * the menu (notice the 1st item is blank), the initial configuration of each menu, and the number of menus to display.
68273 * The menu items are usually comprised of all of the sortable columns in the SlickGrid table.
68274 * As shown below, the initial configuration of the menus is set to the blank item. However, if the SlickGrid table
68275 * is initially displayed with a particular column sort order, it may be useful to preconfigure the menus to match that
68276 * sort order, giving the user some visual feedback.
68277 * You can have define more initial configurations than there are number of menus, any extra configurations will be ignored.
68278 *
68279 * "multi_column_sort_widget":{
68280 *      "menu":[ // same menu contents used for each menu that is created
68281 *          {"name":"", "value":""},
68282 *          {"name":"Experiment", "value":"data_sets.id"},
68283 *          {"name":"Gene Symbol", "value":"genes.acronym"},
68284 *          {"name":"Tissue Location", "value":"structures.name"},
68285 *          {"name":"Age", "value":"ages.embryonic,ages.days"}
68286 *      ],
68287 *      "menu_configurations":[ // the 'asc' arg defines the check/uncheck state of the sort order checkbox
68288 *          {"name":"", "value":"", "asc":false},
68289 *          {"name":"", "value":"", "asc":false},
68290 *          {"name":"", "value":"", "asc":false}
68291 *      ],
68292 *      "num_menus":3 // number of menus to create and display
68293 * }
68294 *
68295 * Example of integration with app code, SlickGrid
68296 * -----------------------------------------------
68297 * There is usually a javascript view associated with the search results. This is the file where you instantiate an
68298 * instance of MultiColumnSortWidget:
68299 *      function build_mcsort() {
68300 *          var menu = _self.settings.mcsort_config.menu;
68301 *          var configs = _self.settings.mcsort_config.menu_configurations;
68302 *          var num_menus = _self.settings.mcsort_config.num_menus;
68303 *          var parent = _self.settings.mcsort_parent_id;
68304 *           _mcsort = new MultiColumnSortWidget(parent, menu, configs, num_menus, do_mcsort); // var defined elsewhere
68305 *      }
68306 *
68307 * Also found in this view are two callbacks, the first used by SlickGrid's single column sort, and the second by
68308 * MultiColumnSortWidget's 'Sort' button:
68309 *
68310 *      function do_sort(sort_column, sort_asc) {
68311 *          _self.model.sort(sort_column, sort_asc);
68312 *
68313 *          if (_mcsort) {
68314 *              // update multicolumn sort UI to display single column sort selection; UI reflects user sort action on SlickGrid
68315 *              _mcsort.setSingleMenuConfiguration(sort_column.name, sort_asc);
68316 *          }
68317 *      }
68318 *
68319 *      function do_mcsort(sort_data) {
68320 *          _self.model.mcsort(sort_data); // SlickGrid model function that performs the actual multicolumnsort
68321 *      }
68322 *
68323 * Finally, there is a cookie-cutter model file used by SlickGrid. You need to add a multicolumn sort function here:
68324 *
68325 *      this.mcsort = function (sort_data) {
68326 *          var sort_order = "";
68327 *          for (var i = 0; i < sort_data.length; ++i) {
68328 *              sort_order += sort_data[i].value + (sort_data[i].asc ? '$asc' : '$desc') + ',';
68329 *          }
68330 *           _search_options.order = sort_order.substring(0, sort_order.length - 1); // remove trailing ','
68331 *          do_gene_search(_search_term, _search_options, 'sort'); // this model's search function. Yours will be different.
68332 *      }
68333 *
68334 * @param container either a CSS id or a jQuery object
68335 * @param {array} menuItems define each item present in a menu
68336 * @param {array} menuConfiguration define state of each menu (default selection) and state of menu's asc checkbox
68337 * @param {number} numMenus number of menus to create
68338 * @param {object} sortCallback callback function that will perform the actual sort process
68339 */
68340
68341function MultiColumnSortWidget(parentContainer, menuItems, menuConfigurations, numMenus, sortCallback) {
68342    var _mcsortIdFragment = "_mcsort"
68343    var _menuIdFragment = "_menu_"; // use to distinguish GUI element ids
68344    var _cbIdFragment = "_cb_"; // use to distinguish GUI element ids
68345    var _buttonFragment = "_button";
68346
68347    var _mcsortClass = "mcsort";
68348    var _menuClass = "mcsort_menu";
68349    var _buttonClass = "mcsort_button";
68350
68351    var _self = this;
68352    var _parentContainer = (parentContainer instanceof jQuery) ? parentContainer : jQuery('#' + parentContainer);
68353    var _mcsortId = _parentContainer.attr('id') + _mcsortIdFragment;
68354    var _buttonId = _mcsortId + _buttonFragment;
68355    var _menuItems = menuItems;
68356    var _menuConfigs = menuConfigurations;
68357    var _numMenus = numMenus;
68358    var _callback = sortCallback;
68359    var _mcsortContainer;
68360    var _sortButton;
68361
68362    // public
68363    /**
68364     *
68365     * @param items array of menu item hashes, name = display name of menu item, value = selection value for the item
68366     * @param configs array of menu configurations for each menu, specifies default selection and checkbox state.
68367     */
68368    this.createUIComponents = function (items, configs, numMenus) {
68369        var description = 'Sort by: ';
68370        var menuDesc = '<select class="' + _menuClass + '">';
68371
68372        // don't create more menus that there are menu items
68373        var numMenuItems = items.length - 1; // don't count the blank menu item
68374        if (numMenus > numMenuItems) {
68375            numMenus = numMenuItems;
68376        }
68377
68378        // construct HTML string that constructs a single menu
68379        for (var i = 0; i < items.length; ++i) {
68380            menuDesc += '<option value="' + items[i].value + '">' + items[i].name + '</option>';
68381        }
68382        menuDesc += '</select>';
68383
68384        // construct HTML string that constructs a single checkbox
68385        var cbDesc = '<input type="checkbox" value="asc" checked="true" title="Sort column in ascending order if checked, otherwise sort in descending order">';
68386
68387        // insert text description that appears to far left of row of menus
68388        _mcsortContainer.text(description);
68389
68390        // create set of menuUnits (a menu and a checkbox), adding a unique CSS id to the <select> and <input> tags,
68391        // and inserting menuUnit into DOM in the mcsort container
68392        var menuId;
68393        var cbId;
68394        var menuElement;
68395        var cbElement;
68396
68397        // create each menu and its checkbox using a default configuration, and inserting it into the DOM
68398        for (i = 0; i < numMenus; ++i) {
68399            menuId = _mcsortId + _menuIdFragment + i;
68400            cbId = _mcsortId + _cbIdFragment + i;
68401            menuElement = jQuery(menuDesc).attr('id', menuId);
68402            cbElement = jQuery(cbDesc).attr('id', cbId);
68403            cbElement.after('asc');
68404            _mcsortContainer.append(menuElement);
68405            _mcsortContainer.append(cbElement);
68406            cbElement.after('asc');
68407
68408        }
68409
68410        // now configure the state of each menu + checkbox pair
68411        _self.setMenuConfigurations(configs);
68412
68413        // lastly, add a pushbutton for 'Sort' and attach it's click handler
68414        _sortButton = jQuery('<button type="button" id="' + _buttonId + '" class="' + _buttonClass + '">Sort</button>');
68415        _mcsortContainer.append(_sortButton);
68416        _sortButton.click(sortButtonPressed);
68417    }
68418
68419    /**
68420     * Configure the current selection of each menu, and the checked/unchecked
68421     * state of its checkbox. Configuration proceeds for the menus from left to right,
68422     * and if there are insufficient configurations for each menu, the remaining menus
68423     * will use a default configuration, where the 1st menu item is selected, and the
68424     * checkbox is checked (selected).
68425     *
68426     * Assumes menus have already been created and inserted into the DOM
68427     *
68428     * @param configs Array of hashes, {name:<menu item name>, value:<menu item value>, asc:<true or false}
68429     */
68430    this.setMenuConfigurations = function (configs) {
68431        // configure the state of each menu + checkbox pair
68432        var menuId;
68433        var cbId;
68434        var itemValue;
68435        var isChecked;
68436
68437        var numConfigs = (configs.length > _numMenus) ? _numMenus : configs.length;
68438        for (var i = 0; i < numConfigs; ++i) {
68439            itemValue = configs[i].value;
68440            isChecked = configs[i].asc;
68441            menuId = _mcsortId + _menuIdFragment + i;
68442            cbId = _mcsortId + _cbIdFragment + i;
68443            jQuery('#' + menuId).val(itemValue); // select menu item based on its value
68444            jQuery('#' + cbId).prop('checked', isChecked);
68445        }
68446
68447        // remaining menus that do not have configuration info are set to default state of 1st menu item is selected,
68448        // checkbox is checked.
68449        if (configs.length < _numMenus) {
68450            for (i = configs.length; i < _numMenus; ++i) {
68451                itemValue = _menuItems[0].value; // use value of 1st menu item, it should normally be ""
68452                isChecked = true; // always set to checked
68453                menuId = _mcsortId + _menuIdFragment + i;
68454                cbId = _mcsortId + _cbIdFragment + i;
68455                jQuery('#' + menuId).val(itemValue);
68456                jQuery('#' + cbId).prop('checked', isChecked);
68457            }
68458        }
68459    }
68460
68461    /**
68462     * When the user performs a single column sort on a SlickGrid column, this function can also be called to
68463     * have the MultiColumnSortWidget mirror the column sort choice visually, including column name and sort direction.
68464     *
68465     * Configures the state of the first menu and checkbox on the left to match the input arguments.
68466     * The remainder of the menus are set to the default state of blank menu item (first item of each menu)
68467     * and checked checkbox.
68468     * If the itemName is invalid, the first menu item in a menu is selected, and the checkbox is checked.
68469     *
68470     * @param itemName menu item that to be selected
68471     * @asc {boolean} selection state of 'asc' checkbox, true = checked, false = unchecked
68472     */
68473    this.setSingleMenuConfiguration = function (itemName, asc) {
68474        var itemValue;
68475        for (var i = 0; i < _menuItems.length; ++i) {
68476            if (_menuItems[i].name == itemName) {
68477                itemValue = _menuItems[i].value;
68478                break;
68479            }
68480        }
68481
68482        if (itemValue == undefined) {
68483            itemValue = _menuItems[0].value;
68484            itemName = _menuItems[0].name;
68485            asc = true;
68486        }
68487
68488        var configs = [
68489            {"name":itemName, "value":itemValue, "asc":asc}
68490        ];
68491        _self.setMenuConfigurations(configs);
68492
68493    }
68494
68495    this.setSortCallback = function (callback) {
68496        _callback = callback;
68497    }
68498
68499    this.saveStates = function () {
68500        // TBD
68501    }
68502
68503    this.restoreStates = function () {
68504        // TBD
68505    }
68506
68507    // privileged/private
68508
68509    /**
68510     * Creates a mcsort div container. If a preexisting container is in the DOM,
68511     * it is removed and re-created, then positioned as the first child element of the parent container.
68512     *
68513     * @return jQuery container div with css id value of _mcsortId
68514     */
68515    function createContainer() {
68516        var container;
68517
68518        container = jQuery('#' + _mcsortId);
68519        if (container.length != 0) {
68520            // remove existing container and its children from the DOM
68521            container.remove();
68522        }
68523
68524        // create new, insert as 1st child of parent
68525        container = jQuery('<div id="' + _mcsortId + '" class="' + _mcsortClass + '"></div>');
68526        _parentContainer.prepend(container);
68527
68528        return container;
68529    }
68530
68531    /**
68532     * Called when the Sort button is pressed.
68533     * Parses values from all the menus, updates the UI, then invokes the external callback
68534     * to pass back the final menu selections in an array of hashes. The array order corresponds the column sort
68535     * order, and the contents of the array hash.
68536     */
68537    function sortButtonPressed() {
68538        // parse out all menu selections and checkbox states, removing duplicates and ignoring 'blank' selections
68539        var selections = parseMenus();
68540
68541        // update visual state of all the menus based on the parsed menu selections
68542        _self.setMenuConfigurations(selections);
68543
68544        // make callback to deliver the sort data
68545        _callback(selections);
68546    }
68547
68548    /**
68549     * Menus are parsed from left to right.
68550     * Rule is: first one wins. This means that is there is a duplicate menu selection encountered, the original and
68551     * its checkbox state are used, and the duplicate is discarded.
68552     * Any menus with blank selections are ignored, along with its accompanying checkbox state.
68553     * @return array of hashes, each hash has keys name, value, asc
68554     */
68555    function parseMenus() {
68556        var selections = [];
68557        var usedMenuVals = [];
68558        var menuId, cbId;
68559        for (var i = 0; i < _numMenus; ++i) {
68560            menuId = _mcsortId + _menuIdFragment + i;
68561            cbId = _mcsortId + _cbIdFragment + i;
68562            var menu = jQuery('#' + menuId);
68563            var cb = jQuery('#' + cbId);
68564            var value = menu.val();
68565            // pass over any menus with values already used, or any values that are blank (including values comprised of space
68565s)
68566            if (jQuery.inArray(value, usedMenuVals) != -1 || value.replace(/ /g, '') == '') {
68567                continue;
68568            }
68569            else {
68570                usedMenuVals.push(value);
68571                var h = {"name":jQuery('#' + menuId + ' option:selected').text(), "value":value, "asc":cb.prop("checked")};
68572                selections.push(h);
68573            }
68574        }
68575
68576        return selections;
68577    }
68578
68579    function init() {
68580        _mcsortContainer = createContainer();
68581        _self.createUIComponents(_menuItems, _menuConfigs, _numMenus);
68582    }
68583
68584    init();
68585}
68586;
68587/**
68588 * Accordions with top level gene categories, enclosed content consisting of gene names.
68589 * Note: Accordion API must use jquery ui v1.10
68590 *
68591 * jQuery accordion terminology:
68592 *   header - the text that always visible which you click on to show/hide content below the header
68593 *   panel - the content that is shown/hidden below an accordion header
68594 *
68595 * @param container
68596 * @param daos
68597 * @param urls
68598 * @param opts
68599 * @constructor
68600 */
68601
68602AIBS.UI.StudyWidget = function(container, daos, urls, opts) {
68603
68604  /** @private */
68605  this.dao_ = daos.data;
68606
68607  /** @private */
68608  this.path_ = urls.search;
68609
68610  /**
68611   * opts may provide a restricted search type (one of 'autism', 'subcortex',
68612   * 'cortex' or 'schizophrenia'.)  If not, use 'gene' search type.
68613   * @private
68614   */
68615  this.searchType_ = opts['mode'] || 'gene';
68616
68617  /** @private */
68618  this.opts_ = {
68619    min_font: 9,      // needed?
68620    max_font: 34,     // needed?
68621    class_names: '',  // needed?
68622    header: true      // needed?
68623  };
68624  jQuery.extend(true, this.opts_, opts);
68625
68626  // begin wrapper
68627  var html = '<div class="studywidget ' + this.opts_.class_names + '">';
68628
68629  // add one box per category
68630  AIBS.array.forEach(opts['gene_categories'], function(cat) {
68631    // category bar
68632    html +=
68633        '<div class="ui-widget-header"><div>' + cat['name'] + '</div></div>';
68634
68635    html += '<div class="studywidget_accordion">'; // begin accordion
68636
68637    // add one accordion section per classification
68638    AIBS.array.forEach(cat['classifications'], function(cl) {
68639      html +=
68640          '<h3><a href="#">' + cl['name'] + '</a></h3>' +
68641          '<div id="gc' + cl['id'] + '">' + // id contains classification id
68642          '</div>'; // "contents", gene items to be added on demand
68643    }, this);
68644
68645    html += '</div>'; // end accordion
68646
68647  }, this);
68648
68649  html += '</div>'; // end wrapper
68650
68651  container[0].innerHTML = html;
68652
68653  // apply jQuery accordion plugin when document is entered
68654  jQuery(jQuery.proxy(function() {
68655    jQuery(".studywidget_accordion").accordion({
68656      heightStyle: "content",
68657      collapsible: true,
68658      header: "h3",
68659      active: false, // no initial expansion, needed to trigger ajax
68660      beforeActivate: jQuery.proxy(function(jevt, ui) {
68661//        var newContent = ui.newContent;
68662//        var collapsing = newContent.length == 0;
68663//        if (!collapsing) {
68664          if (ui.newHeader.length > 0 && ui.newPanel.length > 0) {
68665            this.reqGeneLinks_(ui.newHeader);
68666          }}, this)
68667    });
68668  }, this));
68669};
68670
68671
68672/**
68673 * Populate contents of accordion via RMA query.
68674 * If already populated, skip the query.
68675 *
68676 * @param {!Element} el An accordion header element (the thing you click on)
68677 */
68678AIBS.UI.StudyWidget.prototype.reqGeneLinks_ = function(el) {
68679  var id = el.context.nextElementSibling.id; // accordion panel element, will have a child element consisting of accordion content links
68680  var classificationId = parseInt(AIBS.string.stripNonNumerals(id));
68681  if (!jQuery.data(el[0], 'genesLoaded')) {
68682    var opts = {};
68683
68684    opts['error_callback'] = function(o, status, xhdr) {
68685      AIBS.dbg.error('StudyWidget#reqGeneLinks_/error_callback: ' + status);
68686    };
68687
68688    opts['success_callback'] = jQuery.proxy(function(response_data, status, xhdr) {
68689      var html = '<div><table>' +
68690          '<colgroup><col class=col1><col></colgroup>';
68691
68692      var geneArr = response_data[0].genes;
68693      geneArr.sort(function(geneA, geneB) { // RMA sort unreliable
68694        return AIBS.array.defaultCompare(geneA['acronym'], geneB['acronym']);
68695      });
68696
68697      AIBS.array.forEach(geneArr, function(gene) {
68698        var acronym = gene['acronym'];
68699        var name = gene['name'];
68700        html += '<tr><td><a href="' + this.path_ +
68701            '?search_term=' + acronym +
68702            '&search_type=' + this.searchType_ +
68703            '&exact_match=true' +
68704            '">' + acronym + '</a></td><td>' + name + '</td></tr>';
68705      }, this);
68706      html += '</table></div>';
68707      jQuery('#' + id).append(html); // append html to the child <div> below the <h3>
68708      jQuery(el[0]).data('genesLoaded', true); // attach named boolean data to the <h3> element
68709    }, this);
68710
68711    this.dao_.geneNamesFromClassificationId(classificationId, opts);
68712  }
68713};
68714"use strict";
68715// this depends on tag_container.js
68716
68717function tag_cloud(container, daos, urls, options) {
68718
68719    this.container = container;
68720    this.data = options.gene_classifications;
68721    this.urls = urls;
68722
68723    this.settings = {
68724        min_font:9,
68725        max_font:34,
68726        class_names:'',
68727        header:true,
68728        title:'Browse by Gene Classification'
68729    };
68730    $j.extend(true, this.settings, options);
68731
68732    this.set_title = function(new_title) {
68733        if(this.settings.header)
68734            _cloud_header.innerHTML = new_title;
68735    }
68736
68737
68738    var _self = this;
68739    var _cloud_header = null;
68740    var _cloud_container = null;
68741
68742
68743    function set_font_size(tags) {
68744
68745        var len = tags.length;
68746
68747        var min = 99999;
68748        var max = 0;
68749        for(var i = 0; i < len; i++) {
68750            min = Math.min(min, tags[i].value);
68751            max = Math.max(max, tags[i].value);
68752        }
68753        var range = max - min;
68754        var font_range = _self.settings.max_font - _self.settings.min_font;
68755
68756        var scale = font_range / Math.sqrt(range);
68757
68758        for(i = 0; i < len; i++) {
68759            //var size = (tags[i].value/max) * font_range;
68760            var size = _self.settings.min_font + (Math.sqrt((tags[i].value) - min) * scale);
68761            var limited_size = Math.ceil( Math.min( _self.settings.max_font, Math.max(_self.settings.min_font, size)));
68762            tags[i].font_size = limited_size + 'px';
68763        }
68764        return(tags);
68765    }
68766
68767    function cloud_renderer(tagger) {
68768        var tags = tagger.get_tags();
68769        var tag_container = tagger.container;
68770        var len = tags.length;
68771
68772        tags = set_font_size(tags);
68773
68774        var tag_html = "";
68775        for(var i = 0; i < len; i++) {
68776            var href = _self.urls.search + '?search_type=gene_classification&search_term=' + tags[i].classification_id;
68777            tag_html += "<div class='cloud_tag'><a class='cloud_link' href='" + href + "' style='font-size:" + tags[i].font_size + "'>" + tags[i].label + "</a></div>";
68778        }
68779        tag_container[0].innerHTML = tag_html;
68780    }
68781
68782    function init() {
68783
68784        var tag_container = new AIBS.UI.tag_container(_self.container, {tag_renderer:cloud_renderer});
68785        tag_container.add_tags(_self.data);
68786    }
68787
68788    init();
68789}
68790;
68791"use strict";
68792/**
68793 * I suspect we'll end up with a number of tag containers in our UI toolbox.  This
68794 * is a bare-bones implementation.
68795 * <p>
68796 * Other tag containers might look like the tag collections in Jira, FaceBook, or
68797 * like tag clouds.  Supply a delegate 'tag_renderer' function as an option when instantiating
68798 * this object to do the actual tag rendering for any sort of interesting tag container you like.
68799 * It might work.
68800 * </p>
68801 * <p>
68802 * The default tag renderer assumes an <input type='text'/> element is the container.  A tag_renderer delegate
68803 * has this prototype: tag_renderer(this); through 'this' your renderer will have access to
68804 * this.get_tags & this.container to do its rendering.
68805 * </p>
68806 * <p>
68807 * In all of the methods below the 'tag' object looks like this: {label:'foo', value:'bar'} and
68808 * labels must be unique.
68809 * </p>
68810 * <p>
68811 * This should probably be refactored as a jQuery plugin, so it can be instantiated with something like this:
68812 * <pre>
68813 * $('#md_div').tag_container({
68814 *      tag_renderer:my_cool_tag_cloud_renderer
68815 *    });
68816 * </pre>
68817 */
68818 AIBS.UI.tag_container = (function($j) {
68819
68820 return (
68821
68822    function(container, options) {
68823
68824        this.container = $j(container);
68825        this.renderer = options ? options.tag_renderer ? options.tag_renderer : render_tags : render_tags;
68826
68827        /**
68828         * add a tag to the container
68829         * @param tag {object} Like this: {label:'foo', value:'bar'}
68830         */
68831        this.add_tag = function(tag) {
68832             _tags[tag.label] = tag;
68833             this.renderer(this);
68834        }
68835
68836        /**
68837         * add an array of tags to the container
68838         * @param tags {array} Like this: [{label:'foo', value:'bar'}]
68839         */
68840        this.add_tags = function(tags) {
68841
68842             var len = tags.length;
68843             for(var i = 0; i < len; i++)
68844                _tags[tags[i].label] = tags[i];
68845
68846             this.renderer(this);
68847        }
68848
68849         /**
68850         * remove a tag from the container
68851         * @param tag {object} Like this: {label:'foo', value:'bar'}
68852         */
68853        this.remove_tag = function(tag) {
68854             delete _tags[tag.label];
68855             this.renderer(this);
68856        }
68857
68858        /**
68859         * get an array of all the tags in the container
68860         * @returns {array} Array of tag objects
68861         */
68862        this.get_tags = function() {
68863             var list = [];
68864             for(var key in _tags)
68865                 list.push(_tags[key]);
68866
68867            return(list);
68868        }
68869
68870        /**
68871         * re-renders the tags.  _Don't_ call this from your custom tag_renderer.
68872         */
68873        this.render = function() {
68874            this.renderer(this);
68875        }
68876
68877        /**
68878         * returns the text that's displayed in the container, which in some renderings
68879         * might be different than the set of labels in the tag collection.
68880         */
68881        this.get_displayed_text = function() {
68882            return( this.container.val());
68883        }
68884
68885        /**
68886         * removes all tags from the collection
68887         */
68888        this.clear = function() {
68889            _tags = {};
68890            this.renderer(this);
68891        }
68892
68893        /**
68894         * @private
68895         * @scope AIBS.UI.tag_container
68896         */
68897
68898         var _self = this;
68899         var _tags = {};
68900
68901         function render_tags(me) {
68902             var list = '';
68903             for(var key in _tags)
68904                 list += _tags[key].label + '; ';
68905
68906             if(list.length > 0)
68907                list = list.slice(0, -2);
68908
68909            var control_type = me.container.get(0).tagName;
68910//alert("the container: " + inspect('', me.container.get(0)));
68911            if(control_type == 'INPUT')
68912                me.container.val(list);
68913            else
68914                me.container.text(list);
68915
68916         }
68917
68918  function inspect(prefix, obj) {
68919      var s = "";
68920      for(var key in obj) {
68921          if((typeof obj[key]) == "function")
68922            s = s;
68923          else if((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
68924              s += "\n" + prefix + "key: " + key;// + inspect(prefix + "    ", obj[key]);
68925          else
68926              s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
68927      }
68928      return(s);
68929  }
68930    }
68931
68932 );
68933
68934
68935})(jQuery);
68936"use strict";
68937/**
68938 *
68939 * @param container
68940 * @param experiment_id
68941 * @param service_url
68942 * @param options
68943 */
68944AIBS.UI.threed_thumbnail_widget = function threed_thumbnail(container, experiment_id, service_url, options) {
68945
68946    this.container = $j(container);
68947    this.service_url = service_url;
68948    this.experiment_id = experiment_id;
68949    this.settings = {
68950        project_cell_height:125,        // height in pixels of the container
68951        slider_height:20,               // height in pixels reserved for the slider
68952        num_slider_images:36,           // number of images in an aba  mini thumbnail
68953        thumbnail_dims:[164,99],        // dimensions of a single frame of an aba mini thumbnail filmstrip
68954        animate_duration:0,
68955        include_slider:true
68956    };
68957    $j.extend(true, this.settings, options);
68958
68959
68960    this.set_experiment = function(experiment_id) {
68961
68962        this.experiment_id = experiment_id;
68963
68964        if(!_img_element)
68965            get_image();
68966        else
68967            _img_element.attr('src', this.service_url + this.experiment_id);
68968
68969        position_thumbnail();
68970    }
68971
68972    this.clear = function() {
68973
68974        this.experiment_id = 0;
68975
68976        if(!_img_element)
68977            return;
68978
68979        _img_element.attr('src', '/assets/black.png');
68980        _img_element.css({'background-image':'/assets/black.png'});
68981        _slider_index = 0;
68982        position_thumbnail();
68983    }
68984
68985    var _self = this;
68986    var _the_image = null;
68987    var _the_wrapper = null;
68988    var _cell_width = 0;
68989    var _cell_height = 0;
68990    var _slider_index = 0;
68991    var _img_element = null;
68992    var _the_slider = null;
68993
68994
68995    function build_scaffold() {
68996
68997        _cell_height = _self.settings.project_cell_height;
68998        _cell_width = _self.settings.thumbnail_dims[0];
68999
69000        var html = "<div id='_threed_wrapper_" + _self.experiment_id + "' class='_threed_wrapper'></div>";
69001        _the_wrapper = $j(html)
69002                       .css({
69003                           'padding':'0px',
69004                           'background-color':'#000',
69005                            width: _cell_width+'px',
69006                            height: _cell_height+'px',
69007                            overflow: "hidden",
69008                            background: "#000000 url('/assets/spinner_black.gif') no-repeat center center"
69009                        }).appendTo(_self.container);
69010    }
69011
69012
69013    function build_slider() {
69014
69015        var slider_height = _self.settings.slider_height;
69016        var slider_width = _self.settings.thumbnail_dims[0];
69017
69018        _the_slider = $j("<div class='_threed_slider'>")
69019            .css({
69020                width: slider_width+"px",
69021                height: slider_height+"px"
69022            })
69023            .appendTo(_the_wrapper);
69024
69025        _the_slider.slider({
69026          min: 0,
69027          max: _self.settings.num_slider_images-1,
69028          value: _slider_index,
69029          slide: do_slide,
69030          animate:_self.settings.animate_duration
69031        });
69032    }
69033
69034    function one_revolution() {
69035        var handle = _the_slider.find('a');
69036        $j(handle).animate({
69037            'left':'100%'
69038            },
69039            {
69040            'step':do_animate_step
69041            }
69042        );
69043    }
69044
69045    function do_animate_step(now, fx) {
69046        if(!now)
69047            return;
69048
69049        _slider_index = Math.round( ( (_self.settings.num_slider_images-1)/100 ) * now) ;
69050        position_thumbnail();
69051    }
69052
69053    function do_slide(event, ui) {
69054        _slider_index = ui.value;
69055        position_thumbnail();
69056    }
69057
69058    function get_image() {
69059        _the_image = new Image();
69060        _the_image.onload = on_image_ready;
69061        _the_image.onerror = function() {_the_wrapper.css({background: "#000" }) };
69062
69063        _the_image.src = _self.service_url + _self.experiment_id;
69064    }
69065
69066    function on_image_ready() {
69067        var scaled_width = Math.round(_the_image.width * _cell_width / _self.settings.thumbnail_dims[0]);
69068        var scaled_height = Math.round(_the_image.height * _cell_height / _self.settings.thumbnail_dims[1]);
69069        var div = $j("<div class='_threed_img_wrapper'>").css({
69070                         width: _self.settings.thumbnail_dims[0]+"px",
69071                         height: _self.settings.thumbnail_dims[1]+"px",
69072                         'position':'relative',
69073                         'top':0,
69074                         'overflow':'hidden'});
69075
69076        _img_element = $j("<img id='_threed_img_" + _self.experiment_id + "'>")
69077                        .attr('src',_the_image.src)
69078                        .css({
69079                            'width': (_self.settings.num_slider_images * _self.settings.thumbnail_dims[0])+"px",
69080                            'height': _self.settings.thumbnail_dims[1]+"px",
69081                            'position':'absolute',
69082                            'left':'0px',
69083                            'top':'0px'});
69084
69085
69086        div.append(_img_element);
69087        _the_wrapper.append(div);
69088        _img_element = $j('#_threed_img_'+_self.experiment_id);
69089
69090        if(_self.settings.include_slider)
69091            build_slider();
69092
69093        position_thumbnail();
69094        if(_self.settings.animate_duration)
69095            setTimeout(one_revolution, 250);
69096    }
69097
69098    function position_thumbnail() {
69099        var img_width = _img_element.width() / _self.settings.num_slider_images;
69100        _img_element.css({ left:(-_slider_index * img_width)+"px" });
69101    }
69102
69103    function init() {
69104
69105        build_scaffold();
69106
69107        if(_self.experiment_id)
69108            get_image();
69109    }
69110
69111    init();
69112}
69113;
69114"use strict";
69115var zap_keyboard = {
69116
69117
69118    keyDownEvent : function(evt) {
69119
69120        var viewer_object = evt.memo.viewer;
69121
69122        if (viewer_object) {
69123            var images = viewer_object.getImages();
69124            var image = viewer_object.getImageInfo();
69125            var pos = viewer_object.getPosition();
69126            var keyCode = evt.memo.keyCode;
69127            var key;
69128
69129            switch (keyCode) {
69130                case 70: // "f" = next image
69131                    var nextImage = zap_keyboard.getNextImage(image.id, images);
69132
69133                    if (nextImage != null) {
69134                        zap_keyboard.moveToImage(viewer_object, nextImage.id, image.tier, pos);
69135                    }
69136                    break;
69137                case 68: // "d" = go back to previous image
69138                    var prevImage = zap_keyboard.getPreviousImage(image.id, images);
69139
69140                    if (prevImage != null) {
69141                        zap_keyboard.moveToImage(viewer_object, prevImage.id, image.tier, pos);
69142                    }
69143                    break;
69144                case 69: // "e" = go to beginning of image list
69145                    var begImage;
69146
69147                    for (key in images) {
69148                        begImage = images[key];
69149                        break;
69150                    }
69151                    zap_keyboard.moveToImage(viewer_object, begImage.id, image.tier, pos);
69152                    break;
69153                case 82: // "r" = go to end of the image list
69154                    var endImage;
69155
69156                    for (key in images) {
69157                        endImage = images[key];
69158                    }
69159                    zap_keyboard.moveToImage(viewer_object, endImage.id, image.tier, pos);
69160                    break;
69161 /*               case 84: // "t" = advance all viewers to next image
69162                    //hbaMoveAll(true);
69163                    trace("move all to next");
69164                    break;
69165                case 87: // "w" = reverse all viewers to previous image
69166                    //hbaMoveAll(false);
69167                    trace("move to previous");
69168                    break;
69169                case 83: // "s" = synch
69170                    doSync(viewer_object);
69171                    break;
69172 */
69173                case 83: // "s" = sync
69174                    // trigger an artificial click event on the "Sync" button
69175                    var e = jQuery.Event("click");
69176                    jQuery('#_zap_tool_sync_' + viewer_object.id).trigger(e);
69177                    break;
69178                default: // pass unprocessed key events back to Zap Viewer, such as the zoom keys and arrow keys
69179                    evt.keyCode = keyCode;
69180                    var viewer = evt.memo.viewer;
69181                    viewer.doKey(evt);
69182            }
69183        }
69184    },
69185
69186
69187    getNextImage : function(currentImageId, images) {
69188        var isNextImage = false;
69189        var nextImage;
69190
69191        for (var key in images) {
69192            if (isNextImage) {
69193                nextImage = images[key];
69194                break;
69195            }
69196
69197            if (images[key].id == currentImageId) {
69198                isNextImage = true;
69199            }
69200        }
69201        return nextImage;
69202    },
69203
69204    getPreviousImage : function(currentImageId, images) {
69205        var prevImage;
69206
69207        for (var key in images) {
69208            if (images[key].id == currentImageId) {
69209                break;
69210            }
69211            else {
69212                prevImage = images[key];
69213            }
69214        }
69215        return prevImage;
69216    },
69217
69218    moveToImage : function(viewer, image_id, image_tier, position) {
69219
69220        var moveInfo = {};
69221        moveInfo.saveOnImageChange = viewer.onImageChange;
69222        moveInfo.tier = image_tier;
69223        moveInfo.x = position.normal.x;
69224        moveInfo.y = position.normal.y;
69225        viewer.moveInfo = moveInfo; // attach hash of movement info to the viewer as a property
69226
69227        // temporarily replace the existing onImageChange handler
69228        viewer.onImageChange = zap_keyboard.finishMoveToImage;
69229
69230        viewer.select(image_id);
69231        viewer.moveToImage(image_id);
69232    },
69233
69234    /* applies former image's pan and zoom, refreshes scale bar settings */
69235    finishMoveToImage : function(viewer, image) {
69236        var newTier;
69237        var moveInfo = viewer.moveInfo; // hash of movement info attached to viewer as a property
69238
69239        // restore original handler
69240        viewer.onImageChange = moveInfo.saveOnImageChange;
69241
69242        // edge case, moving from an image at a higher tier than exists in the new image
69243        var info = viewer.getImageInfo();
69244        if (moveInfo.tier > (info.num_tiers - 1)) {
69245            newTier = info.num_tiers - 1;
69246        }
69247        else {
69248            newTier = moveInfo.tier;
69249        }
69250
69251        viewer.setPosition('normal', newTier, moveInfo.x, moveInfo.y);
69252
69253        if(viewer.scalebar) {
69254            var evt = new Object();
69255            var memo = new Object();
69256            memo.viewer = viewer;
69257            memo.image = viewer.getImageInfo();
69258            evt.memo = memo;
69259            viewer.setResolution(memo.image.resolution); // viewer may not have set its global resolution to the image.resolution yet
69260            viewer.scalebar.updateScaleBar(evt);
69261        }
69262    }
69263}
69264;
69265/**
69266 * This function contains common functions used by the ISH search models for gene,
69267 * advanced (faceted), and gene classification. It contains boilerplate functions that support SlickGrid use, as well
69268 * as functions used to support multicolumn sorting.
69269 * When you instantiate one of the specific models, you need to instantiate this function and assign as a property
69270 * of the model instance..
69271 *
69272 * Example:
69273 *
69274 * model = new ish_gene_search_model(daos, model_options);
69275 * model.IshCommon = new IshCommon;
69276 *
69277 * Accessing the common functions within the specific model:
69278 *
69279 * _self.IshCommon.single_column_sort_order(args);
69280 *
69281 * @constructor
69282 */
69283
69284function IshCommon() {
69285// public methods
69286
69287    /**
69288     * Get row metadata
69289     *
69290     * @param row_index
69291     * @param data
69292     * @param column_config
69293     * @return {Object} hash containing row metadata, or empty hash for invalid row
69294     */
69295    this.do_get_item = function(row_index, data, column_config) {
69296        var item = {};
69297        if (data && row_index < data.length) {
69298            var d = data[row_index];
69299            var len = column_config.length;
69300            for (var i = 0; i < len; i++) {
69301                var column = column_config[i];
69302                if (column.field && column.field.length > 0)
69303                    item[column.field] = eval('d.' + column.data);
69304            }
69305            item.experiment_detail = d;
69306            item.failed = d.failed;
69307        }
69308
69309        return(item);
69310    };
69311
69312    /**
69313     * Logic calculating new paging info
69314     *
69315     * @param args hash with key-values for: pageSize, pageNum
69316     * @param page_num
69317     * @param page_size
69318     * @param total_rows
69319     * @return {Object} hash with key-values for: no_change, no_page, page_num, page_size
69320     */
69321    this.calc_paging_info = function(args, page_num, page_size, total_rows) {
69322        var reset_page_zero = false;
69323        var info = {};
69324
69325        info.no_change = false;
69326        info.no_page = false;
69327        info.page_num = page_num;
69328        info.page_size = page_size;
69329
69330        if (args.pageSize != undefined) {
69331            // if the page size hasn't changed, or is smaller than it was, and that's the only field being set, we're done.
69332            if ((args.pageSize == page_size) && (args.pageNum == undefined)) {
69333                info.no_change = true;
69334                return info;
69335            }
69336
69337            if (args.pageSize == 0) {
69338                info.page_size = total_rows + (page_size - 1);
69339                info.no_page = true;
69340            }
69341            else {
69342                if (args.pageSize !== page_size) {
69343                    // if the user changes the rows per page via the UI, then reset the current page number to 0 (see IT-6512)
69344                    reset_page_zero = true;
69345                }
69346                info.page_size = args.pageSize;
69347            }
69348        }
69349
69350        if (args.pageNum != undefined) {
69351            info.page_num = Math.min(args.pageNum, Math.ceil(total_rows / info.page_size));
69352        }
69353        else if (reset_page_zero) {
69354            info.page_num = 0;
69355        }
69356
69357        return info;
69358    };
69359
69360    /**
69361     * Default single column sort, called when user clicks on SlickGrid column title.
69362     *
69363     * @param sort_column column name
69364     * @param sort_asc true = sort ascending, false = sort descending
69365     * @param sort_suffix
69366     * @return {String}
69367     */
69368    this.single_column_sort_order = function(sort_column, sort_asc, sort_suffix) {
69369        // edge case for Age column sorting, can't sort on display value ('field' property)
69370        // assumes definition of metadata fields, 'metadata_days', 'metadata_embryonic'
69371        var sort_order = "";
69372        if (sort_column.id.toUpperCase() == 'AGE') {
69373            sort_order = sortable_age_order(sort_column.metadata_days, sort_column.metadata_embryonic, sort_asc);
69374        }
69375        else {
69376            sort_order = sort_column.field + (sort_asc ? '$asc' : '$desc');
69377        }
69378        sort_order += sort_suffix ? (',' + sort_suffix) : '';
69379
69380        return sort_order;
69381    };
69382
69383    /**
69384     * Multicolumn sort order
69385     *
69386     * Creates a warehouse-friendly string containing the column sorting information.
69387     * Each hash in the sort_data array consists of column keys and values for: name, value, asc
69388     *
69389     * @param sort_data array of hashes with info specifying the column sort order
69390     * @return {String}
69391     */
69392    this.mcsort_order = function(sort_data) {
69393        var sort_order = "";
69394        for (var i = 0; i < sort_data.length; ++i) {
69395            // edge case processing for Age column
69396            if (sort_data[i].name.toUpperCase() == 'AGE') {
69397                sort_order += mcsort_sortable_age_order(sort_data[i]) + ',';
69398            }
69399            else {
69400                sort_order += sort_data[i].value + (sort_data[i].asc ? '$asc' : '$desc') + ',';
69401            }
69402        }
69403
69404        return sort_order.substring(0, sort_order.length - 1); // remove trailing ','
69405    };
69406
69407    /**
69408     * Debug support function, pretty prints key-value pair found in obj
69409     *
69410     * @param prefix description of obj
69411     * @param obj hash object that is pretty printed
69412     * @return {String} formatted string of obj contents
69413     */
69414    this.inspect = function(prefix, obj) {
69415        var s = "";
69416
69417        for (var key in obj) {
69418            if ((typeof obj[key]) == "function")
69419                s = s;
69420            else if ((typeof obj[key]) == "object" && obj[key] !== null && key != "element")
69421                s += "\n" + prefix + "key: " + key + inspect(prefix + "    ", obj[key]);
69422            else
69423                s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
69424        }
69425        return(s);
69426    };
69427
69428    // private
69429
69430    /**
69431     * Create a sort order for SlickGrid single column sort for Age, based on both age in days and the embryonic boolean value.
69432     * Require configuration definition of properties 'metadata_days' and 'metadata_embryonic'
69433     * in the age_column object, this should be done in application.json file.
69434     * The age in days for postnatal ranges is a positive integer value from 0 to n, measured from time of birth.
69435     * The age in days for embryonic donors is also a positive integer value, from 0 to n, but measured from time of
69436     * conception. In order to correctly sort a mix of embryonic and postnatal donors, we have to incorporate the
69437     * boolean value for 'embryonic', which is true for prenatal and false for postnatal donors.
69438     *
69439     * sort the Age column correctly, from prenatal/postconception to postnatal to adult.
69440     * This is done by replacing the "field" property, which contains the display age value (14 yrs)
69441     * with a numeric value, the age in days. For prenatal/postconception, the value is multiplied by -1
69442     * so that we get proper numeric ordering; postnatal and adult ages are all > 0.
69443     * We use nondisplay meta-values that are present in the Age configuration, the properties 'metadata_days'
69444     * and "metadata_embryonic" to configure the "field" property.
69445     *
69446     * @param {number} age_days either days after conception, or days after birth. See param 'is_embryonic'.
69447     * @param {boolean} is_embryonic true if prebirth, false if postbirth.
69448     * @param {boolean} sort_asc boolean sort direction, true = ascending, false = descending
69449     * @return sort order string in AllenInstitute API syntax
69450     */
69451    function sortable_age_order(age_days, is_embryonic, sort_asc) {
69452        var sort_order;
69453
69454        if (!age_days || !is_embryonic) {
69455            // conventional sort order
69456            sort_order = age_column.field + (sort_asc ? '$asc' : '$desc');
69457        }
69458        else {
69459            if (sort_asc)
69460                sort_order = is_embryonic + '$desc,' + age_days + '$asc';
69461            else
69462                sort_order = is_embryonic + '$asc,' + age_days + '$desc';
69463        }
69464
69465        return sort_order;
69466    }
69467
69468    /**
69469     * Reformats the age params from multicolumn sort into a sortable format suitable for a warehouse query.
69470     * Assumes that the age value is in the form 'ages.embryonic,ages.days'
69471     *
69472     * @param age_hash keys name, value, asc
69473     * @return sort_order string that looks like 'ages.embryonic$desc,ages.days$asc' for ascending age or
69474     * 'ages.embryonic$asc,ages.days$desc' for descending age
69475     */
69476    function mcsort_sortable_age_order(age_hash) {
69477        var sort_order = "";
69478
69479        var value = age_hash.value;
69480        var asc = age_hash.asc;
69481        var after_embryonic = value.indexOf(',');
69482        var after_days = value.length;
69483        var start_days = value.indexOf('ages.days');
69484
69485        if (asc) {
69486            sort_order = value.substring(0, after_embryonic) + '$desc,' + value.substring(start_days, after_days) + '$asc';
69487        }
69488        else {
69489            sort_order = value.substring(0, after_embryonic) + '$asc,' + value.substring(start_days, after_days) + '$desc';
69490        }
69491
69492        return sort_order;
69493    }
69494}
69495
69496
69497
69498;
69499"use strict";
69500/**
69501 * Displays the 'About' text panel and contents, which are placed in a <div> with id = "about_block"
69502 * This plugin assumes that there are up to three blocks of HTML text that can be displayed in the panel,
69503 * and are called "first", "second", "third". These are configured in application.json 
69503in a section that can look like this:
69504 * "gene": {
69505 *    "class_name": "about_widget",
69506 *    "title": "NIH Blueprint NHP Atlas",
69507 *    "options": {
69508 *       "file_prefix": <optional, if present the file_prefix should be prepended to any file path in first/second/third hash values>
69509 *       "url_replacement_delimiter": <optional, if present the delimiter should surround a string which will later be replaced by an url>
69510 *       "first": <html for 1st block>,
69511 *       "second": <html for 2nd block>,
69512 *       "third": <html for 3rd block>
69513 *    }
69514 *
69515 * Note: this widget requires jQuery
69516 *
69517 * @param container
69518 * @param daos
69519 * @param urls
69520 * @param options
69521 */
69522function about_widget(container, daos, urls, options) {
69523
69524    this.container = container;
69525    this.settings = {
69526        "file_prefix":null,
69527        "class_prefix":"_aw",
69528        "url_replacement_delimiter":null,
69529        "first":"first",
69530        "second":"second",
69531        "third":"third"
69532    };
69533
69534    $j.extend(true, this.settings, options);
69535
69536
69537    this.set_content = function(content) {
69538
69539        update_content(content);
69540    }
69541
69542
69543    var _self = this;
69544    var _urls = urls;
69545    var _first_block = null;
69546    var _second_block = null;
69547    var _third_block = null;
69548
69549
69550    function build_scaffold() {
69551
69552        var classes = _self.settings.class_prefix;
69553        var id = _self.container.attr('id') + "_aw";
69554
69555        var html = "<div id='_" + id + "_wrapper' class='" + classes + "'>";
69556        html    += "    <div id='_" + id + "_first' class='" + classes + "_first'></div>";
69557        html    += "    <div id='_" + id + "_second' class='" + classes + "_second'></div>";
69558        html    += "    <div id='_" + id + "_third' class='" + classes + "_third'></div>";
69559        html    += "</div>";
69560
69561        _self.container.append(html);
69562
69563        _first_block = $j('#_' + id + "_first")[0];
69564        _second_block = $j('#_' + id + "_second")[0];
69565        _third_block = $j('#_' + id + "_third")[0];
69566    }
69567
69568    function update_content(content) {
69569
69570        if(content.first && content.first.length)
69571            load_html_block( _first_block, content.first);
69572
69573        if(content.second && content.second.length)
69574            load_html_block( _second_block, content.second);
69575
69576        if(content.third && content.third.length)
69577            load_html_block( _third_block, content.third);
69578    }
69579
69580    /**
69581     * Loads HTML content from either an application.json string or from an HTML file on the server, placing inside of
69582     * the specified <div>
69583     * If the HTML is read from a file, url string replacement will occur for specially delimited text (url_replacement_delimiter)
69584     *
69585     * @param {Object} block one of three display <div> elements
69586     * @param {String} content either the HTML for display in the block, or else a relative file path to an HTML file in public dir
69587     */
69588    function load_html_block(block, content) {
69589        // determine the nature of 'content' by seeing if the string begins with a predefined file prefix substring
69590        var file_prefix = _self.settings.file_prefix;
69591        var index = content.indexOf(file_prefix);
69592        var text;
69593
69594        if (index == 0) {
69595            // 'content' is a path to an HTML file
69596            var file_path = content.slice(file_prefix.length);
69597            var file_url = location.protocol + "//" + location.host + "/" + file_path;
69598            var file_contents = jQuery.get(file_url, function(data) {
69599                var text = replace_strings_with_url(data);
69600                block.innerHTML = html_decode(text);
69601            });
69602        }
69603        else {
69604            // 'content' is directly displayable HTML
69605            text = replace_strings_with_url(content);
69606            block.innerHTML = html_decode(text);
69607        }
69608    }
69609
69610    /**
69611     * Replace delimited strings with urls. The urls are defined in _application_load.html.erb
69612     *
69613     * @param {String} content HTML for display
69614     * @returns {String} Original HTML, with delimited strings replaced with urls
69615     */
69616    function replace_strings_with_url(content) {
69617        if (!_self.settings.url_replacement_delimiter) {
69618            return content;
69619        }
69620        else {
69621            var delimiter = _self.settings.url_replacement_delimiter;
69622            var parts = content.split(delimiter);
69623            var whole = "";
69624            var match;
69625            for (var i = 0, len = parts.length; i < len; ++i) {
69626                match = _urls[parts[i]];
69627                whole += (match == undefined) ? parts[i] : match;
69628            }
69629
69630            return whole;
69631        }
69632    }
69633
69634    function html_decode(value){
69635      return(value);
69636    }
69637
69638    function init() {
69639
69640        build_scaffold();
69641        update_content(_self.settings);
69642    }
69643
69644    init();
69645}
69646;
69647
69648function browse_widget(container, daos, urls, options) {
69649
69650    this.container = $j(container);
69651    this.urls = urls;
69652    this.data = options.searches;
69653    this.settings = {
69654       column_count:3,
69655       cell_decorator:null,
69656       table_class:null,
69657       row_class:null,
69658       cell_class:null
69659    };
69660    $j.extend(true, this.settings, options);
69661
69662
69663    this.set_content = function(mode) {
69664        // there are no additional modes for this widget
69665    }
69666
69667
69668    var _self = this;
69669    var _row_count = 0;
69670    var _table_id = _self.container.id + '_browse_table';
69671    var _table = null;
69672    var _data_length = 0;
69673
69674
69675
69676    function build_table() {
69677        _data_length = _self.data.length;
69678        _row_count = Math.ceil(_data_length / _self.settings.column_count);
69679
69680        var table_class = _self.settings.table_class ? '_browse_table ' + _self.settings.table_class : '_browse_table';
69681        var row_class = _self.settings.table_class ? '_browse_row ' + _self.settings.table_class : '_browse_row';
69682        var cell_class = _self.settings.table_class ? '_browse_cell ' + _self.settings.table_class : '_browse_cell';
69683
69684        var html = "<table id='" + _table_id + "' class='" + table_class + "'>";
69685        for(var r = 0; r < _row_count; r++) {
69686            html += "<tr class='" + row_class + "'>";
69687            for(var c = 0; c < _self.settings.column_count; c++)
69688                html += "<td class='" + cell_class + "'></td>";
69689
69690            html += "</tr>";
69691        }
69692
69693        _table = $j(html).appendTo(_self.container);
69694    }
69695
69696    function populate_table() {
69697
69698        var table = _table[0];
69699        var rows = table.rows;
69700        var data = _self.data;
69701        var index = 0;
69702
69703
69704
69705        for(var c = 0; c < _self.settings.column_count; c++) {
69706             for(var r = 0; r < _row_count; r++) {
69707                var cells = rows[r].cells;
69708                if(index == _data_length)
69709                    break;
69710
69711                var val = data[index++];
69712
69713                if(_self.settings.cell_decorator)
69714                    cells[c].innerHTML = _self.settings.cell_decorator(cells[c], val);
69715                else
69716                    //cells[c].innerHTML = make_link(cells[c], val);
69717                    cells[c].innerHTML = generate_cell(cells[c], val);
69718            }
69719        }
69720    }
69721
69722
69723    function make_link(container, data) {
69724
69725        var url = _self.urls.search + '?search_type=differential&domain2=' + data.target;
69726        url += '&domain1=' + data.contrast + '&domain1_threshold=' + data.threshold.join(',');
69727        url += '&image_set=' + (data.set == 'all' ? 'P56' : 'P56coronal');
69728
69729        var html = "<a class='_browse_diff_search_link' href='" + url + "'>" + data.name + "</a>";
69730
69731        return(html);
69732    }
69733
69734    function generate_cell(container, data) {
69735        var html, url;
69736        if(data.title_line) {
69737            html = "<span class = '_browse_category_title'>" + data.name + "</span>";
69738            //$j(container).addClass("_browse_category_title");
69739            return (html);
69740
69741        }
69742        url = _self.urls.search + '?search_type=differential&domain2=' + data.target + '&domain1=' + data.contrast;
69743        html = "<a class='_browse_diff_search_link' href='" + url + "'>" + data.name + "</a>";
69744        return(html);
69745
69746
69747
69748    }
69749
69750    function init() {
69751        build_table();
69752        populate_table();
69753    }
69754
69755    init();
69756}
69757
69758;
69759
69760function browse_widget_with_column_layout(container, daos, urls, options) {
69761
69762    this.container = $j(container);
69763    this.urls = urls;
69764    this.data = options.search_groups;
69765    this.settings = {
69766       column_count:3,
69767       cell_decorator:null,
69768       table_class:null,
69769       row_class:null,
69770       cell_class:null
69771    };
69772    $j.extend(true, this.settings, options);
69773
69774
69775    this.set_content = function(mode) {
69776        // there are no additional modes for this widget
69777    }
69778
69779
69780    var _self = this;
69781    var _row_count = [];
69782    var _table_id = _self.container.id + '_browse_table';
69783    var _table = null;
69784    var _data_length = 0;
69785    var _column_count = 1;
69786
69787
69788    function build_table() {
69789
69790        _column_count = _self.data.length;
69791        var row_count;
69792
69793        var outer_table_class = _self.settings.table_class ? '_browse_table ' + _self.settings.table_class : '_browse_table';
69794        var outer_td_class = _self.settings.outer_td_class ? _self.settings.outer_td_class : '_outer_td';
69795        var odd_or_even = 'td_odd';
69796        var inner_table_class = _self.settings.inner_table_class ? _self.settings.inner_table_class : '_inner_browse_table';
69797        var inner_tr_class = _self.settings.inner_tr_class ? '_browse_row ' + _self.settings.inner_tr_class : '_browse_row';
69798        var inner_td_class = _self.settings.inner_td_class ? '_browse_cell ' + _self.settings.inner_td_class : '_browse_cell';
69799        var html = "<table id='" + _table_id + "' class='" + outer_table_class + "'><tr>";
69800        for(var c = 0; c < _column_count; c++) {
69801            odd_or_even = c%2 == 1 ? 'td_odd' : 'td_even';
69802            var td_id = "outer_td_" + c;
69803            html += "<td id='" + td_id + "' class='" + outer_td_class + " " + odd_or_even + "'>";
69804            row_count = _self.data[c].length;
69805            html += "<table class='" + inner_table_class + "'>";
69806            for(var r = 0; r < row_count; r++) {
69807                var inner_tr_id = "inner_tr_" + r;
69808                var inner_td_id = "inner_td_" + r;
69809                html += "<tr id='" + inner_tr_id + "' class='" + inner_tr_class + "'>";
69810                html += "<td id='" + inner_td_id + "' class='" + inner_td_class + "'>";
69811                html += generate_cell(null, _self.data[c][r]);
69812                html += "</td>";
69813                html += "</tr>";
69814            }
69815            html += "</table>";
69816            html += "</td>";  // outer table's td
69817
69818
69819        }
69820        html += "</tr></table>";
69821        _table = $j(html).appendTo(_self.container);
69822    }
69823
69824    function populate_table() {
69825
69826
69827    }
69828
69829
69830
69831    function generate_cell(container, data) {
69832        var html, url;
69833        if(data.title_line) {
69834            html = "<span class = '_browse_category_title'>" + data.name + "</span>";
69835            //$j(container).addClass("_browse_category_title");
69836            return (html);
69837
69838        }
69839        url = _self.urls.search + '?search_type=differential&domain2=' + data.domain2 + '&domain1=' + data.domain1;
69840        url += '&stage2='+ data.stage2 + '&stage1=' + data.stage1;
69841        html = "<span><a class='_browse_diff_search_link' href='" + url + "'>" + data.name + "</a></span>";
69842        return(html);
69843
69844
69845
69846    }
69847
69848    function init() {
69849        build_table();
69850        //populate_table();
69851    }
69852
69853    init();
69854}
69855
69856;
69857// For a faceted search there are multiple dimensions, each with
69858// their own sub-dimensions, that can be used in building a query.
69859// The facet model is intended to abstract access to the
69860// facet information from the actual encoding of that info.
69861//
69862// Regardless of how the facet information is actually stored/retrieved,
69863// the model should make the facet information available as an array
69864// of ordered_list objects.  See ordered_lists.js for details on
69865// that structure.
69866//
69867// Currently the facet information is included in the application config file.
69868// This incarnation of the facet model expects the input data to look like this:
69869
69870/*                                 [
69871                                            {
69872                                                "name":"Medical Conditions",
69873                                                "id":"medical_conditions",
69874                                                "order":30,
69875                                                "subgroups":[
69876                                                    {
69877                                                        "name":"Disease",
69878                                                        "order":10,
69879                                                        "items":[
69880                                                            {"id":"control","name":"control","order":10,"type":"boolean", "value":true},
69881                                                            {"id":"schizophrenia","name":"schizophrenia","order":20,"type":"boolean", "value":true}
69882                                                        ]
69883                                                    },
69884                                                    {
69885                                                        "name":"Smoker",
69886                                                        "order":20,
69887                                                        "items":[
69888                                                            {"id":"smoker","name":"smoker","order":10,"type":"boolean", "value":true},
69889                                                            {"id":"nonsmoker","name":"non-smoker","order":20,"type":"boolean", "value":true},
69890                                                            {"id":"unknown","name":"unknown","order":30,"type":"boolean", "value":true}
69891                                                        ]
69892                                                    }
69893                                                ]
69894                                            },
69895                                            {
69896                                                "name":"Donor",
69897                                                "id":"donors",
69898                                                "order":10,
69899                                                "subgroups":[
69900                                                    {
69901                                                        "name":"Sex",
69902                                                        "order":10,
69903                                                        "items":[
69904                                                            {"id":"female","name":"female","order":10,"type":"boolean", "value":true},
69905                                                            {"id":"male","name":"male","order":20,"type":"boolean", "value":true}
69906                                                        ]
69907                                                    },
69908                                                    {
69909                                                        "name":"Handedness",
69910                                                        "order":20,
69911                                                        "items":[
69912                                                            {"id":"left","name":"left","order":10,"type":"boolean", "value":true},
69913                                                            {"id":"right","name":"right","order":20,"type":"boolean", "value":true},
69914                                                            {"id":"unknown","name":"unknown","order":30,"type":"boolean", "value":true}
69915                                                        ]
69916                                                    },
69917                                                    {
69918                                                        "name":"Age",
69919                                                        "order":30,
69920                                                        "items":[
69921                                                            {"id":"20_29","name":"20 - 29","order":10,"type":"boolean", "value":true},
69922                                                            {"id":"30_39","name":"30 - 39","order":20,"type":"boolean", "value":true},
69923                                                            {"id":"40_49","name":"40 - 49","order":30,"type":"boolean", "value":true},
69924                                                            {"id":"50_59","name":"50 - 59","order":40,"type":"boolean", "value":true}
69925                                                        ]
69926                                                    }
69927                                                ]
69928                                            }
69929                                    ]
69930*/
69931
69932
69933function facet_model(facet_data) {
69934
69935    this.raw_data = facet_data ? facet_data : {groups:[]};
69936    this.ordered_data = [];
69937
69938    // returns the top-level groups of facets as an
69939    // ordered_list structure.  This is a named & ordered list of lists.
69940    this.get_groups = function() {
69941        return(this.ordered_data.sub_lists);
69942    }
69943
69944    // returns the next-to-top level lists of facets as an
69945    // array of ordered_list structures.
69946    this.get_lists = function() {
69947
69948        var lists = [];
69949        var len = this.ordered_data.sub_lists.length;
69950        for(var i = 0; i < len; i++)
69951            lists.push(this.ordered_data.sub_lists[i].sub_lists);
69952
69953        return(lists);
69954    }
69955
69956
69957    var _self = this;
69958
69959
69960    function init() {
69961        _self.ordered_data = new ordered_list('top', 'top_name', 0, false, null, load_groups(_self.raw_data.groups) );
69962    }
69963
69964    function load_groups(data) {
69965
69966        if(!data)
69967            return(null);
69968
69969        var list = [];
69970        var list_len = data.length;
69971        for(var i = 0; i  < list_len; i++) {
69972            list.push(new ordered_list(data[i].id, data[i].name, data[i].order, data[i].ignore_when_all_selected,
69973                                       load_items(data[i].items), load_groups(data[i].subgroups) ));
69974        }
69975
69976        return(list)
69977    }
69978
69979    function load_items(items) {
69980
69981        if(!items)
69982            return(null);
69983
69984        var list = [];
69985        for(var i = 0; i < items.length; i++) {
69986           var item = items[i];
69987           list.push(new list_item(item.id, item.name, item.order, item.type, item.value, item.qtype))
69988        }
69989
69990        return(list);
69991    }
69992
69993    function inspect(prefix, obj) {
69994        var s = "";
69995        for(key in obj) {
69996            if((typeof obj[key]) == "function")
69997                s = s;
69998            else if((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
69999                s += "\n" + prefix + "key: " + key + inspect(prefix + "    ", obj[key]);
70000            else
70001                s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
70002        }
70003        return(s);
70004    }
70005    init();
70006}
70007;
70008function faceted_search_ui(dao, definition, info_widget, options) {
70009
70010    this.the_dao = dao;
70011    this.info_widget = info_widget;
70012    this.definition = definition;
70013
70014    this.settings = {
70015        search_on_update:false,
70016        exact_match:false,
70017        on_search:null,
70018        on_error:null,
70019        on_ready:null
70020    };
70021    $j.extend(this.settings, true, options);
70022
70023
70024    this.get_state = function () {
70025        return(get_search_state());
70026    }
70027
70028    this.set_state = function (state) {
70029        set_search_state(state);
70030    }
70031
70032    /* private */
70033    var _self = this;
70034    var _info_widget = _self.info_widget;
70035    var _closed = true;
70036    var _selected_item = false;
70037    var _search_text_box = null;
70038    var _facets = null;
70039    var _facet_table = null;
70040    var _search_on_update = null;
70041
70042
70043    function get_search_state() {
70044        var exact = get_exact_search_state();
70045        var term = _search_text_box.val();
70046        if (_selected_item)
70047            term = '"' + term + '"';
70048
70049        var state = {
70050            exact_match:exact,
70051            search_term:term,
70052            search_type:_self.definition.id
70053        }
70054
70055        state = get_facet_values(state);
70056        return(state);
70057    }
70058
70059
70060    function set_search_state(state) {
70061        var exact = document.getElementById('_exact_match');
70062        if(exact)
70063            exact.checked = state.exact_match;
70064
70065        if (state.search_term)
70066            _search_text_box.val(state.search_term);
70067
70068        set_facet_state(state);
70069
70070        if (_self.settings.on_ready)
70071            _self.settings.on_ready();
70072    }
70073
70074    function load_facets() {
70075        _facets = new facet_model(_self.definition.options);
70076    }
70077
70078    function set_facet_state(state) {
70079
70080        // parse the range queries
70081        for (var key in state) {
70082            if (key.indexOf('facet_range_') == 0) {
70083                var new_facet = parse_range_facet(state[key]);
70084                delete state[key];
70085                $j.extend(state, new_facet);
70086            }
70087        }
70088
70089        var control_lists = $j('.facet_list_container');
70090        for (var i = 0; i < control_lists.length; i++) {
70091            var facet_state = state['facet_' + control_lists[i].id];
70092            var checkboxes = $j('#' + control_lists[i].id + ' input[type="checkbox"]');
70093
70094            if (facet_state) {
70095                var facet_items = facet_state.split(',');
70096                for (var k = 0; k < checkboxes.length; k++)
70097                    checkboxes[k].checked = ($j.inArray(checkboxes[k].name, facet_items) >= 0);
70098            } else {
70099                var ignore_when_all_selected = $j('#' + control_lists[i].id).hasClass('ignore_when_all_selected');
70100
70101                for (var k = 0; k < checkboxes.length; k++) {
70102                    if (ignore_when_all_selected)
70103                        checkboxes[k].checked = true;
70104                    else
70105                        checkboxes[k].checked = false;
70106
70107                }
70108            }
70109        }
70110    }
70111
70112    // expecting a string that looks like this:
70113    // ( (facet_age_days *gte 7300 AND facet_age_days *lte 10585) OR (facet_age_days *gte 10950 AND facet_age_days *lte 14235) )
70114    // each range is enclosed in parens, and is joined to other ranges by ' OR '.
70115    // Each range is then composed of two terms joined by an 'AND '.  These
70116    // need to be separated and each range's components collected.  The components
70117    // are a field, operator and value.  Ultimately we want to end up
70118    // with the two value components from each range joined in a string like so [value]_[value],
70119    // in the proper order.  The [value]_[value] string is what is used as the ID for
70120    // the checkbox that will need to have its state set.
70121    function parse_range_facet(range_state) {
70122
70123        // this is a job for a parser.
70124        var facet_options = [];
70125
70126        // get rid of the parens and separate all of the ranges
70127        var ranges = $j.trim(range_state.replace(/\(/gi, '').replace(/\)/gi, '')).split(' OR ');
70128
70129
70130        for (var i = 0; i < ranges.length; i++) {
70131            var range = ranges[i];
70132            var terms = range.split(' AND ');
70133
70134            // each range should have two terms; usually a 'greater-than' and a 'less than'
70135            if (terms.length != 2)
70136                break;
70137
70138            var pair = []
70139            for (var j = 0; j < terms.length; j++) {
70140
70141                // each term has three components, a name, an operator and a value.
70142                // we don't really care about the operator.
70143                var term = terms[j];
70144                var components = term.split(' ');
70145
70146                if (components.length != 3)
70147                    break;
70148
70149                pair.push({
70150                    name:components[0],
70151                    value:components[2]
70152                });
70153            }
70154
70155            if (pair.length != 2)
70156                break;
70157
70158            // we need to put the values in the proper order.  This assumes that the lower value
70159            // always comes first.
70160            var option_id = '';
70161            try {
70162                if (parseFloat(pair[0].value) <= parseFloat(pair[1].value))
70163                    option_id = pair[0].value + '_' + pair[1].value;
70164                else
70165                    option_id = pair[1].value + '_' + pair[0].value;
70166
70167                facet_options.push({
70168                    name:pair[0].name,
70169                    id:option_id
70170                });
70171            } catch (e) {
70172                // not numeric values?
70173            }
70174        }
70175
70176        if (facet_options.length == 0)
70177            return;
70178
70179        // now paste together the bits into the same format that the non-range
70180        // facet objects have.
70181        var new_facet = {};
70182        var id_list = '';
70183        for (var k = 0; k < facet_options.length; k++) {
70184            if (k)
70185                id_list += ',';
70186
70187            id_list += facet_options[k].id;
70188        }
70189
70190        new_facet[facet_options[0].name] = id_list;
70191        return(new_facet);
70192    }
70193
70194    function build_search_controls() {
70195        var search_box_html = "<div class='facet_term_controls'>";
70196        search_box_html += "<div class='faceted_text_and_button'><div class='_search_text_label'>" + _self.definition.input_title + "</div>";
70197        search_box_html += "<textarea id='_search_text' class='_search_widget_bulk_area _sw_search_text'></textarea>";
70198        search_box_html += "<div id='_search_button' class='_search_widget_bulk_button'>Search</div></div>";
70199        search_box_html += "<div class='facet_exact_match'><input type='checkbox' " + (_self.definition.options.exact_match ? 'checked' : '') + " id='_exact_match'/>Show exact matches only</div>";
70200        search_box_html += "<div class='_search_on_update'><input type='checkbox' " + (_self.definition.options.search_on_update ? 'checked' : '') + " id='_search_on_update'/>Update results when criteria change</div></div>";
70201
70202        var facet_table_html = build_facet_controls();
70203        var whole_shebang = "<div class='faceted_search_container'>" + facet_table_html + search_box_html + "</div>";
70204
70205        _info_widget.set_content(1, 0, '');
70206        _info_widget.set_content(0, 1, whole_shebang);
70207        _info_widget.set_content(0, 2, "");
70208
70209        _search_text_box = $j('#_search_text');
70210        _search_on_update = $j('#_search_on_update');
70211        var search_button = $j('#_search_button');
70212        $j('.facet_item_check').bind('click', do_facet_click);
70213
70214        // control-ify
70215        search_button.button();
70216        search_button.bind('click', do_search);
70217    }
70218
70219    function get_facet_values(state) {
70220
70221        var facets = {};
70222        var facet_add = false;
70223
70224        var lists = $j('.facet_list_container');
70225        for (var j = 0; j < lists.length; j++) {
70226            var val_add = false;
70227            var range_add = false;
70228
70229            //var selected_opts = $j('#' + lists[j].id + ' input[checked=true]');
70230            var selected_opts = $j('#' + lists[j].id + ' input:checked');
70231
70232            var opts = $j('#' + lists[j].id + ' .facet_item_check');
70233
70234            var ignore_when_all_selected = $j('#' + lists[j].id).hasClass('ignore_when_all_selected');
70235            if (ignore_when_all_selected && selected_opts.length == opts.length)
70236                continue;
70237
70238            var vals = '';
70239            for (var k = 0; k < opts.length; k++) {
70240                if (opts[k].checked) {
70241
70242                    var qtype = opts[k].getAttribute('qtype');
70243
70244                    switch (qtype) {
70245                        case 'range':
70246                            if (vals != '')
70247                                vals += ' OR'
70248                            else
70249                                vals += ' (';
70250
70251                            vals += ' (';
70252                            var extents = opts[k].name.split('_');
70253                            vals += 'facet_' + lists[j].id + ' *gte ' + extents[0];
70254                            vals += ' AND ';
70255                            vals += 'facet_' + lists[j].id + ' *lte ' + extents[1];
70256                            vals += ')';
70257
70258                            facet_add = true;
70259                            range_add = true;
70260                            break;
70261                        default:
70262                            if (vals != '')
70263                                vals += ','
70264
70265                            vals += opts[k].name;
70266                            val_add = true;
70267                            facet_add = true;
70268                    }
70269
70270                }
70271            }
70272
70273            if (val_add)
70274                facets['facet_' + lists[j].id] = vals;
70275
70276            if (range_add) {
70277                vals += ')';
70278                facets['facet_range_' + lists[j].id] = vals;
70279            }
70280        }
70281
70282        if (facet_add) {
70283            state.facets = facets;
70284        }
70285
70286        return(state);
70287    }
70288
70289    function build_facet_controls() {
70290
70291        var lists = _facets.get_lists();
70292        if (!lists || lists.length == 0)
70293            return;
70294
70295        // build a table to hold all of the facet lists
70296        var table_html = "<table class='facet_table'><tr>";
70297        for (var i = 0; i < lists.length; i++) {
70298
70299            // for each facet type...
70300            for (var k = 0; k < lists[i].length; k++) {
70301
70302                // for each facet list create a container
70303                var facet = lists[i][k];
70304                table_html += "<td>";
70305
70306                if (facet.ignore_when_all_selected)
70307                    table_html += "<div class='facet_list_container ignore_when_all_selected' id='" + facet.id + "'>";
70308                else
70309                    table_html += "<div class='facet_list_container' id='" + facet.id + "'>";
70310
70311                table_html += "<div class='facet_label'>" + facet.name + "</div>";
70312
70313                // for each item in the list create a labeled control
70314                var items = facet.items;
70315                for (var j = 0; j < items.length; j++) {
70316                    var item = items[j];
70317                    table_html += "<div class='facet_item'>";
70318                    switch (item.type) {
70319                        case 'boolean':
70320                            table_html += "<input class='facet_item_check' qtype='" + (item.qtype ? item.qtype : 'default') + "' name='" + item.id + "' type='checkbox'" + (item.value == true ? ' checked' : '') + "/>";
70321                            table_html += "<div class='facet_item_label'>" + item.name + "</div>";
70322                            break;
70323                        default :
70324                            table_html += "<div class='facet_item_label'>" + item.name + "</div>";
70325                    }
70326                    table_html += "</div>"; // close facet_item
70327                }
70328                table_html += "</div></td>"; // close facet_list_container and the table cell
70329            }
70330        }
70331        table_html += "</tr></table>"; // close the single row and the facet_table
70332
70333        return(table_html);
70334    }
70335
70336    function do_start_search(event, ui) {
70337        if (_closed)
70338            _search_text_box.autocomplete('close');
70339    }
70340
70341    function do_open(event, ui) {
70342        if (_closed)
70343            _search_text_box.autocomplete('close');
70344    }
70345
70346    function do_facet_click(event, ui) {
70347        if (_search_on_update && _search_on_update.is(':checked'))
70348            do_search(event, ui);
70349    }
70350
70351    function get_exact_search_state() {
70352        var exact = $j('#_exact_match');
70353
70354        return( exact ? exact[0] ? exact[0].checked : false : false );
70355    }
70356
70357    function do_search(event, ui) {
70358        var term = _search_text_box.val();
70359
70360        if (_self.settings.on_search)
70361            _self.settings.on_search(_self, get_search_state());
70362    }
70363
70364    function init() {
70365        load_facets();
70366        var lists = _facets.get_lists();
70367        build_search_controls();
70368    }
70369
70370    function inspect(prefix, obj) {
70371        var s = "";
70372        for (key in obj) {
70373            if ((typeof obj[key]) == "function")
70374                s = s;
70375            else if ((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
70376                s += "\n" + prefix + "key: " + key + inspect(prefix + "    ", obj[key]);
70377            else
70378                s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
70379        }
70380        return(s);
70381    }
70382
70383    init();
70384}
70385;
70386
70387
70388
70389function ordered_list(id, name, order, ignore_when_all_selected, items, sub_lists) {
70390
70391    this.id = id;
70392    this.name = name;
70393    this.order = order;
70394    this.ignore_when_all_selected = ignore_when_all_selected;
70395    this.items = items;
70396    this.sub_lists = sub_lists ? sub_lists : [];
70397
70398    this.add_items = function(items) {
70399       this.items = this.items.concat(items);
70400       sort_items();
70401    }
70402
70403    this.add_sub_lists = function(sub_lists) {
70404       this.sub_lists = this.sub_lists.concat(sub_lists);
70405       sort_lists();
70406    }
70407
70408
70409    var _self = this;
70410
70411    function init() {
70412       sort_items();
70413       sort_lists();
70414    }
70415
70416    function sort_items() {
70417        if(_self.items)
70418            _self.items = _self.items.sort(function(a, b) { return(a.order - b.order);});
70419    }
70420
70421    function sort_lists() {
70422        if(_self.sub_lists)
70423            _self.sub_lists = _self.sub_lists.sort(function(a, b) { return(a.order - b.order);});
70424    }
70425
70426    init();
70427}
70428
70429function list_item(id, name, order, type, value, qtype) {
70430
70431    this.id = id;
70432    this.name = name;
70433    this.order = order;
70434    this.type = type;
70435    this.value = value;
70436    this.qtype = qtype;
70437}
70438;
70439"use strict";
70440/*
70441 * File: gene_classification_search_plugin.js
70442 * Class: gene_classification_search_plugin
70443 *
70444 * Used to style the UI components for the 'Gene Classification' search modality.
70445 * UI configuration data comes from application.json
70446 */
70447
70448/**
70449 *
70450 * @param dao
70451 * @param definition has search_type, configuration data
70452 * @param info_widget
70453 * @param options
70454 */
70455function gene_classification_search_plugin(dao, definition, info_widget, options) {
70456
70457    this.the_dao = dao;
70458    this.info_widget = info_widget;
70459    this.definition = definition;
70460
70461    this.on_search = options ? options.on_search ? options.on_search : null : null;
70462    this.on_error = options ? options.on_error ? options.on_error : null : null;
70463    this.on_ready = options ? options.on_ready ? options.on_ready : null : null;
70464
70465    this.classification_options = options ? (options.gene_classifications? options.gene_classifications : undefined) : undefined;
70466
70467    /**
70468     * Get state object containing properties describing UI element states
70469     *
70470     * @returns {{exact_match: *, search_term: *, search_type: (*|Function|Function|id|Selector.xpath.id|Selector.criteria.id|Function|Function|id|Selector.xpath.id|Selector.criteria.id|Function|Function|id|Selector.xpath.id|Selector.criteria.id|Function|Function|id|id|id|._newInst.id|id|._newInst.id|._newInst.id|._newInst.id|id|ordered_list.id|list_item.id|id|Function|Function|Function|Function|Function|Function|Function|Function|Function|Function|Function|h.id|Function|Function|Function|Function|Function|*|*|Function|Function|id|Y.id|id|Y.id|id|Y.id|Function|Function|Function|Function|AIBS.UI.info_widget.id|id|AIBS.UI.info_widget.id|id|Function|zapGraphics.id|Ajax.InPlaceEditor._form.id|Ajax.InPlaceEditor._form.id|Ajax.InPlaceEditor._form.id|simpleStrip.id|sliderStrip.id|scaleStrip.id|string|string|string|newState.id|id|id|zapviewer.id|id|string|.callee.id|Selector.root.id|string|Selector.root.id|string|Selector.root.id|string|core.id|.attr.id|id|id|uniqueId.id|id|id|uniqueId.id|id)}}
70471     */
70472    this.get_state = function() {
70473        return(get_search_state());
70474    }
70475
70476    /**
70477     * Set state of all current UI elements
70478     *
70479     * @param state object with properties describing UI element states
70480     */
70481    this.set_state = function(state) {
70482        set_search_state(state);
70483    }
70484
70485    /**
70486     * Remove UI elements from the DOM.
70487     * This function will 'undo' the actions of build_gene_search_controls()
70488     * Parent search widget calls this function just before it deletes the plugin object.
70489     */
70490    this.destroy = function() {
70491        // remove HTML elements (text input, button, checkbox)
70492        // jQuery 'remove' automatically detaches any bound events from DOM elements
70493        $j('#_gene_classification_menu').remove();
70494        $j('#_gene_classification_search_button').remove();
70495
70496        // remove any text
70497        _info_widget.set_content(1,0, "");
70498        _info_widget.set_content(0,1, "");
70499    }
70500
70501
70502    var _self = this;
70503    var _info_widget = _self.info_widget;
70504    var _classification_menu;
70505    var _search_button;
70506    var _state = {
70507        search_term: 0, // updated via menu choices or by set_search_state()
70508        search_type: this.definition.id
70509    }
70510
70511    /**
70512     * Get search state object, which is comprised of:
70513     * - selected structure id, from Structure dropdown menu
70514     * - search type
70515     *
70516     * @returns {search_term: number, search_type: string}
70517     */
70518    function get_search_state() {
70519        var state = _state;
70520
70521        return(state);
70522    }
70523
70524    /**
70525     * Set search state of Gene Classification Search UI elements (one menu)
70526     *
70527     * @param state
70528     */
70529    function set_search_state(state) {
70530        if (state.search_term) {
70531            $j('#_gene_classification_menu').val(state.search_term);
70532            _state.search_term = state.search_term;
70533        }
70534
70535        if(_self.on_ready)
70536            _self.on_ready();
70537    }
70538
70539    function build_search_controls() {
70540        var title_html = "Find genes corresponding to the gene classifications";
70541        var classification_menu = build_classification_menu(_self.classification_options);
70542
70543        var menu_and_button_html = '<table><tr>';
70544        menu_and_button_html += '<td>Gene Classifications<br>' + classification_menu + '</td>';
70545        menu_and_button_html += '<td>&nbsp;<br><div id="_gene_classification_search_button">Search</div></td>';
70546        menu_and_button_html += '</tr></table>';
70547
70548        _info_widget.set_content(1,0, title_html);
70549        _info_widget.set_content(0,1, menu_and_button_html);
70550
70551        _classification_menu = $j('#_gene_classification_menu');
70552        _search_button = $j('#_gene_classification_search_button');
70553
70554        // control-ify
70555        _search_button.button();
70556
70557        // bind events
70558        _classification_menu.bind('change', do_classification_menu_change);
70559        _search_button.bind('click', do_classification_search);
70560    }
70561
70562
70563    /**
70564     *
70565     * @param classifications array of hashes with 'label', 'classification_id', 'value'
70566     * @returns {string}
70567     */
70568    function build_classification_menu(classifications) {
70569        var classification_id;
70570        var len = classifications.length;
70571
70572        var html = "<select id='_gene_classification_menu' class='_sw_search_text'>";
70573        html += "<option value='0'>Select to Search</option>";
70574        for(var i = 0; i < len; i++) {
70575            classification_id = classifications[i].classification_id;
70576            html += "<option value='" + classification_id + "'>" + classifications[i].label + "</option>";
70577        }
70578        html += "</select>";
70579
70580        return html;
70581    }
70582
70583    function do_error(xhdr, text_status, error) {
70584        var err = xhdr.msg == undefined ? error : xhdr.msg;
70585
70586        if(_self.on_error)
70587            return(_self.on_error(_self, err));
70588
70589        throw "Error fetching gene classification set: " + err;
70590    }
70591
70592    function do_classification_menu_change(event, ui) {
70593        var new_term = _classification_menu.val();
70594        _state.search_term = new_term;
70595    }
70596
70597    /**
70598     * Event handler for 'Search' button
70599     *
70600     * @param event
70601     * @param ui
70602     */
70603    function do_classification_search(event, ui) {
70604        var classification_id = _state.search_term;
70605
70606        if(!classification_id || classification_id == '0')
70607            return;
70608
70609        if(_self.on_search)
70610            _self.on_search(_self, get_search_state());
70611    }
70612
70613    function init() {
70614        build_search_controls();
70615    }
70616
70617    init();
70618}
70619;
70620"use strict";
70621/**
70622 *
70623 * @param dao
70624 * @param definition
70625 * @param info_widget
70626 * @param options
70627 */
70628function gene_search_plugin(dao, definition, info_widget, options) {
70629
70630    this.the_dao = dao;
70631    this.info_widget = info_widget;
70632    this.definition = definition;
70633
70634    this.get_state = function() {
70635        return(get_gene_search_state());
70636    }
70637
70638    this.set_state = function(state) {
70639        set_gene_search_state(state);
70640    }
70641
70642    /**
70643     * Remove UI elements from the DOM.
70644     * This function will 'undo' the actions of build_gene_search_controls()
70645     * Parent search widget calls this function just before it deletes the plugin object.
70646     */
70647    this.destroy = function() {
70648        // remove HTML elements (text input, button, checkbox)
70649        // jQuery 'remove' automatically detaches any bound events from DOM elements
70650        $j('#_search_text').remove();
70651        $j('#_search_button').remove();
70652        $j('#_search_exact').remove();
70653
70654        // remove text
70655        _info_widget.set_content(1,0, "");
70656        _info_widget.set_content(0,2, "");
70657    }
70658
70659    this.on_search = options ? options.on_search ? options.on_search : null : null;
70660    this.on_error = options ? options.on_error ? options.on_error : null : null;
70661    this.on_ready = options ? options.on_ready ? options.on_ready : null : null;
70662
70663
70664    var _self = this;
70665    var _info_widget = _self.info_widget;
70666    var _type_ahead_callback = null;
70667    var _closed = true;
70668    var _selected_item = false;
70669    var _search_text_box = null;
70670
70671
70672    function get_gene_search_state() {
70673        var exact = get_exact_search_state();
70674        var type = $j('input[name=search_type]:checked').val();
70675        var term = _search_text_box.val();
70676        if(_selected_item)
70677            exact = true;
70678
70679        var state = {
70680            exact_match:exact,
70681            search_term:term,
70682            search_type:_self.definition.id
70683        }
70684        return(state);
70685    }
70686
70687    function set_gene_search_state(state) {
70688        var exact = document.getElementById('_search_exact');
70689        if(exact)
70690            exact.checked = state.exact_match;
70691
70692        if(state.search_term)
70693            _search_text_box.val( state.search_term );
70694
70695        if(_self.on_ready)
70696            _self.on_ready();
70697    }
70698
70699    function build_gene_search_controls() {
70700
70701        var search_box_html = "<input type='text' id='_search_text' class='_sw_search_text'/>";
70702        search_box_html += "<div id='_search_button'>Search</div>";
70703        var exact_html = "<input type='checkbox' id='_search_exact'/>Show exact matches only";
70704
70705        _info_widget.set_content(1,0, "Enter Gene Name, Gene Symbol, Homolog, Entrez Gene ID or NCBI Accession number:");
70706        _info_widget.set_content(0,1, search_box_html);
70707        _info_widget.set_content(0,2, exact_html);
70708
70709        _search_text_box = $j('#_search_text');
70710        var search_button = $j('#_search_button');
70711
70712        // control-ify
70713        search_button.button();
70714        search_button.bind('click', do_gene_search);
70715
70716        _search_text_box.autocomplete(
70717            {
70718             source:do_gene_auto_complete,
70719             select:do_gene_search,
70720             search:do_start_search,
70721             open:do_open,
70722             appendTo:_self.info_widget.container,
70723             delay:100
70724            });
70725
70726        _search_text_box.bind('keyup', do_gene_enter_check);
70727    }
70728
70729    function do_start_search(event, ui) {
70730        if(_closed)
70731            _search_text_box.autocomplete('close');
70732    }
70733
70734    function do_open(event, ui) {
70735        if(_closed)
70736            _search_text_box.autocomplete('close');
70737    }
70738
70739    function do_gene_auto_complete(request, response) {
70740        _type_ahead_callback = response;
70741        var opts = {
70742           success_callback:gene_autocomplete_responder,
70743           error_callback:dummy,
70744           limit:10,
70745           exact_match:get_exact_search_state()
70746        };
70747
70748        _self.the_dao.gene_typeahead(request.term, opts);
70749    }
70750
70751    function gene_autocomplete_responder(response_data, text_status, xhdr) {
70752
70753        if(!response_data)
70754            return;
70755
70756        var ret = [];
70757        var len = response_data.length;
70758//TODO: get rid of the explicit reference to a field name
70759        for(var i = 0; i < len; i++)
70760            ret.push(response_data[i].term);
70761
70762        _type_ahead_callback(ret);
70763    }
70764
70765    function dummy(xhdr, text_status, error) {
70766        // swallow this type ahead error
70767    }
70768
70769    function get_exact_search_state() {
70770       var exact = $j('#_search_exact');
70771
70772       return( exact ? exact[0] ? exact[0].checked : false : false );
70773    }
70774
70775    function do_gene_search(event, ui) {
70776        var term = '';
70777        if(ui && ui.item) {
70778            //term = '"' + ui.item.value + '"';
70779            _search_text_box.val(ui.item.value);
70780
70781            _selected_item = true;
70782        }
70783        else {
70784            term = _search_text_box.val();
70785            _selected_item = false;
70786
70787            if(!term || term.length < 1)
70788                return(false);
70789        }
70790
70791        if(_self.on_search)
70792            _self.on_search(_self, get_gene_search_state());
70793
70794        return(false);
70795    }
70796
70797    function do_gene_enter_check(event, ui) {
70798        if(event.keyCode == 13) {
70799            _search_text_box.autocomplete('close');
70800            _closed = true;
70801            do_gene_search(null, null);
70802        } else {
70803            _closed = false;
70804        }
70805    }
70806
70807    function init() {
70808        build_gene_search_controls();
70809    }
70810
70811    init();
70812}
70813;
70814"use strict";
70815/**
70816 *
70817 * @param container
70818 * @param dao
70819 * @param plugins array of plugin definitions, comes from application.json
70820 * @param options
70821 */
70822function ish_search_widget(container, dao, plugins, options) {
70823
70824// public properties
70825    this.container = $j(container);
70826    this.the_dao = dao;
70827    this.options = options;
70828    this.plugins = plugins;
70829    this.stage_list = options ? ( options.stage_list ? options.stage_list : null ) : null;
70830    this.structure_list = options ? ( options.structure_list ? options.structure_list : null ) : null;
70831
70832// public methods
70833    this.id = function () {
70834        if (_info_widget)
70835            return(_info_widget.id);
70836
70837        return(null);
70838    };
70839
70840    this.get_state = function () {
70841        return(do_get_state());
70842    };
70843
70844    this.set_state = function (new_state) {
70845        do_set_state(new_state);
70846    };
70847
70848    this.get_definition = function() {
70849        if(_current_plugin)
70850            return(_current_plugin.definition);
70851
70852        return({});
70853    };
70854
70855// event hooks
70856    this.on_search = options ? options.on_search ? options.on_search : null : null;
70857    this.on_search_type_change = options ? options.on_search_type_change ? options.on_search_type_change : null : null;
70858    this.on_ontology_error = options ? options.on_ontology_error ? options.on_ontology_error : do_ontology_error : do_ontology_error;
70859    this.on_search_ready = options ? options.on_search_ready ? options.on_search_ready : null : null;
70860    this.on_search_error = options ? options.on_search_error ? options.on_search_error : null : null;
70861    this.on_help = options ? options.on_help ? options.on_help : null : null;
70862
70863
70864// private properties
70865    var _self = this;
70866    var _info_widget = null;
70867    var _installed_plugins = {};
70868    var _current_plugin = null;
70869
70870
70871    function by_plugin_order_field(a, b) {
70872        return(a.order - b.order);
70873    }
70874
70875    /**
70876     * Gets plugin definition hash
70877     *
70878     * @param id hash key, value uniquely identifies a plugin
70879     * @returns {*}
70880     */
70881    function plugin_definition_by_id(id) {
70882        for (var i = 0; i < _self.plugins.length; i++)
70883            if (_self.plugins[i].id == id)
70884                return(_self.plugins[i]);
70885
70886        return(null);
70887    }
70888
70889    /**
70890     * Called whenever the user switches search type (radio button change)
70891     *
70892     * @param plugin
70893     * @returns {Object}
70894     */
70895    function install_plugin(plugin) {
70896        var plugin_options = {};
70897        $j.extend(true, plugin_options, plugin.options);
70898
70899        // see if there are any external options (not from application.json) defined specifically for this plugin.
70900        // Assume that the caller to the ish_search_widget will set a hash key with the plugin's 'id' as an identifier.
70901        if (_self.options[plugin.id])
70902            plugin_options[plugin.id] = _self.options[plugin.id];
70903
70904        // add options from application.json
70905        if (plugin.options)
70906            $j.extend(true, plugin_options, plugin.options);
70907
70908        plugin_options.on_search = do_search;
70909        plugin_options.on_ready = do_ready;
70910        plugin_options.on_error = do_error;
70911        plugin_options.on_help = do_help;
70912        plugin_options.stage_list = _self.stage_list;
70913        plugin_options.structure_list = _self.structure_list;
70914
70915
70916        var instance_str = 'new ' + plugin.name + '(_self.the_dao, plugin, _info_widget, plugin_options);';
70917        var p = eval(instance_str);
70918        _installed_plugins[plugin.id] = p;
70919        return(p);
70920    }
70921
70922    function do_search(sender, state) {
70923        state.page_num = 0;
70924
70925	// hidden plugins should go back to being hidden when a search is initiated on
70926	// any other search.
70927	for (var i = 0; i < _self.plugins.length; i++) {
70928	    var plugin = _self.plugins[i];
70929
70930            var input = $j('input[name=search_type]').filter('[value=' + plugin.id + ']');
70931
70932	    if (plugin.hidden == true && plugin.id != state.search_type) {
70933		input.parent().hide();
70934	    }
70935	}
70936
70937        if (_self.on_search)
70938            _self.on_search(state);
70939    }
70940
70941    function do_ready() {
70942        if (_self.on_search_ready)
70943            _self.on_search_ready();
70944    }
70945
70946
70947    function do_error(msg) {
70948        if(_self.on_search_error)
70949            _self.on_search_error(msg);
70950    }
70951
70952    function do_help(url) {
70953        if (_self.on_help)
70954            _self.on_help(url);
70955    }
70956
70957    function remove_plugin(plugin_id) {
70958        // do we need to do some manual removal of DOM elements?  probably.
70959        var plugin = _installed_plugins[plugin_id];
70960        if (plugin.destroy)
70961            plugin.destroy();
70962            delete _installed_plugins[plugin_id];
70963    }
70964
70965    function install_default_plugin() {
70966        for (var i = 0; i < _self.plugins.length; i++) {
70967            if (_self.plugins[i].default_search) {
70968                _current_plugin = install_plugin(_self.plugins[i]);
70969                return;
70970            }
70971        }
70972    }
70973
70974    function do_get_state() {
70975        var state = _current_plugin.get_state();
70976        if (state)
70977            state.search_type = _current_plugin.definition.id;
70978        return(state);
70979    }
70980
70981    function do_set_state(state) {
70982        if (!_current_plugin)
70983            return;
70984
70985        var type = state.search_type;
70986
70987        var input = $j('input[name=search_type]').filter('[value=' + type + ']').attr('checked', 'checked');
70988
70989        // if the containing nobr was hidden, unhide it now that it's been requested.
70990        input.parent().show();
70991
70992        do_search_type_change();
70993        _current_plugin.set_state(state);
70994    }
70995
70996    function build_table() {
70997
70998        // describe the table
70999        var layout = {
71000            columns: 3,
71001            rows: 3,
71002            spans: [
71003                {row: 0, column: 0, column_span: 1, row_span: 3},
71004                {row: 0, column: 1, column_span: 2, row_span: 1},
71005                {row: 1, column: 1, column_span: 2, row_span: 1},
71006                {row: 2, column: 1, column_span: 2, row_span: 1},
71007                {row: 0, column: 2, column_span: 1, row_span: 1}
71008            ]
71009        };
71010        // describe how data should be mapped to cells in the table
71011        var data_map = [];
71012        // which cells require a callback to massage the data
71013        // presentation before placing it in the table.
71014        var decorators = {};
71015
71016        var options = {
71017            table_class: '_search_widget'
71018        };
71019
71020        // build the widget.
71021        _info_widget = new AIBS.UI.info_widget(_self.container, layout, data_map, decorators, options);
71022    }
71023
71024    function build_search_type_controls() {
71025
71026        plugins.sort(by_plugin_order_field);
71027
71028        var control_html = "<div id='_search_type_wrapper'>";
71029
71030        for (var i = 0; i < _self.plugins.length; i++) {
71031            var p = _self.plugins[i];
71032            // The option for hiding a control if desired was for agea search; you can only
71033            // do an agea search via the URL string (entry from the agea page), so hide it
71034            // until the controller explicitly requests a hidden search type.
71035            control_html += "<div" + (p.hidden == true ? " style=\"display:none\"" : "") + ">";
71036            control_html += "<input type='radio' id='" + p.id + "' name='search_type' ";
71037            control_html += p.default_search == true ? 'checked ' : ' ';
71038            control_html += "value='" + p.id + "'/> ";
71039            control_html += "<label for='" + p.id + "'>" + p.label + "</label>";
71040            control_html += "</div>";
71041        }
71042        control_html += "</div>";
71043
71044        _info_widget.set_content(0, 0, control_html);
71045        $j('#_search_type_wrapper input[name=search_type]:radio').bind('click', do_search_type_change);
71046    }
71047
71048    /**
71049     * Event callback in response to click on on a Search radio button or a call to do_set_state()
71050     */
71051    function do_search_type_change() {
71052        var new_type = $j('input[name=search_type]:checked').get(0).value;
71053        var new_definition = plugin_definition_by_id(new_type);
71054
71055        // only do something if the requested type differs from the current type
71056        if (_current_plugin.definition.id != new_definition.id) {
71057
71058            // Make sure this is empty before plugins start laying things out.
71059            for (var col = 1; col < 3; col++)
71060                for (var row = 0; row < 3; row++)
71061                    if (_info_widget.get_cell(row, col)) _info_widget.set_content(col, row, '');
71062
71063            remove_plugin(_current_plugin.definition.id);
71064            _current_plugin = install_plugin(new_definition);
71065
71066            if (_self.on_search_type_change)
71067                _self.on_search_type_change(_self, new_type);
71068        }
71069    }
71070
71071    function init() {
71072        build_table();
71073        build_search_type_controls();
71074        install_default_plugin();
71075    }
71076
71077    init();
71078
71079}
71080;
71081"use strict";
71082/**
71083 *
71084 * @param container
71085 * @param state
71086 * @param options
71087 */
71088function manual_annotation_query_builder(container, state, options) {
71089
71090    this.container = $j(container);
71091    this.state = state;
71092    this.settings = {
71093        "max_rows": 4,
71094        on_ready: null,
71095        on_error: null,
71096        on_search: null,
71097        on_remove_row: null,
71098        on_add_row: null,
71099        on_help: null
71100    };
71101
71102    $j.extend(true, this.settings, options);
71103
71104
71105    this.set_state = function (new_state) {
71106
71107        if (!new_state)
71108            return;
71109
71110        for (var i = 0; i < new_state.length; i++) {
71111
71112            var state = new_state[i];
71113
71114            if (i)
71115                add_query_row(state, false);
71116            else {
71117                // this is the first row, and we've already built that
71118                // as part of the initialization, so fetch the first row
71119                // from the collection and set its state.
71120                if (!_query_rows)
71121                    return;
71122
71123                for (var key in _query_rows) {
71124
71125                    if (!_query_rows[key].settings.header_only)
71126                        _query_rows[key].set_state(state);
71127                }
71128            }
71129
71130        }
71131
71132        // do_all_ready();
71133    };
71134
71135    this.get_state = function () {
71136
71137        var state = {
71138            query_set: []
71139        };
71140
71141        for (var key in _query_rows) {
71142
71143            if (!_query_rows[key].settings.header_only) {
71144
71145                var row_state = _query_rows[key].get_state();
71146                state.query_set.push(row_state);
71147            }
71148        }
71149
71150        return(state);
71151    };
71152
71153    /**
71154     * Mark the controls in the query builder that are not in a valid state to perform a query.
71155     */
71156    this.validate_state = function () {
71157
71158        var all_valid = true;
71159
71160        for (var key in _query_rows) {
71161
71162            if (!_query_rows[key].settings.header_only) {
71163
71164                var row_state = _query_rows[key].get_state();
71165                var control_index = 0;
71166                for (var control in row_state) {
71167
71168//                    trace("control: " + control + " has value " + row_state[control] + " and length " + row_state[control].length);
71169                    //Almost any state is valid, as long as every row has a value for each control
71170                    var valid = (row_state[control] && row_state[control].length > 0);
71171                    _query_rows[key].set_control_class(control_index++, 'invalid', !valid);
71172
71173                    all_valid = all_valid && valid;
71174                }
71175            }
71176        }
71177
71178        return(all_valid);
71179    };
71180
71181
71182    var _self = this;
71183    var _id = 'manual_annotation_query_builder_' + Math.floor(Math.random() * 10001);
71184    var _table = null;
71185    var _query_rows = {};
71186    var _row_count = 0;
71187    var _ready_count = 0;
71188
71189
71190    function build_scaffold() {
71191
71192        var html = "<table class='manual_anno_qbuilder_table' id='" + _id + "'></table>";
71193        _table = $j(html).appendTo(_self.container);
71194    }
71195
71196
71197    function add_query_row(row_state, header_only) {
71198
71199        var row = _table[0].insertRow(-1);
71200        var cell = row.insertCell(-1);
71201        var index = _table[0].rows.length - 1;
71202        var qrow = new query_row(
71203            cell,
71204            index,
71205            row_state,
71206            {
71207                layout: _self.settings.row_layout,
71208                on_ready: do_row_ready,
71209                on_add_row: add_query_row,
71210                on_delete_row: remove_query_row,
71211                on_search: _self.settings.on_search,
71212                on_help: _self.settings.on_help,
71213                header_only: !!header_only
71214            });
71215
71216        var qrow_id = qrow.get_id();
71217        $j(row).attr('q_id', qrow_id);
71218        _query_rows[qrow_id] = qrow;
71219
71220        if (!header_only)
71221            _row_count++;
71222
71223        update_row_manage_states();
71224
71225        if (_self.settings.on_add_row)
71226            _self.settings.on_add_row();
71227    }
71228
71229    function remove_query_row(row) {
71230
71231        var row_key = row.get_id();
71232        _query_rows[row_key].destroy();
71233
71234        for (var i = 0; i < _table[0].rows.length; i++) {
71235
71236            var r = _table[0].rows[i];
71237            if ($j(r).attr('q_id') == row_key) {
71238                _table[0].deleteRow(i);
71239                break;
71240            }
71241        }
71242
71243        delete _query_rows[row_key];
71244
71245        _row_count--;
71246        update_row_manage_states();
71247        if (_self.settings.on_remove_row)
71248            _self.settings.on_remove_row();
71249    }
71250
71251    function update_row_manage_states() {
71252
71253        var i = 0;
71254        for (var row in _query_rows) {
71255
71256            if (!_query_rows[row].settings.header_only) {
71257
71258                var first = !i;
71259                var last = (++i == _row_count);
71260                var terminal = (i == _self.settings.max_rows);
71261                _query_rows[row].set_row_manage_state(first, last, terminal);
71262            }
71263        }
71264
71265    }
71266
71267    function do_row_ready(query_row) {
71268
71269        if (++_ready_count == _row_count)
71270            do_all_ready();
71271    }
71272
71273    function do_all_ready() {
71274
71275        if (_self.settings.on_ready)
71276            _self.settings.on_ready(_self);
71277    }
71278
71279    function init() {
71280
71281        build_scaffold();
71282        add_query_row(null, true);
71283        add_query_row(null, false);
71284    }
71285
71286
71287    init();
71288
71289}
71290
71291
71292AIBS = AIBS || {};
71293AIBS.Components = AIBS.Components || {};
71294AIBS.Components.control_builders = {
71295
71296    build_ontology_select: function (container, scope, config) {
71297
71298        // the 'control' object:
71299        var control = {
71300
71301            html_control: null,
71302            widget: null,
71303            config: config,
71304
71305            get_state: function () {
71306
71307                var state = [];
71308                var structure_tags = this.widget.get_state();
71309                for (var i = 0; i < structure_tags.length; i++) {
71310
71311                    state.push(structure_tags[i].value);
71312                }
71313                return(state);
71314            },
71315
71316            set_state: function (state) {
71317
71318                this.widget.set_state(state)
71319            },
71320
71321            name: "ontology_autocomplete",
71322
71323            set_class: function (class_name, add) {
71324
71325                if (add)
71326                    $j(this.html_control[0].parentNode).find('.tagsinput').addClass(class_name);
71327                else
71328                    $j(this.html_control[0].parentNode).find('.tagsinput').removeClass(class_name);
71329            }
71330        };
71331
71332        // first build a regular input element
71333        var html = "<input type='text' class='" + config.class_name + "_ontology_autocomplete' style='width:135px'/>";
71334        control.html_control = $j(html).appendTo(container);
71335
71336        // then ontology_autocompletify it.
71337        control.widget = new AIBS.Components.ontology_component(
71338            control.html_control,
71339            config.ontology_url,
71340            {
71341                on_ready: config.on_ready,
71342                ontology_options: config.options.ontology_options
71343            });
71344
71345        return(control);
71346    },
71347
71348    build_dropdown_checklist: function (container, scope, config) {
71349
71350
71351        // the 'control' object:
71352        var control = {
71353
71354            html_control: null,
71355            config: config,
71356
71357            get_state: function () {
71358
71359                var selected = [];
71360                var options = this.html_control[0].options;
71361                for (var i = 0; i < options.length; i++) {
71362
71363                    if (options[i].selected && (options[i].value != "Any"))
71364                        selected.push(options[i].value);
71365                }
71366                return(selected);
71367            },
71368
71369            set_state: function (state) {
71370
71371                if (!state)
71372                    return;
71373
71374                var options = this.html_control[0].options;
71375
71376
71377                for (var j = 0; j < options.length; j++)
71378                    options[j].selected = ($j.inArray(options[j].value, state) >= 0);
71379
71380                this.html_control.dropdownchecklist('refresh');
71381            },
71382
71383            name: "dropdown_checklist",
71384
71385            set_class: function (class_name, add) {
71386
71387                if (add)
71388                    $j(this.html_control[0].parentNode).find('.ui-dropdownchecklist').addClass(class_name);
71389                else
71390                    $j(this.html_control[0].parentNode).find('.ui-dropdownchecklist').removeClass(class_name);
71391            }
71392        };
71393
71394        // first build a regular select element
71395        var item_name, item_value;
71396        var html = "<select class='" + config.class_name + "_dropdowncheck' multiple='multiple'>";
71397        for (var i = 0, len = config.options.values.length; i < len; ++i) {
71398            var item = config.options.values[i];
71399            // values can either be an array of strings or an array of hashes {name:<item name>, value:<item value}
71400            if (typeof item == "string") {
71401                item_name = item_value = item;
71402            }
71403            else {
71404                item_name = item.name;
71405                item_value = item.value;
71406            }
71407            var selected = (config.options.selected && config.options.selected[item_name]) ? 'selected' : '';
71408            html += "<option value='" + item_value + "' " + selected + ">" + item_name + "</option>";
71409        }
71410        html += "</select>";
71411
71412        control.html_control = $j(html).appendTo(container);
71413
71414        // then dropdownchecklistify it
71415        control.html_control.dropdownchecklist({firstItemChecksAll: true, width: config.width});
71416
71417        return(control);
71418    },
71419
71420    build_deleter_adder: function (container, scope, config) {
71421
71422        var _id = scope.get_id();
71423
71424        // the 'control' object:
71425        var control = {
71426
71427            html_control: null,
71428            config: config,
71429
71430            get_state: function () {
71431
71432                return(null);
71433            },
71434
71435            set_state: function (state) {
71436
71437            },
71438
71439            name: "deleter_adder"
71440        };
71441
71442        // make a couple of buttons for deleting the row or adding a new row
71443        var html = "<div id='" + _id + "deleter_adder_wrapper' class='deleter_adder_wrapper'>";
71444        html += "<div id='" + _id + "adder_button' class='adder_deleter_button' title='Add another criteria row'></div>";
71445        html += "<div id='" + _id + "deleter_button' class='adder_deleter_button' title='Remove criteria row'></div>";
71446        html += "<span id='" + _id + "search_button' class='adder_deleter_search_button' title='Search'>Search</span>";
71447        html += "</div>";
71448
71449        control.html_control = $j(html).appendTo(container);
71450
71451        // add handlers for the button click events
71452        $j("#" + _id + "deleter_button").button({
71453            icons: {
71454                primary: "ui-icon-closethick"
71455            },
71456            text: false
71457        }).bind('click', function () {
71458//                trace("delete row");
71459                if (scope.settings.on_delete_row)
71460                    scope.settings.on_delete_row(scope);
71461            });
71462
71463        $j("#" + _id + "adder_button").button({
71464            icons: {
71465                primary: "ui-icon-plusthick"
71466            },
71467            text: false
71468        }).bind('click', function () {
71469//                trace("add row");
71470                if (scope.settings.on_add_row)
71471                    scope.settings.on_add_row(null);
71472            });
71473
71474        $j("#" + _id + "search_button").button({icons: {}}).bind('click', function () {
71475//            trace("do search");
71476            if (scope.settings.on_search)
71477                scope.settings.on_search(null);
71478        });
71479    }
71480
71481
71482};
71483"use strict";
71484/**
71485 *
71486 * @param container
71487 * @param index
71488 * @param state
71489 * @param options
71490 */
71491function query_row(container, index, state, options) {
71492
71493    this.container = $j(container);
71494    this.index = index;
71495    this.state = state;
71496    this.settings = {
71497        "header_only":false,
71498        "on_ready":null,
71499        "on_delete_row":null,
71500        "on_add_row":null,
71501        "on_search":null,
71502        "on_help":null
71503    };
71504    $j.extend(true, this.settings, options);
71505
71506    this.set_state = function(new_state) {
71507
71508        if(!new_state || !_controls)
71509            return;
71510
71511        for(var key in new_state) {
71512
71513            var field = _controls[key];
71514            if(field && field.control)
71515                field.control.set_state(new_state[key]);
71516        }
71517    };
71518
71519    this.get_state = function() {
71520
71521        var state = {};
71522        for(var key in _controls) {
71523
71524            if(_controls[key].control)
71525                state[key] = _controls[key].control.get_state();
71526        }
71527
71528        return(state);
71529    };
71530
71531    this.destroy = function() {
71532
71533        if(_controls["structure"])
71534            _controls["structure"].control.widget.destroy();
71535
71536        $j('#'+_id).remove();
71537    };
71538
71539    this.get_id = function() {
71540
71541        return(_id);
71542    };
71543
71544    this.set_row_manage_state = function(first, last, terminal) {
71545
71546        set_manage_button_state(first, last, terminal);
71547    };
71548
71549    this.set_control_class = function(control_index, class_name, add) {
71550
71551        var index = 0;
71552        for(var key in _controls) {
71553
71554            if(index++ == control_index) {
71555
71556                var control = _controls[key].control;
71557//trace("matched control index " + control_index + ", control = " + control + ", class = " + control.name);
71558
71559                if(control.set_class) {
71560
71561//                    trace("set class method available, add class = " + add + ", class_name = " + class_name);
71562                    control.set_class(class_name, add);
71563                    return;
71564                }
71565            }
71566        }
71567    }
71568
71569    var _self = this;
71570    var _id = 'qbuilder_row' + Math.floor(Math.random() * 10001);
71571    var _table = null;
71572    var _controls = {};
71573    var _control_wait_count = 0;
71574
71575
71576    function set_manage_button_state(first, last, terminal) {
71577
71578        if(terminal) {
71579
71580            $j('#' + _id + 'adder_button').hide();
71581            $j('#' + _id + 'deleter_button').show();
71582            $j('#' + _id + 'search_button').show();
71583            return;
71584        }
71585
71586        if(first && last) {
71587
71588            $j('#' + _id + 'adder_button').show();
71589            $j('#' + _id + 'deleter_button').hide();
71590            $j('#' + _id + 'search_button').show();
71591            return;
71592        }
71593
71594        if(first && !last) {
71595
71596            $j('#' + _id + 'adder_button').hide();
71597            $j('#' + _id + 'deleter_button').show();
71598            $j('#' + _id + 'search_button').hide();
71599            return;
71600        }
71601
71602        if(!first && last) {
71603
71604            $j('#' + _id + 'adder_button').show();
71605            $j('#' + _id + 'deleter_button').show();
71606            $j('#' + _id + 'search_button').show();
71607            return;
71608        }
71609
71610        if(!first && !last) {
71611
71612            $j('#' + _id + 'adder_button').hide();
71613            $j('#' + _id + 'deleter_button').show();
71614            $j('#' + _id + 'search_button').hide();
71615            return;
71616        }
71617
71618    }
71619
71620    function build_scaffold() {
71621
71622        // the container here is expected to be a <td> but could be anything...
71623        var html = "<table class='builder_row_table' id='" + _id + "'>";
71624
71625        if(_self.settings.header_only) {
71626
71627            html += "<tr>";
71628            for(var item in _self.settings.layout) {
71629                var control = _self.settings.layout[item];
71630
71631                if(control.label == '') {
71632
71633                   html += "<td class='" + control.html_class + "_header final'>";
71634                   html += control.label;
71635                   html += "<div class='man_anno_search_help' id='" + _id + "_help_button'></div>";
71636                   html += "</td>";
71637                }
71638                else {
71639
71640                    html += "<td class='" + control.html_class + "_header'>" + control.label  + "</td>";
71641                }
71642            }
71643            html += "</tr></table>";
71644        }
71645        else {
71646
71647            // a row for the controls
71648            html += "<tr>";
71649            for(item in _self.settings.layout) {
71650                control = _self.settings.layout[item];
71651                html += "<td id='" + _id + item + "' class='" + control.html_class + "'></td>";
71652            }
71653            html += "</tr></table>";
71654        }
71655
71656        _table = $j(html).appendTo(_self.container);
71657        var help = $j('#' + _id + '_help_button');
71658        if(help && help.length)
71659            help.button({icons:{
71660                            primary:"ui-icon-help"
71661                        },
71662                        text:false
71663                      }).bind('click', function() {
71664//                             trace("manual search help");
71665                             do_help();
71666                             if(_self.settings.on_help)
71667                                _self.settings.on_help();
71668                      });
71669
71670    }
71671
71672    function do_help() {
71673
71674        if(_self.settings.help_url)
71675            window.open(_self.settings.help_url, '_help', 'status=0,toolbar=0,menubar=0,width=800,height=600,scrollbars=1');
71676    }
71677
71678    function build_controls() {
71679        var build_delegate_fnc;
71680
71681        if(_self.settings.header_only)
71682            return;
71683
71684        for(var item in _self.settings.layout) {
71685
71686            var container = $j('#' + _id + item);
71687            var config = _self.settings.layout[item];
71688
71689            if(config.control.wait_for_ready) {
71690                config.control.on_ready = control_ready;
71691                _control_wait_count++;
71692            }
71693
71694            if(config.control.build_delegate) {
71695                build_delegate_fnc = eval(config.control.build_delegate);  // convert JSON string to function reference
71696                _controls[item] = {
71697                    id:item,
71698                    control:build_delegate_fnc(container, _self, config.control)
71699                };
71700            }
71701            else
71702                container.text("Build a control around here.");
71703        }
71704    }
71705
71706    function control_ready() {
71707
71708        if(--_control_wait_count == 0) {
71709
71710            if(_self.settings.on_ready)
71711                _self.settings.on_ready(_self);
71712        }
71713    }
71714
71715    function init() {
71716
71717        build_scaffold();
71718        build_controls();
71719
71720        if(_self.state)
71721            _self.set_state(_self.state);
71722    }
71723
71724    init();
71725}
71726;
71727"use strict";
71728/**
71729 * It would be really nice to be able to simply JSON.stringify
71730 * and JSON.parse the query_set.  Unfortunately the History component
71731 * used in this app URLdecodes anything pushed onto the state stack and
71732 * puts it on the URL.  This breaks a page reload.  Someday soon that
71733 * History component should be replaced with a better-behaving version,
71734 * at which point these ugly methods can be replaced with JSON.parse &
71735 * JSON.stringify.
71736 *
71737 * The query_set is an Array of query_rows, each of which contains these fields:
71738 *        row[n] = {
71739 *                   structure:[], // values are structure ids
71740 *                   age:[],
71741 *                   level:[],
71742 *                   density:[]
71743 *               };
71744 *
71745 * @param init_term  This object can be initialized with either an encoded string or a query_set object.
71746 */
71747function manual_annotation_query_set(init_term) {
71748
71749    this.query_set = [];
71750
71751    this.stringify = function () {
71752        var encoded_term = "qset";
71753        for (var i = 0; i < this.query_set.length; i++) {
71754            var qrow = this.query_set[i];
71755            encoded_term += "+s" + i + "*" + qrow.structure.join(',');
71756            encoded_term += "+a" + i + "*" + qrow.age.join(',');
71757            encoded_term += "+i" + i + "*" + qrow.intensity.join(',');
71758            encoded_term += "+d" + i + "*" + qrow.density.join(',');
71759        }
71760
71761        encoded_term = encoded_term.replace('qset+', '');
71762        return(encoded_term);
71763    };
71764
71765    /**
71766     *
71767     * @param str encoded string
71768     * @returns {Array} query set
71769     */
71770    this.parse = function (str) {
71771        this.query_set = [];
71772
71773        // convert the string into a hash of term/value pairs
71774        var terms = decodeURIComponent(str).replace('qset+', '').split('+');
71775        var term_hash = {};
71776        for (var i = 0; i < terms.length; i++) {
71777            var pair = terms[i].split('*');
71778            if (pair && pair.length && pair.length == 2)
71779                term_hash[pair[0]] = pair[1];
71780        }
71781
71782        // There is a variable number of query rows.
71783        // Every row should have at least one structure.
71784        // All other fields are optional.
71785        var count = 0;
71786        var done = false;
71787        while (!done) {
71788            var structure = term_hash[("s" + count)];
71789            if (!structure)
71790                done = true;
71791            else {
71792                var row = {
71793                    structure: [],
71794                    age: [],
71795                    intensity: [],
71796                    density: []
71797                };
71798
71799                row.structure = structure.split(',');
71800
71801                if (term_hash[("a" + count)])
71802                    row.age = term_hash[("a" + count)].split(',');
71803
71804                if (term_hash[("i" + count)])
71805                    row.intensity = term_hash[("i" + count)].split(',');
71806
71807                if (term_hash[("d" + count)])
71808                    row.density = term_hash[("d" + count)].split(',');
71809
71810                this.query_set.push(row);
71811            }
71812
71813            count++;
71814        }
71815
71816        return(this.query_set);
71817    };
71818
71819    // This object can be initialized with either an encoded string or a query_set object.
71820    if (init_term) {
71821        if (typeof init_term === 'string')
71822            this.parse(init_term);
71823        else if (Object.prototype.toString.call(init_term) === '[object Array]')
71824            this.query_set = init_term;
71825        else
71826            throw "Unknown initializer for manual_annotation_query_set.";
71827    }
71828
71829}
71830;
71831"use strict";
71832/**
71833 *
71834 * @param dao
71835 * @param definition
71836 * @param info_widget
71837 * @param options
71838 */
71839function manual_annotation_search_plugin(dao, definition, info_widget, options) {
71840
71841    this.the_dao = dao;
71842    this.info_widget = info_widget;
71843    this.definition = definition;
71844    this.help_url = options ? options[definition.id] ? options[definition.id].help_url : '/' : '/';
71845    this.on_search = null;
71846    this.on_error = null;
71847    this.on_ready = null;
71848    this.settings = {
71849
71850    };
71851    $j.extend(true, this.settings, options);
71852
71853    this.get_state = function(encode) {
71854
71855        return(get_search_state(encode));
71856    };
71857
71858    this.set_state = function(state) {
71859
71860        if(_state_set)
71861            return;
71862
71863         load_state(state);
71864    };
71865
71866    this.on_search = options ? options.on_search ? options.on_search : null : null;
71867    this.on_error = options ? options.on_error ? options.on_error : null : null;
71868    this.on_ready = options ? options.on_ready ? options.on_ready : null : null;
71869    this.on_help = options ? options.on_help ? options.on_help : null : null;
71870
71871
71872
71873    var _self = this;
71874    var _info_widget = _self.info_widget;
71875    var _query_builder = null;
71876    var _all_ready = false;
71877    var _state_set = false;
71878    var _queued_state = null;
71879
71880
71881    function get_color_map() {
71882
71883        try {
71884            var url = window.location.href;
71885            var qstring = url.split('?')[1];
71886            var pairs = qstring.split('&');
71887            var params = {};
71888            for(var i = 0; i < pairs.length; i++) {
71889
71890                var pair_vals = pairs[i].split('=');
71891                if(pair_vals && pair_vals.length == 2)
71892                    params[pair_vals[0]] = pair_vals[1];
71893            }
71894
71895            return(params['colormap']);
71896
71897        } catch(e) {
71898
71899            trace("failed parsing debug options: " + e);
71900        }
71901
71902        return(null);
71903    }
71904
71905    function get_search_state(encode) {
71906
71907        //trace("returning empty state from the manual annotation search plugin");
71908        var state = {
71909            search_type:"manual_annotation",
71910            search_term:""
71911        };
71912
71913        if(!_query_builder || !_all_ready)
71914            return(state);
71915
71916        var result_set = _query_builder.get_state();
71917        if(result_set)
71918            state.search_term = encode_search_term(result_set.query_set);
71919
71920        var colormap = get_color_map();
71921        if(colormap)
71922            state.colormap = colormap;
71923
71924        return(state);
71925    }
71926
71927    function set_search_state(state) {
71928
71929        if(!state || !state.search_term)
71930            return;
71931
71932        if(_query_builder && _all_ready)
71933            _query_builder.set_state(decode_search_term(state.search_term));
71934        else
71935            _queued_state = state;
71936    }
71937
71938    function encode_search_term(query_set) {
71939
71940        var q = new manual_annotation_query_set(query_set);
71941        return(q.stringify());
71942    }
71943
71944    function decode_search_term(term) {
71945
71946        var q = new manual_annotation_query_set(term);
71947        return(q.query_set);
71948    }
71949
71950    function load_state(state) {
71951
71952        set_search_state(state);
71953        _state_set = true;
71954    }
71955
71956    function build_search_controls() {
71957
71958        var qbuilder_id = 'manual_anno_qbuilder_wrapper' + Math.floor(Math.random() * 10001);
71959        _info_widget.set_content(1,0, 'Search expert manual annotation of gene expression intensity and density by structure and postnatal age');
71960        _info_widget.set_content(0,1, '<div class="manual_anno_qbuilder_wrapper" id="' + qbuilder_id + '"></div>');
71961        _info_widget.set_content(0,2, '');
71962
71963        var options = {};
71964        $j.extend(true, options, _self.settings[_self.definition.id]);
71965
71966        options.on_search = do_search,
71967        options.on_help = do_help,
71968        options.on_ready = do_ready,
71969//        options.on_remove_row = do_search; // don't want to automatically do a search when a row is removed!
71970
71971        _query_builder = new manual_annotation_query_builder('#'+qbuilder_id, {/* initial state */}, options);
71972    }
71973
71974
71975    function do_ready() {
71976
71977//        trace("the query builder says it's ready");
71978        _all_ready = true;
71979
71980        if(_queued_state)
71981           load_state(_queued_state);
71982
71983        _queued_state = null;
71984
71985        if(_self.on_ready)
71986            _self.on_ready();
71987    }
71988
71989    function do_search() {
71990        if(!_query_builder.validate_state())
71991            return;
71992
71993        if(_self.on_search)
71994            _self.on_search(_self, get_search_state());
71995    }
71996
71997    function do_help() {
71998        if (_self.on_help)
71999            _self.on_help(_self.help_url);
72000    }
72001
72002
72003    function init() {
72004        build_search_controls();
72005    }
72006
72007
72008    init();
72009}
72010
72011;
72012
72013
72014function microarray_gene_search_plugin(dao, definition, info_widget, options) {
72015
72016    this.the_dao = dao;
72017    this.info_widget = info_widget;
72018    this.definition = definition;
72019
72020    this.get_state = function() {
72021        return(get_gene_search_state());
72022    }
72023
72024    this.set_state = function(state) {
72025        set_gene_search_state(state);
72026    }
72027
72028    this.on_search = options ? options.on_search ? options.on_search : null : null;
72029    this.on_error = options ? options.on_error ? options.on_error : null : null;
72030    this.on_ready = options ? options.on_ready ? options.on_ready : null : null;
72031    this.on_search_term_lookup = options ? options.on_search_term_lookup ? options.on_search_term_lookup : null : null;
72032
72033
72034    var _self = this;
72035    var _info_widget = _self.info_widget;
72036    var _type_ahead_callback = null;
72037    var _closed = true;
72038    var _selected_item = false;
72039    var _search_text_box = null;
72040    //var _tag_container = null;
72041    var _display_donors;
72042    var _product_ids = _self.definition.product_ids;
72043
72044    function get_gene_search_state() {
72045        var exact = get_exact_search_state();
72046        var type = $j('input[name="search_type"]:checked').val();
72047        var term = _search_text_box.val();
72048/*
72049
72050        var terms = _tag_container.get_tags();
72051        //alert("get_state terms:"+ terms);
72052
72053        var term, terms_array, new_terms_array;
72054        //terms_array = terms.split(',');
72055        new_terms_array = terms.map(filter_term);
72056
72057        term = new_terms_array.join(',');
72058        //alert("get_state new terms:"+term);
72059*/
72060
72061
72062        if(_selected_item)
72063            term = '"' + term + '"';
72064
72065 //alert("the definition: " + inspect('', _self.definition));
72066        var state = {
72067            exact_match:exact,
72068            search_term:term,
72069            search_type:_self.definition.id
72070        }
72071        if(_display_donors) {
72072            state.donors = _display_donors;
72073        }
72074
72075        return(state);
72076    }
72077
72078              function inspect(prefix, obj) {
72079                var s = "";
72080                for(key in obj) {
72081                    if((typeof obj[key]) == "function")
72082                        s = s;
72083                    else if((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
72084                        s += "\n" + prefix + "key: " + key + inspect(prefix + "    ", obj[key]);
72085                    else
72086                        s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
72087                }
72088                return(s);
72089            }
72090
72091    function set_gene_search_state(state) {
72092        var exact = document.getElementById('_search_exact');
72093        if(exact)
72094            exact.checked = state.exact_match;
72095
72096        if(state.search_term)
72097            _search_text_box.val( state.search_term );
72098
72099        if(state.donors)
72100            _display_donors = state.donors;
72101
72102        if(_self.on_ready)
72103            _self.on_ready();
72104    }
72105
72106    function build_gene_search_controls() {
72107
72108        var search_box_html = "<input type='text' id='_search_text' class='_sw_search_text'/>";
72109        search_box_html += "<div id='_search_button'>Search</div>";
72110        var exact_html = "<input type='checkbox' id='_search_exact'/>Show exact matches only";
72111
72112        _info_widget.set_content(1,0, _self.definition.input_title);
72113        _info_widget.set_content(0,1, search_box_html);
72114        _info_widget.set_content(0,2, exact_html);
72115
72116        _search_text_box = $j('#_search_text');
72117        var search_button = $j('#_search_button');
72118
72119        // control-ify
72120        search_button.button();
72121        search_button.bind('click', do_gene_search);
72122
72123        _search_text_box.autocomplete(
72124            {
72125             source:do_gene_auto_complete,
72126             select:do_gene_search,
72127             search:do_start_search,
72128             open:do_open,
72129             appendTo:_self.info_widget.container,
72130             delay:100
72131            });
72132
72133//        _tag_container = new AIBS.UI.ontology_tag_container(_search_text_box, {
72134//            on_autocomplete:gene_lookup_search,
72135//            on_tag_added:do_add_tag,
72136//            on_tag_removed:do_remove_tag
72137//            //default_text: settings.default_text
72138//        });
72139
72140        _search_text_box.bind('keyup', do_gene_enter_check);
72141    }
72142
72143/*
72144    function do_add_tag(tag) {
72145            //alert("added: "+tag);
72146    }
72147
72148    function do_remove_tag(tag) {
72149        // alert("removed: "+tag);
72150    }
72151*/
72152
72153    function gene_lookup_search(term, callback) {
72154        var match = [];
72155        if(!_self.on_search_term_lookup) {
72156            callback(match); // return an empty array of matched entries
72157        }
72158        else {
72159            match = _self.on_search_term_lookup(term.term, _product_ids);
72160            callback(match);
72161        }
72162
72163    }
72164
72165
72166    function do_start_search(event, ui) {
72167        if(_closed)
72168            _search_text_box.autocomplete('close');
72169    }
72170
72171    function do_open(event, ui) {
72172        if(_closed)
72173            _search_text_box.autocomplete('close');
72174    }
72175
72176    function do_gene_auto_complete(request, response) {
72177        _type_ahead_callback = response;
72178        var opts = {
72179           success_callback:gene_autocomplete_responder,
72180           error_callback:dummy,
72181           limit:10,
72182           exact_match:get_exact_search_state()
72183        };
72184
72185        _self.the_dao.gene_typeahead(request.term, opts);
72186    }
72187
72188    function gene_autocomplete_responder(response_data, text_status, xhdr) {
72189
72190        if(!response_data)
72191            return;
72192
72193        var ret = [];
72194        var len = response_data.length;
72195//TODO: get rid of the explicit reference to a field name
72196        // remove duplicate terms - when a gene is involved to multiple products, multiple GeneLookup records will be found for this gene
72197        var term_cache = {};
72198
72199        for(var i = 0; i < len; i++)
72200            if(!term_cache[response_data[i].term]) {
72201                ret.push(response_data[i].term);
72202                term_cache[response_data[i].term] = true;
72203            }
72204
72205
72206        _type_ahead_callback(ret);
72207    }
72208
72209    function dummy(xhdr, text_status, error) {
72210        // swallow this type ahead error
72211    }
72212
72213    function get_exact_search_state() {
72214       var exact = $j('#_search_exact');
72215
72216       return( exact ? exact[0] ? exact[0].checked : false : false );
72217    }
72218
72219    function do_gene_search(event, ui) {
72220        var term = '';
72221        if(ui && ui.item) {
72222            //term = '"' + ui.item.value + '"';
72223            _search_text_box.val(ui.item.value);
72224
72225            _selected_item = true;
72226        }
72227        else {
72228            term = _search_text_box.val();
72229/*            term = _tag_container.get_tags();
72230            //alert("orig term:"+term);
72231            term = filter_term(term);
72232            //alert("modified term:"+term);*/
72233
72234            _selected_item = false;
72235
72236            if(!term || term.length < 1) {
72237                if(_self.on_error)
72238                    _self.on_error("Empty search term");
72239            }
72240        }
72241
72242        if(_self.on_search) {
72243            var temp_state = get_gene_search_state();
72244            if(temp_state.donors)   {
72245                 delete temp_state.donors;   // need to remove the "donors" state if it was set by a previous search
72246            }
72247            _self.on_search(_self, temp_state);
72248        }
72249
72250        return(false);
72251    }
72252
72253    function do_gene_enter_check(event, ui) {
72254        if(event.keyCode == 13) {
72255            _search_text_box.autocomplete('close');
72256            _closed = true;
72257            do_gene_search(null, null);
72258        } else {
72259            _closed = false;
72260        }
72261    }
72262
72263/*    function filter_term(term) {
72264        var from_tag_input = false, result_array, new_term;
72265        var tag_input_patt = /(symbol|alias|name): (.+) \(id:(\d+)\)/;
72266        result_array = tag_input_patt.exec(term);
72267        if(!result_array) {
72268            return term;   // no match -- term not from a generated tag
72269        }
72270        else {
72271            new_term = result_array[3];
72272            //alert("term:" + new_term);
72273            return new_term;
72274
72275        }
72276
72277
72278
72279    }*/
72280
72281    function init() {
72282        build_gene_search_controls();
72283    }
72284
72285    init();
72286}
72287;
72288
72289
72290function nhp_macrodissection_differential_search_ui(dao, definition, info_widget, options) {
72291
72292    this.the_dao = dao;
72293    this.info_widget = info_widget;
72294    this.definition = definition;
72295    this.help_url = options ? options[definition.id] ? options[definition.id].help_url : '/' : '/';
72296    this.threshold = options ? options.threshold ? options.threshold : '1' : '1';
72297    this.settings = {
72298        default_contrast_domains:[8],
72299        default_target_domains:null
72300    }
72301    $j.extend(true, this.settings, options);
72302
72303    this.get_state = function() {
72304        return(get_differential_search_state());
72305    }
72306
72307    this.set_state = function(state) {
72308        set_differential_search_state(state);
72309    }
72310
72311    this.destroy = function() {
72312
72313        if(this.search_domain_1 && this.search_domain_1.ontology_autocomplete)
72314            this.search_domain_1.ontology_autocomplete('destroy');
72315
72316        if(this.search_domain_2 && this.search_domain_2.ontology_autocomplete)
72317            this.search_domain_2.ontology_autocomplete('destroy');
72318    }
72319
72320    this.on_search = options ? options.on_search ? options.on_search : null : null;
72321    this.on_error = options ? options.on_error ? options.on_error : null : null;
72322    this.on_ready = options ? options.on_ready ? options.on_ready : null : null;
72323
72324    var _self = this;
72325    var _info_widget = _self.info_widget;
72326    var _set_state = null;
72327    var _ontology_model = null;
72328    var _initializing = false;
72329    var _donor_list = null;
72330    //var _id = Math.floor(Math.random() * 10201);
72331
72332    var _stage_list;
72333    //var _stage_lis = options ? options.stage_list ? options.stage_list : null : null;
72334    var _structure_list;
72335
72336    function set_selections(options, selections) {
72337      for(var i = 0; i < options.length; i++)
72338          options[i].selected = selections.include(options[i].id);
72339    }
72340
72341
72342    function get_differential_search_state() {
72343
72344        $j('.diff_structure').dropdownchecklist('close');    // close both select
72345        $j('.diff_structure').dropdownchecklist('refresh');
72346
72347        var selected_structures_1 = $j('#_search_domain_1 option:selected');
72348        var selected_structures_2 = $j('#_search_domain_2 option:selected');
72349
72350        var selected_structures1 = $j.map(selected_structures_1, function(op){ if(op.id != '0') return op.id;});  // get an array of strings
72351        var selected_structures1_str = selected_structures1.join(',');
72352
72353        var selected_structures2 = $j.map(selected_structures_2, function(op){ if(op.id != '0') return op.id;});  // get an array of strings
72354        var selected_structures2_str = selected_structures2.join(',');
72355
72356        $j('.diff_stage').dropdownchecklist('close');
72357        $j('.diff_stage').dropdownchecklist('refresh');
72358        var selected_stage_options_temp = $j('#_search_stage_1 option:selected');
72359        var selected_stage_1 = $j.map(selected_stage_options_temp, function(op){ if(op.id != '0') return op.id;});  // get an array of strings
72360
72361        var selected_stage_1_str = selected_stage_1.join(',');
72362
72363        selected_stage_options_temp = $j('#_search_stage_2 option:selected');
72364        var selected_stage_2 = $j.map(selected_stage_options_temp, function(op){ if(op.id != '0') return op.id;});  // get an array of strings
72365
72366        var selected_stage_2_str = selected_stage_2.join(',');
72367
72368        var state = {
72369            domain1:selected_structures1_str,
72370            domain2:selected_structures2_str,
72371            stage1:selected_stage_1_str,
72372            stage2:selected_stage_2_str,
72373            search_type:_self.definition.id
72374        }
72375        return(state);
72376    }
72377
72378    function set_differential_search_state(state) {
72379
72380        _set_state = {};
72381        _set_state.domain1 = state.domain1;
72382        _set_state.domain2 = state.domain2;
72383
72384        _set_state.search_type = _self.definition.id;
72385        //_set_state.selected_donors = state.selected_donors;
72386        _set_state.stage1 = state.stage1;
72387        _set_state.stage2 = state.stage2;
72388
72389        // need to call load_differential_state after _set_state is populated
72390        load_differential_state();
72391    }
72392
72393    function load_differential_state() {
72394        _initializing = true;
72395
72396        if(_set_state && _set_state.domain1 && _set_state.domain1.length > 0) {
72397            set_selections($j('#_search_domain_1 option'), _set_state.domain1.split(','));
72398        }
72399        else {
72400            var domain1_select = $j('#_search_domain_1 option[id=0]');
72401            domain1_select.attr('selected', 'selected');
72402        }
72403
72404        if(_set_state && _set_state.domain2 && _set_state.domain2.length > 0) {
72405            set_selections($j('#_search_domain_2 option'), _set_state.domain2.split(','));
72406        }
72407        else {
72408            var domain2_select = $j('#_search_domain_2 option[id=0]');
72409            domain2_select.attr('selected', 'selected');
72410        }
72411
72412
72413        if(_set_state && _set_state.stage1 && _set_state.stage1.length > 0) {
72414            var selected_stage1_array = _set_state.stage1.split(',');
72415            var stage1_select = $j('#_search_stage_1 option');
72416            stage1_select.removeAttr('selected');   // remove all the selected attr
72417            for( var i = 0; i < selected_stage1_array.length; i ++ ){
72418                stage1_select = $j('#_search_stage_1 option[id="' + selected_stage1_array[i] +'"]');
72419                stage1_select.attr('selected', 'selected');
72420            }
72421        }
72422        else {
72423            var stage1_select = $j('#_search_stage_2 option[id=0]');
72424            stage1_select.attr('selected', 'selected');
72425        }
72426
72427        if(_set_state && _set_state.stage2 && _set_state.stage2.length > 0) {
72428            var selected_stage2_array = _set_state.stage2.split(',');
72429            var stage2_select = $j('#_search_stage_2 option');
72430            stage2_select.removeAttr('selected');   // remove all the selected attr
72431            for( var i = 0; i < selected_stage2_array.length; i ++ ){
72432                stage2_select = $j('#_search_stage_2 option[id="' + selected_stage2_array[i] +'"]');
72433                stage2_select.attr('selected', 'selected');
72434            }
72435        }
72436        else {
72437            var stage2_select = $j('#_search_stage_2 option[id=0]');
72438            stage2_select.attr('selected', 'selected');
72439        }
72440
72441        $j('.diff_stage').dropdownchecklist("refresh");
72442
72443
72444//        // if there is no state data, set a default contrast structure
72445//        if(!_set_state) {
72446//           if(_self.settings.default_contrast_domains)
72447//              _self.search_domain_1.ontology_autocomplete('set_structure_ids', _self.settings.default_contrast_domains);
72448//           if(_self.settings.default_target_domains)
72449//              _self.search_domain_2.ontology_autocomplete('set_structure_ids', _self.settings.default_target_domains);
72450//        }
72451
72452        _initializing = false;
72453        _set_state = null;
72454
72455        if(_self.on_ready)
72456            _self.on_ready();
72457    }
72458
72459    function build_differential_search_controls() {
72460
72461        var search_box1_html = "<table id='differential_controls'><tr><td class='td_label'><div class='_diff_search_input_label'>Target Structure(s), Age(s)</div></td>";
72462        //search_box1_html +=    "<td class='td_structure'><input type='text' id='_search_domain_2_" + _id + "' class='_sw_search_text'/></td>";
72463        search_box1_html += "<td class='td_structure'>";
72464        search_box1_html += "<select id='_search_domain_2' class='_sw_search_text diff_structure'/>";
72465        search_box1_html += "<option id=0 >[All Structures]</option>";
72466        for(var i = 0; i < _structure_list.length; i++ ) {
72467            search_box1_html += "<option id=" + _structure_list[i].id + ">" + _structure_list[i].acronym + ' - ' +_structure_list[i].name + "</option>";
72468        }
72469        search_box1_html += "</td>";   // for <td class='td_structure'>
72470        search_box1_html += "<td class='td_stage'><select id='_search_stage_2' class='_sw_search_text diff_stage'/>";
72471        search_box1_html += "<option id=0 >[All Stages]</option>";
72472        for(var i=0; i<_stage_list.length; i++) {
72473            search_box1_html += "<option id='" + _stage_list[i].name + "'>" + _stage_list[i].name + "</option>";
72474        }
72475        search_box1_html += "</select></td>";
72476        search_box1_html += "<td class='td_controls'><div id='_swap_button' class='_sw_icon_button'></div>";
72477        search_box1_html += "<div id='_diff_help_button' class='_sw_icon_button'></div></td></tr>";
72478
72479        var search_box2_html = "<tr><td class='td_label'><div class='_diff_search_input_label'>Contrast Structure(s), Age(s)</div></td>";
72480        //search_box2_html +=   "<td class='td_structure'><input type='text' id='_search_domain_1_" + _id + "' class='_sw_search_text'/></td>";
72481        search_box2_html +=   "<td class='td_structure'>";
72482        search_box2_html += "<select id='_search_domain_1' class='_sw_search_text diff_structure'/>";
72483        search_box2_html += "<option id=0 >[All Structures]</option>";
72484        for(var i = 0; i < _structure_list.length; i++ ) {
72485            search_box2_html += "<option id=" + _structure_list[i].id + ">" + _structure_list[i].acronym + ' - ' +_structure_list[i].name + "</option>";
72486        }
72487        search_box2_html += "</td>";   // for <td class='td_structure'>
72488
72489        search_box2_html += "<td class='td_stage'><select id='_search_stage_1' class='_sw_search_text diff_stage'/>";
72490        search_box2_html += "<option id=0 >[All Stages]</option>";
72491        for(i=0; i<_stage_list.length; i++) {
72492            search_box2_html += "<option id='" + _stage_list[i].name + "'>" + _stage_list[i].name + "</option>";
72493        }
72494        search_box2_html += "</select></td>";
72495        search_box2_html += "<td class='td_controls'><div id='_search_button'>Search</div></td></tr></table>";
72496
72497        _info_widget.set_content(1,0, search_box1_html+search_box2_html);
72498        _info_widget.set_content(0,1, '');
72499        _info_widget.set_content(0,2, '');
72500
72501
72502        _self.diff_search_button = $j('#_search_button');
72503        _self.diff_help_button = $j('#_diff_help_button');
72504        _self.swap_button = $j('#_swap_button');
72505
72506        // control-ify
72507        _self.diff_search_button.button();
72508        _self.diff_search_button.bind('click', do_diff_search);
72509
72510        _self.diff_help_button.button({icons:{secondary:'ui-icon-help'}});
72511        _self.diff_help_button.bind('click', do_diff_help);
72512
72513        _self.swap_button.button({icons:{secondary:'ui-icon-refresh'}});
72514        _self.swap_button.bind('click', do_domain_swap);
72515
72516
72517        $j('.diff_structure').dropdownchecklist({icon:{placement:'right',toOpen:'ui-icon-triangle-1-s'},
72518                                      forceMultiple:true,
72519                                      firstItemChecksAll: true,
72520                                      maxDropHeight:600,
72521                                      width:250,
72522                                      zIndex:9999});
72523
72524        $j('.diff_stage').dropdownchecklist({icon:{placement:'right',toOpen:'ui-icon-triangle-1-s'},
72525                                      forceMultiple:true,
72526                                      firstItemChecksAll: true,
72527                                      maxDropHeight:300,
72528                                      width:250,
72529                                      zIndex:9999});
72530
72531    }
72532
72533
72534
72535    function do_diff_search(event, ui) {
72536        if(_initializing)
72537            return;
72538
72539        if(_self.on_search)
72540            _self.on_search(_self, get_differential_search_state());
72541    }
72542
72543    function do_diff_help(event, ui) {
72544        window.open(_self.help_url, '_help', 'status=0,toolbar=0,menubar=0,width=800,height=600,scrollbars=1,resizable=1');
72545    }
72546
72547    function do_domain_swap(event, ui) {
72548
72549        $j('.diff_structure').dropdownchecklist('close');
72549    // close both select
72550        $j('.diff_structure').dropdownchecklist('refresh');
72551
72552        var selected_structures_1 = $j('#_search_domain_1 option:selected');
72553        var selected_structures_2 = $j('#_search_domain_2 option:selected');
72554
72555        var selected_structures1 = $j.map(selected_structures_1, function(op){ if(op.id != '0') return op.id;});  // get an array of strings
72556        var selected_structures2 = $j.map(selected_structures_2, function(op){ if(op.id != '0') return op.id;});  // get an array of strings
72557
72558        set_selections($j('#_search_domain_1 option'), selected_structures2);
72559        set_selections($j('#_search_domain_2 option'), selected_structures1);
72560
72561        $j('.diff_stage').dropdownchecklist('close');    // close both select
72562        $j('.diff_stage').dropdownchecklist('refresh');
72563
72564        var selected_stages_1 = $j('#_search_stage_1 option:selected');
72565        var selected_stages_2 = $j('#_search_stage_2 option:selected');
72566
72567        var selected_stages1 = $j.map(selected_stages_1, function(op){ if(op.id != '0') return op.id;});  // get an array of strings
72568        var selected_stages2 = $j.map(selected_stages_2, function(op){ if(op.id != '0') return op.id;});  // get an array of strings
72569
72570        set_selections($j('#_search_stage_1 option'), selected_stages2);
72571        set_selections($j('#_search_stage_2 option'), selected_stages1);
72572
72573
72574//        if(check_domains())
72575//            do_diff_search(null, null);
72576
72577        do_diff_search(null, null);
72578    }
72579
72580    function tags_to_string(tags) {
72581        var s = "";
72582        for(var i = 0; i < tags.length; i++)
72583            s += tags[i].label + '; ';
72584
72585        return(s.slice(0,-2));
72586    }
72587
72588
72589    function fetch_donor_list(product_ids) {
72590        var dao_options = {
72591            success_callback: received_donor_list,
72592            error_callback: received_no_donor_list
72593        };
72594
72595        _self.the_dao.donor_info_by_product_ids( product_ids, dao_options);
72596    }
72597
72598    function received_donor_list(response_data, text_status, xhdr) {
72599
72600        var donor_array = new Array();
72601        var donor_index = 0;
72602
72603        for ( var i = 0; i < response_data.length; i++ ) {
72604            var donor = response_data[i];
72605                donor_array[donor_index++] = {"donor":{"id":donor.id,"name":donor.name}}
72606        }
72607        _donor_list = donor_array;
72608
72609        build_differential_search_controls();
72610    }
72611
72612    function received_no_donor_list()
72613    {
72614        alert("Failed to fetch a list of donors.");
72615    }
72616
72617
72618
72619    function init() {
72620        if(_self.settings.stage_list && _self.settings.structure_list )  {
72621            _stage_list = _self.settings.stage_list;
72622            _structure_list = _self.settings.structure_list;
72623            build_differential_search_controls();
72624        }
72625        else
72626            fetch_donor_list('');   // use the default product id provided in the theme
72627
72628
72629    }
72630
72631    init();
72632}
72633;
72634
72635
72636function nhp_microdissection_differential_search_ui(dao, definition, info_widget, options) {
72637
72638    this.the_dao = dao;
72639    this.info_widget = info_widget;
72640    this.definition = definition;
72641    this.help_url = options ? options[definition.id] ? options[definition.id].help_url : '/' : '/';
72642    this.threshold = options ? options.threshold ? options.threshold : '1' : '1';
72643    this.settings = {
72644        default_contrast_domains:[8],
72645        default_target_domains:null
72646    }
72647    $j.extend(true, this.settings, options);
72648
72649    this.get_state = function() {
72650        return(get_differential_search_state());
72651    }
72652
72653    this.set_state = function(state) {
72654        set_differential_search_state(state);
72655    }
72656
72657    this.destroy = function() {
72658
72659        if(this.search_domain_1 && this.search_domain_1.ontology_autocomplete)
72660            this.search_domain_1.ontology_autocomplete('destroy');
72661
72662        if(this.search_domain_2 && this.search_domain_2.ontology_autocomplete)
72663            this.search_domain_2.ontology_autocomplete('destroy');
72664    }
72665
72666    this.on_search = options ? options.on_search ? options.on_search : null : null;
72667    this.on_error = options ? options.on_error ? options.on_error : null : null;
72668    this.on_ready = options ? options.on_ready ? options.on_ready : null : null;
72669
72670    var _self = this;
72671    var _info_widget = _self.info_widget;
72672    var _set_state = null;
72673    var _ontology_model = null;
72674    var _initializing = false;
72675    var _donor_list = null;
72676    var _id = Math.floor(Math.random() * 10201);
72677
72678    var _stage_list;
72679    //var _stage_list = options ? options.stage_list ? options.stage_list : null : null;
72680
72681    function check_domains() {
72682        var d1 = _self.search_domain_1.ontology_autocomplete('get_tags');
72683        var d2 = _self.search_domain_2.ontology_autocomplete('get_tags');
72684
72685        return(d1.length && d2.length);
72686    }
72687
72688    function set_selections(options, selections) {
72689      for(var i = 0; i < options.length; i++)
72690          options[i].selected = selections.include(options[i].id);
72691    }
72692
72693
72694    function get_differential_search_state() {
72695
72696        if(!check_domains()) {
72697            alert("Both 'Target' and 'Contrast' domains must be set in order to do a differential search.");
72698            return(null);
72699        }
72700
72701        var d1 = _self.search_domain_1.ontology_autocomplete('get_tags');
72702        var d2 = _self.search_domain_2.ontology_autocomplete('get_tags');
72703
72704        var d1_ids = [];
72705        var d2_ids = [];
72706        for(var i = 0; i < d1.length; i++) d1_ids.push(d1[i].value );
72707        for(var i = 0; i < d2.length; i++) d2_ids.push(d2[i].value );
72708        var d1_ids_str = d1_ids.join(',');
72709        var d2_ids_str = d2_ids.join(',');
72710
72711        $j('.diff_stage').dropdownchecklist('close');
72712        $j('.diff_stage').dropdownchecklist('refresh');
72713        var selected_stage_options_temp = $j('#_search_stage_1 option:selected');
72714        var selected_stage_1 = $j.map(selected_stage_options_temp, function(op){ if(op.id != '0') return op.id;});  // get an array of strings
72715
72716        var selected_stage_1_str = selected_stage_1.join(',');
72717
72718        selected_stage_options_temp = $j('#_search_stage_2 option:selected');
72719        var selected_stage_2 = $j.map(selected_stage_options_temp, function(op){ if(op.id != '0') return op.id;});  // get an array of strings
72720
72721        var selected_stage_2_str = selected_stage_2.join(',');
72722
72723        var state = {
72724            domain1:d1_ids_str,
72725            domain2:d2_ids_str,
72726//            domain1_threshold:[0,50],
72727//            domain2_threshold:[t_val,50],
72728//            image_set: (_self.image_set_check.get(0).checked == true) ? 'P56coronal' : 'P56',
72729            //selected_donors:selected_donors_str,
72730            stage1:selected_stage_1_str,
72731            stage2:selected_stage_2_str,
72732            search_type:_self.definition.id
72733        }
72734        return(state);
72735    }
72736
72737    function set_differential_search_state(state) {
72738        // the call to do_search_type_change provokes a load of
72739        // the ontology.  We have to wait until that completes
72740        // before setting the control states.  That on-load' handler
72741        // will call load_differential_state. So there's not much to
72742        // do here except record the state that should be set when
72743        // everything's ready.
72744//        var t_val = _self.threshold;
72745//        var t_val_list = state.domain2_threshold;
72746//        if(t_val_list) {
72747//           var vals = t_val_list.split(',');
72748//           if(vals && vals.length)
72749//              t_val = vals[0];
72750//        }
72751
72752        _set_state = {};
72753        _set_state.domain1 = state.domain1;
72754        _set_state.domain2 = state.domain2;
72755        //_set_state.image_set = state.image_set;
72756        _set_state.search_type = _self.definition.id;
72757        //_set_state.selected_donors = state.selected_donors;
72758        _set_state.stage1 = state.stage1;
72759        _set_state.stage2 = state.stage2;
72760        //_set_state.t_val = t_val;
72761        //_self.threshold_slider.slider('value', t_val);
72762    }
72763
72764    function load_differential_state() {
72765        _initializing = true;
72766
72767        if(_set_state && _set_state.domain1 && _set_state.domain1.length > 0)
72768            _self.search_domain_1.ontology_autocomplete('set_structure_ids', _set_state.domain1.split(','));
72769
72770        if(_set_state && _set_state.domain2 && _set_state.domain2.length > 0)
72771            _self.search_domain_2.ontology_autocomplete('set_structure_ids', _set_state.domain2.split(','));
72772
72773        if(_set_state && _set_state.stage1 && _set_state.stage1.length > 0) {
72774            var selected_stage1_array = _set_state.stage1.split(',');
72775            var stage1_select = $j('#_search_stage_1 option');
72776            stage1_select.removeAttr('selected');   // remove all the selected attr
72777            for( var i = 0; i < selected_stage1_array.length; i ++ ){
72778                stage1_select = $j('#_search_stage_1 option[id="' + selected_stage1_array[i] +'"]');
72779                stage1_select.attr('selected', 'selected');
72780            }
72781        }
72782        else {
72783            var stage1_select = $j('#_search_stage_2 option[id=0]');
72784            stage1_select.attr('selected', 'selected');
72785        }
72786
72787        if(_set_state && _set_state.stage2 && _set_state.stage2.length > 0) {
72788            var selected_stage2_array = _set_state.stage2.split(',');
72789            var stage2_select = $j('#_search_stage_2 option');
72790            stage2_select.removeAttr('selected');   // remove all the selected attr
72791            for( var i = 0; i < selected_stage2_array.length; i ++ ){
72792                stage2_select = $j('#_search_stage_2 option[id="' + selected_stage2_array[i] +'"]');
72793                stage2_select.attr('selected', 'selected');
72794            }
72795        }
72796        else {
72797            var stage2_select = $j('#_search_stage_2 option[id=0]');
72798            stage2_select.attr('selected', 'selected');
72799        }
72800
72801        $j('.diff_stage').dropdownchecklist("refresh");
72802
72803
72804//        // if there is no state data, set a default contrast structure
72805//        if(!_set_state) {
72806//           if(_self.settings.default_contrast_domains)
72807//              _self.search_domain_1.ontology_autocomplete('set_structure_ids', _self.settings.default_contrast_domains);
72808//           if(_self.settings.default_target_domains)
72809//              _self.search_domain_2.ontology_autocomplete('set_structure_ids', _self.settings.default_target_domains);
72810//        }
72811
72812        _initializing = false;
72813        _set_state = null;
72814
72815        if(_self.on_ready)
72816            _self.on_ready();
72817    }
72818
72819    function build_differential_search_controls() {
72820
72821        var search_box1_html = "<table id='differential_controls'><tr><td class='td_label'><div class='_diff_search_input_label'>Target Structure(s), Age(s)</div></td>";
72822        search_box1_html +=    "<td class='td_structure'><input type='text' id='_search_domain_2_" + _id + "' class='_sw_search_text'/></td>";
72823        search_box1_html += "<td class='td_stage'><select id='_search_stage_2' class='_sw_search_text diff_stage'/>";
72824        search_box1_html += "<option id=0 >[All Stages]</option>";
72825        for(var i=0; i<_stage_list.length; i++) {
72826            search_box1_html += "<option id='" + _stage_list[i].name + "'>" + _stage_list[i].name + "</option>";
72827        }
72828        search_box1_html += "</select></td>";
72829        search_box1_html += "<td class='td_controls'><div id='_swap_button' class='_sw_icon_button'></div>";
72830        search_box1_html += "<div id='_diff_help_button' class='_sw_icon_button'></div></td></tr>";
72831
72832        var search_box2_html = "<tr><td class='td_label'><div class='_diff_search_input_label'>Contrast Structure(s), Age(s)</div></td>";
72833        search_box2_html +=   "<td class='td_structure'><input type='text' id='_search_domain_1_" + _id + "' class='_sw_search_text'/></td>";
72834        search_box2_html += "<td class='td_stage'><select id='_search_stage_1' class='_sw_search_text diff_stage'/>";
72835        search_box2_html += "<option id=0 >[All Stages]</option>";
72836        for(i=0; i<_stage_list.length; i++) {
72837            search_box2_html += "<option id='" + _stage_list[i].name + "'>" + _stage_list[i].name + "</option>";
72838        }
72839        search_box2_html += "</select></td>";
72840        search_box2_html += "<td class='td_controls'><div id='_search_button'>Search</div></td></tr></table>";
72841
72842/*
72843        // differential donor selection
72844        var search_box3_html = "<div class='_diff_search_input_label'>Selected Donor(s)</div>";
72845        search_box3_html += "<span id='_diff_donor_selection'><select id='diff_donors' multiple='multiple'>";
72846
72847        //var donor_list_index = 0;
72848        search_box3_html += "<option id=0 >[All Donors]</option>";
72849        for(var i=0; i<_donor_list.length; i++) {
72850            search_box3_html += "<option id=" + _donor_list[i].donor.id + ">" + _donor_list[i].donor.name + "</option>";
72851        }
72852
72853        search_box3_html += "</select></span>";
72854
72855*/
72856
72857
72858        _info_widget.set_content(1,0, search_box1_html+search_box2_html);
72859        _info_widget.set_content(0,1, '');
72860        _info_widget.set_content(0,2, '');
72861
72862//        _info_widget.set_content(0,1, search_box2_html);
72863//        _info_widget.set_content(0,2, search_box3_html);
72864
72865
72866
72867// they want to reverse the position of the target & contrast domains.
72868// changing the name here seems like the lowest-impact solution.
72869        _self.search_domain_1 = $j('#_search_domain_1_' + _id);
72870        _self.search_domain_2 = $j('#_search_domain_2_' + _id);
72871
72872        _self.diff_search_button = $j('#_search_button');
72873        _self.diff_help_button = $j('#_diff_help_button');
72874        _self.swap_button = $j('#_swap_button');
72875
72876        // control-ify
72877        _self.diff_search_button.button();
72878        _self.diff_search_button.bind('click', do_diff_search);
72879
72880        _self.diff_help_button.button({icons:{secondary:'ui-icon-help'}});
72881        _self.diff_help_button.bind('click', do_diff_help);
72882
72883        _self.swap_button.button({icons:{secondary:'ui-icon-refresh'}});
72884        _self.swap_button.bind('click', do_domain_swap);
72885
72886        if(!_ontology_model)
72887            _ontology_model = new ontology_model(_self.the_dao, 9, {on_ready:do_ontology_load, on_error:_self.on_ontology_error});
72888        else
72889            do_ontology_load(_ontology_model);
72890
72891        $j('.diff_stage').dropdownchecklist({icon:{placement:'right',toOpen:'ui-icon-triangle-1-s'},
72892                                      forceMultiple:true,
72893                                      firstItemChecksAll: true,
72894                                      maxDropHeight:300,
72895                                      width:100,
72896                                      zIndex:9999});
72897
72898    }
72899
72900
72901    function do_ontology_load(model) {
72902
72903        // this selector should apply the ontology_autocomplete behavior to
72904        // both of the domain inputs
72905        //$j('._sw_search_text').ontology_autocomplete(model, {/* empty options for now */});
72906
72907        // make sure each time only one text box is selected by jquery when initializing ontology_autocomplete
72908        _self.search_domain_1.ontology_autocomplete(model, {default_open_nodes:_self.settings.open_structures});
72909        _self.search_domain_2.ontology_autocomplete(model, {default_open_nodes:_self.settings.open_structures});
72910        load_differential_state();
72911    }
72912
72913    function do_ontology_error(model, error) {
72914        var msg = "Due to an error in communicating with the data service, differential search is not available at this time.  ";
72915        msg +=  "Please try again in a few minutes.";
72916        msg += "Error: " + error;
72917        alert(msg);
72918    }
72919
72920    function do_diff_search(event, ui) {
72921        if(_initializing)
72922            return;
72923        var search_state;
72924        if(_self.on_search) {
72925            search_state = get_differential_search_state(); // when one or more ontology widgets with value unset, search_state is null
72926            if(search_state)
72927               _self.on_search(_self, search_state);
72928        }
72929    }
72930
72931    function do_diff_help(event, ui) {
72932        window.open(_self.help_url, '_help', 'status=0,toolbar=0,menubar=0,width=800,height=600,scrollbars=1,resizable=1');
72933    }
72934
72935    function do_domain_swap(event, ui) {
72936        var d1 = _self.search_domain_1.ontology_autocomplete('get_structure_string');
72937        var d2 = _self.search_domain_2.ontology_autocomplete('get_structure_string');
72938
72939        _self.search_domain_1.ontology_autocomplete('set_structure_string', d2);
72940        _self.search_domain_2.ontology_autocomplete('set_structure_string', d1);
72941
72942        $j('.diff_stage').dropdownchecklist('close');
72942    // close both select
72943        $j('.diff_stage').dropdownchecklist('refresh');
72944
72945        var selected_stages_1 = $j('#_search_stage_1 option:selected');
72946        var selected_stages_2 = $j('#_search_stage_2 option:selected');
72947
72948        var selected_stages1 = $j.map(selected_stages_1, function(op){ if(op.id != '0') return op.id;});  // get an array of strings
72949        var selected_stages2 = $j.map(selected_stages_2, function(op){ if(op.id != '0') return op.id;});  // get an array of strings
72950
72951        set_selections($j('#_search_stage_1 option'), selected_stages2);
72952        set_selections($j('#_search_stage_2 option'), selected_stages1);
72953
72954
72955        if(check_domains())
72956            do_diff_search(null, null);
72957    }
72958
72959    function tags_to_string(tags) {
72960        var s = "";
72961        for(var i = 0; i < tags.length; i++)
72962            s += tags[i].label + '; ';
72963
72964        return(s.slice(0,-2));
72965    }
72966
72967
72968    function fetch_donor_list(product_ids) {
72969        var dao_options = {
72970            success_callback: received_donor_list,
72971            error_callback: received_no_donor_list
72972        };
72973
72974        _self.the_dao.donor_info_by_product_ids( product_ids, dao_options);
72975    }
72976
72977    function received_donor_list(response_data, text_status, xhdr) {
72978
72979        var donor_array = new Array();
72980        var donor_index = 0;
72981
72982        for ( var i = 0; i < response_data.length; i++ ) {
72983            var donor = response_data[i];
72984                donor_array[donor_index++] = {"donor":{"id":donor.id,"name":donor.name}}
72985        }
72986        _donor_list = donor_array;
72987
72988        build_differential_search_controls();
72989    }
72990
72991    function received_no_donor_list()
72992    {
72993        alert("Failed to fetch a list of donors.");
72994    }
72995
72996    function init() {
72997        if(_self.settings.stage_list)  {
72998            _stage_list = _self.settings.stage_list;
72999            build_differential_search_controls();
73000        }
73001        else
73002            fetch_donor_list('');   // use the default product id provided in the theme
73003
73004
73005    }
73006
73007    init();
73008}
73009;
73010"use strict";
73011/**
73012 *
73013 * @param browse_container
73014 * @param search_container
73015 * @param page
73016 * @param options
73017 */
73018function ish_browse_controller(browse_container, search_container, page, options) {
73019
73020    this.container = $j(browse_container);
73021    this.search_container = $j(search_container);
73022    this.page = page;
73023    this.settings = {};
73024    $j.extend(true, this.settings, options);
73025
73026
73027    var _self = this;
73028    var _the_app = _self.page.application;
73029    var _the_page = _self.page;
73030    var _the_daos = _the_app.DAOs();
73031    var _page_config = _the_page.get_config();
73032    var _component_configs = _page_config.components;
73033    var _urls = _the_app.settings.urls;
73034
73035    var _search_widget = null;
73036    var _landing_widget = null;
73037
73038    function build_search_widget() {
73039        var search_options = {
73040            // plugin-specific options, hash key is plugin 'id' from application.json
73041            manual_annotation: {
73042                foobar: "floobydust" // !!!! just a placeholder to see if the options are being passed along
73043            },
73044            on_search:load_search_page,
73045            on_search_type_change:do_search_type_change,
73046            on_help:do_help,
73047            on_ontology_error:show_ontology_error
73048        };
73049
73050        _search_widget = new ish_search_widget(_self.search_container, _the_daos.data, _component_configs.search_widget.search_plugins, search_options);
73051    }
73052
73053    function do_search_type_change(sender, new_type) {
73054
73055        if(_landing_widget)
73056            _landing_widget.set_landing_mode(new_type);
73057    }
73058
73059    function do_help(url) {
73060        // if the url came from an application.json setting, then it is a key to the hash in _urls, and will not have
73061        // prefix 'http://'
73062        console.log("--> url before = " + url);
73063
73064        if (url.indexOf('http') != 0) {
73065            console.log("--> url before = " + url);
73066            url = _urls[url];
73067            console.log("--> url after = " + url);
73068        }
73069        window.open(url, '_help', 'status=0,toolbar=0,menubar=0,width=800,height=600,scrollbars=1');
73070    }
73071
73072    function encode_each_term(term, delimiter) {
73073        var terms = term.split(delimiter);
73074
73075        if(terms.length == 1)
73076            return(encodeURIComponent(trim(term)));
73077
73078        var term_string = '';
73079        for(var i = 0; i < terms.length; i++)
73080            term_string += trim(terms[i]) + '$';
73081
73082        var new_term = encodeURIComponent(term_string);
73083
73084        return(new_term);
73085    }
73086
73087    function encode_term(term) {
73088
73089        return(encode_each_term(encode_each_term(term, '\n'), '\t'));
73090    }
73091
73092
73093    function build_state_string(state) {
73094
73095        var state_string = "?";
73096        var count = 0;
73097        var val = "";
73098
73099        for(var key in state) {
73100            if(count++)
73101              state_string += "&";
73102
73103            if( (typeof state[key]) == "object" && ( state[key] instanceof Array ) && state[key] !== null ){
73104                state_string += encode_array(state[key], key);
73105            }
73106            else if((typeof state[key]) == "object" && state[key] !== null) {
73107                state_string += encode_object(state[key]);
73108            } else {
73109                val =  encode_term(new String(state[key]));
73110                state_string += key + "=" + val;
73111            }
73112
73113        }
73114
73115        return(state_string);
73116    }
73117
73118    /**
73119     *
73120     * @param {Object} obj hash of key-value pairs, transformed into key1=value1&key2=value2&key3=value3...
73121     * @returns {string}
73122     */
73123    function encode_object(obj) {
73124        var val = ''
73125        for(var key in obj) {
73126            if(val.length)
73127                val += '&';
73128            val += key + '=' + encodeURIComponent(new String(obj[key]));
73129        }
73130        return(val);
73131    }
73132
73133    function encode_array(arr,attr_key) {
73134        var target_str = '';
73135        for(var i=0; i < arr.length; i++) {
73136            if(i>0)
73137                target_str += ',';
73138            else
73139                target_str += attr_key + '=';
73140            target_str += arr[i];
73141        }
73142        return target_str;
73143    }
73144
73145
73146    function trim(s)
73147    {
73148        var l = 0;
73149        var r = s.length - 1;
73150
73151        while(l < s.length && s[l] == ' ') l++;
73152        while(r > l && s[r] == ' ') r -= 1;
73153
73154        return s.substring(l, r+1);
73155    }
73156
73157    /**
73158     * Callback function chained all the way from from a Search plugin.
73159     *
73160     * @param state - hash of key/value pairs that constitute the url args used for the search url.
73161     */
73162    function load_search_page(state) {
73163
73164        state.page_num = 0;
73165        state.page_size = 22;
73166        state.no_paging = false;
73167
73168        var url = _urls.search + build_state_string(state);
73169        window.location.href = url;
73170    }
73171
73172    function show_ontology_error(err) {
73173         trace("ontology error: " + err);
73174    }
73175
73176    function build_landing_widget() {
73177
73178        _landing_widget = new landing_widget(_self.container, _the_daos, _urls, _component_configs.landing_widget );
73179    }
73180
73181    function init() {
73182
73183        build_search_widget();
73184        build_landing_widget();
73185    }
73186
73187
73188    init();
73189}
73190;
73191"use strict";
73192/**
73193 *
73194 * @param container
73195 * @param store
73196 * @param options
73197 */
73198function ish_gene_search_selection_control_widget(container, store, options) {
73199
73200// public properties
73201   this.container = $j(container);
73202   this.store = store;
73203   this.options = options;
73204
73205
73206
73207// public methods
73208   this.set_select_text = function(new_text) {
73209       set_view_text(new_text);
73210   }
73211
73212
73213
73214// event hooks
73215   this.on_view_selections = options ? options.on_view_selections ? options.on_view_selections : null : null;
73216   this.on_clear_selections = options ? options.on_clear_selections ? options.on_clear_selections : null : null;
73217
73218
73219// private properties
73220    var _self = this;
73221    var _view_button = null;
73222    var _clear_button = null;
73223    var _menu_button = null;
73224    var _list_name = 'View Selections';
73225    var _count = 0;
73226
73227
73228// private methods
73229    function build_scaffold() {
73230         var html = "<div id='_select_widget_wrapper'>";
73231             html += "<div class='_select_widget_button _select_widget_view' title='View the selected experiments in a new window'>View Selections</div>";
73232             html += "<div class='_select_widget_menu_button _select_widget_button' title='Manage Selections'></div>";
73233             html += "<div class='_select_widget_button _select_widget_clear' title='Remove all the selections from the current list'>Clear Selections</div>";
73234             html += "</div>";
73235
73236        $j(html).appendTo(_self.container);
73237
73238        _view_button = $j('#_select_widget_wrapper ._select_widget_view');
73239        _menu_button = $j('#_select_widget_wrapper ._select_widget_menu_button');
73240        _clear_button = $j('#_select_widget_wrapper ._select_widget_clear');
73241
73242        _view_button.button();   //({icons:{secondary:'ui-icon-help'}});
73243        _menu_button.button({icons:{secondary:'ui-icon-triangle-1-n'}});   //({icons:{secondary:'ui-icon-help'}});
73244        _clear_button.button();
73245
73246        _view_button.bind('click', do_view_click);
73247        _clear_button.bind('click', do_clear_click);
73248        _menu_button.bind('click', do_menu_click);
73249    }
73250
73251    function setup_store() {
73252        _self.store.on_add = do_count_change;
73253        _self.store.on_remove = do_count_change;
73254        _self.store.on_clear = do_count_change;
73255        _count = _self.store.count();
73256        _list_name = _self.store.list_name;
73257
73258        update_view_label();
73259    }
73260
73261    function do_count_change() {
73262        _count = _self.store.count();
73263        update_view_label();
73264    }
73265
73266    function do_menu_click() {
73267        alert("Create a new list? Change the active list? Not yet.");
73268    }
73269
73270    function  update_view_label() {
73271        var label = "";
73272
73273        if(_list_name == 'default')
73274            label = "View Selections (" + _count + ")";
73275        else
73276            label = "View '" + _list_name + "' List (" + _count + ")";
73277
73278        set_view_text(label);
73279    }
73280
73281    function set_view_text(text) {
73282        _view_button.find('.ui-button-text').text(text);
73283    }
73284
73285    function do_view_click(event, ui) {
73286         if(_self.on_view_selections) {
73287            var list = _self.store.get_list();
73288            var a = [];
73289            for(var key in list) a.push(key);
73290
73291            if(a.length)
73292                _self.on_view_selections(a);
73293            else
73294                alert("There are no selections in your list.");
73295         }
73296    }
73297
73298    function do_clear_click(event, ui) {
73299        _self.store.clear();
73300         if(_self.on_clear_selections)
73301            _self.on_clear_selections();
73302    }
73303
73304    function init() {
73305        build_scaffold();
73306        setup_store();
73307    }
73308
73309
73310// initialization
73311    init();
73312
73313
73314}
73315;
73316"use strict";
73317/**
73318 *
73319 * @param container         The primary container within which all of the search UI will live
73320 *
73321 * @param page              The page object contains the configuration data for the page, which references the
73322 *                          application object, which manages things like the service URLs as well as application-
73323 *                          wide user data.  The page also manages page state, which is encoded on the page URL,
73324 *                          and a busy indicator which is initially associated with the container given in the
73325 *                          controller instantiation.
73326 *
73327 * @param options           optional settings
73328 */
73329function ish_search_controller(container, page, options) {
73330
73331    this.container = $j(container);
73332    this.page = page;
73333    this.settings = {
73334        format: 'html',
73335        show_preview: false,
73336        xml_link: true,
73337        xml_root: 'experiment_search'
73338    };
73339
73340    $j.extend(true, this.settings, options);
73341
73342    this.get_data = function () {
73343        if (_search_model)
73344            return(_search_model.get_data());
73345
73346        return([]);
73347    };
73348
73349    var _self = this;
73350    var _the_app = _self.page.application;
73351    var _the_page = _self.page;
73352    var _the_daos = _the_app.DAOs();
73353    var _page_config = _the_page.get_config();
73354    var _component_configs = _page_config.components;
73355    var _urls = _the_app.settings.urls;
73356    var _selection_store = _the_app.experiment_id_store();
73357    var _structure_ids = _the_app.app_data().coarse_structure_ids;
73358    var _graph_structures = _the_app.app_data().coarse_structure_set;
73359
73360    var _search_widget = null;
73361    var _preview_widget = null;
73362    var _selection_widget = null;
73363    var _mcsort_widget = null;
73364
73365    var _search_model = null;
73366    var _search_view = null;
73367    var _paging_state = {
73368        page_num: 0,
73369        page_size: 20,
73370        no_paging: false
73371    };
73372    var _resize_timer = 0;
73373
73374    var _initializing = false;
73375    var _resizing = false;
73376    var _previous_search = "";
73377    var _first_search = true; // one time display of #selection_control_container, #data_link
73378    var _searching = false; // prevents adjust_size() from executing during a search. That's all this does.
73379    var _queued_state = null;
73380    var _last_search_attempted = {search_type: ''};
73381
73382
73383    function init() {
73384
73385        if (_self.settings.format != 'html') {
73386
73387            // don't render html, just do the search
73388            var state = _the_page.get_state();
73389            do_search(state);
73390
73391        } else {
73392
73393            // render the HTML view
73394            _initializing = true;
73395
73396            // create containers
73397            build_scaffold();
73398
73399            // resize containers to fit between an upper and lower element
73400            resize_containers('#search_container', '#pFooter');
73401
73402            // listen for back/forward button events
73403            _the_page.on_page_change = do_page_change;
73404
73405            // build components
73406            build_components();
73407
73408            // setup event handlers
73409            $j('#search_container').bind('resize', do_resize);
73410
73411            // set search widget state and wait for ready event
73412            var state = _the_page.get_state();
73413
73414            _initializing = false;
73415
73416            load_state(state);
73417        }
73418    }
73419
73420    function do_page_change(state) {
73421        load_state(state);
73422    }
73423
73424    function load_state(state) {
73425        if (!$j.isEmptyObject(state)) {
73426            _queued_state = state;
73427
73428            var widget = get_search_widget(_queued_state.search_type);
73429            widget.set_state(_queued_state);
73430        }
73431    }
73432
73433    function get_search_widget(search_type) {
73434        // no longer do anything fancy
73435        return(_search_widget);
73436    }
73437
73438    function build_components() {
73439
73440        // search widget
73441        var search_options = {
73442            on_search: do_search,
73443            on_ontology_error: show_ontology_error,
73444            on_search_ready: do_search_ready,
73445            on_search_error: show_search_error,
73446            on_help: do_help
73447        };
73448        var search_plugins = _component_configs.search_widget.search_plugins;
73449        _search_widget = new ish_search_widget($j('#search_container'), _the_daos.data, search_plugins, search_options);
73450
73451        // selection widget (view/clear selections)
73452        var selection_options = {
73453            on_clear_selections: do_clear_selections,
73454            on_view_selections: do_view_selections
73455        };
73456        _selection_widget = new ish_gene_search_selection_control_widget($j('#selection_control_container'), _selection_store, selection_options);
73457
73458        // the selection controls are hidden until there are search results.
73459        $j('#selection_control_container').hide();
73460    }
73461
73462    function build_scaffold() {
73463
73464        var html = "";
73465        html += "<div id='search_container'></div>";
73466        html += "<div id='search_result_common_container'>";    // add a container shared by ISH and meta search results
73467        html += "   <table id='search_block'>";
73468        html += "      <tr>";
73469        html += "          <td id='result_cell'>";
73470        html += "              <div id='result_block'>";
73471        html += "              <div id='pager_container'></div>";
73472        html += "              <div id='search_results_container'></div>";
73473        html += "              <div id='selection_control_container'></div>";
73474        html += "              </div>";
73475        html += "          </td>";
73476        html += "      </tr>";
73477
73478        if (_self.settings.xml_link) {
73479            html += "  <tr>";
73480            html += "     <td>";
73481            html += "        <div id='data_link' class='info_blockx data_link_block'>";
73482            html += "           This data is also available as <a id='xml_link' href='javascript:export_xml(\"";
73483            html +=                                            _self.settings.xml_root + "\", true, \"experiments\");' >XML</a>";
73484            html += "        </div>";
73485            html += "        <form id='xml_form' action='/export/ish/data.xml' method='POST' target='_blank'>";
73486            html += "           <input type='hidden' name='type' value='application/xml'/>";
73487            html += "           <input id='xml_form_data' type='hidden' name='data' value=''/>";
73488            html += "        </form>";
73489            html += "     </td>"
73490            html += "  </tr>";
73491        }
73492
73493        html += "   </table>";
73494        html += "</div>";
73495
73496        $j(html).appendTo(_self.container);
73497    }
73498
73499    /**
73500     * Callback function chained all the way from from a Search plugin.
73501     *
73502     * @param state - hash of key/value pairs that constitute the url args used for the search url.
73503     */
73504    function do_search_ready() {
73505
73506        if (_initializing)
73507            return;
73508
73509        if (_queued_state) {
73510            var widget = get_search_widget(_queued_state.search_type);
73511            var state = widget.get_state();
73512
73513            do_search(state);
73514            _queued_state = null;
73515        }
73516    }
73517
73518    /**
73519     *
73520     * @param search_state - hash of key/value pairs that constitute the url args used for the search url, and paging info
73521     */
73522    function do_search(search_state) {
73523
73524        try {
73525
73526            if (!search_state)
73527                return;
73528
73529            _searching = true;
73530            $j('#busy_container').css('display', 'none');
73531
73532            // merge in default paging info, if it's not present
73533            var state = {};
73534
73535            _paging_state.page_num = 0;
73536            if (_search_model)
73537                _search_model.reset_page_num();
73538
73539            $j.extend(true, state, search_state, _paging_state);
73540
73541            if (state.search_type != _previous_search)
73542                _search_model = create_search_model(state);
73543
73544            _last_search_attempted = state;
73545            _search_model.search(state.search_term, state);
73546
73547        } catch (e) {
73548            search_failure(_search_model, e);
73549        }
73550    }
73551
73552    function create_search_model(search_state) {
73553
73554        var widget = get_search_widget(search_state.search_type);
73555        var model_options = {
73556            on_search_failure: search_failure,
73557            on_search_begin: do_search_begin,
73558            on_search_complete: do_search_complete,  // ideally these three methods would be handled by the view
73559            on_sort_complete: do_sort_complete,      //
73560            on_page_complete: do_page_complete,      //
73561            page_size: search_state.page_size,
73562            page_num: search_state.page_num,
73563            no_page: search_state.no_paging,
73564            product_names: widget.get_definition().products
73565        };
73566        var model = null;
73567        var daos = {
73568            rma_dao: _the_daos.data,
73569            analysis_dao: _the_daos.analysis
73570        };
73571
73572        var base_config = _component_configs.result_table.column_config.base_config;
73573        var custom_config = [];
73574        var combined_config;
73575        var config_name = "column_config";
73576
73577        switch (search_state.search_type) {
73578            case 'gene':
73579                custom_config = _component_configs.result_table.column_config.gene_column_config;
73580                combined_config = combine_column_configs(base_config, custom_config, config_name);
73581                $j.extend(true, model_options, combined_config);
73582                model = new ish_gene_search_model(daos, model_options);
73583                model.IshCommon = new IshCommon;
73584                break;
73585            case 'advanced_gene':
73586                custom_config = _component_configs.result_table.column_config.gene_column_config;
73587                combined_config = combine_column_configs(base_config, custom_config, config_name);
73588                $j.extend(true, model_options, combined_config);
73589                model = new faceted_search_model(daos, model_options);
73590                model.IshCommon = new IshCommon;
73591                break;
73592            case 'gene_classification':
73593                custom_config = _component_configs.result_table.column_config.gene_classification_column_config;
73594                combined_config = combine_column_configs(base_config, custom_config, config_name);
73595                $j.extend(true, model_options, combined_config);
73596                model = new gene_classification_search_model(daos, model_options);
73597                model.IshCommon = new IshCommon;
73598                break;
73599            case 'manual_annotation':
73600                custom_config = _component_configs.result_table.column_config.manual_annotation_column_config;
73601                combined_config = combine_column_configs(base_config, custom_config, config_name);
73602                $j.extend(true, model_options, combined_config);
73603                model = new manual_annotation_search_model(daos, model_options);
73604                model.IshCommon = new IshCommon;
73605                break;
73606            default:
73607                throw 'Unknown search type requested.';
73608        }
73609
73610        model.onPagingInfoChanged.subscribe(on_page_info_change);
73611
73612        return(model);
73613    }
73614
73615    function create_search_view(search_state, model) {
73616
73617        var view_options = {
73618            urls: _urls,
73619            structure_ids: _structure_ids,
73620            graph_structures: (_graph_structures ? _graph_structures.length ? _graph_structures[0].structure_sets_structures : [] : []),
73621            mcsort_parent_id: "result_block",
73622            mcsort_config: {},
73623            on_row_over: do_row_over,
73624            on_row_out: do_row_out,
73625            on_row_click: do_row_click
73626        };
73627
73628        var custom_config = [];
73629        var mcsort_config = {};
73630
73631        switch (search_state) {
73632            case 'gene':
73633                custom_config = _component_configs.result_table.column_config.gene_column_config;
73634                mcsort_config = _component_configs.result_table.multicolumn_sort_config.gene_mcsort_config;
73635                break;
73636            case 'advanced_gene':
73637                custom_config = _component_configs.result_table.column_config.advanced_gene_column_config;
73638                mcsort_config = _component_configs.result_table.multicolumn_sort_config.gene_mcsort_config;
73639                break;
73640            case 'gene_classification':
73641                custom_config = _component_configs.result_table.column_config.gene_classification_column_config;
73642                mcsort_config = _component_configs.result_table.multicolumn_sort_config.gene_mcsort_config;
73643                break;
73644            case 'manual_annotation':
73645                custom_config = _component_configs.result_table.column_config.manual_annotation_column_config;
73646                mcsort_config = _component_configs.result_table.multicolumn_sort_config.manual_annotation_mcsort_config;
73647                break;
73648            default:
73649                throw 'Unknown search view requested.';
73650        }
73651
73652        // add base_config + custom columns array elements to the view_options' column_config array
73653        // add the required columns to the view_options hash
73654        var base_config = _component_configs.result_table.column_config.base_config;
73655        var config_name = "column_config";
73656        var combined_config = combine_column_configs(base_config, custom_config, config_name);
73657        $j.extend(true, view_options, combined_config);
73658        $j.extend(true, view_options, {"mcsort_config": mcsort_config});
73659
73660        // TODO: remove magic number (row height) from ish_search_view call
73661        var view = new ish_search_view($j('#search_results_container'), model, _selection_store, 21, view_options);
73662
73663        return (view);
73664    }
73665
73666    /**
73667     * Combine (join) contents of two column configs
73668     * Column config consists of array of hashes: [{column_name:column_value}]
73669     *
73670     * @param config1
73671     * @param config2
73672     * @param {String} name of new hash
73673     * @returns {Object} hash containing array of combined column config
73674     */
73675    function combine_column_configs(config1, config2, name) {
73676        var new_config = {};
73677        var copy_1 = [];
73678        var copy_2 = [];
73679
73680        $j.extend(true, copy_1, config1);
73681        $j.extend(true, copy_2, config2);
73682
73683        new_config[name] = [];
73684
73685        var i, len;
73686        for (i = 0, len = copy_1.length; i < len; ++i) {
73687            new_config[name].push(copy_1[i]);
73688        }
73689        for (i = 0, len = copy_2.length; i < len; ++i) {
73690            new_config[name].push(copy_2[i]);
73691        }
73692
73693        return new_config;
73694    }
73695
73696
73697    function do_search_complete(model) {
73698
73699        if (_self.settings.format != 'html') {
73700
73701            if (_self.settings.on_ready)
73702                _self.settings.on_ready(model);
73703
73704        } else {
73705
73706            var state = {};
73707            $j.extend(true, state, _last_search_attempted);
73708
73709            if (state.search_type != _previous_search) {
73710                if (_search_view) {
73711                    _search_view.destroy();
73712                    _search_view = null;
73713                }
73714            }
73715
73716            if (_search_view)
73717                _search_view.refresh();
73718            else {
73719                _search_view = create_search_view(state.search_type, model);
73720                _previous_search = state.search_type;
73721            }
73722
73723            //update_download_link();
73724            transition_results_in();
73725            _searching = false;
73726        }
73727    }
73728
73729
73730    function do_search_begin(model) {
73731        _searching = true;
73732        $j('#busy_container').css('display', 'block');
73733        $j('#search_results_container').css({'visibility': 'hidden'});
73734    }
73735
73736    function do_page_complete(model) {
73737        transition_results_in();
73738        _search_view.refresh();
73739        _searching = false;
73740    }
73741
73742    function do_sort_complete(model) {
73743        transition_results_in();
73744
73745        _search_view.refresh();
73746        _searching = false;
73747    }
73748
73749    function do_row_over(item) {
73750
73751        return(true);
73752    }
73753
73754    function do_row_out(item) {
73755
73756        return(true);
73757    }
73758
73759    function do_row_click(item) {
73760        // do nothing for now
73761        return(true);
73762    }
73763
73764    function on_page_info_change(paging_info) {
73765        if (_self.settings.format != 'html')
73766            return;
73767
73768        _paging_state.page_size = paging_info.pageSize;
73769        _paging_state.page_num = paging_info.pageNum;
73770        _paging_state.no_paging = paging_info.showAll;
73771
73772        var widget = get_search_widget(_last_search_attempted.search_type);
73773        var search_state = widget.get_state(true);
73774        var state = {};
73775        $j.extend(true, state, _paging_state, search_state);
73776
73777        _the_page.set_state(state);
73778    }
73779
73780    /** END the searching handlers **/
73781
73782    function transition_results_in() {
73783        $j('#busy_container').css('display', 'none');
73784        $j('#search_results_container').css({'visibility': 'visible'});
73785
73786//TODO: handle this some other way?
73787        if (!$j.browser.msie) {
73788            $j('#search_results_container').hide();
73789            $j('#search_results_container').fadeIn(400);
73790        }
73791
73792        if (_first_search) {
73793            $j('#selection_control_container').fadeIn(400);
73794            $j('#data_link').fadeIn(400);
73795            _first_search = false;
73796        }
73797    }
73798
73799    function do_clear_selections() {
73800        if (_search_view.clear_selections)
73801            _search_view.clear_selections();
73802    }
73803
73804    function do_view_selections(list) {
73805        var ids = list.join(',');
73806        var url = _urls.ivt + "?id=" + ids + "&popup=true";
73807        window.open(url, '_ivt', 'status=0,toolbar=0,menubar=0,width=1000,height=760,scrollbars=1,resizable=1');
73808    }
73809
73810
73811    function do_resize(event, ui) {
73812        if (_resizing)
73813            return;
73814// Comment out this code to prevent re-rendering of search results table each time you click on a different search type radio button
73815//        if (_resize_timer == 0)
73816//            _resize_timer = setTimeout(function () {
73817//                adjust_size();
73818//            }, 250);
73819    }
73820
73821    function do_help(url) {
73822        // if the url came from an application.json setting, then it is a key to the hash in _urls, and will not have
73823        // prefix 'http://'
73824        console.log("--> url before = " + url);
73825
73826        if (url.indexOf('http') != 0) {
73827            console.log("--> url before = " + url);
73828            url = _urls[url];
73829            console.log("--> url after = " + url);
73830        }
73831        window.open(url, '_help', 'status=0,toolbar=0,menubar=0,width=800,height=600,scrollbars=1');
73832    }
73833
73834    function adjust_size() {
73835        if (_resize_timer != 0)
73836            clearTimeout(_resize_timer);
73837        _resize_timer = 0;
73838
73839        if (_searching)
73840            return;
73841
73842        var prev_page_size = _paging_state.page_size;
73843        resize_containers('#search_container', '#pFooter');
73844
73845        if (_search_model/* && (prev_page_size != _paging_state.page_size)*/)
73846            _search_model.setPagingOptions({pageSize: _paging_state.page_size});
73847
73848        if (_search_view)
73849            _search_view.refresh();
73850    }
73851
73852    function resize_containers(element_above, element_below) {
73853        _resizing = true;
73854
73855        // TODO: replace height_hack with dynamic height calculation;
73855 hack needed when multicolumn sort UI added
73856        var height_hack = 24;
73857
73858        // measure the height of the window, the position of the elements
73859        // above and below that we have to fit between, and apply minimums
73860        //var $window = $j('#search_block');
73861        var win = {
73862            height: $j(window).height(),
73863            width: $j(window).width()
73864        }
73865
73866        var page_config = _component_configs.result_table.layout;
73867
73868        var $above = $j(element_above);
73869        var top = $above.offset().top + $above.outerHeight();
73870        var height = win.height - top - $j(element_below).outerHeight();
73871        var view_height = Math.max(height, page_config.min_view_height);
73872        var total_width = win.width;
73873
73874        var result_width = Math.max(page_config.min_result_width, ((total_width - page_config.min_preview_width) * 0.9));
73875        var preview_width = page_config.min_preview_width ? Math.max(page_config.min_preview_width, ((total_width - result_width) - 60)) : 0;
73876        page_config.number_of_rows = Math.floor((view_height - 80 /*magic number should be replaced with component measurements*/) / page_config.row_height);
73877        _paging_state.page_size = page_config.number_of_rows;
73878
73879        // set heights
73880        $j('#result_cell').css({width: result_width + 'px', 'text-align': 'center'});
73881        $j('#result_block').css({height: (view_height + height_hack) + 'px', width: (result_width - 20) + 'px', 'text-align': 'left'});
73882        $j('#selection_control_container').css({'text-align': 'left', width: parseInt(result_width - 20) + 'px'});
73883        $j('#search_results_container').css({height: (view_height - 58), width: parseInt(result_width - 20) + 'px'});
73884
73885        // set width
73886        if (page_config.min_preview_width)
73887            $j('#preview_cell').css({minWidth: preview_width});
73888
73889        _resizing = false;
73890    }
73891
73892
73893    function search_failure(model, error) {
73894        // we're destroying the _view object by showing the error, so
73895        // rebuild it.
73896        transition_results_in();
73897        show_search_error(error);
73898    }
73899
73900    function show_ontology_error(msg) {
73901        var cont = $j('#search_results_container');
73902        var err_msg = 'Due to an error communicating with the data service, differential search is not available at this time.  Please try again in a few minutes';
73903
73904        if (msg && msg.length)
73905            err_msg += ' (' + msg + ')';
73906        else
73907            err_msg += '.';
73908
73909        container_error(cont, err_msg);
73910    }
73911
73912    function show_search_error(msg) {
73913        var cont = $j('#search_results_container');
73914        var err_msg = 'Your search could not be completed';
73915
73916        if (msg) {
73917            if (msg.msg)
73918                err_msg += ' (' + msg.msg + ')';
73919            else
73920                err_msg += ' (' + msg + ')';
73921        }
73922        else
73923            err_msg += '.';
73924
73925        err_msg += '<br/><br/>Please try again.';
73926
73927        container_error(cont, err_msg);
73928    }
73929
73930    function container_error(container, msg) {
73931        // it would be nice to have one central
73932        // place for handling UI error reporting...
73933        //     if(_error_manager) {
73934        //         _error_manager.ui_report(_self, container, msg);
73935        //         return;
73936        //     }
73937
73938        var err_html = "<div class='err_notice ui-state-error'>" + msg + "</div>";
73939        container.html(err_html);
73940    }
73941
73942    function on_widgets_ready() {
73943
73944        // if there's search state available, run the search
73945    }
73946
73947    init();
73948}
73949;
73950"use strict";
73951/*
73952 * This is the panel that appears to the right of the search results table, and contains additional metadata that is
73953 * updated for each row that the user clicks on in the search results table.
73954 *
73955 * Notes: CSS ids with 'pw' in name stand for 'preview widget'
73956 */
73957AIBS.UI.ish_search_preview_info = (function ($j) {
73958
73959    return (
73960
73961        function (container, options) {
73962
73963            // public properties
73964            this.container = $j(container);
73965            this.settings = {
73966                jquery_style: false,
73967                on_ready: null,
73968                on_neuroblast_search: null,
73969                brain_explorer_url: '',
73970                related_data_url: '',
73971                threed_thumbnail_url: '',
73972                data: null,
73973                graph_structures: [],
73974                default_structure_id: 8
73975            };
73976
73977            $j.extend(true, this.settings, options);
73978
73979
73980            // public methods
73981            /**
73982             * Set the Gene data to be displayed
73983             * @param data  AIBS.DATA.ISH_DAO.experiment_detail().probes[0].gene
73984             * @param persist
73985             */
73986            this.set_data = function (data, persist) {
73987
73988                _info_widget.set_data(data);
73989                if (persist) {
73990                    _persistent_data = data;
73991                    set_selection();
73992                }
73993            }
73994
73995            this.clear = function (persistent) {
73996
73997                do_clear();
73998                _persistent_data = null;
73999                clear_selection();
74000
74001                if (_persistent_data)
74002                    _info_widget.set_data(_persistent_data);
74003            }
74004
74005            this.get_state = function () {
74006                return(do_get_state());
74007            }
74008
74009            this.set_state = function (state) {
74010                do_set_state(state);
74011            }
74012
74013            /**
74014             * A unique ID for this widget. This is not an HTML ID, it is an integer value scoped to
74015             * AIBS.UI.
74016             */
74017            this.id = function () {
74018                if (_info_widget)
74019                    return(_info_widget.id);
74020
74021                return(null);
74022            }
74023
74024
74025            // private properties
74026            /**
74027             * @private
74028             * @scope AIBS.UI.ish_gene_widget
74029             */
74030            var _self = this;
74031            var _info_widget = null;
74032            var _ontology_model = false;
74033            var _seed = 0;
74034            var _structure_selector = null;
74035            var _received_state = null;
74036            var _ontology_loaded = false;
74037            var _persistent_data = null;
74038            var _graph_container_id = 'null';
74039            var _3D_thumb = null;
74040            var _3D_file_link = null;
74041
74042
74043            /**
74044             * The layout, data_map & decorators are defined for this info widget.
74045             */
74046            function build_table() {
74047
74048                // describe the table
74049                var layout = {
74050                    columns: 2,
74051                    rows: 9,
74052                    spans: [
74053                        // header row
74054                        {row: 0, column: 0, column_span: 2, row_span: 1},
74055
74056                        // neuroblast controls
74057                        {row: 2, column: 0, column_span: 2, row_span: 1},
74058
74059                        // spacer
74060                        {row: 3, column: 0, column_span: 2, row_span: 1},
74061
74062                        // row 4 has the brain explorer controls (now deleted from code, might need to reformat?)
74063
74064                        // 3d thumbnail
74065                        {row: 5, column: 0, column_span: 2, row_span: 1},
74066
74067                        // spacer
74068                        {row: 6, column: 0, column_span: 2, row_span: 1},
74069
74070                        // related data controls
74071                        {row: 7, column: 0, column_span: 2, row_span: 1},
74072                        {row: 8, column: 0, column_span: 2, row_span: 1}
74073
74074                    ]
74075                };
74076                // describe how data should be mapped to cells in the table
74077                var data_map = [
74078                    // a couple fields that just need the experiment ID
74079                    {field: 'id', cell: [0, 0]},   // used to set the seed ID for neuroblast
74080                    {field: 'probes[0].gene.acronym', cell: [1, 1]},
74081                    {field: 'id', cell: [0, 5]},
74082                    {field: 'id', cell: [0, 8]} // the experiment ID is used to create the related data link
74083                ];
74084
74085                // which cells require a callback to massage the data presentation before
74086                // placing it in the table.
74087                var decorators = {
74088                    '0,0': set_seed,
74089                    '1,1': show_gene,
74090                    '0,5': make_be_thumbnail,
74091                    '0,8': make_related_link
74092                };
74093
74094                var options = {
74095                    table_class: '_preview_widget',
74096                    jquery_theme: false
74097                };
74098
74099                // build the widget
74100                _info_widget = new AIBS.UI.info_widget(_self.container, layout, data_map, decorators, options);
74101
74102                // set the static field labels
74103                set_labels(_info_widget);
74104            }
74105
74106            function set_selection() {
74107                if (_persistent_data && _persistent_data.plane_of_section.name != 'coronal') {
74108                    $j('#_pw_image_set_check').attr('checked', false);
74109                    $j('#_pw_image_set_check').attr('disabled', true);
74110                }
74111                else
74112                    $j('#_pw_image_set_check').removeAttr('disabled');
74113
74114
74115                $j('#_pw_nb_search').button({disabled: false});
74116                //$j('#_pw_neuroblast').attr('disabled',false);
74117                $j('#_pw_neuroblast').dropdownchecklist('enable');
74118            }
74119
74120            function clear_selection() {
74121
74122                $j('#_pw_image_set_check').attr('checked', false);
74123                $j('#_pw_image_set_check').attr('disabled', true);
74124
74125                $j('#_pw_nb_search').button({disabled: true});
74126
74127                $j('#_pw_neuroblast').dropdownchecklist('disable');
74128                $j('#_pw_neuroblast').val([_self.settings.default_structure_id]);
74129                $j('#_pw_neuroblast').dropdownchecklist('refresh');
74130            }
74131
74132            function do_get_state() {
74133
74134                if (!_seed) {
74135                    alert("A correlation search must be done in relation to a specific experiment.  Please select an experiment from the main table by clicking on it, then try again.");
74136                    return(null);
74137                }
74138
74139                var selections = $j('#_pw_neuroblast').val();
74140                if (!selections || !selections.length) {
74141                    alert("Please select at least one structure.");
74142                    return(null);
74143                }
74144
74145                var state = {
74146                    domain: selections,
74147                    rows: [_seed],
74148                    image_set: ($j('#_pw_image_set_check')[0].checked == true) ? 'P56coronal' : 'P56',
74149                    search_type: 'neuroblast'
74150                }
74151                return(state);
74152            }
74153
74154            function do_set_state(state) {
74155                if (!state)
74156                    return;
74157
74158                _received_state = state;
74159                _seed = parseInt(_received_state.rows);
74160
74161                var domain = _received_state.domain ? _received_state.domain.split(',') : [];
74162                var select = $j('#_pw_neuroblast');
74163                select.val(domain);
74164                select.dropdownchecklist('refresh');
74165
74166                _received_state = null;
74167
74168                if (_self.settings.on_ready)
74169                    _self.settings.on_ready();
74170            }
74171
74172
74173            function load_neuroblast_state() {
74174                //  _structure_selector.ontology_autocomplete('set_structure_ids', _received_state.domain.split(','));
74175                _seed = parseInt(_received_state.rows);
74176                _received_state = null;
74177
74178                if (_self.settings.on_ready)
74179                    _self.settings.on_ready();
74180            }
74181
74182
74183            function do_clear() {
74184                // what's this?
74185                _info_widget.set_content(0, 8, "");
74186                _info_widget.set_content(1, 1, "");
74187                if (_3D_thumb)
74188                    _3D_thumb.clear();
74189
74190                if (_3D_file_link)
74191                    _3D_file_link.set_url(null);
74192            }
74193
74194            /**
74195             * The static field labels are set
74196             * @param widget The underlying info_widget
74197             */
74198            function set_labels(widget) {
74199
74200
74201                //   var open_close_button = "<span id='_pw_open_close' class='ui-state-default ui-corner-all ui-icon-container'>";
74202                //   open_close_button += "<span class='ui-icon ui-icon-pin-s' title='Click to hide this preview pane'></span></span>";
74203                //   _info_widget.set_content(0,0, open_close_button);
74204
74205                var neuroblast_input = "<select id='_pw_neuroblast' multiple='multiple'/>";
74206                var selected = '';
74207                for (var i = 0; i < _self.settings.neuroblast_structures[0].structure_sets_structures.length; i++) {
74208                    var opt = _self.settings.neuroblast_structures[0].structure_sets_structures[i];
74209                    if (parseInt(opt.structure_id) == _self.settings.default_structure_id)
74210                        selected = 'selected';
74211                    else
74212                        selected = '';
74213                    neuroblast_input += "<option value='" + opt.structure_id + "' " + selected + ">" + opt.name + " (" + opt.acronym + ")</option>";
74214                }
74215                neuroblast_input += "</select>";
74216                neuroblast_input += "<div id='_pw_image_set_wrapper'><input type='checkbox' id='_pw_image_set_check'/>Coronal data only</div><div id='_pw_nb_search'>Search</div>";
74217                _info_widget.set_content(0, 1, "Correlation");
74218                _info_widget.set_content(0, 2, neuroblast_input);
74219
74220                _info_widget.set_content(0, 4, "BrainExplorer");
74221
74222                // 5 spacer
74223                _info_widget.set_content(0, 7, "Related Institute Data");
74224
74225                $j('#_pw_open_close').hover(function () {
74226                    $j(this).toggleClass("ui-state-hover");
74227                }).bind('click', do_open_close);
74228
74229                var header_class = _self.settings.jquery_style ? 'ui-widget-header' : '_pw_header';
74230                $j('._preview_widget td[colspan="2"]').addClass(header_class);
74231                $j('._preview_widget td[colspan!="2"]').addClass('_pw_label');
74232
74233                $j('#_pw_neuroblast').dropdownchecklist({
74234                    emptyText: "Select Structures...",
74235                    explicitClose: "Close...",
74236                    icon: {
74237                        placement: 'right',
74238                        toOpen: 'ui-icon-triangle-1-s',
74239                        toClose: 'ui-icon-triangle-1-w'
74240                    },
74241                    zIndex: 200
74242                });
74243                $j('#_pw_neuroblast').dropdownchecklist('disable');
74244
74245
74246                $j('#_pw_nb_search').button().bind('click', do_neuroblast_search);
74247                clear_selection();
74248
74249                if (!_ontology_model) {
74250                    _ontology_model = new ontology_model(null, 9, {on_ready: do_ontology_load, on_error: _self.on_ontology_error});
74251                }
74252                else {
74253                    do_ontology_load(_ontology_model);
74254                }
74255
74256            }
74257
74258            function do_neuroblast_search(event, ui) {
74259
74260                var state = do_get_state();
74261                if (!state)
74262                    return;
74263
74264                if (_self.settings.on_neuroblast_search)
74265                    _self.settings.on_neuroblast_search(_self, state);
74266            }
74267
74268            function do_ontology_load(model) {
74269                /*
74270                 // the model has finished loading and the UI has been drawn.
74271                 _structure_selector = $j('#_pw_neuroblast');
74272                 _structure_selector.ontology_autocomplete(model, {horizontal_align:'right'});
74273
74274                 // now that the UI is fully available, load existing state
74275                 // if there's some just waiting around for us--
74276                 */
74277                _ontology_loaded = true;
74278                if (_received_state)
74279                    load_neuroblast_state();
74280            }
74281
74282
74283            function do_ontology_error(model, error) {
74284                var msg = "Due to an error in communicating with the data service, Correlation search is not available at this time.  ";
74285                msg += "Please try again in a few minutes.";
74286                msg += "Error: " + error;
74287                alert(msg);
74288            }
74289
74290            function do_open_close(event, ui) {
74291
74292                var $cont = $j(this);
74293                var $this = $cont.find('.ui-icon');
74294                if ($this.hasClass('ui-icon-pin-s')) {
74295                    $this.removeClass('ui-icon-pin-s');
74296                    $this.addClass('ui-icon-pin-w');
74297                    $this.attr('title', 'Click to show the preview pane');
74298                } else {
74299                    $this.removeClass('ui-icon-pin-w');
74300                    $this.addClass('ui-icon-pin-s');
74301                    $this.attr('title', 'Click to hide this preview pane');
74302                }
74303            }
74304
74305            function make_related_link(container, data) {
74306                if (!data)
74307                    return("")
74308
74309                var jpg_url = _self.settings.related_data_url + '?section_data_set=' + data;
74310                var html_link = jpg_url.replace('.jpg', '.html');
74311                var link = "<a class='_pw_related_link' href='" + html_link + "' target='_related_data'><img id='_pw_related_data_img' src='" + jpg_url + "'/></a>";
74312                return(link);
74313            }
74314
74315            function make_be_thumbnail(container, data) {
74316                if (!data)
74317                    return("")
74318
74319                if (!_3D_thumb) {
74320                    //var link = "<a class='_pw_3dlink' href='" + _self.settings.brain_explorer_url + data + "'></a>";
74321                    var link = "<div id='_pw_3d_thumbnail_placeholder'></div>";
74322                    $j(link).appendTo(container);
74323                    _3D_thumb = new AIBS.UI.threed_thumbnail_widget('#_pw_3d_thumbnail_placeholder', data, _self.settings.threed_thumbnail_url, {});
74324                }
74325                else {
74326                    _3D_thumb.set_experiment(data);
74327                }
74328                return(null);
74329            }
74330
74331            function set_seed(container, data) {
74332                if (data)
74333                    _seed = data;
74334
74335                return(null);
74336            }
74337
74338
74339            function show_gene(container, data) {
74340                if (!data)
74341                    return("");
74342
74343                return(data);
74344            }
74345
74346
74347            function show_expressions(response_data, text_status, xhdr) {
74348                var data = response_data ? response_data[0] : null;
74349                if (!data)
74350                    return;
74351                new expression_graph_widget($j('#' + _graph_container_id), data.structure_unionizes, _self.settings.graph_structures, {graph_mode: 'small', tooltip: true});
74352            }
74353
74354
74355            function graph_failure(model, error) {
74356                // swallowing this error
74357            }
74358
74359            /**
74360             * A decorator function that creates a comma-delimited list out of an array of tag names.
74361             * @param container
74362             * @param data
74363             */
74364            function list_tags(container, data) {
74365                if (!data)
74366                    return("");
74367
74368                var s = "";
74369                for (var i = 0; i < data.length; i++) {
74370                    if (i)
74371                        s += ", ";
74372                    s += data[i].name
74373                }
74374                return(s);
74375            }
74376
74377            /**
74378             * A decorator function that creates a comma-delimited list out of an array of alias acronyms.
74379             * @param container
74380             * @param data
74381             */
74382            function list_aliases(container, data) {
74383                if (!data)
74384                    return("");
74385
74386                var s = "";
74387                for (var i = 0; i < data.length; i++) {
74388                    if (i)
74389                        s += ", ";
74390                    s += data[i].acronym
74391                }
74392                var w = $j(container).width();
74393                var d = "<div class='_pw_list' style='width:" + w + "px;' title='" + s + "'>" + s + "</div>";
74394                return(d);
74395            }
74396
74397
74398            function inspect(prefix, obj) {
74399                var s = "";
74400                for (var key in obj) {
74401                    if ((typeof obj[key]) == "function")
74402                        s = s;
74403                    else if ((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
74404                        s += "\n" + prefix + "key: " + key + inspect(prefix + "    ", obj[key]);
74405                    else
74406                        s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
74407                }
74408                return(s);
74409            }
74410
74411            /**
74412             * builds the table.
74413             */
74414            function init() {
74415                build_table();
74416            }
74417
74418            init();
74419        }
74420
74421
74422        );
74423
74424})(jQuery);
74425"use strict";
74426/**
74427 *
74428 * @param container
74429 * @param model
74430 * @param experiment_selections
74431 * @param table_row_height
74432 * @param options
74433 */
74434function ish_search_view(container, model, experiment_selections, table_row_height, options) {
74435
74436    this.container = $j(container);
74437    this.model = model;
74438
74439    this.on_row_over = options ? options.on_row_over ? options.on_row_over : null : null;
74440    this.on_row_out = options ? options.on_row_out ? options.on_row_out : null : null;
74441    this.on_row_click = options ? options.on_row_click ? options.on_row_click : null : null;
74442
74443    this.settings = {
74444        urls: null,
74445        structure_ids: [],
74446        pager_container: null,
74447        row_over_class: '_row_over',
74448        selected_row_class: '_row_select',
74449        column_config: [],
74450        mcsort_config: {},
74451        table_options: {
74452            rowCssClasses: row_formatter,
74453            rowHeight: table_row_height,
74454            syncColumnCellResize: true,
74455            forceFitColumns: true,
74456            enableAsyncPostRender: true,
74457            asyncPostRenderDelay: 5,
74458            enableAddRow: false,
74459            leaveSpaceForNewRows: false,
74460            autoHeight: false,
74461            showSecondaryHeaderRow: false
74462        }
74463    };
74464
74465
74466    if (options)
74467        $j.extend(true, this.settings, options);
74468
74469
74470    this.refresh = function () {
74471        _result_table.invalidate();
74472    }
74473
74474    this.clear_selections = function () {
74475        do_clear_selections();
74476    }
74477
74478    this.destroy = function () {
74479
74480        unbind_events();
74481
74482        for (var key in _plots) {
74483            _plots[key].shutdown();
74484            delete _plots[key];
74485        }
74486
74487        if (_result_table)
74488            _result_table.destroy();
74489    }
74490
74491    var _self = this;
74492    var _mcsort = null; // multicolumn sort widget instance
74493    var _result_table = null; // Slick.Grid instance
74494    var _the_pager = null; // Slick.Controls.Pager instance
74495    var _table_ready = false;
74496    var _expression_node_index = {};
74497    var _experiment_id_lists = experiment_selections;
74498    var _previous_selection = null;
74499    var _plots = {};
74500
74501
74502    function init() {
74503        eval_columns();
74504        build_mcsort();
74505        build_table();
74506        bind_events();
74507    }
74508
74509    function eval_columns() {
74510        var len = _self.settings.column_config.length;
74511        for (var i = 0; i < len; i++) {
74512            if (_self.settings.column_config[i].formatter)
74513                _self.settings.column_config[i].formatter = eval(_self.settings.column_config[i].formatter);
74514
74515            if (_self.settings.column_config[i].asyncPostRender)
74516                _self.settings.column_config[i].asyncPostRender = eval(_self.settings.column_config[i].asyncPostRender);
74517        }
74518
74519        _self.settings.column_config.sort(column_sorter);
74520    }
74521
74522    function column_sorter(a, b) {
74523        return(a.order - b.order);
74524    }
74525
74526    function bind_events() {
74527        $j('._experiment_master_check').bind('click', do_master_check);
74528        $j('._aibs_row').live('mouseover', do_mouse_over);
74529        $j('._aibs_row').live('mouseout', do_mouse_out);
74530        $j('._aibs_row').live('click', do_row_click);
74531    }
74532
74533    function unbind_events() {
74534        $j('._experiment_master_check').unbind('click');
74535        $j('._aibs_row').die('mouseover');
74536        $j('._aibs_row').die('mouseout');
74537        $j('._aibs_row').die('click');
74538    }
74539
74540    function do_mouse_over(event, ui) {
74541        var item = _self.model.getItem(parseInt($j(this).attr('row')));
74542
74543        // the caller can cancel the default behavior by returning false
74544        if (_self.on_row_over)
74545            if (!_self.on_row_over(item))
74546                return;
74547
74548        // default behavior adds the '_row_over' class
74549        $j(this).addClass(_self.settings.row_over_class);
74550        $j(this).find('.slick-cell').addClass(_self.settings.row_over_class);
74551    }
74552
74553    function do_mouse_out(event, ui) {
74554        var item = _self.model.getItem(parseInt($j(this).attr('row')));
74555
74556        // the caller can cancel the default behavior by returning false
74557        if (_self.on_row_out)
74558            if (!_self.on_row_out(item))
74559                return;
74560
74561        $j(this).removeClass(_self.settings.row_over_class);
74562        $j(this).find('.slick-cell').removeClass(_self.settings.row_over_class);
74563    }
74564
74565    function do_row_click(event, ui) {
74566        var row = $j(this);
74567        var item = _self.model.getItem(parseInt(row.attr('row')));
74568
74569        // the caller can cancel the default behavior by returning false
74570        if (_self.on_row_click)
74571            if (!_self.on_row_click(item))
74572                return;
74573
74574        if (_previous_selection) {
74575            _previous_selection.removeClass(_self.settings.selected_row_class);
74576            // _previous_selection.find('.slick-cell').removeClass(_self.settings.selected_row_class);
74577        }
74578
74579        row.addClass(_self.settings.selected_row_class);
74580        // row.find('.slick-cell').addClass(_self.settings.selected_row_class);
74581
74582        _previous_selection = row;
74583    }
74584
74585    /**
74586     * Build multicolumn sort widget, put instance into _mcsort
74587     */
74588    function build_mcsort() {
74589        var menu = _self.settings.mcsort_config.menu;
74590        var configs = _self.settings.mcsort_config.menu_configurations;
74591        var num_menus = _self.settings.mcsort_config.num_menus;
74592        var parent = _self.settings.mcsort_parent_id;
74593        _mcsort = new MultiColumnSortWidget(parent, menu, configs, num_menus, do_mcsort);
74594    }
74595
74596    function build_table() {
74597        _result_table = new Slick.Grid(_self.container, _self.model, _self.settings.column_config, _self.settings.table_options);
74598        _result_table.onSort = do_sort;
74599        _the_pager = new Slick.Controls.Pager(_self.model, _result_table, $j('#pager_container'));
74600        _table_ready = true;
74601    }
74602
74603    function inspect(prefix, obj) {
74604        var s = "";
74605        for (var key in obj) {
74606            if ((typeof obj[key]) == "function")
74607                s = s;
74608            else if ((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
74609                s += "\n" + prefix + "key: " + key + inspect(prefix + "    ", obj[key]);
74610            else
74611                s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
74612        }
74613        return(s);
74614    }
74615
74616    /*
74617     * Column sort callback invoked by SlickGrid when user clicks on sortable column header
74618     *
74619     * @param sort_column {Object} SlickGrid object containing properties used for column sorting, including name:<column heading name>,
74620     *                       field:<sort field specification>. Example name:'Gene Symbol', field:'genes.acronym'
74621     * @param sort_asc {Boolean} sort direction, true if ascending, false if descending
74622     */
74623    function do_sort(sort_column, sort_asc) {
74624        _self.model.sort(sort_column, sort_asc);
74625
74626        if (_mcsort) {
74627            // update multicolumn sort UI to display single column sort selection; UI reflects user sort action on SlickGrid
74628            _mcsort.setSingleMenuConfiguration(sort_column.name, sort_asc);
74629        }
74630    }
74631
74632    /**
74633     *  Multicolumn sort callback, resulting from pressing "Sort" button
74634     *
74635     * @param sort_data array of menu selection info, consisting of hashes {name:<menu selection>, value:<menu value>,
74636     *                      asc:<true=ascending, false=descending>}
74637     */
74638    function do_mcsort(sort_data) {
74639        _self.model.mcsort(sort_data);
74640    }
74641
74642    function row_formatter(row_data) {
74643        var classes = "_aibs_row";
74644        if (row_data.failed)
74645            classes += " _failed ";
74646
74647        return(classes);
74648    }
74649
74650    function expression_cell_formatter(cell_node, row, dataContext, colDef) {
74651
74652        var exp_opts = {
74653            success_callback: show_expressions,
74654            error_callback: graph_failure,
74655            structure_ids: _self.settings.structure_ids
74656        };
74657
74658        var id = dataContext['data_sets.id'];
74659        if (!id)
74660            return;
74661
74662        var failed = dataContext['failed'];
74663
74664        if (!failed) {
74665            cell_node.innerHTML = 'loading...';
74666            _expression_node_index[id] = cell_node;
74667            _self.model.get_expression(id, exp_opts);//.rma_dao.experiment_expressions(id, exp_opts);
74668        }
74669    }
74670
74671    /**
74672     * the manual annotation expression data is shown as some sort of x-y axis visual graph
74673     *
74674     * @param cell_node
74675     * @param row
74676     * @param dataContext
74677     * @param colDef
74678     */
74679    function manual_expression_cell_formatter(cell_node, row, dataContext, colDef) {
74680
74681        new manual_annotation_chart(
74682            cell_node,
74683            dataContext,
74684            {
74685                age_label_link: colDef.formatter_options.path,
74686                age_label_link_title: colDef.formatter_options.tool_tip
74687            });
74688
74689        // check checkboxes for experiments that are already in the user's cart
74690        $j(cell_node).find('._experiment_select_check').each(function (check) {
74691
74692            var id = this.getAttribute('experiment.id');
74693            if (_experiment_id_lists.exists(id))
74694                this.checked = true;
74695        });
74696
74697        // add checkbox binding for the chart checkboxes.
74698        $j(cell_node).find('._experiment_select_check').bind('click', do_select_check);
74699    }
74700
74701
74702    function man_anno_download_cell_formatter(cell_node, row, dataContext, colDef) {
74703
74704        if (!dataContext || !dataContext.data_sets || !dataContext.data_sets.length)
74705            return;
74706
74707        var data_set_ids = dataContext.data_sets.map(function (item) {
74708            return(item.id)
74709        }).join(',');
74710
74711        var query = _self.settings.urls.data_service + "ManualAnnotation/query.csv?criteria=[section_data_set_id$in" + data_set_ids + "],structure,section_data_set(genes,plane_of_section,specimen(donor(age)))" +
74712            ",rma::options[tabular$eq'manual_annotations.section_data_set_id as section_data_set,genes.acronym as gene,ages.name as age,plane_of_sections.name as plane_of_section,structures.acronym as structure,manual_annotations.intensity_call,manual_annotations.density_call,manual_annotations.pattern_call,manual_annotations.mixed_population_call,manual_annotations.comment']" +
74713            ",rma::options[num_rows$eq'all']" +
74714            ",rma::options[order$eq'ages.days,structures.graph_order']";
74715
74716        $j(cell_node).html("<a href=\"" + query + "\" style='position:relative;top:80%;left:30%;' target='_new_query'>CSV</a>");
74717    }
74718
74719
74720    function attach_check_select(cell_node, row, dataContext, colDef) {
74721        var check = $j(cell_node).find('._experiment_select_check')[0];
74722        if (check)
74723            $j(check).bind('click', do_select_check)
74724    }
74725
74726
74727    function show_expressions(response_data, text_status, xhdr) {
74728        var data = response_data ? response_data[0] : null;
74729        if (!data)
74730            return;
74731
74732        var exp_id = data.id;
74733        var container = $(_expression_node_index[exp_id]);
74734        if (_plots[exp_id]) {
74735            //alert("plot already exists: " + inspect('', _plots[exp_id]));
74736            _plots[exp_id].shutdown();
74737            delete _plots[exp_id];
74738        }
74739
74740        try {
74741            //_plots[exp_id] = new expression_graph_widget(container, data.structure_unionizes, _self.settings.graph_structures, {graph_mode:'small', tooltip:false});
74742            build_graphlette(container, data.structure_unionizes, _self.settings.graph_structures);
74743        } catch (e) {
74744            // this throws an exception if the container has no dimensions, which happens frequently as the table is being resized/scrolled/rebuilt.
74745        }
74746
74747    }
74748
74749    function build_graphlette(container, expression_data, structure_data) {
74750
74751        var separation = 2;
74752        var max_bar_height = 5;
74753        var len = structure_data.length;
74754        var total_width = $j(container).innerWidth();
74755        var total_height = $j(container).innerHeight();
74756        var bar_width = (total_width / len) - separation;
74757        var expr_index = index_expressions(expression_data);
74758
74759
74760        var html = "<div class='graphlette_container'><div class='inner_graphlette_container'>";
74761
74762        for (var i = 0; i < len; i++) {
74763            var e = expr_index[structure_data[i].structure_id];
74764            if (e) {
74765                var energy = Math.max(0, (Math.log(e.expression_energy) / Math.log(2)));
74766                var height = (energy / max_bar_height) * total_height;
74767                var color = '#' + e.structure.color_hex_triplet;
74768
74769                html += "<div class='graphlette' style='width:" + bar_width + "px;height:" + height + "px;background-color:" + color + "'></div>";
74770            } else {
74771                html += "<div class='graphlette' style='width:" + bar_width + "px;height:0px;background-color:#000'></div>";
74772            }
74773        }
74774        html += "</div></div>";
74775
74776        container.innerHTML = html;
74777    }
74778
74779    function index_expressions(expression_set) {
74780        if (!expression_set)
74781            return({});
74782
74783        var index = {};
74784        var len = expression_set.length;
74785
74786        for (var i = 0; i < len; i++) {
74787            index[expression_set[i].structure.id] = expression_set[i];
74788        }
74789
74790        return(index);
74791    }
74792
74793
74794    /**
74795     * Generic cell formatter that turns the cell text into a link, accompanied by the human homolog symbol (if any)
74796     *
74797     * @param row
74798     * @param cell
74799     * @param value
74800     * @param columnDef
74801     * @param dataContext
74802     * @returns {*}
74803     */
74804    function link_cell_formatter(row, cell, value, columnDef, dataContext) {
74805        if (value == undefined)
74806            return '';
74807
74808        try {
74809            var options = columnDef.formatter_options;
74810            var homologous_genes = eval('dataContext.' + options.homologous_genes);
74811
74812            // only retrieve human homolog (no mice!)
74813            var human_homolog = '';
74814            if (homologous_genes != undefined) {
74815                var len = homologous_genes.length;
74816                for (var i = 0, len = homologous_genes.length; i < len; ++i ) {
74817                    if (homologous_genes[i].organism_id == 1) {
74818                        human_homolog = '<span title="human homolog"> (' + homologous_genes[i].acronym + ')</span>';
74819                        break;
74820                    }
74821                }
74822            }
74823
74824            var val = eval('dataContext.' + options.field);
74825            return("<a href='" + options.path + val + "'>" + value + "</a>" + human_homolog);
74826        } catch (e) {
74827            return(value);
74828        }
74829
74830    }
74831
74832    function manual_dual_expression_cell_formatter(row, cell, value, columnDef, dataContext) {
74833
74834
74835        var html = "<div class='man_anno_horiz_bar bar_intensity' title='Expression intensity: " + dataContext.intensity_call + "' style='width:" 
74835+ (+dataContext.intensity_call * 10) + "%'></div>";
74836        html += "<div class='man_anno_horiz_bar bar_density' title='Expression density: " + dataContext.density_call + "' style='width:" + (+dataContext.density_call * 10) + "%'></div>";
74837        return(value === undefined ? '' : html);
74838    }
74839
74840
74841    /**
74842     * Formats an image with a link, or just the image itself if you leave 'path' and 'path_field' unconfigured
74843     * Uses these configuration values:
74844     *  src_field - image value
74845     *  path_field - link value
74846     *  src - image path
74847     *  path - link path
74848     *  file_extension - file extension of image
74849     *  img_class - CSS class for img tag
74850     *  hover_text - image mouseover text
74851     *
74852     * @param row
74853     * @param cell
74854     * @param value
74855     * @param columnDef contains formatter_options from application.json
74856     * @param dataContext
74857     * @returns {string}
74858     */
74859    function image_link_cell_formatter(row, cell, value, columnDef, dataContext) {
74860
74861        try {
74862            var comps = columnDef.formatter_options;
74863            var src_val = eval('dataContext.' + comps.src_field);
74864            var path_val = eval('dataContext.' + comps.path_field);
74865            var img_class = (comps.img_class ? comps.img_class : 'related_img');
74866
74867            var href_open_tag = "";
74868            var href_close_tag = "";
74869            var hover_text = "";
74870
74871            if (comps.path != undefined && comps.path_field != undefined) {
74872                href_open_tag = "<a href='" + comps.path + path_val + "'>";
74873                href_close_tag = "</a>";
74874            }
74875
74876            if (comps.hover_text != undefined)
74877                hover_text = " title='" + comps.hover_text + "' ";
74878
74879            var img_tag = "<img class='" + img_class + "' src='" + comps.src + src_val + comps.file_extension + "'" + hover_text + "/>";
74880            return(href_open_tag + img_tag + href_close_tag);
74881
74882        } catch (e) {
74883            return('');
74884        }
74885    }
74886
74887    function related_image_link_cell_formatter(row, cell, value, columnDef, dataContext) {
74888
74889        try {
74890            var comps = columnDef.formatter_options;
74891
74892            var src_field = eval('dataContext.' + comps.src_field);
74893            var path_field = eval('dataContext.' + comps.path_field);
74894            if (isNaN(src_field) || isNaN(path_field))
74895                return('');
74896
74897            var src_val = '?section_data_set=' + src_field;
74898            var path_val = '?section_data_set=' + path_field;
74899            var img_class = (comps.img_class ? comps.img_class : 'related_img');
74900
74901            return("<a href='" + _self.settings.urls.related_data.replace('.jpg', '.html') + path_val + "'><img class='" + img_class + "' src='" + _self.settings.urls.related_data + src_val + "'/></a>");
74902
74903        } catch (e) {
74904            return('');
74905        }
74906    }
74907
74908    /**
74909     * adds the expression summary image to the cell.  Also builds some checkboxes (for now...)
74910     *
74911     * @param row
74912     * @param cell
74913     * @param value
74914     * @param columnDef
74915     * @param dataContext
74916     * @returns {string}
74917     */
74918    function expression_summary_image_link_cell_formatter(row, cell, value, columnDef, dataContext) {
74919        // edge condition: empty SlickGrid row
74920        if (dataContext.result_type == undefined)
74921            return "";
74922
74923        var ages = ['E11.5', 'E13.5', 'E15.5', 'E18.5', 'P4', 'P14', 'P28'];
74924
74925        try {
74926            var comps = columnDef.formatter_options;
74927            var src_val = eval('dataContext.' + comps.src_field);
74928            var row_data = expression_summary_row_data(dataContext);
74929            var img_class = (comps.img_class ? comps.img_class : '');
74930            var highlight_age = comps.highlight_age ? comps.highlight_age : undefined;
74931
74932            var html = "<div class='exp_summ_img_wrapper'>";
74933            html += "<table class='exp_summ_check_wrapper'>";
74934
74935            var the_age, open_tag, close_tag;
74936            for (var i = 0; i < ages.length; ++i) {
74937                the_age = ages[i];
74938                open_tag = close_tag = "";
74939                if (row_data[the_age].is_search_age && highlight_age) {
74940                    open_tag = highlight_age.open_tag ? highlight_age.open_tag : "";
74941                    close_tag = highlight_age.close_tag ? highlight_age.close_tag : "";
74942                }
74943                html += "<tr><td>" + open_tag + the_age + close_tag + "</td><td>";
74944                html += "<input class='_experiment_select_check' experiment.id='" + row_data[the_age].id + "' " + row_data[the_age].disabled + " type='checkbox'/></td></tr>";
74945            }
74946            html += "</table>"; // close the checkbox wrapper
74947
74948            html += "<img class='" + img_class + "' src='" + comps.src + src_val + comps.file_extension + "'/>";
74949            html += "</div>"; // close the image wrapper
74950
74951            return(html);
74952
74953        } catch (e) {
74954            return("error formatting expression summary: " + e);
74955        }
74956    }
74957
74958
74959    /**
74960     * this is intended to make it possible for different data formats
74961     * to respond in the same way to these questions:
74962     *
74963     *      Should the checkbox for age X be enabled?
74964     *      What is the experiment id for age X?
74965     *
74966     * returns an anonymous object that looks like this:
74967     * {
74968     *      'E11.5':{
74969     *          disabled: 'disabled' | ''
74970     *          id: the id of the data set corresponding to this age.
74971     *      },
74972     *      'E13.5':{
74973     *          disabled: 'disabled' | ''
74974     *          id: the id of the data set corresponding to this age.
74975     *      },
74976     *      etc. for all 7 ages.
74977     * }
74978     *
74979     * @param dataContext
74980     * @returns {Object} hash with keys corresponding to the age names, values a hash with keys 'disabled', 'id'
74981     */
74982    function expression_summary_row_data(dataContext) {
74983
74984        var ages = ['E11.5', 'E13.5', 'E15.5', 'E18.5', 'P4', 'P14', 'P28'];
74985        var answers = {};
74986
74987        // some sniffing of the dataContext here will be necessary to determine how to get the answers...
74988        // for a neuroblast search it works like this.  I added the result_type field
74989        // in the neuroblast_search_model's get_item method to make sniffing easier.
74990        if (dataContext.result_type) {
74991            switch (dataContext.result_type) {
74992                case 'gene_search':
74993                case 'gene_classification_search':
74994                    var data_sets = dataContext.experiment_detail.data_sets;
74995                    for (var i = 0; i < ages.length; i++) {
74996                        answers[ages[i]] = {
74997                            disabled: (data_sets[ages[i]] ? '' : 'disabled'),
74998                            id: (data_sets[ages[i]] ? data_sets[ages[i]].data_set_id : 0)
74999                        }
75000                    }
75001                    break;
75002                default:
75003                    break;
75004            }
75005        }
75006
75007        return(answers);
75008    }
75009
75010    /**
75011     * this is called post-render, that is after the expression_summary_image_link_cell_formatter
75012     * has built the heatmap table for each row.
75013     *
75014     * @param cell_node
75015     * @param row
75016     * @param dataContext
75017     * @param colDef
75018     */
75019    function expression_summary_check_select(cell_node, row, dataContext, colDef) {
75020
75021        // check checkboxes for experiments that are already in the user's cart
75022        $j(cell_node).find('input').each(function (check) {
75023
75024            var id = this.getAttribute('experiment.id');
75025            if (_experiment_id_lists.exists(id))
75026                this.checked = true;
75027        });
75028
75029        // add checkbox binding for the chart checkboxes.
75030        $j(cell_node).find('input')
75031            .bind('click', do_select_check);
75032    }
75033
75034    function asynch_color_structure_cell(cell_node, row, dataContext, colDef) {
75035
75036        if (dataContext && dataContext.color_hex_triplet)
75037            $j(cell_node).css({"background-color": '#' + dataContext.color_hex_triplet});
75038    }
75039
75040    function asynch_color_age_cell(cell_node, row, dataContext, colDef) {
75041
75042        if (!dataContext || !dataContext.age_name)
75043            return;
75044
75045        var color = "";
75046        var back = "";
75047        switch (dataContext.age_name) {
75048
75049            case "0 mo":
75050                back = "#606060";
75051                color = "#fff";
75052                break;
75053            case "3 mo":
75054                back = "#808080";
75055                color = "#fff";
75056                break;
75057            case "12 mo":
75058                back = "#C0C0C0";
75059                color = "#000";
75060                break;
75061            case "48 mo":
75062                back = "#DDDDDD";
75063                color = "#000";
75064                break;
75065            default:
75066                back = "#000000";
75067                color = "#fff";
75068        }
75069
75070        var style = {"background-color": back, color: color};
75071
75072        $j(cell_node).css(style);
75073    }
75074
75075    function experiment_cell_formatter(row, cell, value, columnDef, dataContext) {
75076        if (value) {
75077            return("<a href='/experiment/show/" + value + "'>" + value + "</a>");
75078        } else {
75079            return "";
75080        }
75081    }
75082
75083    function gene_cell_formatter(row, cell, value, columnDef, dataContext) {
75084
75085        return("<a href='/gene/show/" + dataContext["genes.id"] + "'>" + value + "</a>");
75086    }
75087
75088    /**
75089     * Structure cell displays structure acronym, add hover text showing structure name
75090     * The field name for structure name is defined in application.json as {formatter_options:{field:<data access field>}}
75091     *
75092     * @param row
75093     * @param cell
75094     * @param value
75095     * @param columnDef
75096     * @param dataContext
75097     * @returns {string}
75098     */
75099    function structure_cell_formatter(row, cell, value, columnDef, dataContext) {
75100        if (value == undefined)
75101            return '';
75102
75103        var struct_name_field = columnDef.formatter_options.field;
75104
75105        return("<div title='" + dataContext[struct_name_field] + "'>" + value + "</div>");
75106    }
75107
75108    function text_replace_cell_formatter(row, cell, value, columnDef, dataContext) {
75109        try {
75110            var reps = columnDef.formatter_options.replacements;
75111            var new_str = value;
75112            for (var i = 0; i < reps.length; i++)
75113                new_str = new_str.replace(new RegExp(reps[i].from), reps[i].to);
75114
75115            return(new_str);
75116        } catch (e) {
75117            return(value);
75118        }
75119    }
75120
75121    function selected_cell_formatter(row, cell, value, columnDef, dataContext) {
75122        var html = "<input type='checkbox' class='_experiment_select_check' title='Add/Remove this item to your selection list.' ";
75123
75124        var id = dataContext['data_sets.id'] ? dataContext['data_sets.id'] : dataContext.data_set_id ? dataContext.data_set_id : 0;
75125        html += " experiment.id='" + id + "'";
75126
75127        if (dataContext.failed || id == 0)
75128            html += " disabled";
75129
75130        html += _experiment_id_lists.exists(id) ? ' checked ' : '';
75131        html += "/>";
75132        return(html);
75133    }
75134
75135    function do_select_check(event, ui) {
75136        if (this.disabled)
75137            return;
75138
75139        var id = this.getAttribute('experiment.id');
75140
75141        if (this.checked)
75142            _experiment_id_lists.add(id);
75143        else
75144            _experiment_id_lists.remove(id);
75145
75146        // we don't want checking the checkbox to select the row.
75147        event.stopPropagation();
75148    }
75149
75150
75151    function do_master_check(event, ui) {
75152        var checked = this.checked;
75153        $j('._experiment_select_check').each(
75154            function (index, el) {
75155                if (el.disabled)
75156                    return;
75157
75158                var id = el.getAttribute('experiment.id')
75159                if (id == '0')
75160                    return;
75161
75162                el.checked = checked;
75163
75164                if (el.checked)
75165                    _experiment_id_lists.add(id);
75166                else
75167                    _experiment_id_lists.remove(id);
75168            }
75169        );
75170    }
75171
75172    function do_clear_selections() {
75173        $j('._experiment_select_check').each(
75174            function (index, el) {
75175                if (el.disabled)
75176                    return;
75177
75178                var id = el.getAttribute('experiment.id')
75179                if (id == '0')
75180                    return;
75181
75182                el.checked = false;
75183            }
75184        );
75185    }
75186
75187
75188    function graph_failure(model, error) {
75189        // swallowing this error
75190    }
75191
75192    function do_sort_complete(model) {
75193        _result_table.invalidate();
75194    }
75195
75196    init();
75197
75198}
75199;
75200"use strict";
75201/**
75202 *
75203 * @param container
75204 * @param daos
75205 * @param urls
75206 * @param options
75207 */
75208function landing_widget(container, daos, urls, options) {
75209
75210    this.container = $j(container);
75211    this.daos = daos;
75212    this.urls = urls;
75213    this.settings = {
75214        "wrapper_class":"_lw"
75215    };
75216    $j.extend(true, this.settings, options);
75217
75218
75219    this.set_landing_mode = function(new_mode) {
75220        build_widgets(new_mode);
75221    }
75222
75223
75224
75225    var _self = this;
75226    var _widgets = {};
75227    var _widget_containers = {};
75228    var _widget_headers = {};
75229
75230
75231    function build_widgets(mode) {
75232
75233        var widgets = _self.settings.components;
75234        for(var widget in widgets) {
75235
75236            var widget_config = _self.settings.components[widget];
75237            var mode_config = mode ? widget_config.modes[mode] : widget_config.modes[widget_config.initial_mode];
75238
75239            if(mode_config) {
75240                var widget_constructor = "new " + mode_config.class_name + "(_widget_containers[widget], _self.daos, _self.urls, mode_config.options)";
75241
75242                if(_widgets[widget])
75243                    delete _widgets[widget];
75244
75245                _widget_headers[widget][0].innerHTML = mode_config.title;
75246                _widgets[widget] = eval(widget_constructor);
75247            }
75248        }
75249    }
75250
75251    function build_scaffold() {
75252
75253        var html = '<div id="landing_block">';
75254
75255        var widgets = _self.settings.components;
75256        for(var widget in widgets) {
75257
75258            html += '   <div id="' + widget + '_block" class="' + _self.settings.wrapper_class + ' _' + widget + '_class">';
75259            html += '     <div id="' + widget + '_inner_block" class="' + _self.settings.wrapper_class + '_inner _' + widget + '_inner_class">';
75260            html += '       <div id="' + widget + '_header" class="' + _self.settings.wrapper_class + '_header _' + widget + '_header_class"></div>';
75261            html += '       <div id="' + widget + '_container" class="' + _self.settings.wrapper_class + '_container _' + widget + '_container_class"></div>';
75262            html += '     </div>';
75263            html += '   </div>';
75264        }
75265
75266        html += '</div>';
75267        _self.container.append(html);
75268
75269        for(var widget in widgets) {
75270            _widget_containers[widget] = $j('#' + widget + '_container');
75271            _widget_headers[widget] = $j('#' + widget + '_header');
75272        }
75273    }
75274
75275    function init() {
75276
75277        build_scaffold();
75278        build_widgets();
75279    }
75280
75281
75282    init();
75283}
75284;
75285"use strict";
75286/**
75287 * This Faceted Search model uses numerous common functions defined in IshCommon.js
75288 * It follows model interface defined by SlickGrid
75289 *
75290 * @return {*}
75291 * @constructor
75292 */
75293// lifted from slick.model.js
75294function EventHelper() {
75295    this.handlers = [];
75296
75297    this.subscribe = function (fn) {
75298        this.handlers.push(fn);
75299    };
75300
75301    this.notify = function (args) {
75302        for (var i = 0; i < this.handlers.length; i++) {
75303            this.handlers[i].call(this, args);
75304        }
75305    };
75306
75307    return this;
75308}
75309
75310/**
75311 *
75312 * @param daos
75313 * @param options
75314 */
75315function faceted_search_model(daos, options) {
75316
75317//public properties
75318    this.rma_dao = daos.rma_dao;
75319    this.options = options;
75320
75321// public methods
75322
75323    this.reset_page_num = function() {
75324        _page_num = 0;
75325        this.onPagingInfoChanged.notify(this.getPagingInfo());
75326    };
75327
75328// these two methods are used by the search_controllers
75329    /**
75330     *
75331     * @param term url arg, 'search_term'
75332     * @param options all the url args
75333     * @returns {*}
75334     */
75335    this.search = function (term, options) {
75336        return(do_gene_search(term, options, 'search'));
75337    };
75338
75339    this.sort = function (sort_column, sort_asc) {
75340        _search_options.order = _self.IshCommon.single_column_sort_order(sort_column, sort_asc, options.sort_suffix);
75341        if(_current_facets)
75342            _search_options.facets = _current_facets;
75343
75344        do_gene_search(_search_term, _search_options, 'sort');
75345    };
75346
75347    /**
75348     * Multicolumn sort
75349     * @param sort_data array of hashes containing keys name, value, asc.
75350     */
75351    this.mcsort = function (sort_data) {
75352        _search_options.order = _self.IshCommon.mcsort_order(sort_data);
75353        do_gene_search(_search_term, _search_options, 'sort');
75354    };
75355
75356    this.get_data = function () {
75357        return({experiments:_the_data});
75358    };
75359
75360// end those two methods that are used by the search_controllers.
75361
75362
75363// these two support the implied interface for the slick.grid
75364    this.getItem = function (row_index) {
75365        return(_self.IshCommon.do_get_item(row_index, _the_data, _self.options.column_config));
75366    };
75367
75368    this.getLength = function () {
75369        return(_page_size);
75370    };
75371// end slick.grid functions
75372
75373
75374// these functions & events support the implied interface required by slick.paging
75375    this.onPagingInfoChanged = new EventHelper();
75376
75377    this.setPagingOptions = function (args) {
75378        var info = _self.IshCommon.calc_paging_info(args, _page_num, _page_size, _total_rows);
75379        _no_page = info.no_page;
75380        if (info.no_change) {
75381            return;
75382        }
75383        else {
75384            _page_num = info.page_num;
75385            _page_size = info.page_size;
75386        }
75387
75388        this.onPagingInfoChanged.notify(this.getPagingInfo());
75389
75390        _search_options.limit = _page_size;
75391        _search_options.offset = _page_num * _page_size;
75392        if(_current_facets)
75393            _search_options.facets = _current_facets;
75394
75395        do_gene_search(_search_term, _search_options, 'page');
75396    };
75397
75398    this.getPagingInfo = function () {
75399        return {pageSize:_page_size,
75400            pageNum:_page_num,
75401            totalRows:_total_rows,
75402            showAll:_no_page};
75403    };
75404//  end slick.paging support
75405
75406
75407// event hooks
75408    this.on_search_begin = this.options ? this.options.on_search_begin ? this.options.on_search_begin : null : null;
75409    this.on_search_complete = this.options ? this.options.on_search_complete ? this.options.on_search_complete : null : null;
75410    this.on_sort_complete = this.options ? this.options.on_sort_complete ? this.options.on_sort_complete : null : null;
75411    this.on_page_complete = this.options ? this.options.on_page_complete ? this.options.on_page_complete : null : null;
75412    this.on_search_failure = this.options ? this.options.on_search_failure ? this.options.on_search_failure : null : null;
75413
75414
75415// private properties
75416    var _self = this;
75417    var _search_term = '';
75418    var _search_options = {};
75419    var _the_data = null;
75420    var _page_size = _self.options ? _self.options.page_size ? _self.options.page_size : 25 : 25;
75421    var _page_num = _self.options ? _self.options.page_num ? _self.options.page_num : 0 : 0;
75422    var _no_page = _self.options ? _self.options.no_page ? _self.options.no_page : false : false;
75423    var _total_rows = 0;
75424    var _callback = null;
75425    var _default_order = 'genes.acronym$asc' + (this.options.sort_suffix ? ',' + this.options.sort_suffix : ',data_sets.id');
75426    var _current_facets = null;
75427    var _last_query  = {};
75428
75429
75430    function do_gene_search(term, options, source) {
75431
75432        term = $j.trim(term);
75433
75434        if (_self.on_search_begin)
75435            _self.on_search_begin(_self);
75436        _search_term = term;
75437
75438        if (options) {
75439            if (options.limit) {
75440                _page_size = options.limit ? options.limit : _page_size;
75441                _page_num = options.offset ? options.offset / _page_size : _page_num;
75442            } else if (options.page_num) {
75443                _page_num = options.page_num;
75444            }
75445        }
75446
75447        var opts = {
75448            order:_default_order,
75449            product_name:_self.options.product_names
75450        };
75451        $j.extend(opts, options);
75452
75453        opts.limit = opts.limit ? opts.limit : _page_size;
75454        opts.offset = opts.offset ? opts.offset : _page_num * _page_size;
75455
75456        _search_options = opts;
75457
75458        _callback = null;
75459        switch (source) {
75460            case 'search':
75461                _callback = _self.on_search_complete;
75462                break;
75463            case 'sort':
75464                _callback = _self.on_sort_complete;
75465                break;
75466            case 'page':
75467                _callback = _self.on_page_complete;
75468                break;
75469            default:
75470                throw 'Unknown search source';
75471        }
75472
75473        // Note: _callback is invoked from inside show_search_results
75474        opts.success_callback = show_search_results;
75475        opts.error_callback = erroror;
75476
75477        if (options.facets) {
75478            // Note: if facets/ranges already parsed, skip capture_facets.
75479            // If it's not parsed, it will have a hash value, if parsed, it will be a string
75480            if (typeof options.facets === 'object') {
75481                var q = capture_facets(options.facets);
75482                opts.facets = q.facets;
75483                opts.filter = q.ranges;
75484            }
75485            _current_facets = options.facets;
75486        }
75487        else {
75488            _current_facets = null;
75489        }
75490
75491
75492        return(_self.rma_dao.experiment_search_by_gene(_search_term, opts));
75493    }
75494
75495
75496    /**
75497     * Parse facet url args and reformats to Sphinx-friendly format.
75498     * Example:
75499     *  facet_donor_age="3 mo","12 mo","48 mo" --> ' @donor_age="3 mo"|"12 mo"|"48 mo"
75500     *
75501     * @param options
75502     * @returns {{}}
75503     */
75504    function capture_facets(options) {
75505
75506        var queries = {};
75507        var facet_queries = '';
75508        var ranges = "";
75509
75510            for (var key in options) {
75511                if (key.indexOf('facet_range_') == 0)
75512                    ranges += options[key].replace(/facet_/g, '').replace(/\*gte/g, '>=').replace(/\*lte/g, '<=');
75513            else if (key.indexOf('facet_') == 0)
75514                facet_queries += ' @' + key.substr(6) + ' ' + options[key].replace(/,/g, '|');
75515            }
75516
75517        queries.facets = facet_queries;
75518        queries.ranges = ranges;
75519        return(queries);
75520    }
75521
75522    function capture_range(option) {
75523
75524    }
75525
75526    function show_search_results(response_data, text_status, xhdr) {
75527
75528        _the_data = response_data;
75529        _total_rows = parseInt(_the_data.response.rows_available);
75530
75531        if (_callback)
75532            _callback(_self);
75533
75534        // let the pager know about it
75535        _self.onPagingInfoChanged.notify(_self.getPagingInfo());
75536    }
75537
75538    function erroror(xhdr, text_status, error) {
75539        var err = xhdr.msg == undefined ? error : xhdr;
75540        if (_self.on_search_failure)
75541            _self.on_search_failure(_self, err);
75542    }
75543
75544}
75545
75546
75547
75548;
75549"use strict";
75550/**
75551 * This Gene Classification Search model uses numerous common functions defined in IshCommon.js
75552 * It follows model interface defined by SlickGrid
75553 *
75554 */
75555
75556// lifted from slick.model.js
75557function EventHelper() {
75558    this.handlers = [];
75559
75560    this.subscribe = function (fn) {
75561        this.handlers.push(fn);
75562    };
75563
75564    this.notify = function (args) {
75565        for (var i = 0; i < this.handlers.length; i++) {
75566            this.handlers[i].call(this, args);
75567        }
75568    };
75569
75570    return this;
75571}
75572
75573
75574/**
75575 *
75576 * @param daos
75577 * @param options
75578 */
75579function gene_classification_search_model(daos, options) {
75580
75581//public properties
75582    this.rma_dao = daos.rma_dao;
75583    this.options = options;
75584
75585// public methods
75586
75587    this.reset_page_num = function () {
75588        _page_num = 0;
75589        this.onPagingInfoChanged.notify(this.getPagingInfo());
75590    };
75591
75592// these two methods are used by the search_controllers
75593    /**
75594     *
75595     * @param term url arg, 'search_term'
75596     * @param options all the url args
75597     * @returns {*}
75598     */
75599    this.search = function (term, options) {
75600        return(do_gene_classification_search(term, options, 'search'));
75601    };
75602
75603    this.sort = function (sort_column, sort_asc) {
75604        _search_options.order = _self.IshCommon.single_column_sort_order(sort_column, sort_asc, options.sort_suffix);
75605        do_gene_classification_search(_search_term, _search_options, 'sort');
75606    };
75607
75608    /**
75609     * Multicolumn sort
75610     * @param sort_data array of hashes containing key-value pairs for name, value, asc.
75611     */
75612    this.mcsort = function (sort_data) {
75613        _search_options.order = _self.IshCommon.mcsort_order(sort_data);
75614        do_gene_classification_search(_search_term, _search_options, 'sort');
75615    };
75616
75617    /**
75618     * Used to get search results data to turn into XML
75619     *
75620     * @returns {Object} hash of {experiments:[search results]}
75621     */
75622    this.get_data = function () {
75623        return({experiments: _the_data});
75624    };
75625
75626    this.get_expression = function (id, exp_opts) {
75627
75628        this.rma_dao.experiment_expressions(id, exp_opts);
75629
75630    }
75631// end those two methods that are used by the search_controllers.
75632
75633
75634// these two support the implied interface for the slick.grid
75635    this.getItem = function (row_index) {
75636        return(_self.IshCommon.do_get_item(row_index, _the_data, _self.options.column_config));
75637    };
75638
75639    this.getLength = function () {
75640        return(_page_size);
75641    };
75642// end slick.grid functions
75643
75644
75645// these functions & events support the implied interface required by slick.paging
75646    this.onPagingInfoChanged = new EventHelper();
75647
75648    this.setPagingOptions = function (args) {
75649        var info = _self.IshCommon.calc_paging_info(args, _page_num, _page_size, _total_rows);
75650        _no_page = info.no_page;
75651        if (info.no_change) {
75652                return;
75653        }
75654        else {
75655            _page_num = info.page_num;
75656            _page_size = info.page_size;
75657        }
75658
75659        this.onPagingInfoChanged.notify(this.getPagingInfo());
75660
75661        _search_options.limit = _page_size;
75662        _search_options.offset = _page_num * _page_size;
75663
75664        do_gene_classification_search(_search_term, _search_options, 'page');
75665    };
75666
75667    this.getPagingInfo = function () {
75668        return {pageSize: _page_size,
75669            pageNum: _page_num,
75670            totalRows: _total_rows,
75671            showAll: _no_page};
75672    };
75673//  end slick.paging support
75674
75675
75676// event hooks
75677    this.on_search_begin = this.options ? this.options.on_search_begin ? this.options.on_search_begin : null : null;
75678    this.on_search_complete = this.options ? this.options.on_search_complete ? this.options.on_search_complete : null : null;
75679    this.on_sort_complete = this.options ? this.options.on_sort_complete ? this.options.on_sort_complete : null : null;
75680    this.on_page_complete = this.options ? this.options.on_page_complete ? this.options.on_page_complete : null : null;
75681    this.on_search_failure = this.options ? this.options.on_search_failure ? this.options.on_search_failure : null : null;
75682
75683
75684// private properties
75685    var _self = this;
75686    var _search_term = '';
75687    var _search_options = {};
75688    var _the_data = null;
75689    var _page_size = _self.options ? _self.options.page_size ? _self.options.page_size : 25 : 25;
75690    var _page_num = _self.options ? _self.options.page_num ? _self.options.page_num : 0 : 0;
75691    var _no_page = _self.options ? _self.options.no_page ? _self.options.no_page : false : false;
75692    var _total_rows = 0;
75693    var _callback = null;
75694    var _default_order = 'genes.acronym$asc' + (this.options.sort_suffix ? ',' + this.options.sort_suffix : ',data_sets.id');
75695    var _last_query  = {};
75696
75697
75698    function inspect(prefix, obj) {
75699        var s = "";
75700        for (var key in obj) {
75701            if ((typeof obj[key]) == "function")
75702                s = s;
75703            else if ((typeof obj[key]) == "object" && obj[key] !== null & key != "element")
75704                s += "\n" + prefix + "key: " + key + inspect(prefix + "    ", obj[key]);
75705            else
75706                s += "\n" + prefix + "key: " + key + ",   value: " + obj[key];
75707        }
75708        return(s);
75709    }
75710
75711
75712    function do_gene_classification_search(term, options, source) {
75713
75714        term = $j.trim(term);
75715        if (!term || term.length == 0)
75716            throw "Invalid search term; must include at least one character.";
75717
75718        if (_self.on_search_begin)
75719            _self.on_search_begin(_self);
75720
75721        _search_term = term;
75722        if (options) {
75723            if (options.limit) {
75724                _page_size = options.limit ? options.limit : _page_size;
75725                _page_num = options.offset ? options.offset / _page_size : _page_num;
75726            } else if (options.page_num) {
75727                _page_num = options.page_num;
75728            }
75729        }
75730
75731        var opts = {
75732            order: _default_order,
75733            product_name: _self.options.product_names
75734        };
75735        $j.extend(opts, options);
75736
75737        opts.limit = opts.limit ? opts.limit : _page_size;
75738        opts.offset = opts.offset ? opts.offset : _page_num * _page_size;
75739
75740        _search_options = opts;
75741
75742        _callback = null;
75743        switch (source) {
75744            case 'search':
75745                _callback = _self.on_search_complete;
75746                break;
75747            case 'sort':
75748                _callback = _self.on_sort_complete;
75749                break;
75750            case 'page':
75751                _callback = _self.on_page_complete;
75752                break;
75753            default:
75754                throw 'Unknown search source';
75755        }
75756
75757        // Note: _callback is invoked from inside show_search_results
75758        opts.success_callback = show_search_results;
75759        opts.error_callback = erroror;
75760
75761        return(_self.rma_dao.experiment_search_by_gene_classification(_search_term, opts));
75762    }
75763
75764    function show_search_results(response_data, text_status, xhdr) {
75765
75766        // !!!! You may need to do this later
75767//        massage_data(response_data);
75768        _the_data = response_data;
75769        _total_rows = parseInt(_the_data.response.rows_available);
75770
75771        if (_callback)
75772            _callback(_self);
75773
75774        // let the pager know about it
75775        _self.onPagingInfoChanged.notify(_self.getPagingInfo());
75776    }
75777
75778    /**
75779     * Parse out the age info from data_sets.specimen.donor.age, and glue it to the data_sets object using the age name as
75780     * the hash key. Example: data_sets['E11.5']. Also put the data_set_id into the age hash.
75781     * This function directly modifies the original response_data.
75782     *
75783     * @param response_data
75784     */
75785//    function massage_data(response_data) {
75786//        var len = response_data.length;
75787//        var ds_array, age;
75788//        for (var i = 0; i < len; ++i) {
75789//            ds_array = response_data[i].data_sets;
75790//            for (var j = 0; j < ds_array.length; ++j) {
75791//                age = ds_array[j].specimen.donor.age;
75792//                ds_array[age.name] = {
75793//                    id: age.id,
75794//                    name: age.name,
75795//                    data_set_id: ds_array[j].id
75796//                };
75797//            }
75798//        }
75799//    }
75800
75801    function erroror(xhdr, text_status, error) {
75802        var err = xhdr.msg == undefined ? error : xhdr;
75803        if (_self.on_search_failure)
75804            _self.on_search_failure(_self, err);
75805    }
75806
75807}
75808
75809
75810
75811;
75812"use strict";
75813/**
75814 * This ISH Gene Search model uses numerous common functions defined in IshCommon.js
75815 * It follows model interface defined by SlickGrid
75816 *
75817 * Instantiation example:
75818 *
75819 * model = new ish_gene_search_model(daos, model_options);
75820 * model.IshCommon = new IshCommon;
75821 *
75822 * @return {*}
75823 * @constructor
75824 */
75825// lifted from slick.model.js
75826function EventHelper() {
75827    this.handlers = [];
75828
75829    this.subscribe = function(fn) {
75830        this.handlers.push(fn);
75831    };
75832
75833    this.notify = function(args) {
75834        for (var i = 0; i < this.handlers.length; i++) {
75835            this.handlers[i].call(this, args);
75836        }
75837    };
75838
75839    return this;
75840}
75841
75842
75843/**
75844 *
75845 * @param daos
75846 * @param options
75847 */
75848function ish_gene_search_model(daos, options) {
75849
75850//public properties
75851    this.rma_dao = daos.rma_dao;
75852    this.options = options;
75853
75854// public methods
75855
75856    this.reset_page_num = function() {
75857        _page_num = 0;
75858        this.onPagingInfoChanged.notify(this.getPagingInfo());
75859    };
75860
75861// these two methods are used by the search_controllers
75862    /**
75863     *
75864     * @param term url arg, 'search_term'
75865     * @param options all the url args
75866     * @returns {*}
75867     */
75868    this.search = function(term, options) {
75869        return(do_gene_search(term, options, 'search'));
75870    };
75871
75872    this.sort = function(sort_column, sort_asc) {
75873        _search_options.order = _self.IshCommon.single_column_sort_order(sort_column, sort_asc, options.sort_suffix
75873);
75874        do_gene_search(_search_term, _search_options, 'sort');
75875    };
75876
75877    /**
75878     * Multicolumn sort
75879     * @param sort_data array of hashes containing keys name, value, asc.
75880     */
75881    this.mcsort = function (sort_data) {
75882        _search_options.order = _self.IshCommon.mcsort_order(sort_data);
75883        do_gene_search(_search_term, _search_options, 'sort');
75884    };
75885
75886    /**
75887     * Used to get search results data to turn into XML
75888     *
75889     * @returns {Object} hash of {experiments:[search results]}
75890     */
75891    this.get_data = function () {
75892        return({experiments:_the_data});
75893    };
75894
75895// end those two methods that are used by the search_controllers.
75896
75897
75898// these two support the implied interface for the slick.grid
75899    this.getItem = function(row_index) {
75900        return(_self.IshCommon.do_get_item(row_index, _the_data, _self.options.column_config));
75901    };
75902
75903    this.getLength = function() {
75904        return(_page_size);
75905    };
75906// end slick.grid functions
75907
75908
75909// these functions & events support the implied interface required by slick.paging
75910    this.onPagingInfoChanged = new EventHelper();
75911
75912    this.setPagingOptions = function(args) {
75913        var info = _self.IshCommon.calc_paging_info(args, _page_num, _page_size, _total_rows);
75914        _no_page = info.no_page;
75915        if (info.no_change) {
75916                return;
75917        }
75918        else {
75919            _page_num = info.page_num;
75920            _page_size = info.page_size;
75921        }
75922
75923        this.onPagingInfoChanged.notify(this.getPagingInfo());
75924
75925        _search_options.limit = _page_size;
75926        _search_options.offset = _page_num * _page_size;
75927
75928        do_gene_search(_search_term, _search_options, 'page');
75929    };
75930
75931    this.getPagingInfo = function() {
75932        return {pageSize:_page_size,
75933                pageNum:_page_num,
75934                totalRows:_total_rows,
75935                showAll:_no_page};
75936    };
75937//  end slick.paging support
75938
75939
75940// event hooks
75941    this.on_search_begin = this.options ? this.options.on_search_begin ? this.options.on_search_begin : null : null;
75942    this.on_search_complete = this.options ? this.options.on_search_complete ? this.options.on_search_complete : null : null;
75943    this.on_sort_complete = this.options ? this.options.on_sort_complete ? this.options.on_sort_complete : null : null;
75944    this.on_page_complete = this.options ? this.options.on_page_complete ? this.options.on_page_complete : null : null;
75945    this.on_search_failure = this.options ? this.options.on_search_failure ? this.options.on_search_failure : null : null;
75946
75947
75948// private properties
75949    var _self = this;
75950    var _search_term = '';
75951    var _search_options = {};
75952    var _the_data = null;
75953    var _page_size = _self.options ? _self.options.page_size ? _self.options.page_size : 25 : 25;
75954    var _page_num = _self.options ? _self.options.page_num ? _self.options.page_num : 0 : 0;
75955    var _no_page = _self.options ? _self.options.no_page ? _self.options.no_page : false : false;
75956    var _total_rows = 0;
75957    var _callback = null;
75958    var _default_order = 'genes.acronym$asc' + (this.options.sort_suffix ? ',' + this.options.sort_suffix : ',data_sets.id');
75959    var _last_query  = {};
75960
75961
75962    function inspect(prefix, obj) {
75963        var s = "";
75964        s = _self.IshCommon.inspect(prefix, obj);
75965
75966        return(s);
75967        }
75968
75969    function do_gene_search(term, options, source) {
75970
75971        term = $j.trim(term);
75972        if(!term || term.length == 0)
75973            throw "Invalid search term; must include at least one character.";
75974
75975        if(_self.on_search_begin)
75976            _self.on_search_begin(_self);
75977        _search_term = term;
75978        if(options) {
75979            if(options.limit) {
75980                _page_size = options.limit ? options.limit : _page_size;
75981                _page_num = options.offset ? options.offset / _page_size : _page_num;
75982            } else if(options.page_num) {
75983                _page_num = options.page_num;
75984            }
75985        }
75986
75987        var opts = {
75988            order:_default_order,
75989            product_name:_self.options.product_names
75990        };
75991        $j.extend(opts, options);
75992
75993        opts.limit = opts.limit ? opts.limit : _page_size;
75994        opts.offset = opts.offset ? opts.offset : _page_num*_page_size;
75995
75996        _search_options = opts;
75997
75998        _callback = null;
75999        switch(source) {
76000            case 'search':
76001                _callback = _self.on_search_complete;
76002                break;
76003            case 'sort':
76004                _callback = _self.on_sort_complete;
76005                break;
76006            case 'page':
76007                _callback = _self.on_page_complete;
76008                break;
76009            default:
76010                throw 'Unknown search source';
76011        }
76012
76013        // Note: _callback is invoked from inside show_search_results
76014        opts.success_callback = show_search_results;
76015        opts.error_callback = erroror;
76016
76017        return(_self.rma_dao.experiment_search_by_gene(_search_term, opts));
76018    }
76019
76020    function show_search_results(response_data, text_status, xhdr) {
76021
76022//        massage_data(response_data);
76023        _the_data = response_data;
76024        _total_rows = parseInt(_the_data.response.rows_available);
76025
76026        if(_callback)
76027            _callback(_self);
76028
76029        // let the pager know about it
76030        _self.onPagingInfoChanged.notify(_self.getPagingInfo());
76031    }
76032
76033    /**
76034     * Parse out the age info from data_sets.specimen.donor.age, and glue it to the data_sets object using the age name as
76035     * the hash key. Example: data_sets['E11.5']. Also put the data_set_id into the age hash.
76036     * This function directly modifies the original response_data.
76037     *
76038     * @param response_data
76039     */
76040    function massage_data(response_data) {
76041        var len = response_data.length;
76042        var ds_array, age;
76043        for (var i = 0; i < len; ++i) {
76044            ds_array = response_data[i].data_sets;
76045            for (var j = 0; j < ds_array.length; ++j) {
76046                age = ds_array[j].specimen.donor.age;
76047                ds_array[age.name] = {
76048                    id: age.id,
76049                    embryonic: age.embryonic,
76050                    name: age.name,
76051                    data_set_id: ds_array[j].id
76052                };
76053            }
76054        }
76055    }
76056
76057  function erroror(xhdr, text_status, error) {
76058      var err = xhdr.msg == undefined ? error : xhdr;
76059      if(_self.on_search_failure)
76060          _self.on_search_failure(_self, err);
76061  }
76062
76063}
76064
76065
76066
76067;
76068"use strict";
76069/**
76070 * This Manual Annotation Search model uses numerous common functions defined in IshCommon.js
76071 * It follows model interface defined by SlickGrid
76072 *
76073 * It is different from the other ISH search modalities in that it requires two RMA database retrieval. It makes this
76074 * sequence of calls:
76075 *  do_full_search
76076 *     get_ids // first determine which unique genes are in common between all tuples of structure+age+intensity+density
76077 *             // that the user specified
76078 *        dao.query('ManualAnnotation') // returns gene id, acronymn, name
76079 *  receive_ids
76080 *     fetch_metadata // re-run original query, but this time restricting search criteria to the common gene ids from original query.
76081 *        dao.query('Gene') // returns experiment, gene, structure, age, donor, intensity, density, related data
76082 *
76083 * It also differs in that it retains a local copy of the entire search result set. Column sorting is done locally
76084 * on this search result set, rather that running a new RMA query with additional sort order parameters.
76085 *
76086 */
76087// lifted from slick.model.js
76088function EventHelper() {
76089    this.handlers = [];
76090
76091    this.subscribe = function (fn) {
76092        this.handlers.push(fn);
76093    };
76094
76095    this.notify = function (args) {
76096        for (var i = 0; i < this.handlers.length; i++) {
76097            this.handlers[i].call(this, args);
76098        }
76099    };
76100
76101    return this;
76102}
76103
76104
76105/**
76106 *
76107 * @param daos
76108 * @param options
76109 */
76110function manual_annotation_search_model(daos, options) {
76111//public properties
76112    this.rma_dao = daos.rma_dao;
76113    this.options = options;
76114
76115// public methods
76116
76117    this.reset_page_num = function () {
76118        _page_num = 0;
76119        this.onPagingInfoChanged.notify(this.getPagingInfo());
76120    };
76121
76122// these two methods are used by the search controller or search view
76123    /**
76124     * Called by controller
76125     *
76126     * @param term url arg, 'search_term'
76127     * @param options all the url args
76128     * @returns {*}
76129     */
76130    this.search = function (term, options) {
76131        return(do_search(term, options, 'search'));
76132    };
76133
76134    /**
76135     * Called by view via SlickGrid when user clicks on column header of a sortable column
76136     *
76137     * @param sort_column
76138     * @param {Boolean} sort_asc ascending sort if true, else descending sort
76139     */
76140    this.sort = function (sort_column, sort_asc) {
76141        var field = sort_column.field;
76142        var compare_type = compare_strings;
76143
76144        // edge case for Age sorting, we don't want to sort on descriptive 'age_name', but on value for 'age_days'
76145        if (field == 'age_name' && sort_column.metadata_days) {
76146            field = sort_column.metadata_days;
76147            compare_type = compare_ints;
76148        }
76149
76150        do_sort_one(field, sort_asc, compare_type);
76151
76152        // !!! is this needed???
76153        // let the pager know about it
76154//        _self.onPagingInfoChanged.notify(_self.getPagingInfo());
76155
76156        if(_self.on_page_complete)
76157            _self.on_page_complete(_self);
76158    };
76159
76160    /**
76161     * Multicolumn sort
76162     * Note the edge case logic for handling the 'Age' column
76163     *
76164     * Called when user presses "Sort" button
76165     * Called by search view to be consistent with location of sort function
76166     *
76167     * @param {Array} sort_data array of hashes containing key-value pairs for name, value, asc. Corresponds to pulldown menu choices.
76168     */
76169    this.mcsort = function (sort_data) {
76170        // sort_data[] is ordered 0 to n, reflecting left-to-right menu order in UI. Sort order precedence is left-to-right.
76171
76172        // these values are metadata column value names, like 'gene_symbol', 'structure_name'
76173        var sort1_colname = sort_data[0] ? sort_data[0].value : undefined;
76174        var sort2_colname = sort_data[1] ? sort_data[1].value : undefined;
76175        var sort3_colname = sort_data[2] ? sort_data[2].value : undefined;
76176
76177        var sort1,
76178            sort2,
76179            sort3,
76180            sort_state,
76181            compare_type1,
76182            compare_type2,
76183            compare_type3;
76184
76185        var len = sort_data.length;
76186        var sort_asc = sort_data[0].asc;
76187        if (len == 1) {
76188            compare_type1 = sort1_colname == 'age_days' ? compare_ints : compare_strings;
76189            _the_data.metadata.rows.sort(function (a, b) {
76190                if (sort_asc)
76191                    return( compare_type1(a[sort1_colname], b[sort1_colname]) );
76192                else
76193                    return( compare_type1(b[sort1_colname], a[sort1_colname]) );
76194            });
76195        }
76196        else if (len == 2) {
76197            // 'asc' is either boolean true or false. The 'sort_state' is decimal representation of the base 2 'asc'
76198            // value of the menu items, where we assume true = 1 and false = 0
76199            // Example: menu Experiment (asc), menu Gene Name (desc), equals 10 (base 2), or equals 2 (base 10)
76200            sort1 = sort_data[0].asc * 2;
76201            sort2 = sort_data[1].asc * 1;
76202            sort_state = sort1 + sort2;
76203
76204            compare_type1 = sort1_colname == 'age_days' ? compare_ints : compare_strings;
76205            compare_type2 = sort2_colname == 'age_days' ? compare_ints : compare_strings;
76206
76207            _the_data.metadata.rows.sort(function (a, b) {
76208                switch(sort_state) {
76209                    case 0: // 0, 0 (desc, desc)
76210                        return( compare_type1(b[sort1_colname], a[sort1_colname]) ||
76211                                compare_type2(b[sort2_colname], a[sort2_colname]) );
76212                        break;
76213                    case 1: // 0, 1 (desc, asc)
76214                        return( compare_type1(b[sort1_colname], a[sort1_colname]) ||
76215                                compare_type2(a[sort2_colname], b[sort2_colname]) );
76216                        break;
76217                    case 2: // 1, 0 (asc, desc)
76218                        return( compare_type1(a[sort1_colname], b[sort1_colname]) ||
76219                                compare_type2(b[sort2_colname], a[sort2_colname]) );
76220                        break;
76221                    case 3: // 1, 1 (asc, asc)
76222                        return( compare_type1(a[sort1_colname], b[sort1_colname]) ||
76223                                compare_type2(a[sort2_colname], b[sort2_colname]) );
76224                        break;
76225                    default:
76226                        break;
76227                }
76228            });
76229        }
76230        else if (len == 3) {
76231            // 'asc' is either boolean true or false. The 'sort_state' is decimal representation of the base 2 'asc'
76232            // value of the menu items, where we assume true = 1 and false = 0
76233            // Example: menu Experiment (asc), menu Gene Name (desc), equals 10 (base 2), or equals 2 (base 10)
76234            sort1 = sort_data[0].asc * 4;
76235            sort2 = sort_data[1].asc * 2;
76236            sort3 = sort_data[2].asc * 1;
76237            sort_state = sort1 + sort2 + sort3;
76238
76239            compare_type1 = sort1_colname == 'age_days' ? compare_ints : compare_strings;
76240            compare_type2 = sort2_colname == 'age_days' ? compare_ints : compare_strings;
76241            compare_type3 = sort3_colname == 'age_days' ? compare_ints : compare_strings;
76242
76243            _the_data.metadata.rows.sort(function (a, b) {
76244                switch(sort_state) {
76245                    case 0: // 0, 0, 0 (desc, desc, desc)
76246                        return( compare_type1(b[sort1_colname], a[sort1_colname]) ||
76247                                compare_type2(b[sort2_colname], a[sort2_colname]) ||
76248                                compare_type3(b[sort3_colname], a[sort3_colname]) );
76249                        break;
76250                    case 1: // 0, 0, 1 (desc, desc, asc)
76251                        return( compare_type1(b[sort1_colname], a[sort1_colname]) ||
76252                                compare_type2(b[sort2_colname], a[sort2_colname]) ||
76253                                compare_type3(a[sort3_colname], b[sort3_colname]) );
76254                        break;
76255                    case 2: // 0, 1, 0 (desc, asc, desc)
76256                        return( compare_type1(b[sort1_colname], a[sort1_colname]) ||
76257                                compare_type2(a[sort2_colname], b[sort2_colname]) ||
76258                                compare_type3(b[sort3_colname], a[sort3_colname]) );
76259                        break;
76260                    case 3: // 0, 1, 1 (desc, asc, asc)
76261                        return( compare_type1(b[sort1_colname], a[sort1_colname]) ||
76262                                compare_type2(a[sort2_colname], b[sort2_colname]) ||
76263                                compare_type3(a[sort3_colname], b[sort3_colname]) );
76264                        break;
76265                    case 4: // 1, 0, 0 (asc, desc, desc)
76266                        return( compare_type1(a[sort1_colname], b[sort1_colname]) ||
76267                                compare_type2(b[sort2_colname], a[sort2_colname]) ||
76268                                compare_type3(b[sort3_colname], a[sort3_colname]) );
76269                        break;
76270                    case 5: // 1, 0, 1 (asc, desc, asc)
76271                        return( compare_type1(a[sort1_colname], b[sort1_colname]) ||
76272                                compare_type2(b[sort2_colname], a[sort2_colname]) ||
76273                                compare_type3(a[sort3_colname], b[sort3_colname]) );
76274                        break;
76275                    case 6: // 1, 1, 0 (asc, asc, desc)
76276                        return( compare_type1(a[sort1_colname], b[sort1_colname]) ||
76277                                compare_type2(a[sort2_colname], b[sort2_colname]) ||
76278                                compare_type3(b[sort3_colname], a[sort3_colname]) );
76279                        break;
76280                    case 7: // 1, 1, 1 (asc, asc, asc)
76281                        return( compare_type1(a[sort1_colname], b[sort1_colname]) ||
76282                                compare_type2(a[sort2_colname], b[sort2_colname]) ||
76283                                compare_type3(a[sort3_colname], b[sort3_colname]) );
76284                        break;
76285                    default:
76286                        break;
76287                }
76288            });
76289        }
76290
76291        if(_self.on_page_complete)
76292            _self.on_page_complete(_self);
76293    };
76294
76295    /**
76296     * Used to get search results data to turn into XML
76297     *
76298     * @returns {Object} hash of {experiments:[search results]}
76299     */
76300    this.get_data = function () {
76301        return({experiments:_row_search_metadata.results});
76302    };
76303
76304// end those two methods that are used by the search controller, view.
76305
76306
76307// these two support the implied interface for the slick.grid
76308    this.getItem = function (row_index) {
76309        return(do_get_item(row_index));
76310    };
76311
76312    this.getLength = function () {
76313        return(_page_size);
76314    };
76315// end slick.grid functions
76316
76317
76318// these functions & events support the implied interface required by slick.paging
76319    this.onPagingInfoChanged = new EventHelper();
76320
76321    this.setPagingOptions = function (args) {
76322        var info = _self.IshCommon.calc_paging_info(args, _page_num, _page_size, _total_rows);
76323        _no_page = info.no_page;
76324        if (info.no_change) {
76325            return;
76326        }
76327        else {
76328            _page_num = info.page_num;
76329            _page_size = info.page_size;
76330        }
76331
76332        this.onPagingInfoChanged.notify(this.getPagingInfo());
76333
76334        _search_options.limit = _page_size;
76335        _search_options.offset = _page_num * _page_size;
76336
76337        do_search(_search_term, _search_options, 'page');
76338    };
76339
76340    this.getPagingInfo = function () {
76341        return {pageSize: _page_size,
76342            pageNum: _page_num,
76343            totalRows:_total_rows,
76344            showAll: _no_page};
76345    };
76346//  end slick.paging support
76347
76348
76349// event hooks
76350    this.on_search_begin = this.options ? this.options.on_search_begin ? this.options.on_search_begin : null : null;
76351    this.on_search_complete = this.options ? this.options.on_search_complete ? this.options.on_search_complete : null : null;
76352    this.on_sort_complete = this.options ? this.options.on_sort_complete ? this.options.on_sort_complete : null : null;
76353    this.on_page_complete = this.options ? this.options.on_page_complete ? this.options.on_page_complete : null : null;
76354    this.on_search_failure = this.options ? this.options.on_search_failure ? this.options.on_search_failure : null : null;
76355
76356
76357// private properties
76358    var _self = this;
76359    var _query_timeout = 120000; // msc, same as 2 minutes. Note: each row query takes about 40 seconds
76360    var _search_term = '';
76361    var _search_options = {};
76362    var _default_order = 'acronym$asc' + (this.options.sort_suffix ? ',' + this.options.sort_suffix : '');
76363    var _query_set = null;
76364    var _page_size = _self.options ? _self.options.page_size ? _self.options.page_size : 25 : 25;
76365    var _page_num = _self.options ? _self.options.page_num ? _self.options.page_num : 0 : 0;
76366    var _no_page = _self.options ? _self.options.no_page ? _self.options.no_page : false : false;
76367    var _total_rows = 0;
76368    var _callback = null;
76369    var _the_dao = null;
76370    var _the_data = new_data_set();
76371    var _last_query = ""; // used for xml link
76372    var _row_search_metadata = create_row_search_metadata_container();
76373
76374    /**
76375     * Metadata container used when conducting an RMA search for a given row tuple (structure, age, intensity, density)
76376     *   results - accumulated query results for each row search
76377     *   query_set - search params for each row. After a given row search is completed, the used element is deleted.
76378     *   gene_list - list of common gene ids present for each row
76379     *   final_callback - function called after the last row query has completed
76380     *
76381     * @returns {Object} empty row_search_metadata object
76382     */
76383    function create_row_search_metadata_container() {
76384        return({
76385            results: [],
76386            query_set: [],
76387            gene_list: [],
76388            final_callback: null
76389        });
76390    }
76391
76392    /**
76393     * Simulated structure of RMA response data
76394     *
76395     * @returns {Object} empty response data structure
76396     */
76397    function new_data_set() {
76398        return({
76399            id_list: [],
76400            id_count: 0,
76401            metadata: {
76402                start_row: 0,
76403                num_rows: 0,
76404                rows: {}
76405            }
76406        });
76407    }
76408
76409    /**
76410     * Get row metadata
76411     *
76412     * @param row_index
76413     * @returns {Object} hash of row metadata, or empty hash for invalid row
76414     */
76415    function do_get_item(row_index) {
76416        var item = {};
76417
76418        var data_index = (_page_num * _page_size) + row_index;
76419        if (data_index < _total_rows) {
76420            // prefill column data to empty string. Is this necessary?
76421            var len = _self.options.column_config.length;
76422            for (var i = 0; i < len; i++) {
76423                var column = _self.options.column_config[i];
76424                if (column.field && column.field.length)
76425                    item[column.field] = '';
76426            }
76427
76428            item = _the_data.metadata.rows[data_index];
76429        }
76430
76431        return(item);
76432    }
76433
76434    /**
76435     * Entry point for initial search, paging, and column sorting
76436     *
76437     * @param term
76438     * @param options
76439     * @param source
76440     */
76441    function do_search(term, options, source) {
76442        if (_self.on_search_begin)
76443            _self.on_search_begin(_self);
76444
76445        switch (source) {
76446            case 'search':
76447                do_full_search(term, options);
76448                break;
76449            case 'sort':
76450                do_sort(term, options);
76451                break;
76452            case 'page':
76453                do_page(term, options);
76454                break;
76455            default:
76456                throw 'Unknown search source';
76457        }
76458    }
76459
76460    /**
76461     * Two phase search
76462     * 1) get common gene ids
76463     * 2) get gene data
76464     * @param term
76465     * @param options
76466     */
76467    function do_full_search(term, options) {
76468        _search_term = term;
76469        if (options) {
76470            if (options.limit) {
76471                _page_size = options.limit ? options.limit : _page_size;
76472                _page_num = options.offset ? options.offset / _page_size : _page_num;
76473            } else if (options.page_num) {
76474                _page_num = options.page_num;
76475            }
76476        }
76477
76478        var opts = {
76479            order: _default_order
76480        };
76481        $j.extend(opts, options);
76482        _search_options = opts;
76483
76484        opts.success_callback = receive_ids;
76485        opts.error_callback = erroror;
76486
76487        _callback = _self.on_search_complete;
76488        _query_set = decompose_query(_search_term);
76489
76490        get_ids(_query_set, opts);
76491    }
76492
76493
76494    /**
76495     * Sort one column
76496     * @param {String} sort_column column name
76497     * @param {Boolean} sort_asc ascending sort if true, else descending sort
76498     * @param {Object}compare_type function reference to sorting algorithm. Can be (default) compare_strings or compare_ints.
76499     */
76500    function do_sort_one(sort_column, sort_asc, compare_type) {
76501        if (!compare_type)
76502            compare_type = compare_strings;
76503
76504        var field_name = sort_column;//order_field_for_field_name(sort_column);
76505        _the_data.metadata.rows = _the_data.metadata.rows.sort(function(a, b) {
76506                if(sort_asc)
76507                    return( compare_type(a[field_name], b[field_name]) );
76508                else
76509                    return( compare_type(b[field_name], a[field_name]) );
76510        });
76511    }
76512
76513    function compare_strings(a, b) {
76514        return( a > b ? 1 : b > a ? -1 : 0);
76515    }
76516
76517    function compare_ints(a, b) {
76518        var ai = parseInt(a);
76519        var bi = parseInt(b);
76520        return( ai > bi ? 1 : bi > ai ? -1 : 0);
76521    }
76522    
76523    function do_page(term, options) {
76524        // all of the data is present in this model, so return immediately.
76525        if(_self.on_page_complete)
76526            _self.on_page_complete(_self);
76527    }
76528
76529    function receive_ids(response_data) {
76530        // the response data has a key 'response' which is meaningless in this context.
76531        delete response_data['response'];
76532
76533        // empty skeleton to be populated
76534        _the_data = new_data_set();
76535
76536        _the_data.id_list = response_data;
76537        _the_data.id_count = _the_data.id_list.length;
76538
76539        // now get a page of metadata
76540        fetch_metadata(_query_set, receive_metadata);
76541    }
76542
76543    function receive_metadata(response_data) {
76544        _the_data.metadata.start_row = _page_num * _page_size;
76545        _the_data.metadata.rows = response_data;
76546        _the_data.metadata.num_rows = response_data.length;
76547        _total_rows = response_data.length;
76548
76549        if (_callback)
76550            _callback(_self);
76551
76552        // let the pager know about it
76553        _self.onPagingInfoChanged.notify(_self.getPagingInfo());
76554    }
76555
76556
76557
76558    function erroror(xhdr, text_status, error) {
76559        var err = xhdr.msg == undefined ? error : inspect('', xhdr);
76560
76561        _the_data = new_data_set();
76562        _the_data.id_list = [];
76563        _the_data.id_count = 0;
76564        _total_rows = _the_data.id_count;
76565
76566        if (_self.on_search_failure)
76567            _self.on_search_failure(_self, err);
76568    }
76569
76570    /**
76571     * Decompose encoded search terms from url args.
76572     * Typical term looks like:
76573     *     s0*10038+a0*12 mo,48 mo+i0*0,1,2,3,4,5+d0*0,1,2,3,4,5
76574     * where structure for query row 0 has id = 10038,
76575     * where age for query row 0 has ages = 12 mo and 48 mo
76576     * where intensity for query row 0 has magnitudes = 0, 1, 2, 3, 4, 5
76577     * where density for query row 0 has magnitudes = 0, 1, 2, 3, 4, 5
76578     *
76579     * @param term
76580     * @returns {Array} each array element corresponds to a single 'query row'. The array element is an object:
76581     * {structure[], age[], intensity[], density[]}
76582     */
76583    function decompose_query(term) {
76584        var q = new manual_annotation_query_set(term);
76585        return(q.query_set);
76586    }
76587
76588
76589    /**
76590     * Construct RMA query to find genes common amongst each user-defined row tuple (struct/age/intensity/density)
76591     *
76592     * At each stage of the query (that is, for each row in the query_set) an RMA query is made for
76593     * matching Gene IDs. The return set is then piped to the next stage in the query, until the last
76594     * stage, when the gene IDs are returned as the query result.
76595     *
76596     * @param {Array} query_set each element is a hash of search params for each search row tuple
76597     * @return {String} RMA request
76598     */
76599    function construct_id_request(query_set) {
76600        var row_count = query_set.length;
76601        var query_string = "";
76602
76603        // for each query row in the query_set
76604        for (var i = 0; i < row_count; i++) {
76605            var row = query_set[i];
76606
76607            // for the first row we don't need a Model declaration.
76608            if (i > 0)
76609                query_string += ",model::ManualAnnotation,rma::criteria,";
76610
76611            query_string += "structure[id$in" + row.structure.join(',') + "]";
76612
76613            var intensity_string = row.intensity.map(function (e) {
76614                return("'" + e + "'");
76615            });
76616            query_string += ",[intensity_call$in" + intensity_string.join(',') + "]";
76617
76618            var density_string = row.density.map(function (e) {
76619                return("'" + e + "'");
76620            });
76621            query_string += ",[density_call$in" + density_string.join(',') + "]";
76622
76623            var age_string = row.age.map(function (e) {
76624                return("'" + e + "'");
76625            });
76626            var gene_restriction = (i > 0) ? "[id$in$var" + (i - 1) + "]" : "";
76627            query_string += ",section_data_set(products[id$eq18],genes" + gene_restriction + ",specimen(donor(age[name$in" + age_string.join(',') + "])))";
76628
76629            query_string += ",rma::options[num_rows$eq'all']";
76630            query_string += ",rma::options[count$eqfalse]";
76631
76632            // unless this is the last query row, we're piping the gene IDs to the next stage in the query.
76633            if (i < row_count - 1) {
76634                query_string += ",rma::options[tabular$eq'distinct genes.id']";
76635                query_string += ",pipe::list[var" + i + "$eq'*/id']";
76636            }
76637            else {
76638                query_string += ",rma::options[tabular$eq'distinct genes.id,genes.acronym,genes.name']";
76639            }
76640        }
76641
76642        return(query_string);
76643    }
76644
76645    /**
76646     * Create RMA query for a single row tuple, restricted to the genes common to all of the row tuples
76647     *
76648     * @param {Object} single_query_set hash of search params for each search row tuple
76649     * @param {Array} gene_id_list gene ids common to each search row tuple
76650     * @returns {string} RMA query
76651     */
76652    function construct_row_tuple_metadata_request(single_query_set, gene_id_list) {
76653        var query_string = "";
76654
76655        var row = single_query_set;
76656
76657        query_string += "model::ManualAnnotation,rma::criteria,";
76658        query_string += "structure[id$in" + row.structure.join(',') + "]";
76659
76660        var intensity_string = row.intensity.map(function (e) {
76661            return("'" + e + "'");
76662        });
76663        query_string += ",[intensity_call$in" + intensity_string.join(',') + "]";
76664
76665        var density_string = row.density.map(function (e) {
76666            return("'" + e + "'");
76667        });
76668        query_string += ",[density_call$in" + density_string.join(',') + "]";
76669
76670        var age_string = row.age.map(function (e) {
76671            return("'" + e + "'");
76672        });
76673        query_string += ",section_data_set(products[id$eq18],genes[id$in" + (gene_id_list.join(',')) + "]";
76674        query_string += ",specimen(donor(age[name$in" + age_string.join(',') + "])))";
76675
76676        query_string += ",rma::options[num_rows$eq'all']";
76677        query_string += ",rma::options[count$eqfalse]";
76678
76679        // final tabular output
76680        query_string += ",rma::options[tabular$eq' distinct genes.id as gene_id,genes.acronym as gene_symbol,genes.name as gene_name,";
76681        query_string += "data_sets.id as data_set_id,intensity_call,density_call,structures.id as structure_id,structures.graph_order,";
76682        query_string += "structures.acronym as structure_acronym,structures.name as structure_name,structures.color_hex_triplet,";
76683        query_string += "ages.name as age_name,ages.days as age_days,ages.embryonic,donors.id as donor_id']";
76684
76685        return(query_string);
76686    }
76687
76688    /**
76689     * Conducts a query for common gene ids
76690     *
76691     * @param query_set
76692     * @param options
76693     * @returns {*}
76694     */
76695    function get_ids(query_set, options) {
76696        var new_q = construct_id_request(query_set);
76697
76698        options.criteria = new_q;
76699        options.format = 'json';
76700        options.verb = 'POST';
76701
76702        // we want to fetch all the IDs, ordered as we specified.  The ordering
76703        // of the IDs will be the order in which the data is displayed.
76704        options.limit = 'all';
76705
76706        return(_the_dao.query('ManualAnnotation', options));
76707    }
76708
76709    function fetch_metadata(query_set, callback) {
76710        var gene_set = _the_data.id_list;//.slice(row_start, (row_start + _page_size));
76711        var gene_list = gene_set.map(function (gene) {
76712            return(gene.id);
76713        });
76714
76715        if (!gene_list || !gene_list.length) {
76716            erroror({}, null, "there are no genes matching this criteria");
76717            return;
76718        }
76719
76720
76721        var query_set_copy = [];
76722        $j.extend(true, query_set_copy, query_set);
76723
76724        _row_search_metadata = create_row_search_metadata_container();
76725        _row_search_metadata.query_set = query_set_copy;
76726        _row_search_metadata.final_callback = callback;
76727        _row_search_metadata.gene_list = gene_list;
76728
76729        row_tuple_search(null);
76730
76731        // !!!! old code, only one call, logic not what we want
76732//        var new_q = construct_metadata_request(query_set, gene_list);
76733//
76734//        var options = {};
76735//        options.criteria = new_q;
76736//        options.format = 'json';
76737//        options.verb = 'GET';
76738//        options.success_callback = callback;
76739//        options.error_callback = erroror;
76740//
76741//        // Because we're limiting the metadata by restricting to just the
76742//        // exact set of IDs we're passing in, we are working around the RMA
76743//        // peculiarity of summing inner and outer rows for its row_count.
76744//        options.limit = 'all';
76745//
76746//        _last_query = {};
76747//        $j.extend(true, _last_query, options);
76748//
76749//        return(_the_dao.query('Gene', options));
76750    }
76751
76752    /**
76753     * Gene search on a single tuple row.
76754     *
76755     * This function will be called once for each user-defined tuple row (structure(s), age(s), intensity(s), density(s)),
76756     * accumulating the intermediate search within _row_search_metadata.results.
76757     *
76758     * _row_search_metadata is altered by this function:
76759     *    results - concat new row tuple search results to this array
76760     *    query_set - after row tuple search, delete array element representing the old query params
76761     *
76762     * @param {Object} response_data search results from previous call to row_tuple_search. Initial call to this
76763     *  function sets response_data to null.
76764     */
76765    function row_tuple_search(response_data) {
76766        var options = {};
76767
76768        if (response_data != null) {
76769            // accumulate results from previous query, remove already-used query_set
76770            $j.merge(_row_search_metadata.results, response_data);
76771            _row_search_metadata.query_set.splice(0, 1);
76772        }
76773
76774        if (_row_search_metadata.query_set.length == 0) {
76775            // finished
76776            _row_search_metadata.final_callback(_row_search_metadata.results);
76777        }
76778        else {
76779            var query = construct_row_tuple_metadata_request(_row_search_metadata.query_set[0], _row_search_metadata.gene_list);
76780            options.criteria = query;
76781            options.format = 'json';
76782            options.verb = 'GET';
76783            options.success_callback = row_tuple_search; // recursive
76784            options.error_callback = erroror;
76785
76786            // Because we're limiting the metadata by restricting to just the
76787            // exact set of IDs we're passing in, we are working around the RMA
76788            // peculiarity of summing inner and outer rows for its row_count.
76789            options.limit = 'all';
76790
76791            var options_copy = {};
76792            $j.extend(true, options_copy, options);
76793
76794            _the_dao.query('ManualAnnotation', options_copy);
76795        }
76796    }
76797
76798    function init() {
76799        if (!AIBS.DATA.API_DAO)
76800            throw "The image model requires AIBS.DATA.API_DAO, which is not present.";
76801
76802        _the_dao = new AIBS.DATA.API_DAO(_self.rma_dao.service_url, {timeout:_query_timeout});
76803    }
76804
76805    init();
76806
76807}
76808;
76809
76810function microarray_gene_classification_heatmap_model(daos, options)
76811{
76812    this._rma_dao = daos.data;
76813    this.options = options;
76814
76815
76816    this.fetch_probe_ids_by_gene_classification = function(gene_search_term, options)
76817    {
76818        var term = $j.trim(gene_search_term);
76819        if(!term || term.length == 0)
76820            throw "Invalid search term; must include at least one character.";
76821
76822        var dao_options = {
76823            success_callback: this.options.success_callback,
76824            error_callback: this.options.error_callback
76825        };
76826
76827        this._rma_dao.probe_id_by_gene_classification( term, dao_options);
76828    }
76829
76830
76831    this.search = function( term, options ) {
76832         return this.fetch_probe_ids_by_gene_classification(term, options);
76833    }
76834
76835    var _self = this;
76836};
76837
76838function microarray_heatmap_model(daos, options)
76839{
76840    this._rma_dao = daos.data;
76841    this.options = options;
76842
76843
76844    this.fetch_probe_ids = function(gene_search_term, options)
76845    {
76846        var term = $j.trim(gene_search_term);
76847        if(!term || term.length == 0)
76848            throw "Invalid search term; must include at least one character.";
76849
76850        var dao_options = {
76851            success_callback: this.options.success_callback,
76852            error_callback: this.options.error_callback
76853        };
76854
76855        $j.extend( dao_options, options);
76856        this._rma_dao.affymetrix_probe_id_by_gene_term( term, dao_options);
76857    }
76858
76859    this.search = function( term, options ) {
76860        return this.fetch_probe_ids( term, options);
76861    }
76862
76863    var _self = this;
76864};
76865//
76866// This model only invokes the callback method to create a heatmap control object
76867// This is used by the correlation query as well as the differential query
76868//
76869function microarray_heatmap_pass_through_model(daos, options)
76870{
76871    this._rma_dao = daos.data;
76872    this.options = options;
76873
76874    this.search = function( term, options ) {
76875        // The parameter "term" should be null, and should be ignored
76876        return this.options.prepare_heatmap_method( term, options);
76877    }
76878
76879    var _self = this;
76880};
76881
76882function microarray_browse_controller(browse_container, search_container, page, options) {
76883
76884    this.container = $j(browse_container);
76885    this.search_container = $j(search_container);
76886    this.page = page;
76887    this.settings = {};
76888    $j.extend(true, this.settings, options);
76889
76890
76891    var _self = this;
76892    var _the_app = _self.page.application;
76893    var _the_page = _self.page;
76894    var _the_daos = _the_app.DAOs();
76895    var _page_config = _the_page.get_config();
76896    var _component_configs = _page_config.components;
76897    var _urls = _the_app.settings.urls;
76898
76899    var _search_widget = null;
76900    var _landing_widget = null;
76901
76902    var _donor_list = null;
76903    var _stage_list = null;
76904
76905    function build_search_widget() {
76906
76907        var search_options = {
76908            on_search:load_search_page,
76909            on_search_type_change:do_search_type_change,
76910            on_ontology_error:show_ontology_error,
76911            differential:{
76912                help_url:_urls.micro_differential_help
76913            },
76914            stage_list:_stage_list
76915        };
76916        _search_widget = new ish_search_widget(_self.search_container, _the_daos.data, _component_configs.search_widget.search_plugins, search_options);
76917    }
76918
76919    function do_search_type_change(sender, new_type) {
76920
76921        if(_landing_widget)
76922            _landing_widget.set_landing_mode(new_type);
76923    }
76924
76925    function encode_each_term(term, delimiter) {
76926        var terms = term.split(delimiter);
76927
76928        if(terms.length == 1)
76929            return(trim(term));
76930
76931        var term_string = '';
76932        for(var i = 0; i < terms.length; i++)
76933            term_string += trim(terms[i]) + '$';
76934
76935        return(term_string);
76936    }
76937
76938    function encode_term(term) {
76939        return(encode_each_term(encode_each_term(term, '\n'), '\t'));
76940    }
76941
76942    function encode_object(obj) {
76943        var val = ''
76944        for(var key in obj) {
76945            if(val.length)
76946                val += '&';
76947            val += key + '=' + new String(obj[key]);
76948        }
76949        return(val);
76950    }
76951
76952    function trim(s)
76953    {
76954        var l = 0;
76955        var r = s.length - 1;
76956
76957        while(l < s.length && s[l] == ' ') l++;
76958        while(r > l && s[r] == ' ') r -= 1;
76959
76960        return s.substring(l, r+1);
76961    }
76962
76963//    function load_search_page(state) {
76964//
76965//        state.page_num = 0;
76966//        state.page_size = 22;
76967//        state.no_paging = false;
76968//
76969//        var state_string = "?";
76970//        var count = 0;
76971//        var val = "";
76972//
76973//        for(var key in state) {
76974//            if(count++)
76975//                state_string += "&";
76976//
76977//            val =  encode_term(new String(state[key]));
76978//            state_string += key + "=" + val;
76979//        }
76980//
76981//        //var url = _urls.search + state_string;
76982//        var url = "";
76983//        switch(state.search_type) {
76984//            case 'search':
76985//                url = _urls.search;
76986//                break;
76987//            case 'sleep_study':
76988//                url = _urls.sleep_search;
76989//                break;
76990//            case 'mouse_diversity_study':
76991//                url = _urls.diversity_search;
76992//                break;
76993//            default:
76994//                url = _urls.search;
76995//        }
76996//        url += state_string;
76997//        window.location.href = url;
76998//    }
76999
77000    function load_search_page(state) {
77001
77002        state.page_num = 0;
77003        state.page_size = 22;
77004        state.no_paging = false;
77005
77006        var state_string = "?";
77007        var count = 0;
77008        var val = "";
77009
77010        for(var key in state) {
77011
77012            if(count++)
77013                state_string += "&";
77014
77015            if( (typeof state[key]) == "object" && ( state[key] instanceof Array ) && state[key] !== null ){
77016                state_string += encode_array(state[key], key);
77017            }
77018            else if((typeof state[key]) == "object" && state[key] !== null) {
77019                state_string += encode_object(state[key]);
77020            } else {
77021                val =  encode_term(new String(state[key]));
77022                state_string += key + "=" + val;
77023            }
77024        }
77025
77026        var url = "";
77027        switch(state.search_type) {
77028            case 'search':
77029                url = _urls.search;
77030                break;
77031            case 'sleep_study':
77032                url = _urls.sleep_search;
77033                break;
77034            case 'mouse_diversity_study':
77035                url = _urls.diversity_search;
77036                break;
77037            default:
77038                url = _urls.search;
77039        }
77040        url += state_string;
77041        window.location.href = url;
77042    }
77043
77044    function encode_array(arr,attr_key) {
77045
77046        var target_str = '';
77047        for(var i=0; i < arr.length; i++) {
77048            if(i>0)
77049                target_str += ',';
77050            else
77051                target_str += attr_key + '=';
77052            target_str += arr[i];
77053        }
77054        return target_str;
77055    }
77056
77057    function show_ontology_error(err) {
77058
77059    }
77060
77061    function build_landing_widget() {
77062
77063        _landing_widget = new landing_widget(_self.container, _the_daos, _urls, _component_configs.landing_widget );
77064    }
77065
77066    function init() {
77067
77068        build_search_widget();
77069        build_landing_widget();
77070    }
77071
77072
77073    function prepare_donor_data_before_initialization() {
77074        var dao_options = {
77075            success_callback: received_donor_list,
77076            error_callback: failure_receiving_donor_info
77077        };
77078
77079        _the_daos.data.donor_info_by_product_ids( '', dao_options);  // the app page's default product id will be used
77080    }
77081
77082    function received_donor_list(response_data, text_status, xhdr) {
77083
77084        var donor_array = new Array();
77085        var donor_index = 0;
77086        _stage_list = new Array();
77087
77088        for ( var i = 0; i < response_data.length; i++ ) {
77089            var donor = response_data[i];
77090                donor_array[donor_index++] = {"donor":{"id":donor.id,"name":donor.name}}
77091            if(i==0 || response_data[i].age.id != response_data[i-1].age.id) {
77092                _stage_list.push(donor.age);
77093            }
77094        }
77095        _donor_list = donor_array;
77096
77097        init();
77098    }
77099
77100    function received_no_donor_list()
77101    {
77102        alert("Failed to fetch a list of donors.");
77103    }
77104
77105    function failure_receiving_donor_info(response) {
77106        //var cont = $j('#search_page_content');
77107        var cont = $j('#landing_container');
77108        var err_msg = 'Your search could not be completed';
77109
77110        if(response) {
77111            if(response.msg)
77112                err_msg += ' ('+ response.msg +')';
77113            else {
77114                if (typeof response ===  'string') {
77115                    err_msg += ' ('+ response.msg +')';
77116                }
77117                else {
77118                    err_msg += ' (the data service may be unresponsive)';
77119                }
77120            }
77121        }
77122        else {
77123            err_msg += '.';
77124        }
77125
77126        err_msg += '<br/><br/>Please try later.';
77127
77128        container_error(cont, err_msg);
77129    }
77130
77131    function container_error(container, msg) {
77132        // it would be nice to have one central
77133        // place for handling UI error reporting...
77134        //     if(_error_manager) {
77135        //         _error_manager.ui_report(_self, container, msg);
77136        //         return;
77137        //     }
77138
77139        var err_html = "<div class='err_notice ui-state-error'>" + msg + "</div>";
77140        container.html(err_html);
77141    }
77142
77143    prepare_donor_data_before_initialization();
77144}
77145;
77146// This is a manifest file that'll be compiled into including all the files listed below.
77147// Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
77148// be included in the compiled file accessible from http://example.com/assets/application.js
77149// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
77150// the compiled file.
77151//
77152
77153
77154
77155// -- jquery-migrate needed by ui-spinner.js v1.20
77156
77157
77158
77159
77160// -- SlickGrid requires presence of these 3rd party jquery drag/drop libraries, must load before SlickGrid
77161
77162
77163
77164
77165
77166
77167
77168
77169
77170
77171
77172
77173
77174
77175
77176
77177
77178
77179
77180
77181
77182
77183
77184
77185
77186
77187
77188
77189
77190
77191
77192
77193
77194
77195
77196
77197
77198
77199
77200
77201;

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.