<commit_before>/*
* maskMoney plugin for jQuery
* http://plentz.github.com/jquery-maskmoney/
* version: 2.1.1
* Licensed under the MIT license
*/
;(function($) {
	if(!$.browser){
		$.browser = {};
		$.browser.mozilla = /mozilla/.test(navigator.userAgent.toLowerCase()) && !/webkit/.test(navigator.userAgent.toLowerCase());
		$.browser.webkit = /webkit/.test(navigator.userAgent.toLowerCase());
		$.browser.opera = /opera/.test(navigator.userAgent.toLowerCase());
		$.browser.msie = /msie/.test(navigator.userAgent.toLowerCase());
	}

	var methods = {
		destroy : function(){
			var input = $(this);
			input.unbind('.maskMoney');

			if ($.browser.msie) {
				this.onpaste = null;
			}
			return this;
		},

		mask : function(){
			return this.trigger('mask');
		},

		init : function(settings) {
			settings = $.extend({
				symbol: '',
				symbolStay: false,
				thousands: ',',
				decimal: '.',
				precision: 2,
				defaultZero: true,
				allowZero: false,
				allowNegative: false
			}, settings);

			return this.each(function() {
				var input = $(this);
				var dirty = false;

				function markAsDirty() {
					dirty = true;
				}

				function clearDirt(){
					dirty = false;
				}

				function keypressEvent(e) {
					e = e || window.event;
					var k = e.which || e.charCode || e.keyCode;
					if (k == undefined) return false; //needed to handle an IE "special" event
					if (k < 48 || k > 57) { // any key except the numbers 0-9
						if (k == 45) { // -(minus) key
							markAsDirty();
							input.val(changeSign(input));
							return false;
						} else if (k == 43) { // +(plus) key
							markAsDirty();
							input.val(input.val().replace('-',''));
							return false;
						} else if (k == 13 || k == 9) { // enter key or tab key
							if(dirty){
								clearDirt();
								$(this).change();
							}
							return true;
						} else if ($.browser.mozilla && (k == 37 || k == 39) && e.charCode == 0) {
							// needed for left arrow key or right arrow key with firefox
							// the charCode part is to avoid allowing '%'(e.charCode 0, e.keyCode 37)
							return true;
						} else { // any other key with keycode less than 48 and greater than 57
							preventDefault(e);
							return true;
						}
					} else if (input.val().length >= input.attr('maxlength') && input.attr('maxlength') >= 0) {
						return false;
					} else {
						preventDefault(e);

						var key = String.fromCharCode(k);
						var x = input.get(0);
						var selection = getInputSelection(x);
						var startPos = selection.start;
						var endPos = selection.end;
						x.value = x.value.substring(0, startPos) + key + x.value.substring(endPos, x.value.length);
						maskAndPosition(x, startPos + 1);
						markAsDirty();
						return false;
					}
				}

				function keydownEvent(e) {
					e = e||window.event;
					var k = e.which || e.charCode || e.keyCode;
					if (k == undefined) return false; //needed to handle an IE "special" event

					var x = input.get(0);
					var selection = getInputSelection(x);
					var startPos = selection.start;
					var endPos = selection.end;

					if (k==8) { // backspace key
						preventDefault(e);

						if(startPos == endPos){
							// Remove single character
							x.value = x.value.substring(0, startPos - 1) + x.value.substring(endPos, x.value.length);
							startPos = startPos - 1;
						} else {
							// Remove multiple characters
							x.value = x.value.substring(0, startPos) + x.value.substring(endPos, x.value.length);
						}
						maskAndPosition(x, startPos);
						markAsDirty();
						return false;
					} else if (k==9) { // tab key
						if(dirty) {
							$(this).change();
							clearDirt();
						}
						return true;
					} else if ( k==46 || k==63272 ) { // delete key (with special case for safari)
						preventDefault(e);
						if(x.selectionStart == x.selectionEnd){
							// Remove single character
							x.value = x.value.substring(0, startPos) + x.value.substring(endPos + 1, x.value.length);
						} else {
							//Remove multiple characters
							x.value = x.value.substring(0, startPos) + x.value.substring(endPos, x.value.length);
						}
						maskAndPosition(x, startPos);
						markAsDirty();
						return false;
					} else { // any other key
						return true;
					}
				}

				function focusEvent(e) {
					var mask = getDefaultMask();
					if (input.val() == mask) {
						input.val('');
					} else if (input.val()=='' && settings.defaultZero) {
						input.val(setSymbol(mask));
					} else {
						input.val(setSymbol(input.val()));
					}
					if (this.createTextRange) {
						var textRange = this.createTextRange();
						textRange.collapse(false); // set the cursor at the end of the input
						textRange.select();
					}
				}

				function blurEvent(e) {
					if ($.browser.msie) {
						keypressEvent(e);
					}

					if (input.val() == '' || input.val() == setSymbol(getDefaultMask()) || input.val() == settings.symbol) {
						if(!settings.allowZero){
							input.val('');
						} else if (!settings.symbolStay){
							input.val(getDefaultMask());
						} else {
							input.val(setSymbol(getDefaultMask()));
						}
					} else {
						if (!settings.symbolStay){
							input.val(input.val().replace(settings.symbol,''));
						} else if (settings.symbolStay && input.val() == settings.symbol){
							input.val(setSymbol(getDefaultMask()));
						}
					}
				}

				function preventDefault(e) {
					if (e.preventDefault) { //standard browsers
						e.preventDefault();
					} else { // old internet explorer
						e.returnValue = false
					}
				}

				function maskAndPosition(x, startPos) {
					var originalLen = input.val().length;
					input.val(maskValue(x.value));
					var newLen = input.val().length;
					startPos = startPos - (originalLen - newLen);
					setCursorPosition(input, startPos);
				}

				function mask(){
					var value = input.val();
					input.val(maskValue(value));
				}

				function maskValue(v) {
					v = v.replace(settings.symbol, '');

					var strCheck = '0123456789';
					var len = v.length;
					var a = '', t = '', neg='';

					if(len != 0 && v.charAt(0)=='-'){
						v = v.replace('-','');
						if(settings.allowNegative){
							neg = '-';
						}
					}

					if (len==0) {
						if (!settings.defaultZero) return t;
						t = '0.00';
					}

					for (var i = 0; i<len; i++) {
						if ((v.charAt(i)!='0') && (v.charAt(i)!=settings.decimal)) break;
					}

					for (; i < len; i++) {
						if (strCheck.indexOf(v.charAt(i))!=-1) a+= v.charAt(i);
					}
					var n = parseFloat(a);

					n = isNaN(n) ? 0 : n/Math.pow(10,settings.precision);
					t = n.toFixed(settings.precision);

					i = settings.precision == 0 ? 0 : 1;
					var p, d = (t=t.split('.'))[i].substr(0,settings.precision);
					for (p = (t=t[0]).length; (p-=3)>=1;) {
						t = t.substr(0,p)+settings.thousands+t.substr(p);
					}

					return (settings.precision>0)
					? setSymbol(neg+t+settings.decimal+d+Array((settings.precision+1)-d.length).join(0))
					: setSymbol(neg+t);
				}

				function getDefaultMask() {
					var n = parseFloat('0')/Math.pow(10,settings.precision);
					return (n.toFixed(settings.precision)).replace(new RegExp('\\.','g'),settings.decimal);
				}

				function setSymbol(value){
					if (settings.symbol != ''){
						var operator = '';
						if(value.length != 0 && value.charAt(0) == '-'){
							value = value.replace('-', '');
							operator = '-';
						}

						if(value.substr(0, settings.symbol.length) != settings.symbol){
							value = operator + settings.symbol + value;
						}
					}
					return value;
				}

				function changeSign(input){
					var inputValue = input.val();
					if (settings.allowNegative){
						if (inputValue != '' && inputValue.charAt(0) == '-'){
							return inputValue.replace('-','');
						} else {
							return '-' + inputValue;
						}
					} else {
						return inputValue;
					}
				}

				function setCursorPosition(input, pos) {
					// I'm not sure if we need to jqueryfy input
					$(input).each(function(index, elem) {
						if (elem.setSelectionRange) {
							elem.focus();
							elem.setSelectionRange(pos, pos);
						} else if (elem.createTextRange) {
							var range = elem.createTextRange();
							range.collapse(true);
							range.moveEnd('character', pos);
							range.moveStart('character', pos);
							range.select();
						}
					});
					return this;
				};

				function getInputSelection(el) {
					var start = 0, end = 0, normalizedValue, range, textInputRange, len, endRange;

					if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
						start = el.selectionStart;
						end = el.selectionEnd;
					} else {
						range = document.selection.createRange();

						if (range && range.parentElement() == el) {
							len = el.value.length;
							normalizedValue = el.value.replace(/\r\n/g, "\n");

							// Create a working TextRange that lives only in the input
							textInputRange = el.createTextRange();
							textInputRange.moveToBookmark(range.getBookmark());

							// Check if the start and end of the selection are at the very end
							// of the input, since moveStart/moveEnd doesn't return what we want
							// in those cases
							endRange = el.createTextRange();
							endRange.collapse(false);

							if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
								start = end = len;
							} else {
								start = -textInputRange.moveStart("character", -len);
								start += normalizedValue.slice(0, start).split("\n").length - 1;

								if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
									end = len;
								} else {
									end = -textInputRange.moveEnd("character", -len);
									end += normalizedValue.slice(0, end).split("\n").length - 1;
								}
							}
						}
					}

					return {
						start: start,
						end: end
					};
				} // getInputSelection

				if (!input.attr("readonly")){
					input.unbind('.maskMoney');
					input.bind('keypress.maskMoney', keypressEvent);
					input.bind('keydown.maskMoney', keydownEvent);
					input.bind('blur.maskMoney', blurEvent);
					input.bind('focus.maskMoney', focusEvent);
					input.bind('mask.maskMoney', mask);
				}
			})
		}
	}

	$.fn.maskMoney = function(method) {
		if ( methods[method] ) {
			return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist on jQuery.maskMoney' );
		}
	};
})(jQuery);
<commit_msg>allow changing a number by selecting it even if maxlength is reached. fixes #70<commit_after>/*
* maskMoney plugin for jQuery
* http://plentz.github.com/jquery-maskmoney/
* version: 2.1.1
* Licensed under the MIT license
*/
;(function($) {
	if(!$.browser){
		$.browser = {};
		$.browser.mozilla = /mozilla/.test(navigator.userAgent.toLowerCase()) && !/webkit/.test(navigator.userAgent.toLowerCase());
		$.browser.webkit = /webkit/.test(navigator.userAgent.toLowerCase());
		$.browser.opera = /opera/.test(navigator.userAgent.toLowerCase());
		$.browser.msie = /msie/.test(navigator.userAgent.toLowerCase());
	}

	var methods = {
		destroy : function(){
			var input = $(this);
			input.unbind('.maskMoney');

			if ($.browser.msie) {
				this.onpaste = null;
			}
			return this;
		},

		mask : function(){
			return this.trigger('mask');
		},

		init : function(settings) {
			settings = $.extend({
				symbol: '',
				symbolStay: false,
				thousands: ',',
				decimal: '.',
				precision: 2,
				defaultZero: true,
				allowZero: false,
				allowNegative: false
			}, settings);

			return this.each(function() {
				var input = $(this);
				var dirty = false;

				function markAsDirty() {
					dirty = true;
				}

				function clearDirt(){
					dirty = false;
				}

				function keypressEvent(e) {
					e = e || window.event;
					var k = e.which || e.charCode || e.keyCode;
					if (k == undefined) return false; //needed to handle an IE "special" event
					if (k < 48 || k > 57) { // any key except the numbers 0-9
						if (k == 45) { // -(minus) key
							markAsDirty();
							input.val(changeSign(input));
							return false;
						} else if (k == 43) { // +(plus) key
							markAsDirty();
							input.val(input.val().replace('-',''));
							return false;
						} else if (k == 13 || k == 9) { // enter key or tab key
							if(dirty){
								clearDirt();
								$(this).change();
							}
							return true;
						} else if ($.browser.mozilla && (k == 37 || k == 39) && e.charCode == 0) {
							// needed for left arrow key or right arrow key with firefox
							// the charCode part is to avoid allowing '%'(e.charCode 0, e.keyCode 37)
							return true;
						} else { // any other key with keycode less than 48 and greater than 57
							preventDefault(e);
							return true;
						}
					} else if (canInputMoreNumbers(input)) {
						return false;
					} else {
						preventDefault(e);

						var key = String.fromCharCode(k);
						var x = input.get(0);
						var selection = getInputSelection(x);
						var startPos = selection.start;
						var endPos = selection.end;
						x.value = x.value.substring(0, startPos) + key + x.value.substring(endPos, x.value.length);
						maskAndPosition(x, startPos + 1);
						markAsDirty();
						return false;
					}
				}

				function canInputMoreNumbers(element){
					var reachedMaxLenght = (element.val().length >= element.attr('maxlength') && element.attr('maxlength') >= 0);
					var selection = getInputSelection(element.get(0));
					var start = selection.start;
					var end = selection.end;
					var hasNumberSelected = (selection.start != selection.end && element.val().substring(start,end).match(/\d/))? true : false;
					return reachedMaxLenght && !hasNumberSelected;
				}

				function keydownEvent(e) {
					e = e||window.event;
					var k = e.which || e.charCode || e.keyCode;
					if (k == undefined) return false; //needed to handle an IE "special" event

					var x = input.get(0);
					var selection = getInputSelection(x);
					var startPos = selection.start;
					var endPos = selection.end;

					if (k==8) { // backspace key
						preventDefault(e);

						if(startPos == endPos){
							// Remove single character
							x.value = x.value.substring(0, startPos - 1) + x.value.substring(endPos, x.value.length);
							startPos = startPos - 1;
						} else {
							// Remove multiple characters
							x.value = x.value.substring(0, startPos) + x.value.substring(endPos, x.value.length);
						}
						maskAndPosition(x, startPos);
						markAsDirty();
						return false;
					} else if (k==9) { // tab key
						if(dirty) {
							$(this).change();
							clearDirt();
						}
						return true;
					} else if ( k==46 || k==63272 ) { // delete key (with special case for safari)
						preventDefault(e);
						if(x.selectionStart == x.selectionEnd){
							// Remove single character
							x.value = x.value.substring(0, startPos) + x.value.substring(endPos + 1, x.value.length);
						} else {
							//Remove multiple characters
							x.value = x.value.substring(0, startPos) + x.value.substring(endPos, x.value.length);
						}
						maskAndPosition(x, startPos);
						markAsDirty();
						return false;
					} else { // any other key
						return true;
					}
				}

				function focusEvent(e) {
					var mask = getDefaultMask();
					if (input.val() == mask) {
						input.val('');
					} else if (input.val()=='' && settings.defaultZero) {
						input.val(setSymbol(mask));
					} else {
						input.val(setSymbol(input.val()));
					}
					if (this.createTextRange) {
						var textRange = this.createTextRange();
						textRange.collapse(false); // set the cursor at the end of the input
						textRange.select();
					}
				}

				function blurEvent(e) {
					if ($.browser.msie) {
						keypressEvent(e);
					}

					if (input.val() == '' || input.val() == setSymbol(getDefaultMask()) || input.val() == settings.symbol) {
						if(!settings.allowZero){
							input.val('');
						} else if (!settings.symbolStay){
							input.val(getDefaultMask());
						} else {
							input.val(setSymbol(getDefaultMask()));
						}
					} else {
						if (!settings.symbolStay){
							input.val(input.val().replace(settings.symbol,''));
						} else if (settings.symbolStay && input.val() == settings.symbol){
							input.val(setSymbol(getDefaultMask()));
						}
					}
				}

				function preventDefault(e) {
					if (e.preventDefault) { //standard browsers
						e.preventDefault();
					} else { // old internet explorer
						e.returnValue = false
					}
				}

				function maskAndPosition(x, startPos) {
					var originalLen = input.val().length;
					input.val(maskValue(x.value));
					var newLen = input.val().length;
					startPos = startPos - (originalLen - newLen);
					setCursorPosition(input, startPos);
				}

				function mask(){
					var value = input.val();
					input.val(maskValue(value));
				}

				function maskValue(v) {
					v = v.replace(settings.symbol, '');

					var strCheck = '0123456789';
					var len = v.length;
					var a = '', t = '', neg='';

					if(len != 0 && v.charAt(0)=='-'){
						v = v.replace('-','');
						if(settings.allowNegative){
							neg = '-';
						}
					}

					if (len==0) {
						if (!settings.defaultZero) return t;
						t = '0.00';
					}

					for (var i = 0; i<len; i++) {
						if ((v.charAt(i)!='0') && (v.charAt(i)!=settings.decimal)) break;
					}

					for (; i < len; i++) {
						if (strCheck.indexOf(v.charAt(i))!=-1) a+= v.charAt(i);
					}
					var n = parseFloat(a);

					n = isNaN(n) ? 0 : n/Math.pow(10,settings.precision);
					t = n.toFixed(settings.precision);

					i = settings.precision == 0 ? 0 : 1;
					var p, d = (t=t.split('.'))[i].substr(0,settings.precision);
					for (p = (t=t[0]).length; (p-=3)>=1;) {
						t = t.substr(0,p)+settings.thousands+t.substr(p);
					}

					return (settings.precision>0)
					? setSymbol(neg+t+settings.decimal+d+Array((settings.precision+1)-d.length).join(0))
					: setSymbol(neg+t);
				}

				function getDefaultMask() {
					var n = parseFloat('0')/Math.pow(10,settings.precision);
					return (n.toFixed(settings.precision)).replace(new RegExp('\\.','g'),settings.decimal);
				}

				function setSymbol(value){
					if (settings.symbol != ''){
						var operator = '';
						if(value.length != 0 && value.charAt(0) == '-'){
							value = value.replace('-', '');
							operator = '-';
						}

						if(value.substr(0, settings.symbol.length) != settings.symbol){
							value = operator + settings.symbol + value;
						}
					}
					return value;
				}

				function changeSign(input){
					var inputValue = input.val();
					if (settings.allowNegative){
						if (inputValue != '' && inputValue.charAt(0) == '-'){
							return inputValue.replace('-','');
						} else {
							return '-' + inputValue;
						}
					} else {
						return inputValue;
					}
				}

				function setCursorPosition(input, pos) {
					// I'm not sure if we need to jqueryfy input
					$(input).each(function(index, elem) {
						if (elem.setSelectionRange) {
							elem.focus();
							elem.setSelectionRange(pos, pos);
						} else if (elem.createTextRange) {
							var range = elem.createTextRange();
							range.collapse(true);
							range.moveEnd('character', pos);
							range.moveStart('character', pos);
							range.select();
						}
					});
					return this;
				};

				function getInputSelection(el) {
					var start = 0, end = 0, normalizedValue, range, textInputRange, len, endRange;

					if (typeof el.selectionStart == "number" && typeof el.selectionEnd == "number") {
						start = el.selectionStart;
						end = el.selectionEnd;
					} else {
						range = document.selection.createRange();

						if (range && range.parentElement() == el) {
							len = el.value.length;
							normalizedValue = el.value.replace(/\r\n/g, "\n");

							// Create a working TextRange that lives only in the input
							textInputRange = el.createTextRange();
							textInputRange.moveToBookmark(range.getBookmark());

							// Check if the start and end of the selection are at the very end
							// of the input, since moveStart/moveEnd doesn't return what we want
							// in those cases
							endRange = el.createTextRange();
							endRange.collapse(false);

							if (textInputRange.compareEndPoints("StartToEnd", endRange) > -1) {
								start = end = len;
							} else {
								start = -textInputRange.moveStart("character", -len);
								start += normalizedValue.slice(0, start).split("\n").length - 1;

								if (textInputRange.compareEndPoints("EndToEnd", endRange) > -1) {
									end = len;
								} else {
									end = -textInputRange.moveEnd("character", -len);
									end += normalizedValue.slice(0, end).split("\n").length - 1;
								}
							}
						}
					}

					return {
						start: start,
						end: end
					};
				} // getInputSelection

				if (!input.attr("readonly")){
					input.unbind('.maskMoney');
					input.bind('keypress.maskMoney', keypressEvent);
					input.bind('keydown.maskMoney', keydownEvent);
					input.bind('blur.maskMoney', blurEvent);
					input.bind('focus.maskMoney', focusEvent);
					input.bind('mask.maskMoney', mask);
				}
			})
		}
	}

	$.fn.maskMoney = function(method) {
		if ( methods[method] ) {
			return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
		} else if ( typeof method === 'object' || ! method ) {
			return methods.init.apply( this, arguments );
		} else {
			$.error( 'Method ' +  method + ' does not exist on jQuery.maskMoney' );
		}
	};
})(jQuery);
<|endoftext|>


<commit_before>/**
 * The Modal jQuery plugin
 *
 * @author Alexander Makarov <sam@rmcreative.ru>
 * @link https://github.com/samdark/the-modal
 * @version 1.0
 */
;(function($, window, document, undefined) {
	"use strict";
	/*jshint smarttabs:true*/

	var pluginNamespace = 'the-modal',
		// global defaults
    	defaults = {
			overlayClass: 'themodal-overlay',

			closeOnEsc: true,
			closeOnOverlayClick: true,

			onClose: null,
			onOpen: null
        };

	function getContainer() {
		var container = 'body';

		// IE < 8
		if(document.all && !document.querySelector) {
			container = 'html';
		}

		return $(container);
	}

	function init(els, options) {
		var modalOptions = options;

		if(els.length) {
			els.each(function(){
				$(this).data(pluginNamespace+'.options', modalOptions);
			});
		}
		else {
			$.extend(defaults, modalOptions);
		}

		return {
			open: function(options) {
				var el = els.get(0);
				var localOptions = $.extend({}, defaults, $(el).data(pluginNamespace+'.options'), options);

				getContainer().addClass('lock');

				// close modal if opened
				if($('.'+localOptions.overlayClass).length) {
					$.modal().close();
				}

				var overlay = $('<div/>').addClass(localOptions.overlayClass).prependTo('body');
				overlay.data(pluginNamespace+'.options', options);

				if(el) {
					$(el).clone(true).appendTo(overlay).show();
				}

				if(localOptions.closeOnEsc) {
					$(document).bind('keyup.'+pluginNamespace, function(e){
						if(e.keyCode === 27) {
							$.modal().close();
						}
					});
				}

				if(localOptions.closeOnOverlayClick) {
					overlay.children().on('click.' + pluginNamespace, function(e){
						e.stopPropagation();
					});
					$('.' + localOptions.overlayClass).on('click.' + pluginNamespace, function(e){
						$.modal().close();
					});
				}

				$(document).bind('touchmove.'+pluginNamespace,function(e){
					if(!$(e).parents('.' + localOptions.overlayClass)) {
						e.preventDefault();
					}
				});

				if(localOptions.onOpen) {
					localOptions.onOpen(overlay, localOptions);
				}
			},
			close: function() {
				var el = els.get(0);

				var localOptions = $.extend({}, defaults, options);
				var overlay = $('.' + localOptions.overlayClass);
				$.extend(localOptions, overlay.data(pluginNamespace+'.options'));

				overlay.remove();
				getContainer().removeClass('lock');

				if(localOptions.closeOnEsc) {
					$(document).unbind('keyup.'+pluginNamespace);
				}

				if(localOptions.onClose) {
					localOptions.onClose(overlay, localOptions);
				}
			}
		};
	}

	$.modal = function(options){
		return init($(), options);
	};

	$.fn.modal = function(options) {
		return init(this, options);
	};

})(jQuery, window, document);<commit_msg>Transfer getContainer().addClass('lock');<commit_after>/**
 * The Modal jQuery plugin
 *
 * @author Alexander Makarov <sam@rmcreative.ru>
 * @link https://github.com/samdark/the-modal
 * @version 1.0
 */
;(function($, window, document, undefined) {
	"use strict";
	/*jshint smarttabs:true*/

	var pluginNamespace = 'the-modal',
		// global defaults
    	defaults = {
			overlayClass: 'themodal-overlay',

			closeOnEsc: true,
			closeOnOverlayClick: true,

			onClose: null,
			onOpen: null
        };

	function getContainer() {
		var container = 'body';

		// IE < 8
		if(document.all && !document.querySelector) {
			container = 'html';
		}

		return $(container);
	}

	function init(els, options) {
		var modalOptions = options;

		if(els.length) {
			els.each(function(){
				$(this).data(pluginNamespace+'.options', modalOptions);
			});
		}
		else {
			$.extend(defaults, modalOptions);
		}

		return {
			open: function(options) {
				var el = els.get(0);
				var localOptions = $.extend({}, defaults, $(el).data(pluginNamespace+'.options'), options);

				// close modal if opened
				if($('.'+localOptions.overlayClass).length) {
					$.modal().close();
				}

				getContainer().addClass('lock');

				var overlay = $('<div/>').addClass(localOptions.overlayClass).prependTo('body');
				overlay.data(pluginNamespace+'.options', options);

				if(el) {
					$(el).clone(true).appendTo(overlay).show();
				}

				if(localOptions.closeOnEsc) {
					$(document).bind('keyup.'+pluginNamespace, function(e){
						if(e.keyCode === 27) {
							$.modal().close();
						}
					});
				}

				if(localOptions.closeOnOverlayClick) {
					overlay.children().on('click.' + pluginNamespace, function(e){
						e.stopPropagation();
					});
					$('.' + localOptions.overlayClass).on('click.' + pluginNamespace, function(e){
						$.modal().close();
					});
				}

				$(document).bind('touchmove.'+pluginNamespace,function(e){
					if(!$(e).parents('.' + localOptions.overlayClass)) {
						e.preventDefault();
					}
				});

				if(localOptions.onOpen) {
					localOptions.onOpen(overlay, localOptions);
				}
			},
			close: function() {
				var el = els.get(0);

				var localOptions = $.extend({}, defaults, options);
				var overlay = $('.' + localOptions.overlayClass);
				$.extend(localOptions, overlay.data(pluginNamespace+'.options'));

				overlay.remove();
				getContainer().removeClass('lock');

				if(localOptions.closeOnEsc) {
					$(document).unbind('keyup.'+pluginNamespace);
				}

				if(localOptions.onClose) {
					localOptions.onClose(overlay, localOptions);
				}
			}
		};
	}

	$.modal = function(options){
		return init($(), options);
	};

	$.fn.modal = function(options) {
		return init(this, options);
	};

})(jQuery, window, document);
<|endoftext|>


<commit_before>/**
* jQuery Droptouch Plugin
*/
(function($) {

	$.fn.Touchdown = function() {

		return this.each(function(i,obj) {

			$this = $(this);

			var options;

			for (var i = $this.find('li').length - 1; i >= 0; i--){
				var item = $this.find('li')[i],
					anchor = $(item).find('a');
				options += '<option value="' + anchor.attr('href') + '">' + anchor.text() + '</option>';
			};

			$this.addClass('touchdown-list').after('<select class="touchdown"> ' + options +'</select>');

			// Event handler
			$this.next('select').change(function(){
				window.location = $(this).val();
			});

		});

	};

})(jQuery);
<commit_msg>Some optimisations<commit_after>/**
* jQuery Droptouch Plugin
*/
(function($) {

	$.fn.Touchdown = function() {

		return this.each(function() {

			$this = $(this);

			var li = $this.find('li'),
				optionList;

			for (var i = li.length - 1; i >= 0; i--){

				var anchor = $(li[i]).find('a');
				optionList += '<option value="' + anchor.attr('href') + '">' + anchor.text() + '</option>';

			}

			// DOM manipulation
			$this.addClass('touchdown-list').after('<select class="touchdown"> ' + optionList +'</select>');

			// Event handler
			$this.next('select').change(function(){
				window.location = $(this).val();
			});

		});

	};

})(jQuery);
<|endoftext|>


<commit_before>/*!
 * jQuery UI Lookup 0.1.0
 *
 * Copyright 2011, Simon Bartlett
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * https://github.com/SimonBartlett/jquery-ui-lookup
 */

(function ($) {

	$.lookup = {

		createBodyTemplate: function (name) {
			return '
' +
				'
Search: <input type="text" class="ui-widget-content ui-corner-all" style="padding: 2px;" />
' +
				'

' +
				'
';
		}

	};

	$.widget('ui.lookup', {
		version: '0.1.0',
		options: {
			cancelText: 'Cancel',
			delay: 300,
			disabled: false,
			height: 400,
			minLength: 1,
			modal: 400,
			name: null,
			okText: 'Ok',
			resizable: false,
			select: null,
			source: null,
			title: 'Search',
			value: null,
			width: 600
		},
		_open: null,
		_resizeAutocomplete: function () {
			var context = $(this).is('.ui-dialog-content') ? $(this) : $(this).parent().parent().parent();
			$('.ui-lookup-results ul', context).css({
				top: '',
				left: '',
				overflowY: 'auto',
				overflowX: 'hidden',
				width: $('.ui-lookup-results', context).width(),
				height: context.height() -  $('div:first', context).outerHeight()
			});
		},
		_create: function () {

			if (this.options.name === null) {
				this.options.name = 'dialog_' + Math.floor(Math.random() * 10001).toString();
			}

			var $this = this,
				dialogBody = $.lookup.createBodyTemplate($this.options.name),
				buttons = { };

			buttons[$this.options.okText] = function () {
				$this.options.value = $this.options._value;
				if ($this.options.select) {
					$this.options.select($this.options.value);
				}
				$(this).dialog('close');
			};
			buttons[$this.options.cancelText] = function () {
				$(this).dialog('close');
			};

			$this._dialog = $(dialogBody).dialog({
				autoOpen: false,
				height: $this.options.height,
				modal: true,
				resizable: $this.options.resizable,
				title: $this.options.title,
				width: $this.options.width,
				buttons: buttons,
				resize: $this._resizeAutocomplete,
				open: function (dialogEvent, dialogUi) {
					$this._autocomplete = $('input', $this._dialog)
						.autocomplete({
							appendTo: '#' + $this.options.name + ' .ui-lookup-results',
							delay: $this.options.delay,
							minLength: $this.options.minLength,
							source: $this.options.source,
							open: $this._resizeAutocomplete,
							select: function (event, ui) {
								var selected = $('#ui-active-menuitem');
								setTimeout(function () {
									$this._dialog.find('.ui-lookup-results ul li a').removeClass('ui-state-active');
									selected.addClass('ui-state-active');
								}, 10);
								$this.options._value = ui.item;
								return false;
							},
							focus: function (event, ui) {
								var focused = $('#ui-active-menuitem');
								if (!focused.hasClass('ui-state-active')) {
									focused.addClass('ui-state-hover');
								}
								return false;
							}
						})
						.focus();

					// Copied from jQuery UI Autocomplete, just to comment out: self.close( event );

					var self = $this._autocomplete.data('autocomplete'),
						doc = $this._autocomplete.data('autocomplete').element[0].ownerDocument;

					self.menu.options.selected = function (event, ui) {
						var item = ui.item.data('item.autocomplete'),
							previous = self.previous;

						// only trigger when focus was lost (click on menu)
						if (self.element[0] !== doc.activeElement) {
							self.element.focus();
							self.previous = previous;
							// #6109 - IE triggers two focus events and the second
							// is asynchronous, so we need to reset the previous
							// term synchronously and asynchronously :-(
							setTimeout(function () {
								self.previous = previous;
								self.selectedItem = item;
							}, 1);
						}

						if (false !== self._trigger('select', event, { item: item })) {
							self.element.val(item.value);
						}
						// reset the term after the select event
						// this allows custom select handling to work properly
						self.term = self.element.val();

						//self.close( event );
						self.selectedItem = item;
					};

					//End of jQuery UI code

					self.close = function () {};

					self.menu.element.dblclick(function (event) {
						if (!$(event.target).closest('.ui-menu-item a').length) {
							return;
						}
						event.preventDefault();
						self.menu.select(event);
						$this.options.value = $this.options._value;
						if ($this.options.select) {
							$this.options.select($this.options.value);
						}
						$this._dialog.dialog('close');
					});
				}
			});

			$this._open = function () {
				if (!$this.options.disabled) {
					$this._dialog.dialog('open');
				}
			}

			$this.element.bind('focus', $this._open);
			$this.element.bind('click', $this._open);
		},
		value: function () {
			return this.options.value;
		},
		destroy: function () {
			this.element.bind('focus', this._open);
			this.element.bind('click', this._open);
			this._autocomplete.autocomplete('destroy');
			this._dialog.dialog('destroy');
			$.Widget.prototype.destroy.apply(this, arguments);
		},
		disable: function () {
			this.options.disabled = true;
		},
		enable: function () {
			this.options.disabled = false;
		}
	});

}(jQuery));<commit_msg>Separated input field and label.<commit_after>/*!
 * jQuery UI Lookup 0.1.0
 *
 * Copyright 2011, Simon Bartlett
 * Dual licensed under the MIT or GPL Version 2 licenses.
 *
 * https://github.com/SimonBartlett/jquery-ui-lookup
 */

(function ($) {

	$.lookup = {

		createBodyTemplate: function (name) {
			return '
' +
				'
Search: <input id="' + name + '_search"  type="text" class="ui-widget-content ui-corner-all" style="padding: 2px;" />
' +
				'

' +
				'
';
		}

	};

	$.widget('ui.lookup', {
		version: '0.1.0',
		options: {
			cancelText: 'Cancel',
			delay: 300,
			disabled: false,
			height: 400,
			minLength: 1,
			modal: 400,
			name: null,
			okText: 'Ok',
			resizable: false,
			select: null,
			source: null,
			title: 'Search',
			value: null,
			width: 600
		},
		_open: null,
		_resizeAutocomplete: function () {
			var context = $(this).is('.ui-dialog-content') ? $(this) : $(this).parent().parent();
			$('.ui-lookup-results ul', context).css({
				top: '',
				left: '',
				overflowY: 'auto',
				overflowX: 'hidden',
				width: $('.ui-lookup-results', context).width(),
				height: context.height() -  $('div:first', context).outerHeight()
			});
		},
		_create: function () {

			if (this.options.name === null) {
				this.options.name = 'dialog_' + Math.floor(Math.random() * 10001).toString();
			}

			var $this = this,
				dialogBody = $.lookup.createBodyTemplate($this.options.name),
				buttons = { };

			buttons[$this.options.okText] = function () {
				$this.options.value = $this.options._value;
				if ($this.options.select) {
					$this.options.select($this.options.value);
				}
				$(this).dialog('close');
			};
			buttons[$this.options.cancelText] = function () {
				$(this).dialog('close');
			};

			$this._dialog = $(dialogBody).dialog({
				autoOpen: false,
				height: $this.options.height,
				modal: true,
				resizable: $this.options.resizable,
				title: $this.options.title,
				width: $this.options.width,
				buttons: buttons,
				resize: $this._resizeAutocomplete,
				open: function (dialogEvent, dialogUi) {
					$this._autocomplete = $('input', $this._dialog)
						.autocomplete({
							appendTo: '#' + $this.options.name + ' .ui-lookup-results',
							delay: $this.options.delay,
							minLength: $this.options.minLength,
							source: $this.options.source,
							open: $this._resizeAutocomplete,
							select: function (event, ui) {
								var selected = $('#ui-active-menuitem');
								setTimeout(function () {
									$this._dialog.find('.ui-lookup-results ul li a').removeClass('ui-state-active');
									selected.addClass('ui-state-active');
								}, 10);
								$this.options._value = ui.item;
								return false;
							},
							focus: function (event, ui) {
								var focused = $('#ui-active-menuitem');
								if (!focused.hasClass('ui-state-active')) {
									focused.addClass('ui-state-hover');
								}
								return false;
							}
						})
						.focus();

					// Copied from jQuery UI Autocomplete, just to comment out: self.close( event );

					var self = $this._autocomplete.data('autocomplete'),
						doc = $this._autocomplete.data('autocomplete').element[0].ownerDocument;

					self.menu.options.selected = function (event, ui) {
						var item = ui.item.data('item.autocomplete'),
							previous = self.previous;

						// only trigger when focus was lost (click on menu)
						if (self.element[0] !== doc.activeElement) {
							self.element.focus();
							self.previous = previous;
							// #6109 - IE triggers two focus events and the second
							// is asynchronous, so we need to reset the previous
							// term synchronously and asynchronously :-(
							setTimeout(function () {
								self.previous = previous;
								self.selectedItem = item;
							}, 1);
						}

						if (false !== self._trigger('select', event, { item: item })) {
							self.element.val(item.value);
						}
						// reset the term after the select event
						// this allows custom select handling to work properly
						self.term = self.element.val();

						//self.close( event );
						self.selectedItem = item;
					};

					//End of jQuery UI code

					self.close = function () {};

					self.menu.element.dblclick(function (event) {
						if (!$(event.target).closest('.ui-menu-item a').length) {
							return;
						}
						event.preventDefault();
						self.menu.select(event);
						$this.options.value = $this.options._value;
						if ($this.options.select) {
							$this.options.select($this.options.value);
						}
						$this._dialog.dialog('close');
					});
				}
			});

			$this._open = function () {
				if (!$this.options.disabled) {
					$this._dialog.dialog('open');
				}
			}

			$this.element.bind('focus', $this._open);
			$this.element.bind('click', $this._open);
		},
		value: function () {
			return this.options.value;
		},
		destroy: function () {
			this.element.bind('focus', this._open);
			this.element.bind('click', this._open);
			this._autocomplete.autocomplete('destroy');
			this._dialog.dialog('destroy');
			$.Widget.prototype.destroy.apply(this, arguments);
		},
		disable: function () {
			this.options.disabled = true;
		},
		enable: function () {
			this.options.disabled = false;
		}
	});

}(jQuery));<|endoftext|>


<commit_before>////
//// ...
////

function CyExploreDemoInit(){

    ///
    /// Setup and preamble.
    ///

    //var DEBUG = false;
    var DEBUG = true;

    // Color/name context.
    var context = new bbop.context(amigo.data.context);
    var desired_spread = 1;
    var desired_layout = null;

    // HTML connctions.
    var demo_output_id = 'cydemo';
    var demo_output_elt = '#' + demo_output_id;
    var demo_input_id = 'demo_input';
    var demo_input_elt = '#' + demo_input_id;
    var auto_1_input_id = 'auto_1_input';
    var auto_1_input_elt = '#' + auto_1_input_id;
    var sel_1_input_id = 'sel_1_input';
    var sel_1_input_elt = '#' + sel_1_input_id;
    var sel_2_input_id = 'sel_2_input';
    var sel_2_input_elt = '#' + sel_2_input_id;

    // Aliases.
    var each = bbop.core.each;
    var ll = function(str){
	if( DEBUG && console && console.log ){
	    console.log(str);
	}
    };

    // For now, use a generic manager with a generic response.
    var manager = new bbop.rest.manager.jquery(bbop.rest.response.json);

    // Ready spinner for use.
    var spin = new bbop.widget.spinner('spinloc', '/image/waiting_ac.gif',
				       {'visible_p': false});
    function _spin_show(){
	spin.show();
    }
    function _spin_hide(){
	spin.hide();
    }
    ll('Start ready!');

    ///
    /// Renderer originally lifted from AmiGO 2 demo.
    ///

    //
    var global_graph = null;
    var focus_nodes = {};
    // Destroy cytoscape graph as well as the resetting the global
    // graph object.
    function empty_graph(){
	global_graph = new bbop.model.graph();
	focus_nodes = {};
	jQuery(demo_output_elt).empty();
    }
    // Add to it.
    function add_to_graph(graph_json){

	// Ensure
	if( ! global_graph ){
	    global_graph = new bbop.model.graph();
	}

	// graphs may be either a single object (short) or multiple
	// graph objects (simple). For now, fold them all in to a
	// single graph entity.
	if( ! bbop.core.is_array(graph_json) ){
	    graph_json = [graph_json];
	}
	each(graph_json,
	     function(grg){
		 var tmp_graph = new bbop.model.graph();
		 tmp_graph.load_json(grg);
		 global_graph.merge_in(tmp_graph);
	     });

	// Clear current contents of graph elt.
	jQuery(demo_output_elt).empty();

	CytoDraw(global_graph, bbop.core.get_keys(focus_nodes),
		 desired_layout, context, demo_output_id,
		 _spin_show, _spin_hide, data_call);

	// Update color explanations to the newest.
	var color_clust = [];
	var erels = global_graph.all_predicates();
	each(erels,
	     function(erel){
		 color_clust.push({
				      'label': context.readable(erel),
				      'color': context.color(erel),
				      'priority': context.priority(erel)
				  });
	     });
	color_clust.sort(function(a, b){ return b.priority - a.priority; });
	// Assemble HTML for label display.
	var fc = [];
	fc.push('
');
	each(color_clust,
	     function(c){
		 fc.push('
');
		 fc.push('' + c.label + '');
		 fc.push('
');
	     });
	fc.push('
');
	// Add to DOM.
	jQuery('#color_exp').empty();
	jQuery('#color_exp').append(fc.join(''));
    }

    ///
    /// Demo runner.
    ///

    // Generic responder.
    function _success_callback(resp, man){
	// The response is a generic bbop.rest.response.json.
	// Peel out the graph and render it.
	if( resp.okay() ){
	    add_to_graph(resp.raw());
	}else{
	    ll('the response was not okay');
	}
	_spin_hide();
    }
    function _error_callback(resp, man){
	_spin_hide();
	alert('some kind of error?');
    }
    manager.register('success', 'draw', _success_callback);
    manager.register('error', 'oops', _error_callback);

    // Wrap up a data call with a single argument.
    function data_call(arg1){

	// 'Tis a focus node.
	focus_nodes[arg1] = true;

	// Data call setup.
	var base = 'http://kato.crbs.ucsd.edu:9000/scigraph/graph/neighbors/' +
	    arg1 + '.json';
	var rsrc = base + '?' + 'depth=' + desired_spread;
	manager.resource(rsrc);
	manager.method('get');
	//manager.use_jsonp(true);
	manager.jsonp_callback('callback');

	// Action.
	_spin_show();
	manager.action();
    }

    ///
    /// Activate autocomplete and actions on inputs for demo.
    ///

    // Arguments for autocomplete box.
    var ac_args = {
	position : {
       	    my: "left top",
            at: "left bottom",
	    collision: "none"
	},
	source: function(request, response) {
	    console.log("trying autocomplete on " + request.term);

	    // Argument response from source gets map as argument.
	    var _parse_data_item = function(item){

		// If has a category, append that; if not try to use
		// namespace; otherwise, nothing.
		var appendee = '';
		if( item ){
		    if( item['concept']['categories'] &&
			! bbop.core.is_empty(item['concept']['categories']) ){
			appendee = item['concept']['categories'].join(' ');
		    }else if( item['concept']['fragment'] ){
			// Get first split on '_'.
			var fspl =
			    bbop.core.first_split('_',
						  item['concept']['fragment']);
			if( fspl[0] ){
			    appendee = fspl[0];
			}
		    }
		}

		return {
		    label: item['completion'],
		    tag: appendee,
		    name: item['concept']['fragment']
		};
	    };
	    var _on_success = function(data) {

		// Get the list out of the return.
		if( data && data['list'] ){

		    var ldata = data['list'];

		    // // Pare out duplicates. Assume existance of structure.
		    // var pared_data = [];
		    // var seen_ids = {};
		    // for( var di = 0; di < ldata.length; di++ ){
		    // 	var datum = ldata[di];
		    // 	var datum_id = datum['concept']['uri'];
		    // 	if( ! seen_ids[datum_id] ){
		    // 	    // Only add new ids to pared data list.
		    // 	    pared_data.push(datum);

		    // 	    // Block them in the future.
		    // 	    seen_ids[datum_id] = true;
		    // 	}
		    // }

		    // Map out into the display format.
		    //var map = jQuery.map(pared_data, _parse_data_item);
		    var map = jQuery.map(data['list'], _parse_data_item);
		    response(map);
		}
	    };

	    // Define and run request on service.
	    var query =
		'http://kato.crbs.ucsd.edu:9000/scigraph/vocabulary/autocomplete/' +
		request.term +
		'.json?limit=20&searchSynonyms=true';
	    jQuery.ajax({
			    'url': query,
			    'dataType': 'jsonp',
			    'jsonp': 'callback',
			    'success': _on_success
			});
	},
	messages: {
            noResults: '',
	    results: function() {}
        }
    };

    // Create our own custom rendering to make the categories a little
    // nicer to look at (as minor data).
    // http://jqueryui.com/autocomplete/#custom-data
    function _sub_render(ul, item){
	var li = jQuery('
');
	li.append('' +
		  '' +
		  item.label +
		  '' +
		  ' ' +
		  '' +
		  item.tag +
		  '' +
		  '');
	li.appendTo(ul);
	return li;
    }

    // Activate first autocomplete.
    ac_args['select'] = function(event, ui) {
	event.preventDefault();
	if (ui.item !== null) {
	    ll('got: ' + ui.item.name);
	    jQuery(auto_1_input_elt).val(ui.item.name);
	}
    };
    var jac1 = jQuery(auto_1_input_elt).autocomplete(ac_args);
    jac1.data('ui-autocomplete')._renderItem = _sub_render;

    // Rewire the form to produce the graph.
    jQuery(demo_input_elt).submit(
	function(event){
	    event.preventDefault();

	    var v1 = jQuery(auto_1_input_elt).val() || '';
	    var s1 = jQuery(sel_1_input_elt).val() || '';
	    var s2 = jQuery(sel_2_input_elt).val() || '';

	    // ...
	    if( v1 != '' && s1 != '' && s2 != '' ){

		// Clear out the graph on call.
		empty_graph();

		// alert('TODO: only using demo input; ignoring: ' +
		// 	 [v1, s1, s2].join(', '));
		desired_spread = s1;
		desired_layout = s2;
		focus_nodes[v1] = true;
		data_call(v1);
	    }else{
		alert('insufficient args: ' + [v1, s1, s2].join(', ') ); //+
	    }
	});

    ll('Done ready!');
};

// jQuery gets to bootstrap everything.
jQuery(document).ready(
    function(){
	CyExploreDemoInit();
    });
<commit_msg>use curies instead of fragments<commit_after>////
//// ...
////

function CyExploreDemoInit(){

    ///
    /// Setup and preamble.
    ///

    //var DEBUG = false;
    var DEBUG = true;

    // Color/name context.
    var context = new bbop.context(amigo.data.context);
    var desired_spread = 1;
    var desired_layout = null;

    // HTML connctions.
    var demo_output_id = 'cydemo';
    var demo_output_elt = '#' + demo_output_id;
    var demo_input_id = 'demo_input';
    var demo_input_elt = '#' + demo_input_id;
    var auto_1_input_id = 'auto_1_input';
    var auto_1_input_elt = '#' + auto_1_input_id;
    var sel_1_input_id = 'sel_1_input';
    var sel_1_input_elt = '#' + sel_1_input_id;
    var sel_2_input_id = 'sel_2_input';
    var sel_2_input_elt = '#' + sel_2_input_id;

    // Aliases.
    var each = bbop.core.each;
    var ll = function(str){
	if( DEBUG && console && console.log ){
	    console.log(str);
	}
    };

    // For now, use a generic manager with a generic response.
    var manager = new bbop.rest.manager.jquery(bbop.rest.response.json);

    // Ready spinner for use.
    var spin = new bbop.widget.spinner('spinloc', '/image/waiting_ac.gif',
				       {'visible_p': false});
    function _spin_show(){
	spin.show();
    }
    function _spin_hide(){
	spin.hide();
    }
    ll('Start ready!');

    ///
    /// Renderer originally lifted from AmiGO 2 demo.
    ///

    //
    var global_graph = null;
    var focus_nodes = {};
    // Destroy cytoscape graph as well as the resetting the global
    // graph object.
    function empty_graph(){
	global_graph = new bbop.model.graph();
	focus_nodes = {};
	jQuery(demo_output_elt).empty();
    }
    // Add to it.
    function add_to_graph(graph_json){

	// Ensure
	if( ! global_graph ){
	    global_graph = new bbop.model.graph();
	}

	// graphs may be either a single object (short) or multiple
	// graph objects (simple). For now, fold them all in to a
	// single graph entity.
	if( ! bbop.core.is_array(graph_json) ){
	    graph_json = [graph_json];
	}
	each(graph_json,
	     function(grg){
		 var tmp_graph = new bbop.model.graph();
		 tmp_graph.load_json(grg);
		 global_graph.merge_in(tmp_graph);
	     });

	// Clear current contents of graph elt.
	jQuery(demo_output_elt).empty();

	CytoDraw(global_graph, bbop.core.get_keys(focus_nodes),
		 desired_layout, context, demo_output_id,
		 _spin_show, _spin_hide, data_call);

	// Update color explanations to the newest.
	var color_clust = [];
	var erels = global_graph.all_predicates();
	each(erels,
	     function(erel){
		 color_clust.push({
				      'label': context.readable(erel),
				      'color': context.color(erel),
				      'priority': context.priority(erel)
				  });
	     });
	color_clust.sort(function(a, b){ return b.priority - a.priority; });
	// Assemble HTML for label display.
	var fc = [];
	fc.push('
');
	each(color_clust,
	     function(c){
		 fc.push('
');
		 fc.push('' + c.label + '');
		 fc.push('
');
	     });
	fc.push('
');
	// Add to DOM.
	jQuery('#color_exp').empty();
	jQuery('#color_exp').append(fc.join(''));
    }

    ///
    /// Demo runner.
    ///

    // Generic responder.
    function _success_callback(resp, man){
	// The response is a generic bbop.rest.response.json.
	// Peel out the graph and render it.
	if( resp.okay() ){
	    add_to_graph(resp.raw());
	}else{
	    ll('the response was not okay');
	}
	_spin_hide();
    }
    function _error_callback(resp, man){
	_spin_hide();
	alert('some kind of error?');
    }
    manager.register('success', 'draw', _success_callback);
    manager.register('error', 'oops', _error_callback);

    // Wrap up a data call with a single argument.
    function data_call(arg1){

	// 'Tis a focus node.
	focus_nodes[arg1] = true;

	// Data call setup.
	var base = 'http://kato.crbs.ucsd.edu:9000/scigraph/graph/neighbors/' +
	    arg1 + '.json';
	var rsrc = base + '?' + 'depth=' + desired_spread;
	manager.resource(rsrc);
	manager.method('get');
	//manager.use_jsonp(true);
	manager.jsonp_callback('callback');

	// Action.
	_spin_show();
	manager.action();
    }

    ///
    /// Activate autocomplete and actions on inputs for demo.
    ///

    // Arguments for autocomplete box.
    var ac_args = {
	position : {
       	    my: "left top",
            at: "left bottom",
	    collision: "none"
	},
	source: function(request, response) {
	    console.log("trying autocomplete on " + request.term);

	    // Argument response from source gets map as argument.
	    var _parse_data_item = function(item){

		// If has a category, append that; if not try to use
		// namespace; otherwise, nothing.
		var appendee = '';
		if( item ){
		    if( item['concept']['categories'] &&
			! bbop.core.is_empty(item['concept']['categories']) ){
			appendee = item['concept']['categories'].join(' ');
		    }else if( item['concept']['curie'] ){
			// Get first split on '_'.
			var fspl =
			    bbop.core.first_split('_',
						  item['concept']['curie']);
			if( fspl[0] ){
			    appendee = fspl[0];
			}
		    }
		}

		return {
		    label: item['completion'],
		    tag: appendee,
		    name: item['concept']['curie']
		};
	    };
	    var _on_success = function(data) {

		// Get the list out of the return.
		if( data && data['list'] ){

		    var ldata = data['list'];

		    // // Pare out duplicates. Assume existance of structure.
		    // var pared_data = [];
		    // var seen_ids = {};
		    // for( var di = 0; di < ldata.length; di++ ){
		    // 	var datum = ldata[di];
		    // 	var datum_id = datum['concept']['uri'];
		    // 	if( ! seen_ids[datum_id] ){
		    // 	    // Only add new ids to pared data list.
		    // 	    pared_data.push(datum);

		    // 	    // Block them in the future.
		    // 	    seen_ids[datum_id] = true;
		    // 	}
		    // }

		    // Map out into the display format.
		    //var map = jQuery.map(pared_data, _parse_data_item);
		    var map = jQuery.map(data['list'], _parse_data_item);
		    response(map);
		}
	    };

	    // Define and run request on service.
	    var query =
		'http://kato.crbs.ucsd.edu:9000/scigraph/vocabulary/autocomplete/' +
		request.term +
		'.json?limit=20&searchSynonyms=true';
	    jQuery.ajax({
			    'url': query,
			    'dataType': 'jsonp',
			    'jsonp': 'callback',
			    'success': _on_success
			});
	},
	messages: {
            noResults: '',
	    results: function() {}
        }
    };

    // Create our own custom rendering to make the categories a little
    // nicer to look at (as minor data).
    // http://jqueryui.com/autocomplete/#custom-data
    function _sub_render(ul, item){
	var li = jQuery('
');
	li.append('' +
		  '' +
		  item.label +
		  '' +
		  ' ' +
		  '' +
		  item.tag +
		  '' +
		  '');
	li.appendTo(ul);
	return li;
    }

    // Activate first autocomplete.
    ac_args['select'] = function(event, ui) {
	event.preventDefault();
	if (ui.item !== null) {
	    ll('got: ' + ui.item.name);
	    jQuery(auto_1_input_elt).val(ui.item.name);
	}
    };
    var jac1 = jQuery(auto_1_input_elt).autocomplete(ac_args);
    jac1.data('ui-autocomplete')._renderItem = _sub_render;

    // Rewire the form to produce the graph.
    jQuery(demo_input_elt).submit(
	function(event){
	    event.preventDefault();

	    var v1 = jQuery(auto_1_input_elt).val() || '';
	    var s1 = jQuery(sel_1_input_elt).val() || '';
	    var s2 = jQuery(sel_2_input_elt).val() || '';

	    // ...
	    if( v1 != '' && s1 != '' && s2 != '' ){

		// Clear out the graph on call.
		empty_graph();

		// alert('TODO: only using demo input; ignoring: ' +
		// 	 [v1, s1, s2].join(', '));
		desired_spread = s1;
		desired_layout = s2;
		focus_nodes[v1] = true;
		data_call(v1);
	    }else{
		alert('insufficient args: ' + [v1, s1, s2].join(', ') ); //+
	    }
	});

    ll('Done ready!');
};

// jQuery gets to bootstrap everything.
jQuery(document).ready(
    function(){
	CyExploreDemoInit();
    });
<|endoftext|>


<commit_before>// Copyright 2013-2018, University of Colorado Boulder

/**
 * The navigation bar at the bottom of the screen.
 * For a single-screen sim, it shows the name of the sim at the far left and the PhET button at the far right.
 * For a multi-screen sim, it additionally shows buttons for each screen, and a home button.
 *
 * Layout of NavigationBar adapts to different text widths, icon widths, and numbers of screens, and attempts to
 * perform an "optimal" layout. The sim title is initially constrained to a max percentage of the bar width,
 * and that's used to compute how much space is available for screen buttons.  After creation and layout of the
 * screen buttons, we then compute how much space is actually available for the sim title, and use that to
 * constrain the title's width.
 *
 * The bar is composed of a background (always pixel-perfect), and expandable content (that gets scaled as one part).
 * If we are width-constrained, the navigation bar is in a 'compact' state where the children of the content (e.g.
 * home button, screen buttons, phet menu, title) do not change positions. If we are height-constrained, the amount
 * available to the bar expands, so we lay out the children to fit. See https://github.com/phetsims/joist/issues/283
 * for more details on how this is done.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
define( function( require ) {
  'use strict';

  // modules
  var AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  var A11yButtonsHBox = require( 'JOIST/A11yButtonsHBox' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var HomeButton = require( 'JOIST/HomeButton' );
  var HomeScreenView = require( 'JOIST/HomeScreenView' );
  var inherit = require( 'PHET_CORE/inherit' );
  var joist = require( 'JOIST/joist' );
  var JoistA11yStrings = require( 'JOIST/JoistA11yStrings' );
  var NavigationBarScreenButton = require( 'JOIST/NavigationBarScreenButton' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetButton = require( 'JOIST/PhetButton' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var DerivedProperty = require( 'AXON/DerivedProperty' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var Text = require( 'SCENERY/nodes/Text' );

  // strings
  var simTitleWithScreenNamePatternString = require( 'string!JOIST/simTitleWithScreenNamePattern' );

  // a11y strings
  var simScreensResourcesAndToolsString = JoistA11yStrings.simScreensResourcesAndTools.value;
  var simResourcesAndToolsString = JoistA11yStrings.simResourcesAndTools.value;
  var simScreensString = JoistA11yStrings.simScreens.value;

  // constants
  // for layout of the NavigationBar, used in the following way:
  // [
  //  {TITLE_LEFT_MARGIN}Title{TITLE_RIGHT_MARGIN}
  //  {HOME_BUTTON_LEFT_MARGIN}HomeButton{HOME_BUTTON_RIGHT_MARGIN} (if visible)
  //  {ScreenButtons centered} (if visible)
  //  a11yButtonsHBox (if present){PHET_BUTTON_LEFT_MARGIN}PhetButton{PHET_BUTTON_RIGHT_MARGIN}
  // ]
  var NAVIGATION_BAR_SIZE = new Dimension2( HomeScreenView.LAYOUT_BOUNDS.width, 40 );
  var TITLE_LEFT_MARGIN = 10;
  var TITLE_RIGHT_MARGIN = 25;
  var PHET_BUTTON_LEFT_MARGIN = 6;
  var PHET_BUTTON_RIGHT_MARGIN = 10;
  var PHET_BUTTON_BOTTOM_MARGIN = 0;
  var HOME_BUTTON_LEFT_MARGIN = 5;
  var HOME_BUTTON_RIGHT_MARGIN = HOME_BUTTON_LEFT_MARGIN;
  var SCREEN_BUTTON_SPACING = 0;
  var MINIMUM_SCREEN_BUTTON_WIDTH = 60; // Make sure each button is at least a minimum width so they don't get too close together, see #279

  /**
   * Creates a nav bar.
   * @param {Sim} sim
   * @param {Screen[]} screens
   * @param {BooleanProperty} showHomeScreenProperty
   * @param {Tandem} tandem
   * @constructor
   */
  function NavigationBar( sim, screens, showHomeScreenProperty, tandem ) {
    var self = this;

    // @private
    this.screens = screens;

    Node.call( this, {

      // a11y
      tagName: 'div',
      ariaRole: 'region',
      labelTagName: 'h2',

      // Use a different string, omitting "screens" if single screen sim.
      labelContent: screens.length === 1 ? simResourcesAndToolsString : simScreensResourcesAndToolsString
    } );

    // @private
    this.navigationBarFillProperty = new DerivedProperty(
      [
        showHomeScreenProperty,
        sim.lookAndFeel.navigationBarFillProperty
      ],
      function( showHomeScreen, simNavigationBarFill ) {
        var fill;
        if ( showHomeScreen ) {
          fill = 'black';
        }
        else {
          fill = simNavigationBarFill;
        }
        return fill;
      } );

    // @private - The bar's background (resized in layout)
    this.background = new Rectangle( 0, 0, NAVIGATION_BAR_SIZE.width, NAVIGATION_BAR_SIZE.height, {
      pickable: true,
      fill: this.navigationBarFillProperty
    } );
    this.addChild( this.background );

    // @private - Everything else besides the background in the navigation bar (used for scaling)
    this.barContents = new Node();
    this.addChild( this.barContents );

    var title = sim.name;

    // If the 'screens' query parameter only selects 1 screen, than update the nav bar title to include that screen name.
    if ( phet.chipper.queryParameters.screens && phet.chipper.queryParameters.screens.length === 1 && screens[ 0 ].name ) {
      title = StringUtils.fillIn( simTitleWithScreenNamePatternString, {
        simName: sim.name,
        screenName: screens[ 0 ].name
      } );
    }

    // Sim title
    this.titleTextNode = new Text( title, {
      font: new PhetFont( 16 ),
      fill: sim.lookAndFeel.navigationBarTextFillProperty,
      tandem: tandem.createTandem( 'titleTextNode' ),
      phetioInstanceDocumentation: 'Displays the title of the simulation in the navigation bar (bottom left).'
    } );
    this.titleTextNode.setVisible( false );
    this.barContents.addChild( this.titleTextNode );

    // @public (joist-internal) - PhET button. The transform of this is tracked, so we can mirror it over to the
    // homescreen's button. See https://github.com/phetsims/joist/issues/304.
    this.phetButton = new PhetButton(
      sim,
      this.navigationBarFillProperty,
      tandem.createTandem( 'phetButton' )
    );
    this.barContents.addChild( this.phetButton );

    // @public (joist-internal) - a11y HBox. The transform of this is tracked, so we can mirror it over to the
    // homescreen's a11y HBox. Copied from PhET button above, see https://github.com/phetsims/joist/issues/304.
    this.a11yButtonsHBox = new A11yButtonsHBox(
      sim,
      this.navigationBarFillProperty,
      tandem.createTandem( 'a11yButtonsHBox' )
    );
    this.barContents.addChild( this.a11yButtonsHBox );

    // a11y - tell this node that it is aria-labelledby its own labelContent.
    this.addAriaLabelledbyAssociation( {
      thisElementName: AccessiblePeer.PRIMARY_SIBLING,
      otherNode: this,
      otherElementName: AccessiblePeer.LABEL_SIBLING
    } );

    if ( screens.length === 1 ) {
      /* single-screen sim */

      // title can occupy all space to the left of the PhET button
      this.titleTextNode.maxWidth = HomeScreenView.LAYOUT_BOUNDS.width - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN -
                                    PHET_BUTTON_LEFT_MARGIN - this.a11yButtonsHBox.width - PHET_BUTTON_LEFT_MARGIN -
                                    this.phetButton.width - PHET_BUTTON_RIGHT_MARGIN;
    }
    else {
      /* multi-screen sim */

      // Start with the assumption that the title can occupy (at most) this percentage of the bar.
      var maxTitleWidth = Math.min( this.titleTextNode.width, 0.20 * HomeScreenView.LAYOUT_BOUNDS.width );

      // a11y - container for the homeButton and all the screen buttons.
      var buttons = new Node( {
        tagName: 'nav',
        ariaLabel: simScreensString
      } );
      var buttonsOrderedList = new Node( { tagName: 'ol' } );
      buttons.addChild( buttonsOrderedList );
      buttons.setVisible( false );
      this.barContents.addChild( buttons );

      // @private - Create the home button
      this.homeButton = new HomeButton(
        NAVIGATION_BAR_SIZE.height,
        sim.lookAndFeel.navigationBarFillProperty,
        tandem.createTandem( 'homeButton' ), {
          listener: function() {
            sim.showHomeScreenProperty.value = true;
          },
          // passed to a11yClick in ButtonModel
          a11yEndListener: function() {
            sim.homeScreen.view.highlightedScreenButton.focus();
          }
        } );

      // Add the home button, but only if it isn't turned off with ?homeScreen=false
      phet.chipper.queryParameters.homeScreen && buttonsOrderedList.addChild( this.homeButton );

      /*
       * Allocate remaining horizontal space equally for screen buttons, assuming they will be centered in the navbar.
       * Computations here reflect the left-to-right layout of the navbar.
       */
      // available width left of center
      var availableLeft = ( HomeScreenView.LAYOUT_BOUNDS.width / 2 ) - TITLE_LEFT_MARGIN - maxTitleWidth - TITLE_RIGHT_MARGIN -
                          HOME_BUTTON_LEFT_MARGIN - this.homeButton.width - HOME_BUTTON_RIGHT_MARGIN;

      // available width right of center
      var availableRight = ( HomeScreenView.LAYOUT_BOUNDS.width / 2 ) - PHET_BUTTON_LEFT_MARGIN -
                           this.a11yButtonsHBox.width - PHET_BUTTON_LEFT_MARGIN - this.phetButton.width -
                           PHET_BUTTON_RIGHT_MARGIN;

      // total available width for the screen buttons when they are centered
      var availableTotal = 2 * Math.min( availableLeft, availableRight );

      // width per screen button
      var screenButtonWidth = ( availableTotal - ( screens.length - 1 ) * SCREEN_BUTTON_SPACING ) / screens.length;

      // Create the screen buttons
      var screenButtons = _.map( screens, function( screen ) {
        return new NavigationBarScreenButton(
          sim.lookAndFeel.navigationBarFillProperty,
          sim.screenIndexProperty,
          sim.screens,
          screen,
          NAVIGATION_BAR_SIZE.height, {
            maxButtonWidth: screenButtonWidth,
            tandem: tandem.createTandem( screen.screenTandem.tail + 'Button' )
          } );
      } );

      // Layout out screen buttons horizontally, with equal distance between their centers
      // Make sure each button is at least a minimum size, so they don't get too close together, see #279
      var maxScreenButtonWidth = Math.max( MINIMUM_SCREEN_BUTTON_WIDTH, _.maxBy( screenButtons, function( button ) {
        return button.width;
      } ).width );

      // Compute the distance between *centers* of each button
      var spaceBetweenButtons = maxScreenButtonWidth + SCREEN_BUTTON_SPACING;
      for ( var i = 0; i < screenButtons.length; i++ ) {

        // Equally space the centers of the buttons around the origin of their parent (screenButtonsContainer)
        screenButtons[ i ].centerX = spaceBetweenButtons * ( i - ( screenButtons.length - 1 ) / 2 );
      }

      // @private - Put all screen buttons under a parent, to simplify layout
      this.screenButtonsContainer = new Node( {
        children: screenButtons,
        // NOTE: these layout settings are duplicated in layout(), but are necessary due to title's maxWidth requiring layout
        x: this.background.centerX, // since we have buttons centered around our origin, this centers the buttons
        centerY: this.background.centerY,
        maxWidth: availableTotal // in case we have so many screens that the screen buttons need to be scaled down
      } );
      buttonsOrderedList.addChild( this.screenButtonsContainer );

      // Now determine the actual width constraint for the sim title.
      this.titleTextNode.maxWidth = this.screenButtonsContainer.left - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN -
                                    HOME_BUTTON_RIGHT_MARGIN - this.homeButton.width - HOME_BUTTON_LEFT_MARGIN;
    }

    // initial layout (that doesn't need to change when we are re-laid out)
    this.titleTextNode.left = TITLE_LEFT_MARGIN;
    this.titleTextNode.centerY = NAVIGATION_BAR_SIZE.height / 2;
    this.phetButton.bottom = NAVIGATION_BAR_SIZE.height - PHET_BUTTON_BOTTOM_MARGIN;

    // only if some a11y buttons exist
    if ( this.a11yButtonsHBox.getChildrenCount() > 0 ) {

      // The icon is vertically adjusted in KeyboardHelpButton, so that the centers can be aligned here
      this.a11yButtonsHBox.centerY = this.phetButton.centerY;
    }
    if ( this.screens.length !== 1 ) {
      this.screenButtonsContainer.centerY = NAVIGATION_BAR_SIZE.height / 2;
      this.homeButton.centerY = NAVIGATION_BAR_SIZE.height / 2;
    }

    this.layout( 1, NAVIGATION_BAR_SIZE.width, NAVIGATION_BAR_SIZE.height );

    // a11y - keyboard help button before phet menu button, but only if it exists
    this.accessibleOrder = [
      buttons,
      this.a11yButtonsHBox,
      this.phetButton
    ].filter( function( node ) { return node !== undefined; } );

    showHomeScreenProperty.link( function( showHomeScreen ) {
      self.showHomeScreen = showHomeScreen;
      self.titleTextNode.setVisible( !showHomeScreen );
      if ( buttons ) {
        buttons.setVisible( !showHomeScreen );
      }
    } );
  }

  joist.register( 'NavigationBar', NavigationBar );

  return inherit( Node, NavigationBar, {

    /**
     * Called when the navigation bar layout needs to be updated, typically when the browser window is resized.
     * @param {number} scale
     * @param {number} width
     * @param {number} height
     * @public
     */
    layout: function( scale, width, height ) {

      // resize the background
      this.background.rectWidth = width;
      this.background.rectHeight = height;

      // scale the entire bar contents
      this.barContents.setScaleMagnitude( scale );

      // determine our local-coordinate 'right' side of the screen, so we can expand if necessary
      var right;
      if ( NAVIGATION_BAR_SIZE.width * scale < width ) {
        // expanded
        right = width / scale;
      }
      else {
        // compact
        right = NAVIGATION_BAR_SIZE.width;
      }

      // horizontal positioning
      this.phetButton.right = right - PHET_BUTTON_RIGHT_MARGIN;

      if ( this.a11yButtonsHBox.getChildrenCount() > 0 ) {
        this.a11yButtonsHBox.right = this.phetButton.left - PHET_BUTTON_LEFT_MARGIN;
      }

      // For multi-screen sims ...
      if ( this.screens.length !== 1 ) {

        // Screen buttons centered.  These buttons are centered around the origin in the screenButtonsContainer, so the
        // screenButtonsContainer can be put at the center of the navbar.
        this.screenButtonsContainer.x = right / 2;

        // home button to the left of screen buttons
        this.homeButton.right = this.screenButtonsContainer.left - HOME_BUTTON_RIGHT_MARGIN;

        // max width relative to position of home button
        this.titleTextNode.maxWidth = this.homeButton.left - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN;
      }
    }
  }, {

    // @public
    NAVIGATION_BAR_SIZE: NAVIGATION_BAR_SIZE
  } );
} );<commit_msg>Removed unnecessary line, see https://github.com/phetsims/joist/issues/503<commit_after>// Copyright 2013-2018, University of Colorado Boulder

/**
 * The navigation bar at the bottom of the screen.
 * For a single-screen sim, it shows the name of the sim at the far left and the PhET button at the far right.
 * For a multi-screen sim, it additionally shows buttons for each screen, and a home button.
 *
 * Layout of NavigationBar adapts to different text widths, icon widths, and numbers of screens, and attempts to
 * perform an "optimal" layout. The sim title is initially constrained to a max percentage of the bar width,
 * and that's used to compute how much space is available for screen buttons.  After creation and layout of the
 * screen buttons, we then compute how much space is actually available for the sim title, and use that to
 * constrain the title's width.
 *
 * The bar is composed of a background (always pixel-perfect), and expandable content (that gets scaled as one part).
 * If we are width-constrained, the navigation bar is in a 'compact' state where the children of the content (e.g.
 * home button, screen buttons, phet menu, title) do not change positions. If we are height-constrained, the amount
 * available to the bar expands, so we lay out the children to fit. See https://github.com/phetsims/joist/issues/283
 * for more details on how this is done.
 *
 * @author Sam Reid (PhET Interactive Simulations)
 * @author Chris Malley (PixelZoom, Inc.)
 * @author Jonathan Olson <jonathan.olson@colorado.edu>
 */
define( function( require ) {
  'use strict';

  // modules
  var AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  var A11yButtonsHBox = require( 'JOIST/A11yButtonsHBox' );
  var Dimension2 = require( 'DOT/Dimension2' );
  var HomeButton = require( 'JOIST/HomeButton' );
  var HomeScreenView = require( 'JOIST/HomeScreenView' );
  var inherit = require( 'PHET_CORE/inherit' );
  var joist = require( 'JOIST/joist' );
  var JoistA11yStrings = require( 'JOIST/JoistA11yStrings' );
  var NavigationBarScreenButton = require( 'JOIST/NavigationBarScreenButton' );
  var Node = require( 'SCENERY/nodes/Node' );
  var PhetButton = require( 'JOIST/PhetButton' );
  var PhetFont = require( 'SCENERY_PHET/PhetFont' );
  var DerivedProperty = require( 'AXON/DerivedProperty' );
  var Rectangle = require( 'SCENERY/nodes/Rectangle' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var Text = require( 'SCENERY/nodes/Text' );

  // strings
  var simTitleWithScreenNamePatternString = require( 'string!JOIST/simTitleWithScreenNamePattern' );

  // a11y strings
  var simScreensResourcesAndToolsString = JoistA11yStrings.simScreensResourcesAndTools.value;
  var simResourcesAndToolsString = JoistA11yStrings.simResourcesAndTools.value;
  var simScreensString = JoistA11yStrings.simScreens.value;

  // constants
  // for layout of the NavigationBar, used in the following way:
  // [
  //  {TITLE_LEFT_MARGIN}Title{TITLE_RIGHT_MARGIN}
  //  {HOME_BUTTON_LEFT_MARGIN}HomeButton{HOME_BUTTON_RIGHT_MARGIN} (if visible)
  //  {ScreenButtons centered} (if visible)
  //  a11yButtonsHBox (if present){PHET_BUTTON_LEFT_MARGIN}PhetButton{PHET_BUTTON_RIGHT_MARGIN}
  // ]
  var NAVIGATION_BAR_SIZE = new Dimension2( HomeScreenView.LAYOUT_BOUNDS.width, 40 );
  var TITLE_LEFT_MARGIN = 10;
  var TITLE_RIGHT_MARGIN = 25;
  var PHET_BUTTON_LEFT_MARGIN = 6;
  var PHET_BUTTON_RIGHT_MARGIN = 10;
  var PHET_BUTTON_BOTTOM_MARGIN = 0;
  var HOME_BUTTON_LEFT_MARGIN = 5;
  var HOME_BUTTON_RIGHT_MARGIN = HOME_BUTTON_LEFT_MARGIN;
  var SCREEN_BUTTON_SPACING = 0;
  var MINIMUM_SCREEN_BUTTON_WIDTH = 60; // Make sure each button is at least a minimum width so they don't get too close together, see #279

  /**
   * Creates a nav bar.
   * @param {Sim} sim
   * @param {Screen[]} screens
   * @param {BooleanProperty} showHomeScreenProperty
   * @param {Tandem} tandem
   * @constructor
   */
  function NavigationBar( sim, screens, showHomeScreenProperty, tandem ) {
    var self = this;

    // @private
    this.screens = screens;

    Node.call( this, {

      // a11y
      tagName: 'div',
      ariaRole: 'region',
      labelTagName: 'h2',

      // Use a different string, omitting "screens" if single screen sim.
      labelContent: screens.length === 1 ? simResourcesAndToolsString : simScreensResourcesAndToolsString
    } );

    // @private
    this.navigationBarFillProperty = new DerivedProperty(
      [
        showHomeScreenProperty,
        sim.lookAndFeel.navigationBarFillProperty
      ],
      function( showHomeScreen, simNavigationBarFill ) {
        var fill;
        if ( showHomeScreen ) {
          fill = 'black';
        }
        else {
          fill = simNavigationBarFill;
        }
        return fill;
      } );

    // @private - The bar's background (resized in layout)
    this.background = new Rectangle( 0, 0, NAVIGATION_BAR_SIZE.width, NAVIGATION_BAR_SIZE.height, {
      pickable: true,
      fill: this.navigationBarFillProperty
    } );
    this.addChild( this.background );

    // @private - Everything else besides the background in the navigation bar (used for scaling)
    this.barContents = new Node();
    this.addChild( this.barContents );

    var title = sim.name;

    // If the 'screens' query parameter only selects 1 screen, than update the nav bar title to include that screen name.
    if ( phet.chipper.queryParameters.screens && phet.chipper.queryParameters.screens.length === 1 && screens[ 0 ].name ) {
      title = StringUtils.fillIn( simTitleWithScreenNamePatternString, {
        simName: sim.name,
        screenName: screens[ 0 ].name
      } );
    }

    // Sim title
    this.titleTextNode = new Text( title, {
      font: new PhetFont( 16 ),
      fill: sim.lookAndFeel.navigationBarTextFillProperty,
      tandem: tandem.createTandem( 'titleTextNode' ),
      phetioInstanceDocumentation: 'Displays the title of the simulation in the navigation bar (bottom left).'
    } );
    this.titleTextNode.setVisible( false );
    this.barContents.addChild( this.titleTextNode );

    // @public (joist-internal) - PhET button. The transform of this is tracked, so we can mirror it over to the
    // homescreen's button. See https://github.com/phetsims/joist/issues/304.
    this.phetButton = new PhetButton(
      sim,
      this.navigationBarFillProperty,
      tandem.createTandem( 'phetButton' )
    );
    this.barContents.addChild( this.phetButton );

    // @public (joist-internal) - a11y HBox. The transform of this is tracked, so we can mirror it over to the
    // homescreen's a11y HBox. Copied from PhET button above, see https://github.com/phetsims/joist/issues/304.
    this.a11yButtonsHBox = new A11yButtonsHBox(
      sim,
      this.navigationBarFillProperty,
      tandem.createTandem( 'a11yButtonsHBox' )
    );
    this.barContents.addChild( this.a11yButtonsHBox );

    // a11y - tell this node that it is aria-labelledby its own labelContent.
    this.addAriaLabelledbyAssociation( {
      thisElementName: AccessiblePeer.PRIMARY_SIBLING,
      otherNode: this,
      otherElementName: AccessiblePeer.LABEL_SIBLING
    } );

    if ( screens.length === 1 ) {
      /* single-screen sim */

      // title can occupy all space to the left of the PhET button
      this.titleTextNode.maxWidth = HomeScreenView.LAYOUT_BOUNDS.width - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN -
                                    PHET_BUTTON_LEFT_MARGIN - this.a11yButtonsHBox.width - PHET_BUTTON_LEFT_MARGIN -
                                    this.phetButton.width - PHET_BUTTON_RIGHT_MARGIN;
    }
    else {
      /* multi-screen sim */

      // Start with the assumption that the title can occupy (at most) this percentage of the bar.
      var maxTitleWidth = Math.min( this.titleTextNode.width, 0.20 * HomeScreenView.LAYOUT_BOUNDS.width );

      // a11y - container for the homeButton and all the screen buttons.
      var buttons = new Node( {
        tagName: 'nav',
        ariaLabel: simScreensString
      } );
      var buttonsOrderedList = new Node( { tagName: 'ol' } );
      buttons.addChild( buttonsOrderedList );
      buttons.setVisible( false );
      this.barContents.addChild( buttons );

      // @private - Create the home button
      this.homeButton = new HomeButton(
        NAVIGATION_BAR_SIZE.height,
        sim.lookAndFeel.navigationBarFillProperty,
        tandem.createTandem( 'homeButton' ), {
          listener: function() {
            sim.showHomeScreenProperty.value = true;
          },
          // passed to a11yClick in ButtonModel
          a11yEndListener: function() {
            sim.homeScreen.view.highlightedScreenButton.focus();
          }
        } );

      // Add the home button, but only if it isn't turned off with ?homeScreen=false
      phet.chipper.queryParameters.homeScreen && buttonsOrderedList.addChild( this.homeButton );

      /*
       * Allocate remaining horizontal space equally for screen buttons, assuming they will be centered in the navbar.
       * Computations here reflect the left-to-right layout of the navbar.
       */
      // available width left of center
      var availableLeft = ( HomeScreenView.LAYOUT_BOUNDS.width / 2 ) - TITLE_LEFT_MARGIN - maxTitleWidth - TITLE_RIGHT_MARGIN -
                          HOME_BUTTON_LEFT_MARGIN - this.homeButton.width - HOME_BUTTON_RIGHT_MARGIN;

      // available width right of center
      var availableRight = ( HomeScreenView.LAYOUT_BOUNDS.width / 2 ) - PHET_BUTTON_LEFT_MARGIN -
                           this.a11yButtonsHBox.width - PHET_BUTTON_LEFT_MARGIN - this.phetButton.width -
                           PHET_BUTTON_RIGHT_MARGIN;

      // total available width for the screen buttons when they are centered
      var availableTotal = 2 * Math.min( availableLeft, availableRight );

      // width per screen button
      var screenButtonWidth = ( availableTotal - ( screens.length - 1 ) * SCREEN_BUTTON_SPACING ) / screens.length;

      // Create the screen buttons
      var screenButtons = _.map( screens, function( screen ) {
        return new NavigationBarScreenButton(
          sim.lookAndFeel.navigationBarFillProperty,
          sim.screenIndexProperty,
          sim.screens,
          screen,
          NAVIGATION_BAR_SIZE.height, {
            maxButtonWidth: screenButtonWidth,
            tandem: tandem.createTandem( screen.screenTandem.tail + 'Button' )
          } );
      } );

      // Layout out screen buttons horizontally, with equal distance between their centers
      // Make sure each button is at least a minimum size, so they don't get too close together, see #279
      var maxScreenButtonWidth = Math.max( MINIMUM_SCREEN_BUTTON_WIDTH, _.maxBy( screenButtons, function( button ) {
        return button.width;
      } ).width );

      // Compute the distance between *centers* of each button
      var spaceBetweenButtons = maxScreenButtonWidth + SCREEN_BUTTON_SPACING;
      for ( var i = 0; i < screenButtons.length; i++ ) {

        // Equally space the centers of the buttons around the origin of their parent (screenButtonsContainer)
        screenButtons[ i ].centerX = spaceBetweenButtons * ( i - ( screenButtons.length - 1 ) / 2 );
      }

      // @private - Put all screen buttons under a parent, to simplify layout
      this.screenButtonsContainer = new Node( {
        children: screenButtons,
        // NOTE: these layout settings are duplicated in layout(), but are necessary due to title's maxWidth requiring layout
        x: this.background.centerX, // since we have buttons centered around our origin, this centers the buttons
        centerY: this.background.centerY,
        maxWidth: availableTotal // in case we have so many screens that the screen buttons need to be scaled down
      } );
      buttonsOrderedList.addChild( this.screenButtonsContainer );

      // Now determine the actual width constraint for the sim title.
      this.titleTextNode.maxWidth = this.screenButtonsContainer.left - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN -
                                    HOME_BUTTON_RIGHT_MARGIN - this.homeButton.width - HOME_BUTTON_LEFT_MARGIN;
    }

    // initial layout (that doesn't need to change when we are re-laid out)
    this.titleTextNode.left = TITLE_LEFT_MARGIN;
    this.titleTextNode.centerY = NAVIGATION_BAR_SIZE.height / 2;
    this.phetButton.bottom = NAVIGATION_BAR_SIZE.height - PHET_BUTTON_BOTTOM_MARGIN;

    // only if some a11y buttons exist
    if ( this.a11yButtonsHBox.getChildrenCount() > 0 ) {

      // The icon is vertically adjusted in KeyboardHelpButton, so that the centers can be aligned here
      this.a11yButtonsHBox.centerY = this.phetButton.centerY;
    }
    if ( this.screens.length !== 1 ) {
      this.screenButtonsContainer.centerY = NAVIGATION_BAR_SIZE.height / 2;
      this.homeButton.centerY = NAVIGATION_BAR_SIZE.height / 2;
    }

    this.layout( 1, NAVIGATION_BAR_SIZE.width, NAVIGATION_BAR_SIZE.height );

    // a11y - keyboard help button before phet menu button, but only if it exists
    this.accessibleOrder = [
      buttons,
      this.a11yButtonsHBox,
      this.phetButton
    ].filter( function( node ) { return node !== undefined; } );

    showHomeScreenProperty.link( function( showHomeScreen ) {
      self.titleTextNode.setVisible( !showHomeScreen );
      if ( buttons ) {
        buttons.setVisible( !showHomeScreen );
      }
    } );
  }

  joist.register( 'NavigationBar', NavigationBar );

  return inherit( Node, NavigationBar, {

    /**
     * Called when the navigation bar layout needs to be updated, typically when the browser window is resized.
     * @param {number} scale
     * @param {number} width
     * @param {number} height
     * @public
     */
    layout: function( scale, width, height ) {

      // resize the background
      this.background.rectWidth = width;
      this.background.rectHeight = height;

      // scale the entire bar contents
      this.barContents.setScaleMagnitude( scale );

      // determine our local-coordinate 'right' side of the screen, so we can expand if necessary
      var right;
      if ( NAVIGATION_BAR_SIZE.width * scale < width ) {
        // expanded
        right = width / scale;
      }
      else {
        // compact
        right = NAVIGATION_BAR_SIZE.width;
      }

      // horizontal positioning
      this.phetButton.right = right - PHET_BUTTON_RIGHT_MARGIN;

      if ( this.a11yButtonsHBox.getChildrenCount() > 0 ) {
        this.a11yButtonsHBox.right = this.phetButton.left - PHET_BUTTON_LEFT_MARGIN;
      }

      // For multi-screen sims ...
      if ( this.screens.length !== 1 ) {

        // Screen buttons centered.  These buttons are centered around the origin in the screenButtonsContainer, so the
        // screenButtonsContainer can be put at the center of the navbar.
        this.screenButtonsContainer.x = right / 2;

        // home button to the left of screen buttons
        this.homeButton.right = this.screenButtonsContainer.left - HOME_BUTTON_RIGHT_MARGIN;

        // max width relative to position of home button
        this.titleTextNode.maxWidth = this.homeButton.left - TITLE_LEFT_MARGIN - TITLE_RIGHT_MARGIN;
      }
    }
  }, {

    // @public
    NAVIGATION_BAR_SIZE: NAVIGATION_BAR_SIZE
  } );
} );<|endoftext|>