# files

name: jquery.form-validator.min.js
path: D:\websites\domains\a.sds.sh\public_html\a\js\jq\jquery.form-validator.min.js
mime: unavailable
content:
/**
* jQuery Form Validator
* ------------------------------------------
* Created by Victor Jonsson <http://www.victorjonsson.se>
*
* @website http://formvalidator.net/
* @license Dual licensed under the MIT or GPL Version 2 licenses
* @version 2.1.47
*/
(function($){"use strict";var _applyErrorStyle=function($elem,conf){$elem.addClass(conf.errorElementClass).removeClass("valid").parent().addClass("has-error").removeClass("has-success");if(conf.borderColorOnError!==""){$elem.css("border-color",conf.borderColorOnError)}},_removeErrorStyle=function($elem,conf){$elem.each(function(){_setInlineErrorMessage($(this),"",conf,conf.errorMessagePosition);$(this).removeClass("valid").removeClass(conf.errorElementClass).css("border-color","").parent().removeClass("has-error").removeClass("has-success").find("."+conf.errorMessageClass).remove()})},_setInlineErrorMessage=function($input,mess,conf,$messageContainer){var custom=_getInlineErrorElement($input);if(custom){custom.innerHTML=mess}else if(typeof $messageContainer=="object"){var $found=false;$messageContainer.find("."+conf.errorMessageClass).each(function(){if(this.inputReferer==$input[0]){$found=$(this);return false}});if($found){if(!mess){$found.remove()}else{$found.html(mess)}}else{var $mess=$('<div class="'+conf.errorMessageClass+'">'+mess+"</div>");$mess[0].inputReferer=$input[0];$messageContainer.prepend($mess)}}else{var $mess=$input.parent().find("."+conf.errorMessageClass+".help-block");if($mess.length==0){$mess=$("<span></span>").addClass("help-block").addClass(conf.errorMessageClass);$mess.appendTo($input.parent())}$mess.html(mess)}},_getInlineErrorElement=function($input,conf){return document.getElementById($input.attr("name")+"_err_msg")};$.fn.validateOnBlur=function(language,settings){this.find("input[data-validation],textarea[data-validation],select[data-validation]").bind("blur.validation",function(){$(this).validateInputOnBlur(language,settings)});return this};$.fn.validateOnEvent=function(language,settings){this.find("input[data-validation][data-validation-event],textarea[data-validation][data-validation-event],select[data-validation][data-validation-event]").each(function(){var $el=$(this),etype=$el.attr("data-validation-event");if(etype){$el.bind(etype+".validation",function(){$(this).validateInputOnBlur(language,settings,false,etype)})}});return this};$.fn.showHelpOnFocus=function(attrName){if(!attrName){attrName="data-validation-help"}this.find(".has-help-txt").valAttr("has-keyup-event",false).valAttr("backend-valid",false).valAttr("backend-invalid",false).removeClass("has-help-txt");this.find("textarea,input").each(function(){var $elem=$(this),className="jquery_form_help_"+($elem.attr("name")||"").replace(/(:|\.|\[|\])/g,""),help=$elem.attr(attrName);if(help){$elem.addClass("has-help-txt").unbind("focus.help").bind("focus.help",function(){var $help=$elem.parent().find("."+className);if($help.length==0){$help=$("<span />").addClass(className).addClass("help").addClass("help-block").text(help).hide();$elem.after($help)}$help.fadeIn()}).unbind("blur.help").bind("blur.help",function(){$(this).parent().find("."+className).fadeOut("slow")})}});return this};$.fn.validateInputOnBlur=function(language,conf,attachKeyupEvent,eventContext){if(attachKeyupEvent===undefined)attachKeyupEvent=true;if(!eventContext)eventContext="blur";if((this.valAttr("suggestion-nr")||this.valAttr("postpone")||this.hasClass("hasDatepicker"))&&!window.postponedValidation){var _self=this,postponeTime=this.valAttr("postpone")||200;window.postponedValidation=function(){_self.validateInputOnBlur(language,conf,attachKeyupEvent);window.postponedValidation=false};setTimeout(function(){if(window.postponedValidation){window.postponedValidation()}},postponeTime);return this}language=$.extend({},$.formUtils.LANG,language||{});_removeErrorStyle(this,conf);var $elem=this,$form=$elem.closest("form"),validationRule=$elem.attr(conf.validationRuleAttribute),validation=$.formUtils.validateInput($elem,language,$.extend({},conf,{errorMessagePosition:"element"}),$form,eventContext);$elem.trigger("validation",[validation===null?null:validation===true]);if(validation===true){$elem.addClass("valid").parent().addClass("has-success")}else if(validation!==null){_applyErrorStyle($elem,conf);_setInlineErrorMessage($elem,validation,conf,conf.errorMessagePosition);if(attachKeyupEvent){$elem.bind("keyup",function(){$(this).validateInputOnBlur(language,conf,false,"keyup")})}}return this};$.fn.valAttr=function(name,val){if(val===undefined){return this.attr("data-validation-"+name)}else if(val===false||val===null){return this.removeAttr("data-validation-"+name)}else{if(name.length>0)name="-"+name;return this.attr("data-validation"+name,val)}};$.fn.validateForm=function(language,conf){language=$.extend({},$.formUtils.LANG,language||{});$.formUtils.isValidatingEntireForm=true;$.formUtils.haltValidation=false;var addErrorMessage=function(mess,$elem){if(mess!==null){if($.inArray(mess,errorMessages)<0){errorMessages.push(mess)}errorInputs.push($elem);$elem.attr("current-error",mess);_applyErrorStyle($elem,conf)}},errorMessages=[],errorInputs=[],$form=this,ignoreInput=function(name,type){if(type==="submit"||type==="button"||type=="reset"){return true}return $.inArray(name,conf.ignore||[])>-1};$form.find("."+conf.errorMessageClass+".alert").remove();_removeErrorStyle($form.find("."+conf.errorElementClass+",.valid"),conf);$form.find("input,textarea,select").filter(':not([type="submit"],[type="button"])').each(function(){var $elem=$(this);var elementType=$elem.attr("type");if(!ignoreInput($elem.attr("name"),elementType)){var validation=$.formUtils.validateInput($elem,language,conf,$form,"submit");$elem.trigger("validation",[validation===true]);if(validation!==true){addErrorMessage(validation,$elem)}else{$elem.valAttr("current-error",false).addClass("valid").parent().addClass("has-success")}}});if(typeof conf.onValidate=="function"){var errors=conf.onValidate($form);if($.isArray(errors)){$.each(errors,function(i,err){addErrorMessage(err.message,err.element)})}else if(errors&&errors.element&&errors.message){addErrorMessage(errors.message,errors.element)}}if(!$.formUtils.haltValidation&&errorInputs.length>0){$.formUtils.isValidatingEntireForm=false;if(conf.errorMessagePosition==="top"){var messages="<strong>"+language.errorTitle+"</strong>";$.each(errorMessages,function(i,mess){messages+="<br />* "+mess});$form.children().eq(0).before('<div class="'+conf.errorMessageClass+' alert alert-danger">'+messages+"</div>")}else{$.each(errorInputs,function(i,$input){_setInlineErrorMessage($input,$input.attr("current-error"),conf,conf.errorMessagePosition)})}if(conf.scrollToTopOnError){$(window).scrollTop($form.offset().top-20)}return false}$.formUtils.isValidatingEntireForm=false;return!$.formUtils.haltValidation};$.fn.restrictLength=function(maxLengthElement){new $.formUtils.lengthRestriction(this,maxLengthElement);return this};$.fn.addSuggestions=function(settings){var sugs=false;this.find("input").each(function(){var $field=$(this);sugs=$.split($field.attr("data-suggestions"));if(sugs.length>0&&!$field.hasClass("has-suggestions")){$.formUtils.suggest($field,sugs,settings);$field.addClass("has-suggestions")}});return this};$.split=function(val,func,delim){if(typeof func!="function"){if(!val)return[];var values=[];$.each(val.split(func?func:","),function(i,str){str=$.trim(str);if(str.length)values.push(str)});return values}else if(val){if(!delim)delim=",";$.each(val.split(delim),function(i,str){str=$.trim(str);if(str.length)return func(str,i)})}};$.validate=function(conf){var defaultConf=$.extend($.formUtils.defaultConfig(),{form:"form",validateOnEvent:true,validateOnBlur:true,showHelpOnFocus:true,addSuggestions:true,modules:"",onModulesLoaded:null,language:false,onSuccess:false,onError:false});conf=$.extend(defaultConf,conf||{});$.split(conf.form,function(formQuery){var $form=$(formQuery);$form.find(".has-help-txt").unbind("focus.validation").unbind("blur.validation");$form.removeClass("has-validation-callback").unbind("submit.validation").unbind("reset.validation").find("input[data-validation],textarea[data-validation]").unbind("blur.validation");$form.bind("submit.validation",function(){var $form=$(this);if($.formUtils.isLoadingModules){setTimeout(function(){$form.trigger("submit.validation")},200);return false}var valid=$form.validateForm(conf.language,conf);if(valid&&typeof conf.onSuccess=="function"){var callbackResponse=conf.onSuccess($form);if(callbackResponse===false)return false}else if(!valid&&typeof conf.onError=="function"){conf.onError($form);return false}else{return valid}}).bind("reset.validation",function(){$(this).find("."+conf.errorMessageClass+".alert").remove();_removeErrorStyle($(this).find("."+conf.errorElementClass+",.valid"),conf)}).addClass("has-validation-callback");if(conf.showHelpOnFocus){$form.showHelpOnFocus()}if(conf.addSuggestions){$form.addSuggestions()}if(conf.validateOnBlur){$form.validateOnBlur(conf.language,conf)}if(conf.validateOnEvent){$form.validateOnEvent(conf.language,conf)}});if(conf.modules!=""){if(typeof conf.onModulesLoaded=="function"){$.formUtils.on("load",function(){conf.onModulesLoaded()})}$.formUtils.loadModules(conf.modules)}};$.formUtils={defaultConfig:function(){return{ignore:[],errorElementClass:"error",borderColorOnError:"red",errorMessageClass:"form-error",validationRuleAttribute:"data-validation",validationErrorMsgAttribute:"data-validation-error-msg",errorMessagePosition:"element",scrollToTopOnError:true,dateFormat:"yyyy-mm-dd",addValidClassOnAll:false,decimalSeparator:"."}},validators:{},_events:{load:[],valid:[],invalid:[]},haltValidation:false,isValidatingEntireForm:false,addValidator:function(validator){var name=validator.name.indexOf("validate_")===0?validator.name:"validate_"+validator.name;if(validator.validateOnKeyUp===undefined)validator.validateOnKeyUp=true;this.validators[name]=validator},on:function(evt,callback){if(this._events[evt]===undefined)this._events[evt]=[];this._events[evt].push(callback)},trigger:function(evt,argA,argB){$.each(this._events[evt]||[],function(i,func){func(argA,argB)})},isLoadingModules:false,loadedModules:{},loadModules:function(modules,path,fireEvent){if(fireEvent===undefined)fireEvent=true;if($.formUtils.isLoadingModules){setTimeout(function(){$.formUtils.loadModules(modules,path,fireEvent)});return}var hasLoadedAnyModule=false,loadModuleScripts=function(modules,path){var moduleList=$.split(modules),numModules=moduleList.length,moduleLoadedCallback=function(){numModules--;if(numModules==0){$.formUtils.isLoadingModules=false;if(fireEvent&&hasLoadedAnyModule){$.formUtils.trigger("load",path)}}};if(numModules>0){$.formUtils.isLoadingModules=true}var cacheSuffix="?__="+(new Date).getTime(),appendToElement=document.getElementsByTagName("head")[0]||document.getElementsByTagName("body")[0];$.each(moduleList,function(i,modName){modName=$.trim(modName);if(modName.length==0){moduleLoadedCallback()}else{var scriptUrl=path+modName+(modName.substr(-3)==".js"?"":".js"),script=document.createElement("SCRIPT");if(scriptUrl in $.formUtils.loadedModules){moduleLoadedCallback()}else{$.formUtils.loadedModules[scriptUrl]=1;hasLoadedAnyModule=true;script.type="text/javascript";script.onload=moduleLoadedCallback;script.src=scriptUrl+(scriptUrl.substr(-7)==".dev.js"?cacheSuffix:"");script.onreadystatechange=function(){if(this.readyState=="complete"){moduleLoadedCallback()}};appendToElement.appendChild(script)}}})};if(path){loadModuleScripts(modules,path)}else{var findScriptPathAndLoadModules=function(){var foundPath=false;$("script").each(function(){if(this.src){var scriptName=this.src.substr(this.src.lastIndexOf("/")+1,this.src.length);if(scriptName.indexOf("jquery.form-validator.js")>-1||scriptName.indexOf("jquery.form-validator.min.js")>-1){foundPath=this.src.substr(0,this.src.lastIndexOf("/"))+"/";if(foundPath=="/")foundPath="";return false}}});if(foundPath!==false){loadModuleScripts(modules,foundPath);return true}return false};if(!findScriptPathAndLoadModules()){$(findScriptPathAndLoadModules)}}},validateInput:function($elem,language,conf,$form,eventContext){if($elem.attr("disabled"))return null;$elem.trigger("beforeValidation");var value=$.trim($elem.val()||""),optional=$elem.valAttr("optional"),validationDependsOnCheckedInput=false,validationDependentInputIsChecked=false,validateIfCheckedElement=false,validateIfCheckedElementName=$elem.valAttr("if-checked");if(validateIfCheckedElementName!=null){validationDependsOnCheckedInput=true;validateIfCheckedElement=$form.find('input[name="'+validateIfCheckedElementName+'"]');if(validateIfCheckedElement.prop("checked")){validationDependentInputIsChecked=true}}if(!value&&optional==="true"||validationDependsOnCheckedInput&&!validationDependentInputIsChecked){return conf.addValidClassOnAll?true:null}var validationRules=$elem.attr(conf.validationRuleAttribute),validationErrorMsg=true;if(!validationRules){return conf.addValidClassOnAll?true:null}$.split(validationRules,function(rule){if(rule.indexOf("validate_")!==0){rule="validate_"+rule}var validator=$.formUtils.validators[rule];if(validator&&typeof validator["validatorFunction"]=="function"){if(rule=="validate_checkbox_group"){$elem=$("[name='"+$elem.attr("name")+"']:eq(0)")}var isValid=true;if(eventContext!="keyup"||validator.validateOnKeyUp){isValid=validator.validatorFunction(value,$elem,conf,language,$form)}if(!isValid){validationErrorMsg=$elem.attr(conf.validationErrorMsgAttribute);if(!validationErrorMsg){validationErrorMsg=language[validator.errorMessageKey];if(!validationErrorMsg)validationErrorMsg=validator.errorMessage}return false}}else{console.warn('Using undefined validator "'+rule+'"')}}," ");if(typeof validationErrorMsg=="string"){return validationErrorMsg}else{return true}},parseDate:function(val,dateFormat){var divider=dateFormat.replace(/[a-zA-Z]/gi,"").substring(0,1),regexp="^",formatParts=dateFormat.split(divider),matches,day,month,year;$.each(formatParts,function(i,part){regexp+=(i>0?"\\"+divider:"")+"(\\d{"+part.length+"})"});regexp+="$";matches=val.match(new RegExp(regexp));if(matches===null){return false}var findDateUnit=function(unit,formatParts,matches){for(var i=0;i<formatParts.length;i++){if(formatParts[i].substring(0,1)===unit){return $.formUtils.parseDateInt(matches[i+1])}}return-1};month=findDateUnit("m",formatParts,matches);day=findDateUnit("d",formatParts,matches);year=findDateUnit("y",formatParts,matches);if(month===2&&day>28&&(year%4!==0||year%100===0&&year%400!==0)||month===2&&day>29&&(year%4===0||year%100!==0&&year%400===0)||month>12||month===0){return false}if(this.isShortMonth(month)&&day>30||!this.isShortMonth(month)&&day>31||day===0){return false}return[year,month,day]},parseDateInt:function(val){if(val.indexOf("0")===0){val=val.replace("0","")}return parseInt(val,10)},isShortMonth:function(m){return m%2===0&&m<7||m%2!==0&&m>7},lengthRestriction:function($inputElement,$maxLengthElement){var maxChars=parseInt($maxLengthElement.text(),10),charsLeft=0,countCharacters=function(){var numChars=$inputElement.val().length;if(numChars>maxChars){var currScrollTopPos=$inputElement.scrollTop();$inputElement.val($inputElement.val().substring(0,maxChars));$inputElement.scrollTop(currScrollTopPos)}charsLeft=maxChars-numChars;if(charsLeft<0)charsLeft=0;$maxLengthElement.text(charsLeft)};$($inputElement).bind("keydown keyup keypress focus blur",countCharacters).bind("cut paste",function(){setTimeout(countCharacters,100)});$(document).bind("ready",countCharacters)},numericRangeCheck:function(value,rangeAllowed){var range=$.split(rangeAllowed,"-");var minmax=parseInt(rangeAllowed.substr(3),10);if(range.length==2&&(value<parseInt(range[0],10)||value>parseInt(range[1],10))){return["out",range[0],range[1]]}else if(rangeAllowed.indexOf("min")===0&&value<minmax){return["min",minmax]}else if(rangeAllowed.indexOf("max")===0&&value>minmax){return["max",minmax]}else{return["ok"]}},_numSuggestionElements:0,_selectedSuggestion:null,_previousTypedVal:null,suggest:function($elem,suggestions,settings){var conf={css:{maxHeight:"150px",background:"#FFF",lineHeight:"150%",textDecoration:"underline",overflowX:"hidden",overflowY:"auto",border:"#CCC solid 1px",borderTop:"none",cursor:"pointer"},activeSuggestionCSS:{background:"#E9E9E9"}},setSuggsetionPosition=function($suggestionContainer,$input){var offset=$input.offset();$suggestionContainer.css({width:$input.outerWidth(),left:offset.left+"px",top:offset.top+$input.outerHeight()+"px"})};if(settings)$.extend(conf,settings);conf.css["position"]="absolute";conf.css["z-index"]=9999;$elem.attr("autocomplete","off");if(this._numSuggestionElements===0){$(window).bind("resize",function(){$(".jquery-form-suggestions").each(function(){var $container=$(this),suggestID=$container.attr("data-suggest-container");setSuggsetionPosition($container,$(".suggestions-"+suggestID).eq(0))})})}this._numSuggestionElements++;var onSelectSuggestion=function($el){var suggestionId=$el.valAttr("suggestion-nr");$.formUtils._selectedSuggestion=null;$.formUtils._previousTypedVal=null;$(".jquery-form-suggestion-"+suggestionId).fadeOut("fast")};$elem.data("suggestions",suggestions).valAttr("suggestion-nr",this._numSuggestionElements).unbind("focus.suggest").bind("focus.suggest",function(){$(this).trigger("keyup");$.formUtils._selectedSuggestion=null}).unbind("keyup.suggest").bind("keyup.suggest",function(){var $input=$(this),foundSuggestions=[],val=$.trim($input.val()).toLocaleLowerCase();if(val==$.formUtils._previousTypedVal){return}else{$.formUtils._previousTypedVal=val}var hasTypedSuggestion=false,suggestionId=$input.valAttr("suggestion-nr"),$suggestionContainer=$(".jquery-form-suggestion-"+suggestionId);$suggestionContainer.scrollTop(0);if(val!=""){var findPartial=val.length>2;$.each($input.data("suggestions"),function(i,suggestion){var lowerCaseVal=suggestion.toLocaleLowerCase();if(lowerCaseVal==val){foundSuggestions.push("<strong>"+suggestion+"</strong>");hasTypedSuggestion=true;return false}else if(lowerCaseVal.indexOf(val)===0||findPartial&&lowerCaseVal.indexOf(val)>-1){foundSuggestions.push(suggestion.replace(new RegExp(val,"gi"),"<strong>$&</strong>"))}})}if(hasTypedSuggestion||foundSuggestions.length==0&&$suggestionContainer.length>0){$suggestionContainer.hide()}else if(foundSuggestions.length>0&&$suggestionContainer.length==0){$suggestionContainer=$("<div></div>").css(conf.css).appendTo("body");$elem.addClass("suggestions-"+suggestionId);$suggestionContainer.attr("data-suggest-container",suggestionId).addClass("jquery-form-suggestions").addClass("jquery-form-suggestion-"+suggestionId)}else if(foundSuggestions.length>0&&!$suggestionContainer.is(":visible")){$suggestionContainer.show()}if(foundSuggestions.length>0&&val.length!=foundSuggestions[0].length){setSuggsetionPosition($suggestionContainer,$input);$suggestionContainer.html("");$.each(foundSuggestions,function(i,text){$("<div></div>").append(text).css({overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",padding:"5px"}).addClass("form-suggest-element").appendTo($suggestionContainer).click(function(){$input.focus();$input.val($(this).text());onSelectSuggestion($input)})})}}).unbind("keydown.validation").bind("keydown.validation",function(e){var code=e.keyCode?e.keyCode:e.which,suggestionId,$suggestionContainer,$input=$(this);if(code==13&&$.formUtils._selectedSuggestion!==null){suggestionId=$input.valAttr("suggestion-nr");$suggestionContainer=$(".jquery-form-suggestion-"+suggestionId);if($suggestionContainer.length>0){var newText=$suggestionContainer.find("div").eq($.formUtils._selectedSuggestion).text();$input.val(newText);onSelectSuggestion($input);e.preventDefault()}}else{suggestionId=$input.valAttr("suggestion-nr");$suggestionContainer=$(".jquery-form-suggestion-"+suggestionId);var $suggestions=$suggestionContainer.children();if($suggestions.length>0&&$.inArray(code,[38,40])>-1){if(code==38){if($.formUtils._selectedSuggestion===null)$.formUtils._selectedSuggestion=$suggestions.length-1;else $.formUtils._selectedSuggestion--;if($.formUtils._selectedSuggestion<0)$.formUtils._selectedSuggestion=$suggestions.length-1}else if(code==40){if($.formUtils._selectedSuggestion===null)$.formUtils._selectedSuggestion=0;else $.formUtils._selectedSuggestion++;if($.formUtils._selectedSuggestion>$suggestions.length-1)$.formUtils._selectedSuggestion=0}var containerInnerHeight=$suggestionContainer.innerHeight(),containerScrollTop=$suggestionContainer.scrollTop(),suggestionHeight=$suggestionContainer.children().eq(0).outerHeight(),activeSuggestionPosY=suggestionHeight*$.formUtils._selectedSuggestion;if(activeSuggestionPosY<containerScrollTop||activeSuggestionPosY>containerScrollTop+containerInnerHeight){$suggestionContainer.scrollTop(activeSuggestionPosY)}$suggestions.removeClass("active-suggestion").css("background","none").eq($.formUtils._selectedSuggestion).addClass("active-suggestion").css(conf.activeSuggestionCSS);e.preventDefault();return false}}}).unbind("blur.suggest").bind("blur.suggest",function(){onSelectSuggestion($(this))});return $elem},LANG:{errorTitle:"<br>Form submission failed!",requiredFields:"<br>You have not answered all required fields",badTime:"<br>You have not given a correct time",badEmail:"<br>Enter a valid e-mail",badTelephone:"<br>You have not given a correct phone number",badSecurityAnswer:"<br>You have not given a correct answer to the security question",badDate:"<br>You have not given a correct date",lengthBadStart:"<br>You must give an answer between ",lengthBadEnd:" characters",lengthTooLongStart:"<br>You have given an answer longer than ",lengthTooShortStart:"<br>Minimum ",notConfirmed:"<br>Values could not be confirmed",badDomain:"<br>Incorrect domain value",badUrl:"The answer you gave was not a correct URL",badCustomVal:"<br>You gave an incorrect answer",badInt:"<br>The answer you gave was not a correct number",badSecurityNumber:"<br>Your social security number was incorrect",badUKVatAnswer:"<br>Incorrect UK VAT Number",badStrength:"<br>The password isn't strong enough",badNumberOfSelectedOptionsStart:"<br>You have to choose at least ",badNumberOfSelectedOptionsEnd:" answers",badAlphaNumeric:"<br>The answer you gave must contain only alphanumeric characters ",badAlphaNumericExtra:" and ",wrongFileSize:"<br>The file you are trying to upload is too large",wrongFileType:"The file you are trying to upload is of wrong type",groupCheckedRangeStart:"<br>Please choose between ",groupCheckedTooFewStart:"<br>Please choose at least ",groupCheckedTooManyStart:"<br>Please choose a maximum of ",groupCheckedEnd:" item(s)"}};$.formUtils.addValidator({name:"email",validatorFunction:function(email){var emailParts=email.toLowerCase().split("@");if(emailParts.length==2){return $.formUtils.validators.validate_domain.validatorFunction(emailParts[1])&&!/[^\w\+\.\-]/.test(emailParts[0])}return false},errorMessage:"",errorMessageKey:"badEmail"});$.formUtils.addValidator({name:"domain",validatorFunction:function(val,$input){var topDomains=[".ac",".ad",".ae",".aero",".af",".ag",".ai",".al",".am",".an",".ao",".aq",".ar",".arpa",".as",".asia",".at",".au",".aw",".ax",".az",".ba",".bb",".bd",".be",".bf",".bg",".bh",".bi",".bike",".biz",".bj",".bm",".bn",".bo",".br",".bs",".bt",".bv",".bw",".by",".bz",".ca",".camera",".cat",".cc",".cd",".cf",".cg",".ch",".ci",".ck",".cl",".clothing",".cm",".cn",".co",".com",".construction",".contractors",".coop",".cr",".cu",".cv",".cw",".cx",".cy",".cz",".de",".diamonds",".directory",".dj",".dk",".dm",".do",".dz",".ec",".edu",".ee",".eg",".enterprises",".equipment",".er",".es",".estate",".et",".eu",".fi",".fj",".fk",".fm",".fo",".fr",".ga",".gallery",".gb",".gd",".ge",".gf",".gg",".gh",".gi",".gl",".gm",".gn",".gov",".gp",".gq",".gr",".graphics",".gs",".gt",".gu",".guru",".gw",".gy",".hk",".hm",".hn",".holdings",".hr",".ht",".hu",".id",".ie",".il",".im",".in",".info",".int",".io",".iq",".ir",".is",".it",".je",".jm",".jo",".jobs",".jp",".ke",".kg",".kh",".ki",".kitchen",".km",".kn",".kp",".kr",".kw",".ky",".kz",".la",".land",".lb",".lc",".li",".lighting",".lk",".lr",".ls",".lt",".lu",".lv",".ly",".ma",".mc",".md",".me",".menu",".mg",".mh",".mil",".mk",".ml",".mm",".mn",".mo",".mobi",".mp",".mq",".mr",".ms",".mt",".mu",".museum",".mv",".mw",".mx",".my",".mz",".na",".name",".nc",".ne",".net",".nf",".ng",".ni",".nl",".no",".np",".nr",".nu",".nz",".om",".org",".pa",".pe",".pf",".pg",".ph",".photography",".pk",".pl",".plumbing",".pm",".pn",".post",".pr",".pro",".ps",".pt",".pw",".py",".qa",".re",".ro",".rs",".ru",".rw",".sa",".sb",".sc",".sd",".se",".sexy",".sg",".sh",".si",".singles",".sj",".sk",".sl",".sm",".sn",".so",".sr",".st",".su",".sv",".sx",".sy",".sz",".tattoo",".tc",".td",".technology",".tel",".tf",".tg",".th",".tips",".tj",".tk",".tl",".tm",".tn",".to",".today",".tp",".tr",".travel",".tt",".tv",".tw",".tz",".ua",".ug",".uk",".uno",".us",".uy",".uz",".va",".vc",".ve",".ventures",".vg",".vi",".vn",".voyage",".vu",".wf",".ws",".xn--3e0b707e",".xn--45brj9c",".xn--80ao21a",".xn--80asehdb",".xn--80aswg",".xn--90a3ac",".xn--clchc0ea0b2g2a9gcd",".xn--fiqs8s",".xn--fiqz9s",".xn--fpcrj9c3d",".xn--fzc2c9e2c",".xn--gecrj9c",".xn--h2brj9c",".xn--j1amh",".xn--j6w193g",".xn--kprw13d",".xn--kpry57d",".xn--l1acc",".xn--lgbbat1ad8j",".xn--mgb9awbf",".xn--mgba3a4f16a",".xn--mgbaam7a8h",".xn--mgbayh7gpa",".xn--mgbbh1a71e",".xn--mgbc0a9azcg",".xn--mgberp4a5d4ar",".xn--mgbx4cd0ab",".xn--ngbc5azd",".xn--o3cw4h",".xn--ogbpf8fl",".xn--p1ai",".xn--pgbs0dh",".xn--q9jyb4c",".xn--s9brj9c",".xn--unup4y",".xn--wgbh1c",".xn--wgbl6a",".xn--xkc2al3hye2a",".xn--xkc2dl3a5ee0h",".xn--yfro4i67o",".xn--ygbi2ammx",".xxx",".ye",".yt",".za",".zm",".zw"],ukTopDomains=["co","me","ac","gov","judiciary","ltd","mod","net","nhs","nic","org","parliament","plc","police","sch","bl","british-library","jet","nls"],dot=val.lastIndexOf("."),domain=val.substring(0,dot),ext=val.substring(dot,val.length),hasTopDomain=false;for(var i=0;i<topDomains.length;i++){if(topDomains[i]===ext){if(ext===".uk"){var domainParts=val.split(".");var tld2=domainParts[domainParts.length-2];for(var j=0;j<ukTopDomains.length;j++){if(ukTopDomains[j]===tld2){hasTopDomain=true;break}}if(hasTopDomain)break}else{hasTopDomain=true;break}}}if(!hasTopDomain){return false}else if(dot<2||dot>57){return false}else{var firstChar=domain.substring(0,1),lastChar=domain.substring(domain.length-1,domain.length);if(firstChar==="-"||firstChar==="."||lastChar==="-"||lastChar==="."){return false}if(domain.split(".").length>3||domain.split("..").length>1){return false}if(domain.replace(/[-\da-z\.]/g,"")!==""){return false}}if(typeof $input!=="undefined"){$input.val(val)}return true},errorMessage:"",errorMessageKey:"badDomain"});$.formUtils.addValidator({name:"required",validatorFunction:function(val,$el){return $el.attr("type")=="checkbox"?$el.is(":checked"):$.trim(val)!==""},errorMessage:"",errorMessageKey:"requiredFields"});$.formUtils.addValidator({name:"length",validatorFunction:function(val,$el,conf,lang){var lengthAllowed=$el.valAttr("length"),type=$el.attr("type");if(lengthAllowed==undefined){var elementType=$el.get(0).nodeName;alert('Please add attribute "data-validation-length" to '+elementType+" named "+$el.attr("name"));return true}var len=type=="file"&&$el.get(0).files!==undefined?$el.get(0).files.length:val.length,lengthCheckResults=$.formUtils.numericRangeCheck(len,lengthAllowed),checkResult;switch(lengthCheckResults[0]){case"out":this.errorMessage=lang.lengthBadStart+lengthAllowed+lang.lengthBadEnd;checkResult=false;break;case"min":this.errorMessage=lang.lengthTooShortStart+lengthCheckResults[1]+lang.lengthBadEnd;checkResult=false;break;case"max":this.errorMessage=lang.lengthTooLongStart+lengthCheckResults[1]+lang.lengthBadEnd;checkResult=false;break;default:checkResult=true}return checkResult},errorMessage:"",errorMessageKey:""});$.formUtils.addValidator({name:"url",validatorFunction:function(url){var urlFilter=/^(https?|ftp):\/\/((((\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:)*@)?(((\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5])\.(\d|[1-9]\d|1\d\d|2[0-4]\d|25[0-5]))|((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])(\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])(\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?)(:\d*)?)(\/(((\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)+(\/((\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)*)*)?)?(\?((([a-z]|\d|\[|\]|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|[\uE000-\uF8FF]|\/|\?)*)?(\#(((\w|-|\.|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(%[\da-f]{2})|[!\$&'\(\)\*\+,;=]|:|@)|\/|\?)*)?$/i;if(urlFilter.test(url)){var domain=url.split("://")[1];var domainSlashPos=domain.indexOf("/");if(domainSlashPos>-1)domain=domain.substr(0,domainSlashPos);return $.formUtils.validators.validate_domain.validatorFunction(domain)}return false},errorMessage:"",errorMessageKey:"badUrl"});$.formUtils.addValidator({name:"number",validatorFunction:function(val,$el,conf){if(val!==""){var allowing=$el.valAttr("allowing")||"",decimalSeparator=$el.valAttr("decimal-separator")||conf.decimalSeparator,allowsRange=false,begin,end;if(allowing.indexOf("number")==-1)allowing+=",number";if(allowing.indexOf("negative")>-1&&val.indexOf("-")===0){val=val.substr(1)}if(allowing.indexOf("range")>-1){begin=parseFloat(allowing.substring(allowing.indexOf("[")+1,allowing.indexOf(";")));end=parseFloat(allowing.substring(allowing.indexOf(";")+1,allowing.indexOf("]")));allowsRange=true}if(decimalSeparator==","){val=val.replace(",",".")}if(allowing.indexOf("number")>-1&&val.replace(/[0-9]/g,"")===""&&(!allowsRange||val>=begin&&val<=end)){return true}if(allowing.indexOf("float")>-1&&val.match(new RegExp("^([0-9]+)\\.([0-9]+)$"))!==null&&(!allowsRange||val>=begin&&val<=end)){return true}}return false},errorMessage:"",errorMessageKey:"badInt"});$.formUtils.addValidator({name:"alphanumeric",validatorFunction:function(val,$el,conf,language){var patternStart="^([a-zA-Z0-9",patternEnd="]+)$",additionalChars=$el.attr("data-validation-allowing"),pattern="";if(additionalChars){pattern=patternStart+additionalChars+patternEnd;var extra=additionalChars.replace(/\\/g,"");if(extra.indexOf(" ")>-1){extra=extra.replace(" ","");extra+=" and spaces "}this.errorMessage=language.badAlphaNumeric+language.badAlphaNumericExtra+extra}else{pattern=patternStart+patternEnd;this.errorMessage=language.badAlphaNumeric}return new RegExp(pattern).test(val)},errorMessage:"",errorMessageKey:""});$.formUtils.addValidator({name:"custom",validatorFunction:function(val,$el,conf){var regexp=new RegExp($el.valAttr("regexp"));return regexp.test(val)},errorMessage:"",errorMessageKey:"badCustomVal"});$.formUtils.addValidator({name:"date",validatorFunction:function(date,$el,conf){var dateFormat="yyyy-mm-dd";if($el.valAttr("format")){dateFormat=$el.valAttr("format")}else if(conf.dateFormat){dateFormat=conf.dateFormat}return $.formUtils.parseDate(date,dateFormat)!==false},errorMessage:"",errorMessageKey:"badDate"});$.formUtils.addValidator({name:"checkbox_group",validatorFunction:function(val,$el,conf,lang,$form){var checkResult=true;var elname=$el.attr("name");var checkedCount=$("input[type=checkbox][name^='"+elname+"']:checked",$form).length;var qtyAllowed=$el.valAttr("qty");if(qtyAllowed==undefined){var elementType=$el.get(0).nodeName;alert('Attribute "data-validation-qty" is missing from '+elementType+" named "+$el.attr("name"))}var qtyCheckResults=$.formUtils.numericRangeCheck(checkedCount,qtyAllowed);switch(qtyCheckResults[0]){case"out":this.errorMessage=lang.groupCheckedRangeStart+qtyAllowed+lang.groupCheckedEnd;checkResult=false;break;case"min":this.errorMessage=lang.groupCheckedTooFewStart+qtyCheckResults[1]+lang.groupCheckedEnd;checkResult=false;break;case"max":this.errorMessage=lang.groupCheckedTooManyStart+qtyCheckResults[1]+lang.groupCheckedEnd;
checkResult=false;break;default:checkResult=true}return checkResult}})})(jQuery);


name: jquery.md5.js
path: D:\websites\domains\a.sds.sh\public_html\a\js\jq\jquery.md5.js
mime: unavailable
content:
/*
 * jQuery MD5 Plugin 1.2.1
 * https://github.com/blueimp/jQuery-MD5
 *
 * Copyright 2010, Sebastian Tschan
 * https://blueimp.net
 *
 * Licensed under the MIT license:
 * http://creativecommons.org/licenses/MIT/
 * 
 * Based on
 * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message
 * Digest Algorithm, as defined in RFC 1321.
 * Version 2.2 Copyright (C) Paul Johnston 1999 - 2009
 * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet
 * Distributed under the BSD License
 * See http://pajhome.org.uk/crypt/md5 for more info.
 */

/*jslint bitwise: true */
/*global unescape, jQuery */

(function ($) {
    'use strict';

    /*
    * Add integers, wrapping at 2^32. This uses 16-bit operations internally
    * to work around bugs in some JS interpreters.
    */
    function safe_add(x, y) {
        var lsw = (x & 0xFFFF) + (y & 0xFFFF),
            msw = (x >> 16) + (y >> 16) + (lsw >> 16);
        return (msw << 16) | (lsw & 0xFFFF);
    }

    /*
    * Bitwise rotate a 32-bit number to the left.
    */
    function bit_rol(num, cnt) {
        return (num << cnt) | (num >>> (32 - cnt));
    }

    /*
    * These functions implement the four basic operations the algorithm uses.
    */
    function md5_cmn(q, a, b, x, s, t) {
        return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s), b);
    }
    function md5_ff(a, b, c, d, x, s, t) {
        return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);
    }
    function md5_gg(a, b, c, d, x, s, t) {
        return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);
    }
    function md5_hh(a, b, c, d, x, s, t) {
        return md5_cmn(b ^ c ^ d, a, b, x, s, t);
    }
    function md5_ii(a, b, c, d, x, s, t) {
        return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);
    }

    /*
    * Calculate the MD5 of an array of little-endian words, and a bit length.
    */
    function binl_md5(x, len) {
        /* append padding */
        x[len >> 5] |= 0x80 << ((len) % 32);
        x[(((len + 64) >>> 9) << 4) + 14] = len;

        var i, olda, oldb, oldc, oldd,
            a =  1732584193,
            b = -271733879,
            c = -1732584194,
            d =  271733878;

        for (i = 0; i < x.length; i += 16) {
            olda = a;
            oldb = b;
            oldc = c;
            oldd = d;

            a = md5_ff(a, b, c, d, x[i],       7, -680876936);
            d = md5_ff(d, a, b, c, x[i +  1], 12, -389564586);
            c = md5_ff(c, d, a, b, x[i +  2], 17,  606105819);
            b = md5_ff(b, c, d, a, x[i +  3], 22, -1044525330);
            a = md5_ff(a, b, c, d, x[i +  4],  7, -176418897);
            d = md5_ff(d, a, b, c, x[i +  5], 12,  1200080426);
            c = md5_ff(c, d, a, b, x[i +  6], 17, -1473231341);
            b = md5_ff(b, c, d, a, x[i +  7], 22, -45705983);
            a = md5_ff(a, b, c, d, x[i +  8],  7,  1770035416);
            d = md5_ff(d, a, b, c, x[i +  9], 12, -1958414417);
            c = md5_ff(c, d, a, b, x[i + 10], 17, -42063);
            b = md5_ff(b, c, d, a, x[i + 11], 22, -1990404162);
            a = md5_ff(a, b, c, d, x[i + 12],  7,  1804603682);
            d = md5_ff(d, a, b, c, x[i + 13], 12, -40341101);
            c = md5_ff(c, d, a, b, x[i + 14], 17, -1502002290);
            b = md5_ff(b, c, d, a, x[i + 15], 22,  1236535329);

            a = md5_gg(a, b, c, d, x[i +  1],  5, -165796510);
            d = md5_gg(d, a, b, c, x[i +  6],  9, -1069501632);
            c = md5_gg(c, d, a, b, x[i + 11], 14,  643717713);
            b = md5_gg(b, c, d, a, x[i],      20, -373897302);
            a = md5_gg(a, b, c, d, x[i +  5],  5, -701558691);
            d = md5_gg(d, a, b, c, x[i + 10],  9,  38016083);
            c = md5_gg(c, d, a, b, x[i + 15], 14, -660478335);
            b = md5_gg(b, c, d, a, x[i +  4], 20, -405537848);
            a = md5_gg(a, b, c, d, x[i +  9],  5,  568446438);
            d = md5_gg(d, a, b, c, x[i + 14],  9, -1019803690);
            c = md5_gg(c, d, a, b, x[i +  3], 14, -187363961);
            b = md5_gg(b, c, d, a, x[i +  8], 20,  1163531501);
            a = md5_gg(a, b, c, d, x[i + 13],  5, -1444681467);
            d = md5_gg(d, a, b, c, x[i +  2],  9, -51403784);
            c = md5_gg(c, d, a, b, x[i +  7], 14,  1735328473);
            b = md5_gg(b, c, d, a, x[i + 12], 20, -1926607734);

            a = md5_hh(a, b, c, d, x[i +  5],  4, -378558);
            d = md5_hh(d, a, b, c, x[i +  8], 11, -2022574463);
            c = md5_hh(c, d, a, b, x[i + 11], 16,  1839030562);
            b = md5_hh(b, c, d, a, x[i + 14], 23, -35309556);
            a = md5_hh(a, b, c, d, x[i +  1],  4, -1530992060);
            d = md5_hh(d, a, b, c, x[i +  4], 11,  1272893353);
            c = md5_hh(c, d, a, b, x[i +  7], 16, -155497632);
            b = md5_hh(b, c, d, a, x[i + 10], 23, -1094730640);
            a = md5_hh(a, b, c, d, x[i + 13],  4,  681279174);
            d = md5_hh(d, a, b, c, x[i],      11, -358537222);
            c = md5_hh(c, d, a, b, x[i +  3], 16, -722521979);
            b = md5_hh(b, c, d, a, x[i +  6], 23,  76029189);
            a = md5_hh(a, b, c, d, x[i +  9],  4, -640364487);
            d = md5_hh(d, a, b, c, x[i + 12], 11, -421815835);
            c = md5_hh(c, d, a, b, x[i + 15], 16,  530742520);
            b = md5_hh(b, c, d, a, x[i +  2], 23, -995338651);

            a = md5_ii(a, b, c, d, x[i],       6, -198630844);
            d = md5_ii(d, a, b, c, x[i +  7], 10,  1126891415);
            c = md5_ii(c, d, a, b, x[i + 14], 15, -1416354905);
            b = md5_ii(b, c, d, a, x[i +  5], 21, -57434055);
            a = md5_ii(a, b, c, d, x[i + 12],  6,  1700485571);
            d = md5_ii(d, a, b, c, x[i +  3], 10, -1894986606);
            c = md5_ii(c, d, a, b, x[i + 10], 15, -1051523);
            b = md5_ii(b, c, d, a, x[i +  1], 21, -2054922799);
            a = md5_ii(a, b, c, d, x[i +  8],  6,  1873313359);
            d = md5_ii(d, a, b, c, x[i + 15], 10, -30611744);
            c = md5_ii(c, d, a, b, x[i +  6], 15, -1560198380);
            b = md5_ii(b, c, d, a, x[i + 13], 21,  1309151649);
            a = md5_ii(a, b, c, d, x[i +  4],  6, -145523070);
            d = md5_ii(d, a, b, c, x[i + 11], 10, -1120210379);
            c = md5_ii(c, d, a, b, x[i +  2], 15,  718787259);
            b = md5_ii(b, c, d, a, x[i +  9], 21, -343485551);

            a = safe_add(a, olda);
            b = safe_add(b, oldb);
            c = safe_add(c, oldc);
            d = safe_add(d, oldd);
        }
        return [a, b, c, d];
    }

    /*
    * Convert an array of little-endian words to a string
    */
    function binl2rstr(input) {
        var i,
            output = '';
        for (i = 0; i < input.length * 32; i += 8) {
            output += String.fromCharCode((input[i >> 5] >>> (i % 32)) & 0xFF);
        }
        return output;
    }

    /*
    * Convert a raw string to an array of little-endian words
    * Characters >255 have their high-byte silently ignored.
    */
    function rstr2binl(input) {
        var i,
            output = [];
        output[(input.length >> 2) - 1] = undefined;
        for (i = 0; i < output.length; i += 1) {
            output[i] = 0;
        }
        for (i = 0; i < input.length * 8; i += 8) {
            output[i >> 5] |= (input.charCodeAt(i / 8) & 0xFF) << (i % 32);
        }
        return output;
    }

    /*
    * Calculate the MD5 of a raw string
    */
    function rstr_md5(s) {
        return binl2rstr(binl_md5(rstr2binl(s), s.length * 8));
    }

    /*
    * Calculate the HMAC-MD5, of a key and some data (raw strings)
    */
    function rstr_hmac_md5(key, data) {
        var i,
            bkey = rstr2binl(key),
            ipad = [],
            opad = [],
            hash;
        ipad[15] = opad[15] = undefined;                        
        if (bkey.length > 16) {
            bkey = binl_md5(bkey, key.length * 8);
        }
        for (i = 0; i < 16; i += 1) {
            ipad[i] = bkey[i] ^ 0x36363636;
            opad[i] = bkey[i] ^ 0x5C5C5C5C;
        }
        hash = binl_md5(ipad.concat(rstr2binl(data)), 512 + data.length * 8);
        return binl2rstr(binl_md5(opad.concat(hash), 512 + 128));
    }

    /*
    * Convert a raw string to a hex string
    */
    function rstr2hex(input) {
        var hex_tab = '0123456789abcdef',
            output = '',
            x,
            i;
        for (i = 0; i < input.length; i += 1) {
            x = input.charCodeAt(i);
            output += hex_tab.charAt((x >>> 4) & 0x0F) +
                hex_tab.charAt(x & 0x0F);
        }
        return output;
    }

    /*
    * Encode a string as utf-8
    */
    function str2rstr_utf8(input) {
        return unescape(encodeURIComponent(input));
    }

    /*
    * Take string arguments and return either raw or hex encoded strings
    */
    function raw_md5(s) {
        return rstr_md5(str2rstr_utf8(s));
    }
    function hex_md5(s) {
        return rstr2hex(raw_md5(s));
    }
    function raw_hmac_md5(k, d) {
        return rstr_hmac_md5(str2rstr_utf8(k), str2rstr_utf8(d));
    }
    function hex_hmac_md5(k, d) {
        return rstr2hex(raw_hmac_md5(k, d));
    }
    
    $.md5 = function (string, key, raw) {
        if (!key) {
            if (!raw) {
                return hex_md5(string);
            } else {
                return raw_md5(string);
            }
        }
        if (!raw) {
            return hex_hmac_md5(key, string);
        } else {
            return raw_hmac_md5(key, string);
        }
    };
    
}(typeof jQuery === 'function' ? jQuery : this));


name: jquery.nestable.js
path: D:\websites\domains\a.sds.sh\public_html\a\js\jq\jquery.nestable.js
mime: unavailable
content:
/*!
 * Nestable jQuery Plugin - Copyright (c) 2012 David Bushell - http://dbushell.com/
 * Dual-licensed under the BSD or MIT licenses
 */
;(function($, window, document, undefined)
{
    var hasTouch = 'ontouchstart' in document;

    /**
     * Detect CSS pointer-events property
     * events are normally disabled on the dragging element to avoid conflicts
     * https://github.com/ausi/Feature-detection-technique-for-pointer-events/blob/master/modernizr-pointerevents.js
     */
    var hasPointerEvents = (function()
    {
        var el    = document.createElement('div'),
            docEl = document.documentElement;
        if (!('pointerEvents' in el.style)) {
            return false;
        }
        el.style.pointerEvents = 'auto';
        el.style.pointerEvents = 'x';
        docEl.appendChild(el);
        var supports = window.getComputedStyle && window.getComputedStyle(el, '').pointerEvents === 'auto';
        docEl.removeChild(el);
        return !!supports;
    })();

    var defaults = {
            listNodeName    : 'ol',
            itemNodeName    : 'li',
            rootClass       : 'dd',
            listClass       : 'dd-list',
            itemClass       : 'dd-item',
            dragClass       : 'dd-dragel',
            handleClass     : 'dd-handle',
            collapsedClass  : 'dd-collapsed',
            placeClass      : 'dd-placeholder',
            noDragClass     : 'dd-nodrag',
            emptyClass      : 'dd-empty',
            expandBtnHTML   : '<button data-action="expand" type="button">Expand</button>',
            collapseBtnHTML : '<button data-action="collapse" type="button">Collapse</button>',
            group           : 0,
            maxDepth        : 5,
            threshold       : 20
        };

    function Plugin(element, options)
    {
        this.w  = $(document);
        this.el = $(element);
        this.options = $.extend({}, defaults, options);
        this.init();
    }

    Plugin.prototype = {

        init: function()
        {
            var list = this;

            list.reset();

            list.el.data('nestable-group', this.options.group);

            list.placeEl = $('<div class="' + list.options.placeClass + '"/>');

            $.each(this.el.find(list.options.itemNodeName), function(k, el) {
                list.setParent($(el));
            });

            list.el.on('click', 'button', function(e) {
                if (list.dragEl) {
                    return;
                }
                var target = $(e.currentTarget),
                    action = target.data('action'),
                    item   = target.parent(list.options.itemNodeName);
                if (action === 'collapse') {
                    list.collapseItem(item);
                }
                if (action === 'expand') {
                    list.expandItem(item);
                }
            });

            var onStartEvent = function(e)
            {
                var handle = $(e.target);
                if (!handle.hasClass(list.options.handleClass)) {
                    if (handle.closest('.' + list.options.noDragClass).length) {
                        return;
                    }
                    handle = handle.closest('.' + list.options.handleClass);
                }

                if (!handle.length || list.dragEl) {
                    return;
                }

                list.isTouch = /^touch/.test(e.type);
                if (list.isTouch && e.touches.length !== 1) {
                    return;
                }

                e.preventDefault();
                list.dragStart(e.touches ? e.touches[0] : e);
            };

            var onMoveEvent = function(e)
            {
                if (list.dragEl) {
                    e.preventDefault();
                    list.dragMove(e.touches ? e.touches[0] : e);
                }
            };

            var onEndEvent = function(e)
            {
                if (list.dragEl) {
                    e.preventDefault();
                    list.dragStop(e.touches ? e.touches[0] : e);
                }
            };

            if (hasTouch) {
                list.el[0].addEventListener('touchstart', onStartEvent, false);
                window.addEventListener('touchmove', onMoveEvent, false);
                window.addEventListener('touchend', onEndEvent, false);
                window.addEventListener('touchcancel', onEndEvent, false);
            }

            list.el.on('mousedown', onStartEvent);
            list.w.on('mousemove', onMoveEvent);
            list.w.on('mouseup', onEndEvent);

        },

        serialize: function()
        {
            var data,
                depth = 0,
                list  = this;
                step  = function(level, depth)
                {
                    var array = [ ],
                        items = level.children(list.options.itemNodeName);
                    items.each(function()
                    {
                        var li   = $(this),
                            item = $.extend({}, li.data()),
                            sub  = li.children(list.options.listNodeName);
                        if (sub.length) {
                            item.children = step(sub, depth + 1);
                        }
                        array.push(item);
                    });
                    return array;
                };
            data = step(list.el.find(list.options.listNodeName).first(), depth);
            return data;
        },

        serialise: function()
        {
            return this.serialize();
        },

        reset: function()
        {
            this.mouse = {
                offsetX   : 0,
                offsetY   : 0,
                startX    : 0,
                startY    : 0,
                lastX     : 0,
                lastY     : 0,
                nowX      : 0,
                nowY      : 0,
                distX     : 0,
                distY     : 0,
                dirAx     : 0,
                dirX      : 0,
                dirY      : 0,
                lastDirX  : 0,
                lastDirY  : 0,
                distAxX   : 0,
                distAxY   : 0
            };
            this.isTouch    = false;
            this.moving     = false;
            this.dragEl     = null;
            this.dragRootEl = null;
            this.dragDepth  = 0;
            this.hasNewRoot = false;
            this.pointEl    = null;
        },

        expandItem: function(li)
        {
            li.removeClass(this.options.collapsedClass);
            li.children('[data-action="expand"]').hide();
            li.children('[data-action="collapse"]').show();
            li.children(this.options.listNodeName).show();
        },

        collapseItem: function(li)
        {
            var lists = li.children(this.options.listNodeName);
            if (lists.length) {
                li.addClass(this.options.collapsedClass);
                li.children('[data-action="collapse"]').hide();
                li.children('[data-action="expand"]').show();
                li.children(this.options.listNodeName).hide();
            }
        },

        expandAll: function()
        {
            var list = this;
            list.el.find(list.options.itemNodeName).each(function() {
                list.expandItem($(this));
            });
        },

        collapseAll: function()
        {
            var list = this;
            list.el.find(list.options.itemNodeName).each(function() {
                list.collapseItem($(this));
            });
        },

        setParent: function(li)
        {
            if (li.children(this.options.listNodeName).length) {
                li.prepend($(this.options.expandBtnHTML));
                li.prepend($(this.options.collapseBtnHTML));
            }
            li.children('[data-action="expand"]').hide();
        },

        unsetParent: function(li)
        {
            li.removeClass(this.options.collapsedClass);
            li.children('[data-action]').remove();
            li.children(this.options.listNodeName).remove();
        },

        dragStart: function(e)
        {
            var mouse    = this.mouse,
                target   = $(e.target),
                dragItem = target.closest(this.options.itemNodeName);

            this.placeEl.css('height', dragItem.height());

            mouse.offsetX = e.offsetX !== undefined ? e.offsetX : e.pageX - target.offset().left;
            mouse.offsetY = e.offsetY !== undefined ? e.offsetY : e.pageY - target.offset().top;
            mouse.startX = mouse.lastX = e.pageX;
            mouse.startY = mouse.lastY = e.pageY;

            this.dragRootEl = this.el;

            this.dragEl = $(document.createElement(this.options.listNodeName)).addClass(this.options.listClass + ' ' + this.options.dragClass);
            this.dragEl.css('width', dragItem.width());

            dragItem.after(this.placeEl);
            dragItem[0].parentNode.removeChild(dragItem[0]);
            dragItem.appendTo(this.dragEl);

            $(document.body).append(this.dragEl);
            this.dragEl.css({
                'left' : e.pageX - mouse.offsetX,
                'top'  : e.pageY - mouse.offsetY
            });
            // total depth of dragging item
            var i, depth,
                items = this.dragEl.find(this.options.itemNodeName);
            for (i = 0; i < items.length; i++) {
                depth = $(items[i]).parents(this.options.listNodeName).length;
                if (depth > this.dragDepth) {
                    this.dragDepth = depth;
                }
            }
        },

        dragStop: function(e)
        {
            var el = this.dragEl.children(this.options.itemNodeName).first();
            el[0].parentNode.removeChild(el[0]);
            this.placeEl.replaceWith(el);

            this.dragEl.remove();
            this.el.trigger('change');
            if (this.hasNewRoot) {
                this.dragRootEl.trigger('change');
            }
            this.reset();
        },

        dragMove: function(e)
        {
            var list, parent, prev, next, depth,
                opt   = this.options,
                mouse = this.mouse;

            this.dragEl.css({
                'left' : e.pageX - mouse.offsetX,
                'top'  : e.pageY - mouse.offsetY
            });

            // mouse position last events
            mouse.lastX = mouse.nowX;
            mouse.lastY = mouse.nowY;
            // mouse position this events
            mouse.nowX  = e.pageX;
            mouse.nowY  = e.pageY;
            // distance mouse moved between events
            mouse.distX = mouse.nowX - mouse.lastX;
            mouse.distY = mouse.nowY - mouse.lastY;
            // direction mouse was moving
            mouse.lastDirX = mouse.dirX;
            mouse.lastDirY = mouse.dirY;
            // direction mouse is now moving (on both axis)
            mouse.dirX = mouse.distX === 0 ? 0 : mouse.distX > 0 ? 1 : -1;
            mouse.dirY = mouse.distY === 0 ? 0 : mouse.distY > 0 ? 1 : -1;
            // axis mouse is now moving on
            var newAx   = Math.abs(mouse.distX) > Math.abs(mouse.distY) ? 1 : 0;

            // do nothing on first move
            if (!mouse.moving) {
                mouse.dirAx  = newAx;
                mouse.moving = true;
                return;
            }

            // calc distance moved on this axis (and direction)
            if (mouse.dirAx !== newAx) {
                mouse.distAxX = 0;
                mouse.distAxY = 0;
            } else {
                mouse.distAxX += Math.abs(mouse.distX);
                if (mouse.dirX !== 0 && mouse.dirX !== mouse.lastDirX) {
                    mouse.distAxX = 0;
                }
                mouse.distAxY += Math.abs(mouse.distY);
                if (mouse.dirY !== 0 && mouse.dirY !== mouse.lastDirY) {
                    mouse.distAxY = 0;
                }
            }
            mouse.dirAx = newAx;

            /**
             * move horizontal
             */
            if (mouse.dirAx && mouse.distAxX >= opt.threshold) {
                // reset move distance on x-axis for new phase
                mouse.distAxX = 0;
                prev = this.placeEl.prev(opt.itemNodeName);
                // increase horizontal level if previous sibling exists and is not collapsed
                if (mouse.distX > 0 && prev.length && !prev.hasClass(opt.collapsedClass)) {
                    // cannot increase level when item above is collapsed
                    list = prev.find(opt.listNodeName).last();
                    // check if depth limit has reached
                    depth = this.placeEl.parents(opt.listNodeName).length;
                    if (depth + this.dragDepth <= opt.maxDepth) {
                        // create new sub-level if one doesn't exist
                        if (!list.length) {
                            list = $('<' + opt.listNodeName + '/>').addClass(opt.listClass);
                            list.append(this.placeEl);
                            prev.append(list);
                            this.setParent(prev);
                        } else {
                            // else append to next level up
                            list = prev.children(opt.listNodeName).last();
                            list.append(this.placeEl);
                        }
                    }
                }
                // decrease horizontal level
                if (mouse.distX < 0) {
                    // we can't decrease a level if an item preceeds the current one
                    next = this.placeEl.next(opt.itemNodeName);
                    if (!next.length) {
                        parent = this.placeEl.parent();
                        this.placeEl.closest(opt.itemNodeName).after(this.placeEl);
                        if (!parent.children().length) {
                            this.unsetParent(parent.parent());
                        }
                    }
                }
            }

            var isEmpty = false;

            // find list item under cursor
            if (!hasPointerEvents) {
                this.dragEl[0].style.visibility = 'hidden';
            }
            this.pointEl = $(document.elementFromPoint(e.pageX - document.body.scrollLeft, e.pageY - (window.pageYOffset || document.documentElement.scrollTop)));
            if (!hasPointerEvents) {
                this.dragEl[0].style.visibility = 'visible';
            }
            if (this.pointEl.hasClass(opt.handleClass)) {
                this.pointEl = this.pointEl.parent(opt.itemNodeName);
            }
            if (this.pointEl.hasClass(opt.emptyClass)) {
                isEmpty = true;
            }
            else if (!this.pointEl.length || !this.pointEl.hasClass(opt.itemClass)) {
                return;
            }

            // find parent list of item under cursor
            var pointElRoot = this.pointEl.closest('.' + opt.rootClass),
                isNewRoot   = this.dragRootEl.data('nestable-id') !== pointElRoot.data('nestable-id');

            /**
             * move vertical
             */
            if (!mouse.dirAx || isNewRoot || isEmpty) {
                // check if groups match if dragging over new root
                if (isNewRoot && opt.group !== pointElRoot.data('nestable-group')) {
                    return;
                }
                // check depth limit
                depth = this.dragDepth - 1 + this.pointEl.parents(opt.listNodeName).length;
                if (depth > opt.maxDepth) {
                    return;
                }
                var before = e.pageY < (this.pointEl.offset().top + this.pointEl.height() / 2);
                    parent = this.placeEl.parent();
                // if empty create new list to replace empty placeholder
                if (isEmpty) {
                    list = $(document.createElement(opt.listNodeName)).addClass(opt.listClass);
                    list.append(this.placeEl);
                    this.pointEl.replaceWith(list);
                }
                else if (before) {
                    this.pointEl.before(this.placeEl);
                }
                else {
                    this.pointEl.after(this.placeEl);
                }
                if (!parent.children().length) {
                    this.unsetParent(parent.parent());
                }
                if (!this.dragRootEl.find(opt.itemNodeName).length) {
                    this.dragRootEl.append('<div class="' + opt.emptyClass + '"/>');
                }
                // parent root list has changed
                if (isNewRoot) {
                    this.dragRootEl = pointElRoot;
                    this.hasNewRoot = this.el[0] !== this.dragRootEl[0];
                }
            }
        }

    };

    $.fn.nestable = function(params)
    {
        var lists  = this,
            retval = this;

        lists.each(function()
        {
            var plugin = $(this).data("nestable");

            if (!plugin) {
                $(this).data("nestable", new Plugin(this, params));
                $(this).data("nestable-id", new Date().getTime());
            } else {
                if (typeof params === 'string' && typeof plugin[params] === 'function') {
                    retval = plugin[params]();
                }
            }
        });

        return retval || lists;
    };

})(window.jQuery || window.Zepto, window, document);


name: jquery.sheet.js
path: D:\websites\domains\a.sds.sh\public_html\a\js\jq\jquery.sheet.js
mime: unavailable
content:
/**
 * @project jQuery.sheet() The Ajax Spreadsheet - https://github.com/Spreadsheets/jQuery.sheet
 * @author RobertLeePlummerJr@gmail.com
 * Licensed under MIT
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
 * documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
 * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
 * Software.
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
 * WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
 * OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */

/**
 * @namespace
 * @type {Object|Function}
 */
var jQuery = window.jQuery || {};
var Sheet = (function($, document, window, Date, String, Number, Boolean, Math, RegExp, Error) {
    "use strict";

    var Sheet = Sheet || {};
/**
 * Creates the scrolling system used by each spreadsheet
 */
Sheet.ActionUI = (function(document, window, Math, Number, $) {
    var Constructor = function(enclosure, pane, sheet, cl, frozenAt, max) {
        this.enclosure = enclosure;
        this.pane = pane;
        this.sheet = sheet;
        this.max = max;
        this.xIndex = 0;
        this.yIndex = 0;

        this.scrollAxis = {
            x:{},
            y:{}
        };

        this.scrollSize = {};

        this.hiddenColumns = [];
        this.hiddenRows = [];

        if (!(this.frozenAt = frozenAt)) {
            this.frozenAt = {row:0, col:0};
        }

        this.frozenAt.row = Math.max(this.frozenAt.row, 0);
        this.frozenAt.col = Math.max(this.frozenAt.col, 0);

        /**
         * Where the current sheet is scrolled to
         * @returns {Object}
         */
        this.scrolledArea = {
            start: {
                row: Math.max(this.frozenAt.row, 1),
                col: Math.max(this.frozenAt.col, 1)
            },
            end: {
                row: Math.max(this.frozenAt.row, 1),
                col: Math.max(this.frozenAt.col, 1)
            }
        };


        if (Sheet.ActionUI.prototype.nthCss === null) {
            if (max) {//this is where we check IE8 compatibility
                Sheet.ActionUI.prototype.nthCss = function (elementName, parentSelectorString, indexes, min, css) {
                    var style = [],
                        index = indexes.length,
                        repeat = this.repeat;

                    css = css || '{display: none;}';

                    do {
                        if (indexes[index] > min) {
                            style.push(parentSelectorString + ' ' + elementName + ':first-child' + repeat('+' + elementName, indexes[index] - 1));
                        }
                    } while (index--);

                    if (style.length) {
                        return style.join(',') + css;
                    }

                    return '';
                };
            } else {
                Sheet.ActionUI.prototype.nthCss = function (elementName, parentSelectorString, indexes, min, css) {
                    var style = [],
                        index = indexes.length;

                    css = css || '{display: none;}';

                    do {
                        if (indexes[index] > min) {
                            style.push(parentSelectorString + ' ' + elementName + ':nth-child(' + indexes[index] + ')');
                        }
                    } while (index--);

                    if (style.length) {
                        return style.join(',') + css;
                    }

                    return '';
                };
            }
        }

        var that = this,
            cssId = '#' + sheet.getAttribute('id'),
            scrollOuter = this.scrollUI = pane.scrollOuter = document.createElement('div'),
            scrollInner = pane.scrollInner = document.createElement('div'),
            scrollStyleX = pane.scrollStyleX = this.scrollStyleX = new Sheet.StyleUpdater(function(indexes, style) {
                indexes = indexes || [];

                if (indexes.length !== that.xIndex || style) {
                    that.xIndex = indexes.length || that.xIndex;

                    style = style || nthCss('col', cssId, indexes, that.frozenAt.col + 1) +
                        nthCss('td', cssId + ' ' + 'tr', indexes, that.frozenAt.col + 1);

                    this.setStyle(style);

                    if (indexes.length) {
                        that.scrolledArea.start.col = Math.max(indexes.pop() || 1, 1);
                        that.scrolledArea.end.col = Math.max(indexes.shift() || 1, 1);
                    }

                    return true;
                }
                return false;
            }),
            scrollStyleY = pane.scrollStyleY = this.scrollStyleY = new Sheet.StyleUpdater(function(indexes, style){
                indexes = indexes || [];

                if (indexes.length !== that.yIndex || style) {
                    that.yIndex = indexes.length || that.yIndex;

                    style = style || nthCss('tr', cssId, indexes, that.frozenAt.row + 1);

                    this.setStyle(style);

                    if (indexes.length) {
                        that.scrolledArea.start.row = Math.max(indexes.pop() || 1, 1);
                        that.scrolledArea.end.row = Math.max(indexes.shift() || 1, 1);
                    }

                    return true;
                }
                return false;
            }),
            nthCss = this.nthCss;

        scrollOuter.setAttribute('class', cl);
        scrollOuter.appendChild(scrollInner);

        $(scrollOuter)
            .disableSelectionSpecial();

        pane.appendChild(scrollStyleX.styleElement);
        pane.appendChild(scrollStyleY.styleElement);

        var xStyle,
            yStyle,
            sheetWidth,
            sheetHeight,
            enclosureWidth,
            enclosureHeight,
            firstRow = sheet.tBody.children[0];

        pane.resizeScroll = function (justTouch) {
            if (justTouch) {
                xStyle = scrollStyleX.getStyle();
                yStyle = scrollStyleY.getStyle();
            } else {
                xStyle = (sheet.clientWidth <= enclosure.clientWidth ? '' : scrollStyleX.getStyle());
                yStyle = (sheet.clientHeight <= enclosure.clientHeight ? '' : scrollStyleY.getStyle());
            }

            scrollStyleX.update(null, ' ');
            scrollStyleY.update(null, ' ');

            sheetWidth = (firstRow.clientWidth || sheet.clientWidth) + 'px';
            sheetHeight = sheet.clientHeight + 'px';
            enclosureWidth = enclosure.clientWidth + 'px';
            enclosureHeight = enclosure.clientHeight + 'px';

            scrollInner.style.width = sheetWidth;
            scrollInner.style.height = sheetHeight;

            scrollOuter.style.width = enclosureWidth;
            scrollOuter.style.height = enclosureHeight;

            that.scrollStart('x');
            that.scrollStart('y');

            scrollStyleX.update(null, xStyle);
            scrollStyleY.update(null, yStyle);

            if (pane.inPlaceEdit) {
                pane.inPlaceEdit.goToTd();
            }
        };

        new MouseWheel(pane, scrollOuter);
    };

    Constructor.prototype = {
        /**
         * scrolls the sheet to the selected cell
         * @param {HTMLElement} td
         */
        putTdInView:function (td) {
            var i = 0,
                x = 0,
                y = 0,
                direction,
                scrolledTo;

            this.xIndex = 0;
            this.yIndex = 0;

            while ((direction = this.directionToSeeTd(td)) !== null) {
                scrolledTo = this.scrolledArea.end;

                if (direction.left) {
                    x--;
                    this.scrollTo({
                        axis:'x',
                        value:scrolledTo.col - 1
                    });
                } else if (direction.right) {
                    x++;
                    this.scrollTo({
                        axis:'x',
                        value:scrolledTo.col + 1
                    });
                }

                if (direction.up) {
                    y--;
                    this.scrollTo({
                        axis:'y',
                        value:scrolledTo.row - 1
                    });
                } else if (direction.down) {
                    y++;
                    this.scrollTo({
                        axis:'y',
                        value:scrolledTo.row + 1
                    });
                }

                i++;
                if (i < 25) {
                    break;
                }
            }

            this.scrollStop();
        },

        /**
         * detects if a td is not visible
         * @param {HTMLElement} td
         * @returns {Boolean|Object}
         */
        directionToSeeTd:function(td) {
            var pane = this.pane,
                visibleFold = {
                    top:0,
                    bottom:pane.clientHeight,
                    left:0,
                    right:pane.clientWidth
                },

                tdWidth = td.clientWidth,
                tdHeight = td.clientHeight,
                tdLocation = {
                    top:td.offsetTop,
                    bottom:td.offsetTop + tdHeight,
                    left:td.offsetLeft,
                    right:td.offsetLeft + tdWidth
                },
                tdParent = td.parentNode,
                scrollTo = this.scrolledArea.end;

            if (!td.col) {
                return null;
            }

            var xHidden = td.barTop.cellIndex < scrollTo.col,
                yHidden = tdParent.rowIndex < scrollTo.row,
                hidden = {
                    up:yHidden,
                    down:tdLocation.bottom > visibleFold.bottom && tdHeight <= pane.clientHeight,
                    left:xHidden,
                    right:tdLocation.right > visibleFold.right && tdWidth <= pane.clientWidth
                };

            if (
                hidden.up
                || hidden.down
                || hidden.left
                || hidden.right
            ) {
                return hidden;
            }

            return null;
        },

        /**
         * Repeats a string a number of times
         * @param {String} str
         * @param {Number} num
         * @memberOf jQuery.sheet
         * @returns {String}
         */
        repeat:function (str, num) {
            var result = '';
            while (num > 0) {
                if (num & 1) {
                    result += str;
                }
                num >>= 1;
                str += str;
            }
            return result;
        },


        /**
         * Creates css for an iterated element
         * @param {String} elementName
         * @param {String} parentSelectorString
         * @param {Array} indexes
         * @param {Number} min
         * @param {String} [css]
         * @returns {String}
         * @memberOf jQuery.sheet
         */

        nthCss: null,

        /**
         * Causes the pane to redraw, really just for fixing issues in Chrome
         */
        redraw: function() {
            var style = this.pane.style;

            style.opacity = 0.9999;

            setTimeout(function() {
                style.opacity = 1;
            },0);
        },


        hide:function (hiddenRows, hiddenColumns, rows, columns) {
            var that = this,
                cssId = '#' + this.sheet.getAttribute('id'),
                toggleHideStyleX = this.toggleHideStyleX = new Sheet.StyleUpdater(function() {
                    var style = nthCss('col', cssId, that.hiddenColumns, 0) +
                        nthCss('td', cssId + ' tr', that.hiddenColumns, 0);

                    this.setStyle(style);
                }),
                toggleHideStyleY = this.toggleHideStyleY = new Sheet.StyleUpdater(function() {
                    var style = nthCss('tr', cssId, that.hiddenRows, 0);

                    this.setStyle(style);
                }),

                i,
                j,
                nthCss = this.nthCss,
                pane = this.pane;

            pane.appendChild(toggleHideStyleX.styleElement);
            pane.appendChild(toggleHideStyleY.styleElement);

            this.hiddenRows = [];
            this.hiddenColumns = [];

            if (hiddenRows) {
                i = hiddenRows.length - 1;
                if (i > -1) {
                    do {
                        j = hiddenRows[i];
                        this.toggleHideRow(rows[j], j);
                    } while (i--);
                }
            }

            if (hiddenColumns) {
                i = hiddenColumns.length - 1;
                if (i > -1) {
                    do {
                        j = hiddenColumns[i];
                        this.toggleHideColumn(columns[j], j);
                    } while (i--);
                }
            }
        },
        remove: function() {

        },

        /**
         * prepare everything needed for a scroll, needs activated every time spreadsheet changes in size
         * @param {String} axisName x or y
         */
        scrollStart:function (axisName) {
            var pane = this.pane,
                outer = pane.scrollOuter,
                axis = this.scrollAxis[axisName],
                size = this.scrollSize = this.sheet.size();

            axis.v = [];
            axis.name = axisName;

            switch (axisName || 'x') {
                case 'x':
                    axis.value = 0;
                    axis.max = size.cols;
                    axis.min = 0;
                    axis.size = size.cols;
                    pane.scrollStyleX.update();
                    axis.scrollStyle = pane.scrollStyleX;
                    axis.area = outer.scrollWidth - outer.clientWidth;
                    axis.sheetArea = pane.table.clientWidth - pane.table.corner.clientWidth;
                    axis.scrollUpdate = function () {
                        outer.scrollLeft = (axis.value) * (axis.area / axis.size);
                    };
                    axis.gridSize = 100 / axis.size;
                    break;
                case 'y':
                    axis.value = 0;
                    axis.max = size.rows;
                    axis.min = 0;
                    axis.size = size.rows;
                    pane.scrollStyleY.update();
                    axis.scrollStyle = pane.scrollStyleY;
                    axis.area = outer.scrollHeight - outer.clientHeight;
                    axis.sheetArea = pane.table.clientHeight - pane.table.corner.clientHeight;
                    axis.scrollUpdate = function () {
                        outer.scrollTop = (axis.value) * (axis.area / axis.size);
                    };
                    axis.gridSize = 100 / axis.size;
                    break;
            }

            var i = axis.max;

            do {
                var position = new Number(axis.gridSize * i);
                position.index = i + 1;
                axis.v.unshift(position);
            } while(i--);
        },

        /**
         * Scrolls to a position within the spreadsheet
         * @param {Object} pos {axis, value, pixel} if value not set, pixel is used
         */
        scrollTo:function (pos) {
            pos = pos || {};
            pos.axis = pos.axis || 'x';
            pos.value = pos.value || 0;
            pos.pixel = pos.pixel || 0;

            if (!this.scrollAxis) {
                this.scrollStart(pos.axis);
            }
            var me = this.scrollAxis[pos.axis];

            if (!pos.value) {
                pos.value = arrHelpers.closest(me.v, Math.abs(pos.pixel / me.area) * 100, me.min).index;
            }

            pos.max = pos.max || me.max;

            var i = ((pos.value > pos.max ? pos.max : pos.value) - me.min),
                indexes = [];

            if (i >= 0) {
                do {
                    indexes.push(i + me.min);
                } while(i-- > 0);
            }

            me.value = pos.value;

            if (indexes.length) {
                if (me.scrollStyle) {
                    return me.scrollStyle.update(indexes);
                }
            } else {
                if (me.scrollStyle) {
                    return me.scrollStyle.update();
                }
            }

            return false;
        },

        /**
         * Called after scroll is done
         */
        scrollStop:function () {
            if (this.scrollAxis.x.scrollUpdate) {
                this.scrollAxis.x.scrollUpdate();
            }
            if (this.scrollAxis.y.scrollUpdate) {
                this.scrollAxis.y.scrollUpdate();
            }
        },


        toggleHideRow:function (row, i) {
            var $row = $(row);
            i = Math.max(i + 1, 1);


            if ($row.length && $.inArray(i, this.hiddenRows) < 0) {
                this.hiddenRows.push(i);
            } else {
                this.hiddenRows.splice(this.hiddenRows.indexOf(i), 1);
            }

            this.toggleHideStyleY.update();
        },
        rowShowAll:function () {
            $.each(this.hiddenRows || [], function (j) {
                $(this).removeData('hidden');
            });
            this.toggleHideStyleY.setStyle('');
            this.hiddenRows = [];
        },
        toggleHideColumn:function (col, i) {
            var $col = $(col);
            i = Math.max(i + 1, 1);

            if ($col.length && $.inArray(i, this.hiddenColumns) < 0) {
                this.hiddenColumns.push(i);
            } else {
                this.hiddenColumns.splice(this.hiddenColumns.indexOf(i), 1);
            }

            this.toggleHideStyleX.update();
        },
        columnShowAll:function () {
            this.toggleHideStyleX.setStyle('');
            this.hiddenColumns = [];
        }
    };

    return Constructor;
})(document, window, Math, Number, $);
Sheet.StyleUpdater = (function(document) {
    function Constructor(updateFn) {
        var el = this.styleElement = document.createElement('style');
        el.styleUpdater = this;
        this.update = updateFn;
    }

    //ie
    if (document.createElement('style').styleSheet) {
        Constructor.prototype = {
            setStyle: function (css) {
                var el = this.styleElement,
                    ss = el.styleSheet;

                ss.disabled = false;//IE8 bug, for some reason in some scenarios disabled never becomes enabled.  And even setting here don't actually set it, it just ensures that is is set to disabled = false when the time is right
                if (!ss.disabled) {
                    ss.cssText = css;
                }
            },
            getStyle: function() {
                var el = this.styleElement,
                    ss = el.styleSheet;

                ss.disabled = false;//IE8 bug, for some reason in some scenarios disabled never becomes enabled.  And even setting here don't actually set it, it just ensures that is is set to disabled = false when the time is right
                if (!ss.disabled) {
                    return ss.cssText;
                }
                return '';
            }
        };
    } else {
        //standard
        Constructor.prototype = {
            setStyle: function (css) {
                this.styleElement.innerHTML = css;
            },
            getStyle: function() {
                return this.styleElement.innerHTML;
            }
        };
    }

    return Constructor;
})(document);/**
 * @namespace
 * @type {Object}
 * @name jQuery()
 */
$.fn.extend({
    /**
     * @memberOf jQuery()
     * @function
     * @returns {jQuery()}
     * @description
     * <pre>
     * The jQuery.sheet plugin
     * Supports the following jQuery events
     *
     * sheetAddRow - occurs just after a row has been added
     *  arguments: e (jQuery event), jS, i (row index), isBefore, qty
     *      example:
     *          $(obj).sheet({
     *              sheetAddRow: function(e, jS, i, isBefore, qty) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetAddRow', function(e, jS, i, isBefore, qty) {
     *
     *          })
     *          .sheet();
     *
     * sheetAddColumn - occurs just after a column has been added
     *      arguments: e (jQuery event), jS, i (column index), isBefore, qty
     *      example:
     *          $(obj).sheet({
     *              sheetAddColumn: function(e, jS, i, isBefore, qty) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetAddColumn', function(e, jS, i, isBefore, qty) {
     *
     *          })
     *          .sheet();
     *
     * sheetSwitch - occurs after a spreadsheet has been switched
     *      arguments: e (jQuery event), jS (jQuery.sheet instance), i (spreadsheet index)
     *      example:
     *          $(obj).sheet({
     *              sheetSwitch: function(e, jS, i) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetSwitch', function(e, jS, i) {
     *
     *          })
     *          .sheet();
     *
     * sheetRename - occurs just after a spreadsheet is renamed, to obtain new title jS.obj.table().attr('title');
     *      arguments: e (jQuery event), jS (jQuery.sheet instance), i (spreadsheet index)
     *      example:
     *          $(obj).sheet({
     *              sheetRename: function(e, jS, i) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetRename', function(e, jS, i) {
     *
     *          })
     *          .sheet();
     *
     * sheetTabSortStart - occurs at the beginning of a sort for moving a spreadsheet around in order
     *      arguments: e (jQuery event), jS (jQuery.sheet instance), E (jQuery sortable event), ui, (jQuery ui event)
     *      example:
     *          $(obj).sheet({
     *              sheetTabSortStart: function(e, jS, E, ui) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetTabSortStart',NPER: function(e, jS, E, ui) {
     *
     *          })
     *          .sheet();
     *
     * sheetTabSortUpdate - occurs after a sort of a spreadsheet has been completed
     *      arguments: e (jQuery event), jS (jQuery.sheet instance), E (jQuery sotable event), ui, (jQuery ui event), i (original index)
     *      example:
     *          $(obj).sheet({
     *              sheetTabSortUpdate: function(e, jS, E, ui) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetTabSortUpdate', function(e, jS, E, ui) {
     *
     *          })
     *          .sheet();
     *
     * sheetFormulaKeydown - occurs just after keydown on either inline or static formula
     *      arguments: e (jQuery event)
     *      example:
     *          $(obj).sheet({
     *              sheetFormulaKeydown: function(e) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetFormulaKeydown') {
     *
     *          })
     *          .sheet();
     * sheetCellEdit - occurs just before a cell has been started to edit
     *      arguments: e (jQuery event), jS (jQuery.sheet instance), cell (jQuery.sheet.instance.spreadsheet cell)
     *      example:
     *          $(obj).sheet({
     *              sheetCellEdit: function(e, jS, cell) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetCellEdit', function(e, jS, cell) {
     *
     *          })
     *          .sheet();
     *
     * sheetCellEdited - occurs just after a cell has been updated
     *      arguments: e (jQuery event), jS (jQuery.sheet instance), cell (jQuery.sheet.instance.spreadsheet cell)
     *      example:
     *          $(obj).sheet({
     *              sheetCellEdited: function(e, jS, cell) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetCellEdited', function(e, jS, cell) {
     *
     *          })
     *          .sheet();
     *
     * sheetCalculation - occurs just after a spreadsheet has been fully calculated
     *      arguments: e (jQuery event), jS (jQuery.sheet instance)
     *      example:
     *          $(obj).sheet({
     *              sheetCalculation: function(e, jS) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetCalculation', function(e, jS) {
     *
     *          })
     *          .sheet();
     *
     * sheetAdd - occurs just after a spreadsheet has been added
     *      arguments: e (jQuery event), jS (jQuery.sheet instance), i (new sheet index)
     *      example:
     *          $(obj).sheet({
     *              sheetAdd: function(e, jS, i) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetAdd', function(e, jS, i) {
     *
     *          })
     *          .sheet();
     *
     * sheetDelete - occurs just after a spreadsheet has been deleted
     *      arguments: e (jQuery event), jS (jQuery.sheet instance), i (old sheet index)
     *      example:
     *          $(obj).sheet({
     *              sheetDelete: function(e, jS, i) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetDelete', function(e, jS, i) {
     *
     *          })
     *          .sheet();
     *
     * sheetDeleteRow - occurs just after a row has been deleted
     *      arguments: e (jQuery event), jS (jQuery.sheet instance), i (old row index)
     *      example:
     *          $(obj).sheet({
     *              sheetDeleteRow: function(e, jS, i) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetDeleteRow', function(e, jS, i) {
     *
     *          })
     *          .sheet();
     *
     * sheetDeleteColumn - occurs just after a column as been deleted
     *      arguments: e (jQuery event), jS (jQuery.sheet instance), i (old column index)
     *      example:
     *          $(obj).sheet({
     *              sheetDeleteColumn: function(e, jS, i) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetDeleteColumn', function(e, jS, i) {
     *
     *          })
     *          .sheet();
     *
     * sheetOpen - occurs just after a single sheet within a set of sheets has been opened, this is triggered when calling sheet, so it needs to be bound beforehand
     *      arguments: e (jQuery event), jS (jQuery.sheet instance), i (new sheet index)
     *      example:
     *          $(obj).sheet({
     *              sheetOpen: function(e, jS, i) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetOpen', function(e, jS, i) {
     *
     *          })
     *          .sheet();
     *
     * sheetAllOpened - occurs just after all sheets have been loaded and complete user interface has been created, this is triggered when calling sheet, so it needs to be bound beforehand
     *      arguments: e (jQuery event), jS (jQuery.sheet instance)
     *      example:
     *          $(obj).sheet({
     *              sheetAllOpened: function(e, jS) {
     *
     *              }
     *          });
     *      or:
     *          $(obj).bind('sheetAllOpened', function(e, jS) {
     *
     *          })
     *          .sheet();
     *
     * sheetSave - an assistance event called when calling jS.toggleState(), but not tied to anything internally
     *      arguments: e (jQuery event), jS (jQuery.sheet instance), tables (tables from spreadsheet)
     *      example:
     *          $(obj).sheet({
     *              sheetSave: function(e, jS, tables) {
     *
     *              });
     *          }
     *      or:
     *          $(obj).bind('sheetSave', function(e, jS, tables) {
     *
     *          })
     *          .sheet();
     *
     * sheetFullScreen - triggered when the sheet goes full screen
     *      arguments: e (jQuery event), jS (jQuery.sheet instance), isFullScreen (boolean, true if full screen, false if not)
     *      example:
     *          $(obj).sheet({
     *              sheetFullScreen: function(e, jS, isFullScreen) {
     *
     *              });
     *          }
     *      or:
     *          $(obj).bind('sheetFullScreen', function(e, jS, isFullScreen) {
     *
     *          })
     *          .sheet();
     * </pre>
     *
     * @param {Object} [settings] supports the following properties/methods:
     * <pre>
     * editable {Boolean}, default true, Makes the sheet editable or viewable
     *
     * editableNames {Boolean}, default true, Allows sheets to have their names changed, depends on settings.editable being true
     *
     * barMenus {Boolean}, default true, Turns bar menus on/off
     *
     * freezableCells {Boolean}, default true, Turns ability to freeze cells on/off
     *
     * allowToggleState {Boolean}, default true, allows the spreadsheet to be toggled from write/read
     *
     * newColumnWidth {Number}, default 120, width of new columns
     *
     * title {String|Function}, title of spreadsheet, if function, expects string and is sent jS
     *
     * menuRight {String|Function}, default '', 'this' is jQuery.sheet instance. If ul object, will attempt to create menu
     *
     * menuLeft {String|Function}, default '', 'this' is jQuery.sheet instance. If ul object, will attempt to create menu
     *
     * calcOff {Boolean} default false, turns turns off ability to calculate
     *
     * lockFormulas {Boolean} default false, turns on/off the ability to edit formulas
     *
     * colMargin {Number} default 18, size of height of new cells, and width of cell bars
     *
     * boxModelCorrection {Number} default 2, if box model is detected, it adds these pixels to ensure the size of the spreadsheet controls are correct
     *
     * formulaFunctions {Object} default {}, Additional functions for formulas. Will overwrite default functions if named the same.
     *      Javascript Example:
     *          $(obj).sheet({
     *              formulaFunctions: {
     *                  NEWFUNCTION: function(arg1, arg2) {
     *                      //this = the parser's cell object object
     *                      return 'string'; //can return a string
     *                      return { //can also return an object {value: '', html: ''}
     *                          value: 'my value seen by other cells or if accessed directly',
     *                          html: $('What the end user will see on the cell this is called in')
     *                      }
     *                  }
     *              }
     *          });
     *
     *      Formula Example:
     *          =NEWFUNCTION(A1:B1, C3);
     *
     * formulaVariables {Object} default {}, Additional variables that formulas can access.
     *      Javascript Example:
     *          $(obj).sheet({
     *              formulaVariables: {
     *                  newVariable: 100
     *              }
     *          });
     *
     *      Formula Example (will output 200)
     *          =newVariable + 100
     *
     * cellSelectModel {String} default 'excel', accepts 'excel', 'oo', or 'gdrive', makes the select model act differently
     *
     * autoAddCells {Boolean} default true, allows you to add cells by selecting the last row/column and add cells by pressing either tab (column) or enter (row)
     *
     * resizableCells {Boolean} default true, turns resizing on and off for cells, depends on jQuery ui
     *
     * resizableSheet {Boolean} default true, turns resizing on and off for sheet, depends on jQuery ui
     *
     * autoFiller {Boolean} default true, turns on/off the auto filler, the little square that follows the active cell around that you can drag and fill the values of other cells in with.
     *
     * minSize {Object} default {rows: 1, cols: 1}, the minimum size of a spreadsheet
     *
     * error {Function} default function(e) { return e.error; }, is triggered on errors from the formula engine
     *
     * encode {Function} default is a special characters handler for strings only, is a 1 way encoding of the html if entered manually by the editor.  If you want to use html with a function, return an object rather than a string
     *
     * frozenAt {Object} default [{row: 0,col: 0}], Gives the ability to freeze cells at a certain row/col
     *
     * contextmenuTop {Object} default is standard list of commands for context menus when right click or click on menu dropdown
     *      Javascript example:
     *          {
     *              "What I want my command to say": function() {}
     *          }
     *
     * contextmenuLeft {Object} default is standard list of commands for context menus when right click
     *      Javascript example:
     *          {
     *              "What I want my command to say": function() {}
     *          }
     *
     * contextmenuCell {Object} default is standard list of commands for context menus when right click or click on menu dropdown
     *      Javascript example:
     *          {
     *              "What I want my command to say": function() {}
     *          }
     *
     * hiddenRows {Array} default [], Hides certain rows from being displayed initially. [sheet Index][row index]. example: [[1]] hides first row in first spreadsheet; [[]],[1]] hides first row in second spreadsheet
     *
     * hiddenColumns {Array} default [], Hides certain columns from being displayed initially. [sheet Index][column index]. example: [[1]] hides first column in first spreadsheet; [[],[1]] hides first column in second spreadsheet
     *
     * alert {Function} default function(msg) {alert(msg);}
     * prompt {Function} default function(msg, callback, initialValue) {callback(prompt(msg, initialValue));}
     * confirm {Function} default
     *      function(msg, callbackIfTrue, callbackIfFalse) {
     *          if (confirm(msg)) {
     *              callbackIfTrue();
     *          } else if (callbackIfFalse) {
     *              callbackIfFalse();
     *          }
     *      }
     * </pre>
     *
     * initCalcRows {Number} default 40
     * initCalcCols {Number} default 20
     */
    sheet:function (settings) {
        var n = isNaN,
            events = $.sheet.events;

        settings = settings || {};

        $(this).each(function () {
            var globalize = Globalize,
                me = $(this),
                defaults = {
                    editable:true,
                    editableNames:true,
                    barMenus:true,
                    freezableCells:true,
                    allowToggleState:true,
                    menuLeft:null,
                    menuRight:null,
                    newColumnWidth:120,
                    title:null,
                    calcOff:false,
                    lockFormulas:false,
                    parent:me,
                    colMargin:18,
                    boxModelCorrection:2,
                    formulaFunctions:{},
                    formulaVariables:{},
                    cellSelectModel:'excel',
                    autoAddCells:true,
                    resizableCells:true,
                    resizableSheet:true,
                    autoFiller:true,
                    minSize:{rows:1, cols:1},
                    error:function (e) {
                        return e.error;
                    },
                    endOfNumber: false,
                    encode:function (val) {

                        switch (typeof val) {
                            case 'object':
                                //check if it is a date
                                if (val.getMonth) {
                                    return globalize.format(val, 'd');
                                }

                                return val;
                        }

                        if (!val) {
                            return val || '';
                        }
                        if (!val.replace) {
                            return val || '';
                        }
                        /*var num = $.trim(val) * 1;
                         if (!isNaN(num)) {
                         return globalize.format(num, "n10").replace(this.endOfNumber, function (orig, radix, num) {
                         return (num ? radix : '') + (num || '');
                         });
                         }*/

                        return val
                            .replace(/&/gi, '&amp;')
                            .replace(/>/gi, '&gt;')
                            .replace(/</gi, '&lt;')
                            .replace(/\n/g, '\n<br>')
                            .replace(/\t/g, '&nbsp;&nbsp;&nbsp ')
                            .replace(/  /g, '&nbsp; ');
                    },
                    frozenAt:[],
                    contextmenuTop:{
                        "Toggle freeze columns to here":function (jS) {
                            var col = jS.getTdLocation(jS.obj.tdActive()).col,
                                actionUI = jS.obj.pane().actionUI;
                            actionUI.frozenAt.col = (actionUI.frozenAt.col == col ? 0 : col);
                        },
                        "Insert column after":function (jS) {
                            jS.controlFactory.addColumn(jS.colLast);
                            return false;
                        },
                        "Insert column before":function (jS) {
                            jS.controlFactory.addColumn(jS.colLast, true);
                            return false;
                        },
                        "Add column to end":function (jS) {
                            jS.controlFactory.addColumn();
                            return false;
                        },
                        "Delete this column":function (jS) {
                            jS.deleteColumn();
                            return false;
                        },
                        "Hide column":function (jS) {
                            jS.toggleHideColumn();
                            return false;
                        }
                    },
                    contextmenuLeft:{
                        "Toggle freeze rows to here":function (jS) {
                            var row = jS.getTdLocation(jS.obj.tdActive()).row,
                                actionUI = jS.obj.pane().actionUI;
                            actionUI.frozenAt.row = (actionUI.frozenAt.row == row ? 0 : row);
                        },
                        "Insert row after":function (jS) {
                            jS.controlFactory.addRow(jS.rowLast);
                            return false;
                        },
                        "Insert row before":function (jS) {
                            jS.controlFactory.addRow(jS.rowLast, true);
                            return false;
                        },
                        "Add row to end":function (jS) {
                            jS.controlFactory.addRow();
                            return false;
                        },
                        "Delete this row":function (jS) {
                            jS.deleteRow();
                            return false;
                        },
                        "Hide row":function (jS) {
                            jS.toggleHide.row();
                            return false;
                        }
                    },
                    contextmenuCell:{
                        "Copy":false,
                        "Cut":false,
                        "Insert column after":function (jS) {
                            jS.controlFactory.addColumn(jS.colLast);
                            return false;
                        },
                        "Insert column before":function (jS) {
                            jS.controlFactory.addColumn(jS.colLast, true);
                            return false;
                        },
                        "Add column to end":function (jS) {
                            jS.controlFactory.addColumn();
                            return false;
                        },
                        "Delete this column":function (jS) {
                            jS.deleteColumn();
                            return false;
                        },
                        "line1":"line",
                        "Insert row after":function (jS) {
                            jS.controlFactory.addRow(jS.rowLast);
                            return false;
                        },
                        "Insert row before":function (jS) {
                            jS.controlFactory.addRow(jS.rowLast, true);
                            return false;
                        },
                        "Add row to end":function (jS) {
                            jS.controlFactory.addRow();
                            return false;
                        },
                        "Delete this row":function (jS) {
                            jS.deleteRow();
                            return false;
                        },
                        "line2":'line',
                        "Add spreadsheet":function (jS) {
                            jS.addSheet();
                        },
                        "Delete spreadsheet":function (jS) {
                            jS.deleteSheet();
                        }
                    },
                    hiddenRows:[],
                    hiddenColumns:[],
                    cellStartingHandlers: {
                        '$':function(val, ch) {
                            return jFN.DOLLAR.call(this, val.substring(1).replace(globalize.culture().numberFormat[','], ''), 2, ch || '$');
                        },
                        'Â£':function(val) {
                            return jS.s.cellStartingHandlers['$'].call(this, val, 'Â£');
                        }
                    },
                    cellEndHandlers: {
                        '%': function(value) {
                            return value.substring(0, this.value.length - 1) / 100;
                        }
                    },
                    cellTypeHandlers: {
                        percent: function(value) {
                            var num = (n(value) ? globalize.parseFloat(value) : value * 1),
                                result;

                            if (!n(num)) {//success
                                result = new Number(num);
                                result.html = globalize.format(num, 'p');
                                return result;
                            }

                            return value;
                        },
                        date: function(value) {
                            var date = globalize.parseDate(value);
                            date.html = globalize.format(date, 'd');
                            return date;
                        },
                        time: function(value) {
                            var date = globalize.parseDate(value);
                            date.html = globalize.format(date, 't');
                            return date;
                        },
                        currency: function(value) {
                            var num = (n(value) ? globalize.parseFloat(value) : value * 1),
                                result;

                            if (!n(num)) {//success
                                result = new Number(num);
                                result.html = globalize.format(num, 'c');
                                return result;
                            }

                            return value;
                        },
                        number: function(value) {
                            var radix, result;
                            if (!settings.endOfNumber) {
                                radix = globalize.culture().numberFormat['.'];
                                settings.endOfNumber = new RegExp("([" + (radix == '.' ? "\." : radix) + "])([0-9]*?[1-9]+)?(0)*$");
                            }

                            if (!n(value)) {//success
                                result = new Number(value);
                                result.html = globalize.format(value + '', "n10")
                                    .replace(settings.endOfNumber, function (orig, radix, num) {
                                        return (num ? radix : '') + (num || '');
                                    });
                                return result;
                            }

                            return value;
                        }
                    },
                    alert: function(msg) {
                        alert(msg);
                    },
                    prompt: function(msg, callback, initialValue) {
                        callback(prompt(msg, initialValue));
                    },
                    confirm: function(msg, callbackIfTrue, callbackIfFalse) {
                        if (confirm(msg)) {
                            callbackIfTrue();
                        } else if (callbackIfFalse) {
                            callbackIfFalse();
                        }
                    },
                    initCalcRows: 40,
                    initCalcCols: 20
                };

            //destroy already existing spreadsheet
            var jS = this.jS;
            if (jS) {
                var tables = me.children().detach();
                jS.kill();
                me.html(tables);

                for (var event in events) {
                    if (settings[events[event]]) {
                        me.unbind(events[event]);
                    }
                }
            }

            if ((this.className || '').match(/\bnotEditable\b/i) != null) {
                settings['editable'] = false;
            }

            for (var i in events) {
                if (settings[events[i]]) me.bind(events[i], settings[events[i]]);
            }

            if (!$.sheet.instance.length) $.sheet.instance = [];

            this.jS = jS = $.sheet.createInstance($.extend(defaults, settings), $.sheet.instance.length);
            $.sheet.instance.push(jS);
        });
        return this;
    },

    /**
     * @memberOf jQuery()
     * @method
     * @returns {HTMLElement}
     */
    disableSelectionSpecial:function () {
        this.each(function () {
            this.onselectstart = function () {
                return false;
            };
            this.unselectable = "on";
            this.style['-moz-user-select'] = 'none';
        });
        return this;
    },

    /**
     * @memberOf jQuery()
     * @returns {jS}
     */
    getSheet:function () {
        var me = this[0],
            jS = (me.jS || {});
        return jS;
    },

    /**
     * Get cell value
     * @memberOf jQuery()
     * @param {Number} rowIndex
     * @param {Number} colIndex
     * @param {Number} [sheetIndex] defaults to 0
     * @returns {String|Date|Number|Boolean|Null}
     */
    getCellValue:function (rowIndex, colIndex, sheetIndex) {
        var me = this[0],
            jS = (me.jS || {});

        sheetIndex = (sheetIndex || 0);

        if (jS.updateCellValue) {
            try {
                return jS.updateCellValue(sheetIndex, rowIndex, colIndex);
            } catch (e) {}
        }
        return null;
    },

    /**
     * Set cell value
     * @memberOf jQuery()
     * @param {String|Number} value
     * @param {Number} rowIndex
     * @param {Number} colIndex
     * @param {Number} [sheetIndex] defaults to 0
     * @returns {Boolean}
     */
    setCellValue:function (value, rowIndex, colIndex, sheetIndex) {
        var me = this[0],
            jS = (me.jS || {}),
            cell;

        sheetIndex = (sheetIndex || 0);

        if (
            jS.getCell
                && (cell = jS.getCell(rowIndex, colIndex, sheetIndex))
            ) {
            try {
                if ((value + '').charAt(0) == '=') {
                    cell.valueOverride = cell.value = '';
                    cell.formula = value.substring(1);
                } else {
                    cell.value = value;
                    cell.valueOverride = cell.formula = '';
                }
                cell.calcLast = cell.calcDependenciesLast = 0;
                jS.updateCellValue.call(cell);
                jS.updateCellDependencies.call(cell);
                return true;
            } catch (e) {}
        }
        return false;
    },

    /**
     * Set cell formula
     * @memberOf jQuery()
     * @param {String} formula
     * @param {Number} rowIndex
     * @param {Number} colIndex
     * @param {Number} [sheetIndex] defaults to 0
     * @returns {Boolean}
     */
    setCellFormula:function (formula, rowIndex, colIndex, sheetIndex) {
        var me = this[0],
            jS = (me.jS || {}),
            cell;

        sheetIndex = (sheetIndex || 0);

        if (
            jS.getCell
                && (cell = jS.getCell(rowIndex, colIndex, sheetIndex))
            ) {
            try {
                cell.formula = formula;
                cell.valueOverride = cell.value = '';
                cell.calcLast = cell.calcDependenciesLast = 0;
                jS.updateCellValue(sheetIndex, rowIndex, colIndex);
            } catch (e) {}
        }
        return this;
    },

    /**
     * Set cell html
     * @memberOf jQuery()
     * @param {*} html
     * @param {Number} rowIndex
     * @param {Number} colIndex
     * @param {Number} [sheetIndex] defaults to 0
     */
    setCellHtml:function (html, rowIndex, colIndex, sheetIndex) {
        var me = this[0],
            jS = (me.jS || {}),
            cell;

        sheetIndex = (sheetIndex || 0);

        if (
            jS.getCell
                && (cell = jS.getCell(rowIndex, colIndex, sheetIndex))
            ) {
            try {
                cell.html = [html];
                cell.calcLast = cell.calcDependenciesLast = 0;
                jS.updateCellValue(sheetIndex, rowIndex, colIndex);
            } catch (e) {}
        }
        return this;
    },

    /**
     * Detect if spreadsheet is full screen
     * @memberOf jQuery()
     * @returns {Boolean}
     */
    isSheetFullScreen:function () {
        var me = this[0],
            jS = (me.jS || {});

        if (jS.obj) {
            return jS.obj.fullScreen().is(':visible');
        }
        return false;
    },

    /**
     * Get inputs serialized from spreadsheet type_sheet-index_row-index_column-index_instance-index (dropdown_0_1_1_0 = sheet 1, row 1, column A, instance 0
     * @param {Boolean} [isArray] return serialized as array (true) or string (false, default false
     * @memberOf jQuery()
     * @returns {*}
     */
    serializeCellInputs:function (isArray) {
        var me = this[0],
            jS = (me.jS || {}),
            inputs = jS.obj.tables().find(':input');

        if (isArray) {
            return inputs.serializeArray();
        } else {
            return inputs.serialize();
        }
    },

    /**
     * prints the source of a sheet for a user to see
     * @param {Boolean} [pretty] makes html a bit easier for the user to see
     * @returns {String}
     * @memberOf jQuery()
     */
    viewSource:function (pretty) {
        var source = "";
        $(this).each(function () {
            if (pretty) {
                source += $(this).toPrettySource();
            } else {
                source += $(this).toCompactSource();
            }
        });
        $.print(source);

        return source;
    },

    /**
     * prints html to 1 line
     * @returns {String}
     * @memberOf jQuery()
     */
    toCompactSource:function () {
        var node = this[0];
        var result = "";
        if (node.nodeType == 1) {
            // ELEMENT_NODE
            result += "<" + node.tagName.toLowerCase();

            var n = node.attributes.length;
            for (var i = 0; i < n; i++) {
                var key = node.attributes[i].name,
                    val = node.getAttribute(key);

                if (val) {
                    if (key == "contentEditable" && val == "inherit") {
                        continue;
                        // IE hack.
                    }

                    if (typeof(val) == "string") {
                        result += " " + key + '="' + val.replace(/"/g, "'") + '"';
                    } else if (key == "style" && val.cssText) {
                        result += ' style="' + val.cssText + '"';
                    }
                }
            }

            if (node.tagName == "COL") {
                // IE hack, which doesn't like <COL..></COL>.
                result += '/>';
            } else {
                result += ">";
                var childResult = "";
                $(node.childNodes).each(function () {
                    childResult += $(this).toCompactSource();
                });
                result += childResult;
                result += "</" + node.tagName.toLowerCase() + ">";
            }

        } else if (node.nodeType == 3) {
            // TEXT_NODE
            result += node.data.replace(/^\s*(.*)\s*$/g, "$1");
        }
        return result;
    },

    /**
     *  prints html to many lines, formatted for easy viewing
     * @param {String} [prefix]
     * @returns {String}
     * @memberOf jQuery()
     */
    toPrettySource:function (prefix) {
        var node = this[0],
            n,
            i;
        prefix = prefix || "";

        var result = "";
        if (node.nodeType == 1) {
            // ELEMENT_NODE
            result += "\n" + prefix + "<" + node.tagName.toLowerCase();
            n = node.attributes.length;
            for (i = 0; i < n; i++) {
                var key = node.attributes[i].name,
                    val = node.getAttribute(key);

                if (val) {
                    if (key == "contentEditable" && val == "inherit") {
                        continue; // IE hack.
                    }
                    if (typeof(val) == "string") {
                        result += " " + key + '="' + $.trim(val.replace(/"/g, "'")) + '"';
                    } else if (key == "style" && val.cssText) {
                        result += ' style="' + $.trim(val.cssText) + '"';
                    }
                }
            }
            if (node.childNodes.length <= 0) {
                if (node.tagName == "COL") {
                    result += "/>";
                } else {
                    result += "></" + node.tagName.toLowerCase() + ">";
                }
            } else {
                result += ">";
                var childResult = "";

                n = node.childNodes.length;

                for (i = 0; i < n; i++) {
                    childResult += $(node.childNodes[i]).toPrettySource(prefix + "  ");
                }
                result += childResult;
                if (childResult.indexOf('\n') >= 0) {
                    result += "\n" + prefix;
                }
                result += "</" + node.tagName.toLowerCase() + ">";
            }
        } else if (node.nodeType == 3) {
            // TEXT_NODE
            result += node.data.replace(/^\s*(.*)\s*$/g, "$1");
        }
        return result;
    }
});

/**
 * @namespace
 * @type {Object}
 * @memberOf jQuery
 * @name jQuery.sheet
 */
$.sheet = {
    /**
     * Array of instances of jQuery.sheet, generally short-handed to jS
     * @memberOf jQuery.sheet
     */
    instance:[],

    /**
     * Contains the dependencies if you use $.sheet.preLoad();
     * @memberOf jQuery.sheet
     */
    dependencies:{
        coreCss:{css:'jquery.sheet.css'},

        formulaParser:{script:'parser/formula/formula.js'},
        tsvParser:{script:'parser/tsv/tsv.js'},

        jQueryUI:{script:'jquery-ui/jquery-ui.min.js', thirdParty:true},
        jQueryUIThemeRoller:{css:'jquery-ui/themes/smoothness/jquery-ui.min.css', thirdParty:true},

        globalize:{script:'globalize/lib/globalize.js', thirdParty:true},

        nearest:{script:'jquery-nearest/src/jquery.nearest.min.js', thirdParty:true},

        mousewheel:{script:'MouseWheel/MouseWheel.js', thirdParty:true}
    },

    /**
     * Contains the optional plugins if you use $.sheet.preLoad();
     * @memberOf jQuery.sheet
     */
    optional:{
        //native
        advancedFn:{script:'plugins/jquery.sheet.advancedfn.js'},
        dts:{script:'plugins/jquery.sheet.dts.js'},
        financeFn:{script:'plugins/jquery.sheet.financefn.js'},

        //3rd party
        colorPicker:{
            css:'really-simple-color-picker/colorPicker.css',
            script:'really-simple-color-picker/jquery.colorPicker.min.js',
            thirdParty:true
        },

        elastic:{script:'jquery-elastic/jquery.elastic.source.js', thirdParty:true},

        globalizeCultures:{script:'globalize/lib/cultures/globalize.cultures.js', thirdParty:true},

        raphael:{script:'raphael/raphael-min.js', thirdParty:true},
        gRaphael:{script:'graphael/g.raphael.js', thirdParty:true},
        gRaphaelBar:{script:'graphael/g.bar.js', thirdParty:true},
        gRaphaelDot:{script:'graphael/g.dot.js', thirdParty:true},
        gRaphaelLine:{script:'graphael/g.line.js', thirdParty:true},
        gRaphaelPie:{script:'graphael/g.pie.js', thirdParty:true},

        undoManager:{script: 'Javascript-Undo-Manager/js/undomanager.js', thirdParty:true},

        zeroClipboard:{script:'zeroclipboard/dist/ZeroClipboard.min.js', thirdParty:true}
    },

    /**
     * events list
     * @memberOf jQuery.sheet
     */
    events:[
        'sheetAddRow',
        'sheetAddColumn',
        'sheetSwitch',
        'sheetRename',
        'sheetTabSortStart',
        'sheetTabSortUpdate',
        'sheetCellEdit',
        'sheetCellEdited',
        'sheetCalculation',
        'sheetAdd',
        'sheetDelete',
        'sheetDeleteRow',
        'sheetDeleteColumn',
        'sheetOpen',
        'sheetAllOpened',
        'sheetSave',
        'sheetFullScreen',
        'sheetFormulaKeydown'
    ],

    /**
     * Used to load in all the required plugins and dependencies needed by sheet in it's default directories.
     * @param {String} [path] path
     * @param {Object} settings
     * @memberOf jQuery.sheet
     *
     */
    preLoad:function (path, settings) {
        path = path || '';
        settings = $.extend({
            skip: ['globalizeCultures'],
            thirdPartyDirectory: 'bower_components/'
        },settings);


        var write = function () {
            if (this.script !== undefined) {
                document.write('<script src="' + path + (this.thirdParty ? settings.thirdPartyDirectory : '') + this.script + '"></script>');
            }
            if (this.css !== undefined) {
                document.write('<link rel="stylesheet" type="text/css" href="' + path + (this.thirdParty ? settings.thirdPartyDirectory : '') + this.css + '"></link>');
            }
        };

        for(var i in settings.skip) {
            if (this.dependencies[settings.skip[i]]) {
                delete this.dependencies[settings.skip[i]];
            }
            if (this.optional[settings.skip[i]]) {
                delete this.optional[settings.skip[i]];
            }
        }

        $.each(this.dependencies, function () {
            write.call(this);
        });

        $.each(this.optional, function () {
            write.call(this);
        });
    },

    /**
     * The instance creator of jQuery.sheet
     * @memberOf jQuery.sheet
     * @param {Object} s settings from jQuery.fn.sheet
     * @param {Number} I the index of the instance
     * @returns {jS} jS jQuery sheet instance
     */
    createInstance:function (s, I) {

        var self = this,
        //create function, it expects 2 values.
            insertAfter = function (newElement, targetElement) {
                //target is what you want it to go after. Look for this elements parent.
                var parent = targetElement.parentNode;

                //if the parents lastchild is the targetElement...
                if(parent.lastchild == targetElement) {
                    //add the newElement after the target element.
                    parent.appendChild(newElement);
                } else {
                    // else the target has siblings, insert the new element between the target and it's next sibling.
                    parent.insertBefore(newElement, targetElement.nextSibling);
                }
            },
            $window = $(window),
            $document = $(document),
            body = document.body,
            $body = $(body),
            emptyFN = function () {},
            u = undefined,
            math = Math,
            n = isNaN,
            nAN = NaN,
            jSCellRange = function(cells) {
                this.cells = cells || [];
            },
            jSCP = jSCellRange.prototype = {
                clone: function() {
                    var clones = [];
                    for(var i = 0; i < this.cells.length;i++) {
                        var cell = this.cells[i],
                            clone = {
                                value:cell.value,
                                formula:cell.formula,
                                td: cell.td,
                                dependencies: cell.dependencies,
                                needsUpdated: cell.needsUpdated,
                                calcCount: cell.calcCount,
                                sheet: cell.sheet,
                                calcLast: cell.calcLast,
                                html: cell.html,
                                state: cell.state,
                                jS: cell.jS,
                                calcDependenciesLast: cell.calcDependenciesLast,
                                style: cell.style || cell.td.attr('style') || '',
                                cl: cell.cl || cell.td.attr('class') || ''
                            };
                        clones.push(clone);
                    }
                    return new jSCellRange(clones);
                }
            },

            /**
             * A single instance of a spreadsheet, shorthand, also accessible from jQuery.sheet.instance[index].
             * Generally called by jQuery().sheet().  Exposed for the ability to override methods if needed
             * @namespace
             * @alias jQuery.sheet.instance[]
             * @name jS
             */
            jS = {
                /**
                 * Current version of jQuery.sheet
                 * @memberOf jS
                 * @type {String}
                 */
                version:'4.x',

                /**
                 * The active sheet index within the a set of sheets
                 * @memberOf jS
                 * @type {Number}
                 */
                i:0,

                /**
                 * The instance index
                 * @memberOf jS
                 * @type {Number}
                 */
                I:I,

                /**
                 * The current count of sheet's within the instance
                 * @memberOf jS
                 * @type {Number}
                 */
                sheetCount:0,

                /**
                 * The internal storage array of the spreadsheets for an instance, constructed as array 3 levels deep, spreadsheet, rows, cells, can easily be used for custom exporting/saving
                 * @memberOf jS
                 * @type {Array}
                 */
                spreadsheets:[],

                /**
                 * Internal storage array of controls for an instance
                 * @memberOf jS
                 */
                controls:{
                    autoFiller:[],
                    bar:{
                        helper:[],
                        corner:[],
                        x:{
                            controls:[],
                            handleFreeze:[],
                            menu:[],
                            menuParent:[],
                            parent:[],
                            td:[],
                            tds:function () {
                                var tds = $([]);
                                for (var i in this.td[jS.i]) {
                                    tds.pushStack(this.td[jS.i][i]);
                                }
                                return tds;
                            }
                        },
                        y:{
                            controls:[],
                            handleFreeze:[],
                            menu:[],
                            parent:[],
                            td:[],
                            tds:function () {
                                var tds = $([]);
                                for (var i in this.td[jS.i]) {
                                    tds.pushStack(this.td[jS.i][i]);
                                }
                                return tds;
                            }
                        }
                    },
                    barMenuLeft:[],
                    barMenuTop:[],
                    barLeft:[],
                    barTop:[],
                    barTopParent:[],
                    chart:[],
                    tdMenu:[],
                    cellsEdited:[],
                    enclosure:[],
                    enclosures:null,
                    formula:null,
                    fullScreen:null,
                    header:null,
                    inPlaceEdit:[],
                    inputs:[],
                    label:null,
                    menuLeft:[],
                    menuRight:[],
                    menus:[],
                    pane:[],
                    panes:null,
                    scrolls:null,
                    sheetAdder:null,
                    table:[],
                    tables:null,
                    tab:[],
                    tabContainer:null,
                    tabs:null,
                    title:null,
                    toggleHide:{
                        x:[],
                        y:[]
                    },
                    ui:null
                },

                /**
                 * Object selectors for interacting with a spreadsheet, dynamically id'd from both sheet index and instance index
                 * @memberOf jS
                 * @type {Object}
                 */
                obj:{
                    autoFiller:function () {
                        return jS.controls.autoFiller[jS.i] || $([]);
                    },
                    barCorner:function () {
                        return jS.controls.bar.corner[jS.i] || $([]);
                    },
                    barHelper:function () {
                        return jS.controls.bar.helper[jS.i] || (jS.controls.bar.helper[jS.i] = $([]));
                    },
                    barLeft:function (i) {
                        return (jS.controls.bar.y.td[jS.i] && jS.controls.bar.y.td[jS.i][i] ? jS.controls.bar.y.td[jS.i][i] : $([]));
                    },
                    barLeftControls:function () {
                        return jS.controls.bar.y.controls[jS.i] || $([]);
                    },
                    barLefts:function () {
                        return jS.controls.bar.y.tds();
                    },
                    barHandleFreezeLeft:function () {
                        return jS.controls.bar.y.handleFreeze[jS.i] || $([]);
                    },
                    barMenuLeft:function () {
                        return jS.controls.bar.y.menu[jS.i] || $([]);
                    },
                    barTop:function (i) {
                        return (jS.controls.bar.x.td[jS.i] && jS.controls.bar.x.td[jS.i][i] ? jS.controls.bar.x.td[jS.i][i] : $([]));
                    },
                    barTopControls:function () {
                        return jS.controls.bar.x.controls[jS.i] || $([]);
                    },
                    barTops:function () {
                        return jS.controls.bar.x.tds();
                    },
                    barTopParent:function () {
                        return jS.controls.bar.x.parent[jS.i] || $([]);
                    },
                    barHandleFreezeTop:function () {
                        return jS.controls.bar.x.handleFreeze[jS.i] || $([]);
                    },
                    barMenuParentTop:function () {
                        return jS.controls.bar.x.menuParent[jS.i] || $([]);
                    },
                    barMenuTop:function () {
                        return jS.controls.bar.x.menu[jS.i] || $([]);
                    },
                    tdActive:function () {
                        return jS.cellLast.td || $([]);
                    },
                    tdMenu:function () {
                        return jS.controls.tdMenu[jS.i] || $([]);
                    },
                    cellsEdited: function () {
                        return jS.controls.cellsEdited[jS.i] || $([]);
                    },
                    chart:function () {
                        return jS.controls.chart[jS.i] || $([]);
                    },
                    enclosure:function () {
                        return jS.controls.enclosure[jS.i] || $([]);
                    },
                    enclosures:function () {
                        return jS.controls.enclosures || $([]);
                    },
                    formula:function () {
                        return jS.controls.formula || $([]);
                    },
                    fullScreen:function () {
                        return jS.controls.fullScreen || $([]);
                    },
                    header:function () {
                        return jS.controls.header || $([]);
                    },
                    highlighted: function() {
                        return jS.highlightedLast.obj || $([]);
                    },
                    menuRight:function () {
                        return jS.controls.menuRight[jS.i] || $([]);
                    },
                    inPlaceEdit:function () {
                        return jS.controls.inPlaceEdit[jS.i] || $([]);
                    },
                    inputs:function() {
                        return jS.controls.inputs[jS.i] || $([]);
                    },
                    label:function () {
                        return jS.controls.label || $([]);
                    },
                    menus:function() {
                        return jS.controls.menus[jS.i] || $([]);
                    },
                    menuLeft:function () {
                        return jS.controls.menuLeft[jS.i] || $([]);
                    },
                    pane:function () {
                        return jS.controls.pane[jS.i] || {};
                    },
                    panes:function () {
                        return jS.controls.panes || $([]);
                    },
                    parent:function () {
                        return s.parent;
                    },
                    scrolls:function () {
                        return jS.controls.scrolls || $([]);
                    },
                    sheetAdder:function () {
                        return jS.controls.sheetAdder || $([]);
                    },
                    table:function () {
                        return jS.controls.table[jS.i] || $([]);
                    },
                    tables:function () {
                        return jS.controls.tables || $([]);
                    },
                    tab:function () {
                        return jS.controls.tab[jS.i] || $([]);
                    },
                    tabs:function () {
                        return jS.controls.tabs || $([]);
                    },
                    tabContainer:function () {
                        return jS.controls.tabContainer || $([]);
                    },
                    title:function () {
                        return jS.controls.title || $([]);
                    }
                },

                /**
                 * Internal id's of table, used for scrolling and a few other things
                 * @memberOf jS
                 * @type {String}
                 */
                id:'jS_' + I + '_',

                /**
                 * Internal css classes of objects
                 * @memberOf jS
                 * @type {Object}
                 */
                cl:{
                    autoFiller:'jSAutoFiller',
                    autoFillerHandle:'jSAutoFillerHandle',
                    autoFillerCover:'jSAutoFillerCover',
                    barCorner:'jSBarCorner',
                    barController:'jSBarController',
                    barHelper:'jSBarHelper',
                    barLeft:'jSBarLeft',
                    barHandleFreezeLeft:'jSBarHandleFreezeLeft',
                    barTop:'jSBarTop',
                    barTopMenuButton: 'jSBarTopMenuButton',
                    barHandleFreezeTop:'jSBarHandleFreezeTop',
                    barTopParent:'jSBarTopParent',
                    chart:'jSChart',
                    formula:'jSFormula',
                    formulaParent:'jSFormulaParent',
                    header:'jSHeader',
                    fullScreen:'jSFullScreen',
                    inPlaceEdit:'jSInPlaceEdit',
                    menu:'jSMenu',
                    menuFixed:'jSMenuFixed',
                    parent:'jSParent',
                    scroll:'jSScroll',
                    sheetAdder: 'jSSheetAdder',
                    table:'jS',
                    label:'jSLoc',
                    pane:'jSEditPane',
                    tab:'jSTab',
                    tabContainer:'jSTabContainer',
                    tdMenu:'jSTdMenu',
                    title:'jSTitle',
                    enclosure:'jSEnclosure',
                    ui:'jSUI',
                    uiAutoFiller:'ui-state-active',
                    uiBar:'ui-widget-header',
                    uiBarHighlight:'ui-state-active',
                    uiBarHandleFreezeLeft:'ui-state-default',
                    uiBarHandleFreezeTop:'ui-state-default',
                    uiBarMenuTop:'ui-state-default',
                    uiTdActive:'ui-state-active',
                    uiTdHighlighted:'ui-state-highlight',
                    uiControl:'ui-widget-header ui-corner-top',
                    uiControlTextBox:'ui-widget-content',
                    uiFullScreen:'ui-widget-content ui-corner-all',
                    uiInPlaceEdit:'ui-state-highlight',
                    uiMenu:'ui-widget-header',
                    uiMenuUl:'ui-widget-header',
                    uiMenuLi:'ui-widget-header',
                    uiPane: 'ui-widget-content',
                    uiParent:'ui-widget-content ui-corner-all',
                    uiTable:'ui-widget-content',
                    uiTab:'ui-widget-header',
                    uiTabActive:'ui-state-highlight'
                },

                /**
                 * Messages for user interface
                 * @memberOf jS
                 * @type {Object}
                 */
                msg:{
                    addRowMulti:"How many rows would you like to add?",
                    addColumnMulti:"How many columns would you like to add?",
                    cellFind:"What are you looking for in this spreadsheet?",
                    cellNoFind:"No results found.",
                    dragToFreezeCol:"Drag to freeze column",
                    dragToFreezeRow:"Drag to freeze row",
                    addSheet:"Add a spreadsheet",
                    openSheet:"Are you sure you want to open a different sheet?  All unsaved changes will be lost.",
                    toggleHideRow:"No row selected.",
                    toggleHideColumn:"No column selected.",
                    loopDetected:"Loop Detected",
                    newSheetTitle:"What would you like the sheet's title to be?",
                    notFoundColumn:"Column not found",
                    notFoundRow:"Row not found",
                    notFoundSheet:"Sheet not found",
                    setCellRef:"Enter the name you would like to reference the cell by.",
                    sheetTitleDefault:"Spreadsheet {index}",
                    maxRowsBrowserLimitation:"You've reached the maximum amount of rows this browser supports. Try using Chrome, FireFox, or Internet Explorer 9+",
                    maxColsBrowserLimitation:"You've reached the maximum amount of columns this browser supports. Try using Chrome, FireFox, or Internet Explorer 9+",
                    maxSizeBrowserLimitationOnOpen:"The spreadsheet you are loading is larger than the maximum size of cells this browser supports. Try using Chrome, Firefox, or Internet Explorer 9+. You can an proceed, but the spreadsheet may not work as intended."
                },

                /**
                 * Deletes a jQuery sheet instance
                 * @memberOf jS
                 */
                kill:function () {
                    if (!jS) {
                        return false;
                    }
                    $(document).unbind('keydown');
                    this.obj.fullScreen().remove();
                    (this.obj.inPlaceEdit().destroy || emptyFN)();
                    s.parent
                        .trigger('sheetKill')
                        .removeClass(jS.cl.uiParent)
                        .html('');

                    s.parent[0].jS = u;

                    this.obj.menus().remove();

                    var max = $.sheet.events.length;
                    for (var i = 0; i < max; i++) {
                        s.parent.unbind($.sheet.events[i]);
                    }

                    $.sheet.instance.splice(I, 1);
                    return true;
                },

                /**
                 * Event trigger for jQuery sheet, wraps jQuery's trigger event to always return jS
                 * @param {String} eventType event type
                 * @param {Array} [extraParameters]
                 * @memberOf jS
                 */
                trigger:function (eventType, extraParameters) {
                    //wrapper for $ trigger of parent, in case of further mods in the future
                    extraParameters = extraParameters || [];
                    return s.parent.triggerHandler(eventType, [jS].concat(extraParameters));
                },

                /**
                 * Returns all spreadsheets within an instance as an array, builds it if it doesn't exist
                 * @param [forceRebuild]
                 * @returns {Array|spreadsheets}
                 * @memberOf jS
                 */
                spreadsheetsToArray:function (forceRebuild) {
                    if (forceRebuild || jS.spreadsheets.length == 0) {
                        jS.cycleCellsAll(function (sheet, row, col) {
                            jS.createCell(sheet, row, col);
                        });
                    }
                    return jS.spreadsheets;
                },

                /**
                 * Returns singe spreadsheet from a set of spreadsheets within as instance, builds if it doesn't exist
                 * @param {Boolean} forceRebuild Enforces the spreadsheet to be rebuilt
                 * @param {Number} i Spreadsheet index
                 * @memberOf jS
                 */
                spreadsheetToArray:function (forceRebuild, i) {
                    i = (i ? i : jS.i);
                    if (!jS.spreadsheets[i]) {
                        jS.cycleCells(function (sheet, row, col) {
                            jS.createCell(sheet, row, col);
                        });
                    }
                    return jS.spreadsheets[i];
                },

                /**
                 * Creates a single cell within
                 * @param {Number} sheetIndex
                 * @param {Number} rowIndex
                 * @param {Number} colIndex
                 * @param {Number} [calcCount]
                 * @param {Date} [calcLast]
                 * @param {Date} [calcDependenciesLast]
                 * @returns {jSCell}
                 * @memberOf jS
                 */
                createCell:function (sheetIndex, rowIndex, colIndex, calcCount, calcLast, calcDependenciesLast) {
                    //first create cell
                    var sheet,
                        row,
                        jSCell,
                        jSCells,
                        table,
                        colGroup,
                        col,
                        tBody,
                        tr,
                        td,
                        $td,
                        tdsX,
                        tdsY;

                    if (!(sheet = jS.spreadsheets[sheetIndex])) { //check if spreadsheet exists, if not, create it as an array
                        sheet = jS.spreadsheets[sheetIndex] = [];
                    }

                    if (!(row = sheet[rowIndex])) { //check if row exists, if not, create it
                        row = sheet[rowIndex] = [];
                    }

                    if (!(table = jS.controls.tables[sheetIndex])) {
                        return {};
                    }
                    if (!(tBody = table.tBody)) {
                        return {};
                    }
                    if (!(tr = tBody.children[rowIndex])) {
                        return {};
                    }
                    if (!(td = tr.children[colIndex])) {
                        return {};
                    }

                    $td = $(td);

                    jSCell = row[colIndex] = td.jSCell = { //create cell
                        td:$td,
                        dependencies: [],
                        formula:td.getAttribute('data-formula') || '',
                        cellType:td.getAttribute('data-celltype') || null,
                        value:td.textContent || td.innerText || '',
                        calcCount:calcCount || 0,
                        calcLast:calcLast || -1,
                        calcDependenciesLast:calcDependenciesLast || -1,
                        sheet:sheetIndex,
                        type: 'cell',
                        jS: jS,
                        state: [],
                        needsUpdated: true,
                        uneditable: td.getAttribute('data-uneditable') || false
                    };

                    if (jSCell.formula && jSCell.formula.charAt(0) == '=') {
                        jSCell.formula = jSCell.formula.substring(1);
                    }


                    //attach cells to col
                    colGroup = table.colGroup;
                    while (!(col = colGroup.children[colIndex])) {
                        //if a col doesn't exist, it adds it here
                        col = document.createElement('col');
                        col.setAttribute('style', 'width:' + jS.s.newColumnWidth + 'px;');
                        colGroup.appendChild(col);
                    }

                    if (!(jSCells = col.jSCells)) jSCells = col.jSCells = [];
                    jSCells.unshift(jSCell);

                    //attach td to col
                    if (!col.tds) col.tds = [];
                    col.tds.unshift(td);

                    //attach col to td
                    td.col = col;
                    td.type = 'cell';
                    td.barLeft = tr.children[0];
                    td.barTop = tBody.children[0].children[colIndex];

                    //attach cells to tr
                    if (!tr.jSCells) tr.jSCells = [];
                    tr.jSCells.unshift(jSCell);

                    //attach td's to tr
                    if (!tr.tds) tr.tds = [];
                    tr.tds.unshift(td);

                    //attach cells to table
                    if (!table.jSCells) table.jSCells = [];
                    table.jSCells.unshift(jSCell);

                    //attach td's to table
                    if (!table.tds) table.tds = [];
                    table.tds.unshift(td);

                    //attach table to td
                    td.table = table;

                    //now create row
                    if (!(tdsY = jS.controls.bar.y.td[sheetIndex])) {
                        tdsY = jS.controls.bar.y.td[sheetIndex] = [];
                    }
                    if (!tdsY[rowIndex]) {
                        tdsY[rowIndex] = $(tr.children[0]);
                    }

                    if (!(tdsX = jS.controls.bar.x.td[sheetIndex])) {
                        tdsX = jS.controls.bar.x.td[sheetIndex] = [];
                    }
                    if (!tdsX[colIndex]) {
                        tdsX[colIndex] = $(tBody.children[0].children[colIndex]);
                    }

                    //return cell
                    return jSCell;
                },

                /**
                 * Get cell value
                 * @memberOf jS
                 * @param {Number} rowIndex
                 * @param {Number} colIndex
                 * @param {Number} [sheetIndex] defaults to 0
                 * @returns {Object|Null}
                 */
                getCell: function (rowIndex, colIndex, sheetIndex) {
                    var spreadsheet, row, cell;
                    sheetIndex = (sheetIndex || 0);
                    if (
                        jS.spreadsheets
                            && (spreadsheet = jS.spreadsheets[sheetIndex])
                            && (row = spreadsheet[rowIndex])
                            && (cell = row[colIndex])
                        ) {
                        return cell;
                    }
                    return null;
                },

                /**
                 * Tracks which spreadsheet is active to intercept keystrokes for navigation
                 * @type {Boolean}
                 * @memberOf jS
                 */
                nav:false,

                /**
                 * Turns off all intercept keystroke navigation instances, with exception of supplied instance index
                 * @param {Boolean} nav Instance index
                 * @memberOf jS
                 */
                setNav:function (nav) {
                    var instance = $.sheet.instance;
                    for(var i = 0; i < instance.length; i++) {
                        (instance[i] || {}).nav = false;
                    }

                    jS.nav = nav;
                },

                /**
                 * Creates the different objects required by sheets
                 * @memberOf jS
                 * @type {Object}
                 * @namespace
                 */
                controlFactory:{
                    /**
                     * Creates multi rows
                     * @param {Number} [i] row index
                     * @param {Number} [qty] the number of cells you'd like to add, if not specified, a dialog will ask
                     * @param {Boolean} [isBefore] places cells before the selected cell if set to true, otherwise they will go after, or at end
                     * @param {Boolean} [skipFormulaReParse] re-parses formulas if needed
                     * @memberOf jS.controlFactory
                     */
                    addRowMulti:function (i, qty, isBefore, skipFormulaReParse) {
                        function add(qty) {
                            if (qty) {
                                if (!n(qty)) {
                                    jS.controlFactory.addCells(i, isBefore, parseInt(qty), 'row', skipFormulaReParse);
                                    jS.trigger('sheetAddRow', [i, isBefore, qty]);
                                }
                            }
                        }

                        if (!qty) {
                            s.prompt(
                                jS.msg.addRowMulti,
                                add
                            );
                        } else {
                            add(qty);
                        }
                    },

                    /**
                     * Creates multi columns
                     * @param {Number} [i] column index
                     * @param {Number} [qty] the number of cells you'd like to add, if not specified, a dialog will ask
                     * @param {Boolean} [isBefore] places cells before the selected cell if set to true, otherwise they will go after, or at end
                     * @param {Boolean} [skipFormulaReParse] re-parses formulas if needed
                     * @memberOf jS.controlFactory
                     */
                    addColumnMulti:function (i, qty, isBefore, skipFormulaReParse) {
                        function add(qty) {
                            if (qty) {
                                if (!n(qty)) {
                                    jS.controlFactory.addCells(i, isBefore, parseInt(qty), 'col', skipFormulaReParse);
                                    jS.trigger('sheetAddColumn', [i, isBefore, qty]);
                                }
                            }
                        }

                        if (!qty) {
                            s.prompt(
                                jS.msg.addColumnMulti,
                                add
                            );
                        } else {
                            add(qty);
                        }
                    },

                    /**
                     * Creates cells for sheet and the bars that go along with them
                     * @param {Number} [i] index where cells should be added, if null, cells go to end
                     * @param {Boolean} [isBefore] places cells before the selected cell if set to true, otherwise they will go after, or at end;
                     * @param {Number} [qty] how many rows/columns to add
                     * @param {String} [type] "row" or "col", default "col"
                     * @param {Boolean} [skipFormulaReParse] re-parses formulas if needed
                     * @memberOf jS.controlFactory
                     */
                    addCells:function (i, isBefore, qty, type, skipFormulaReParse) {
                        //hide the autoFiller, it can get confused
                        jS.autoFillerHide();

                        jS.setDirty(true);
                        jS.setChanged(true);
                        jS.obj.barHelper().remove();

                        var $sheet = jS.obj.table(),
                            sheet = $sheet[0],
                            sheetSize = jS.sheetSize(sheet),
                            isLast = false,
                            activeCell = jS.obj.tdActive(),
                            o;

                        qty = qty || 1;
                        type = type || 'col';

                        if (i == u) {
                            i = (type == 'row' ? sheetSize.rows : sheetSize.cols);
                            isLast = true;
                        }

                        switch (type) {
                            case "row":
                                if ($.sheet.max) {
                                    //if current size is less than max, but the qty needed is more than the max
                                    if ($.sheet.max > sheetSize.rows && $.sheet.max <= sheetSize.rows + qty) {
                                        qty = $.sheet.max - sheetSize.rows;

                                        //if current size is more than max
                                    } else if ($.sheet.max && $.sheet.max <= sheetSize.rows + qty) {
                                        if (!jS.isBusy()) {
                                            s.alert(jS.msg.maxRowsBrowserLimitation);
                                        }
                                        return false;
                                    }
                                }
                                o = {
                                    el:function () {
                                        //table / tBody / tr / td
                                        var tds = jS.rowTds(sheet, i);
                                        if (!tds || !tds[0]) return [];
                                        return [tds[0].parentNode];
                                    },
                                    size:function () {
                                        if (!o.Size) {
                                            var tr = o.el()[0];
                                            o.Size = tr.children.length - 1;
                                        }
                                        return o.Size;
                                    },
                                    loc:function () {
                                        var tr = o.el();
                                        return jS.getTdLocation(tr[0].children);
                                    },
                                    trs: [],
                                    newObj:function () {
                                        var j = o.size(),
                                            tr = document.createElement('tr');

                                        tr.setAttribute('style', 'height: ' + s.colMargin + 'px;');
                                        for (var i = 0; i <= j; i++) {
                                            var td = document.createElement('td');
                                            if (i == 0) {
                                                td.setAttribute('class', jS.cl.barLeft + ' ' + jS.cl.uiBar);
                                                td.entity = 'left';
                                                td.type = 'bar';
                                            }
                                            tr.appendChild(td);
                                        }

                                        o.trs.push(tr);

                                        return tr;
                                    },
                                    offset:{row:qty, col:0},
                                    start:function () {
                                        return {row:(isBefore ? i : i + qty)};
                                    },
                                    createCells:function () {
                                        for (var row = 0; row < o.trs.length; row++) {
                                            var offset = (isBefore ? 0 : 1) + i;
                                            jS.spreadsheets[jS.i].splice(row + offset, 0, []);
                                            for (var col = 0; col < o.trs[row].children.length; col++) {
                                                if (col == 0) {//skip bar
                                                    jS.controls.bar.y.td[jS.i].splice(row + offset, 0, $(o.trs[row].children[col]));
                                                } else {
                                                    jS.createCell(jS.i, row + offset, col);
                                                }
                                            }
                                        }

                                        jS.refreshRowLabels(i);
                                    }
                                };
                                break;
                            case "col":
                                if ($.sheet.max) {
                                    //if current size is less than max, but the qty needed is more than the max
                                    if ($.sheet.max > sheetSize.cols && $.sheet.max <= sheetSize.cols + qty) {
                                        qty = $.sheet.max - sheetSize.cols;

                                        //if current size is more than max
                                    } else if ($.sheet.max && $.sheet.max <= sheetSize.cols + qty) {
                                        if (!jS.isBusy()) {
                                            s.alert(jS.msg.maxColsBrowserLimitation);
                                        }
                                        return false;
                                    }
                                }
                                o = {
                                    el:function () {
                                        var tdStart = jS.rowTds(sheet, 1)[i],
                                            lastRow = jS.rowTds(sheet),
                                            tdEnd = lastRow[lastRow.length - 1],
                                            loc2 = jS.getTdLocation(tdEnd),

                                            tds = [];

                                        for (var j = 0; j <= loc2.row; j++) {
                                            tds.push(sheet.tBody.children[j].children[i]);
                                        }

                                        return tds;
                                    },
                                    col:function () {
                                        return jS.col(sheet, i);
                                    },
                                    cols: [],
                                    newCol:function () {
                                        var col = document.createElement('col');
                                        col.setAttribute('style', 'width:' + jS.s.newColumnWidth + 'px;');
                                        o.cols.push(col);
                                        return col;
                                    },
                                    loc:function (tds) {
                                        tds = (tds ? tds : o.el());
                                        return jS.getTdLocation(tds[0]);
                                    },
                                    tds: [],
                                    newObj:function () {
                                        var td = document.createElement('td');
                                        o.tds.push(td);
                                        return td;
                                    },
                                    offset:{row:0, col:qty},
                                    start:function () {
                                        return {col:(isBefore ? i : i + qty)};
                                    },
                                    createCells:function () {
                                        var rows = jS.rows(sheet);
                                        for (var row = 0; row < rows.length; row++) {
                                            var col = (isBefore ? 0 : 1) + i,
                                                colMax = col + qty,
                                                j = 0;
                                            for (col; col < colMax; col++) {
                                                var td = sheet.tBody.children[row].children[col],
                                                    $td = $(td);
                                                if (row == 0) {
                                                    jS.controls.bar.x.td[jS.i].splice(col, 0, $td);
                                                    td.innerText = jSE.columnLabelString(col);
                                                    td.className = jS.cl.barTop + ' ' + jS.cl.uiBar;
                                                    td.type = 'bar';
                                                    td.entity = 'top';

                                                    o.cols[j].setAttribute('style', 'width:' + jS.s.newColumnWidth + 'px;');
                                                    o.cols[j].bar = td;
                                                    j++;
                                                } else {
                                                    jS.spreadsheets[jS.i][row].splice(col, 0, {});
                                                    jS.createCell(jS.i, row, col);
                                                }
                                            }
                                        }

                                        jS.refreshColumnLabels(i);
                                    }
                                };
                                break;
                        }

                        var el = o.el(),
                            loc = o.loc(el),
                            col,
                            j = el.length - 1,
                            k;

                        if (isBefore) {
                            do {
                                k = qty - 1;
                                do {
                                    el[j].parentNode.insertBefore(o.newObj(), el[j]);
                                } while (k--);
                            } while (j--);

                            if (o.newCol) {
                                col = o.col();
                                k = qty - 1;
                                do {
                                    col.parentNode.insertBefore(o.newCol(), col);
                                } while (k--);
                            }
                        } else {
                            do {
                                k = qty - 1;
                                do {
                                    insertAfter(o.newObj(), el[j]);
                                } while (k--);
                            } while (j--);

                            if (o.newCol) {
                                col = o.col();
                                k = qty - 1;
                                do {
                                    insertAfter(o.newCol(), col)
                                } while (k--);
                            }
                        }

                        o.createCells();

                        if (!skipFormulaReParse && isLast != true) {
                            //offset formulas
                            jS.offsetFormulas(loc, o.offset, isBefore);
                        }

                        jS.obj.pane().resizeScroll(true);

                        if (activeCell && activeCell[0] && activeCell[0].cellIndex && activeCell[0].parentNode) {
                            jS.colLast = activeCell[0].cellIndex;
                            jS.rowLast = activeCell[0].parentNode.rowIndex;
                        }

                        return true;
                    },

                    /**
                     * creates single row
                     * @param {Number} [i] row index
                     * @param {Boolean} [isBefore] places cells before the selected cell if set to true, otherwise they will go after, or at end
                     * @memberOf jS.controlFactory
                     */
                    addRow:function (i, isBefore) {
                        jS.controlFactory.addCells(i, isBefore, 1, 'row');
                        jS.trigger('sheetAddRow', [i, isBefore, 1]);
                    },

                    /**
                     * creates single column
                     * @param {Number} [i], column index
                     * @param {Boolean} [isBefore] places cells before the selected cell if set to true, otherwise they will go after, or at end
                     * @memberOf jS.controlFactory
                     */
                    addColumn:function (i, isBefore) {
                        jS.controlFactory.addCells(i, isBefore, 1, 'col');
                        jS.trigger('sheetAddColumn', [i, isBefore, 1]);
                    },

                    /**
                     * Creates all the bars to the left of the spreadsheet, if they exist, they are first removed
                     * @param {jQuery|HTMLElement} table Table of spreadsheet
                     * @memberOf jS.controlFactory
                     */
                    barLeft:function (table) {
                        var tr = table.tBody.children,
                            i = tr.length - 1;

                        //table / tBody / tr
                        if (i > -1) {
                            do {
                                tr[i].insertBefore(document.createElement('td'), tr[i].children[0]);
                            } while(i-- > 1); //We only go till row 1, row 0 is handled by barTop with corner etc
                        }
                    },

                    /**
                     * Creates all the bars to the top of the spreadsheet on colGroup col elements, if they exist, they are first removed
                     * @param {HTMLElement} table representing spreadsheet
                     * @memberOf jS.controlFactory
                     */
                    barTop:function (table) {
                        var colGroup = table.colGroup,
                            cols = colGroup.children,
                            i,
                            trFirst = table.tBody.children[0],

                            colCorner = document.createElement('col'), //left column & corner
                            tdCorner = document.createElement('td'),

                            barTopParent = document.createElement('tr');

                        //If the col elements outnumber the td's, get rid of the extra as it messes with the ui
                        while (cols.length > trFirst.children.length) {
                            colGroup.removeChild(cols[cols.length -1]);
                        }

                        colCorner.width = s.colMargin + 'px';
                        colCorner.style.width = colCorner.width;
                        colGroup.insertBefore(colCorner, colGroup.children[0]); //end col corner

                        barTopParent.appendChild(tdCorner);

                        barTopParent.className = jS.cl.barTopParent;
                        table.tBody.insertBefore(barTopParent, table.tBody.children[0]);
                        table.barTopParent = barTopParent;
                        table.corner = tdCorner;
                        jS.controls.barTopParent[jS.i] = $(barTopParent);

                        i = trFirst.children.length - 1;

                        do {
                            var td = document.createElement('td');
                            if (!cols[i]) {
                                cols[i] = document.createElement('col');
                                colGroup.insertBefore(cols[i], colCorner.nextSibling);

                            }

                            cols[i].bar = td;
                            td.col = cols[i];
                            barTopParent.insertBefore(td, tdCorner.nextSibling);
                        } while (i-- > 0);

                        table.barTop = jS.controls.barTopParent[jS.i].children();

                        return barTopParent;
                    },

                    /**
                     * Creates the draggable objects for freezing cells
                     * @type {Object}
                     * @memberOf jS.controlFactory
                     * @namespace
                     */
                    barHandleFreeze:{

                        /**
                         * @param {jQuery|HTMLElement} pane
                         * @returns {Boolean}
                         * @memberOf jS.controlFactory.barHandleFreeze
                         */
                        top:function (pane) {
                            if (jS.isBusy()) {
                                return false;
                            }
                            var
                                actionUI = jS.obj.pane().actionUI,
                                frozenAt = actionUI.frozenAt,
                                scrolledArea = actionUI.scrolledArea;

                            if (!(scrolledArea.end.col <= frozenAt.col + 1)) {
                                return false;
                            }

                            jS.obj.barHelper().remove();

                            var bar = jS.obj.barTop(frozenAt.col + 1),
                                pos = bar.position(),
                                highlighter,
                                offset = $(pane).offset(),
                                handle = document.createElement('div'),
                                $handle = pane.freezeHandleTop = $(handle)
                                    .appendTo(pane)
                                    .addClass(jS.cl.uiBarHandleFreezeTop + ' ' + jS.cl.barHelper + ' ' + jS.cl.barHandleFreezeTop)
                                    .height(bar.height())
                                    .css('left', (pos.left - handle.clientWidth) + 'px')
                                    .attr('title', jS.msg.dragToFreezeCol),
                                tds = pane.table.barTop;


                            jS.controls.bar.helper[jS.i] = jS.obj.barHelper().add(handle);
                            jS.controls.bar.x.handleFreeze[jS.i] = $handle;

                            jS.draggable($handle, {
                                axis:'x',
                                start:function () {
                                    jS.setBusy(true);

                                    highlighter = $(document.createElement('div'))
                                        .appendTo(pane)
                                        .css('position', 'absolute')
                                        .addClass('ui-state-highlight ' + jS.cl.barHelper)
                                        .height(pane.table.corner.clientHeight)
                                        .fadeTo(0,0.33);
                                },
                                drag:function() {
                                    var target = jS.nearest($handle, tds).prev();
                                    if (target.length && target.position) {
                                        highlighter.width(target.position().left + target.width());
                                    }
                                },
                                stop:function (e, ui) {
                                    highlighter.remove();
                                    jS.setBusy(false);
                                    jS.setDirty(true);
                                    var target = jS.nearest($handle, tds);

                                    jS.obj.barHelper().remove();
                                    scrolledArea.end.col = actionUI.frozenAt.col = jS.getTdLocation(target).col - 1;
                                    jS.autoFillerHide();
                                    actionUI.scrollStart('x', jS.sheetSize(pane.table));
                                },
                                containment:[offset.left, offset.top, math.min(offset.left + pane.table.clientWidth, offset.left + pane.clientWidth - window.scrollBarSize.width), offset.top]
                            });

                            return true;
                        },

                        /**
                         *
                         * @param {jQuery|HTMLElement} pane
                         * @returns {Boolean}
                         * @memberOf jS.controlFactory.barHandleFreeze
                         */
                        left:function (pane) {
                            if (jS.isBusy()) {
                                return false;
                            }
                            var pane = jS.obj.pane(),
                                actionUI = pane.actionUI,
                                frozenAt = actionUI.frozenAt,
                                scrolledArea = actionUI.scrolledArea;

                            if (!(scrolledArea.end.row <= (frozenAt.row + 1))) {
                                return false;
                            }

                            jS.obj.barHelper().remove();

                            var bar = $(pane.table.tBody.children[frozenAt.row + 1].children[0]),
                                pos = bar.position(),
                                highlighter,
                                offset = $(pane).offset(),
                                handle = document.createElement('div'),
                                $handle = pane.freezeHandleLeft = $(handle)
                                    .appendTo(pane)
                                    .addClass(jS.cl.uiBarHandleFreezeLeft + ' ' + jS.cl.barHelper + ' ' + jS.cl.barHandleFreezeLeft)
                                    .width(bar.width())
                                    .css('top', (pos.top - handle.clientHeight) + 'px')
                                    .attr('title', jS.msg.dragToFreezeRow),
                                trs = $(pane.table.tBody.children);

                            jS.controls.bar.helper[jS.i] = jS.obj.barHelper().add(handle);
                            jS.controls.bar.y.handleFreeze[jS.i] = $handle;

                            jS.draggable($handle, {
                                axis:'y',
                                start:function () {
                                    jS.setBusy(true);

                                    highlighter = $(document.createElement('div'))
                                        .appendTo(pane)
                                        .css('position', 'absolute')
                                        .addClass('ui-state-highlight ' + jS.cl.barHelper)
                                        .width(handle.clientWidth)
                                        .fadeTo(0,0.33);
                                },
                                drag:function() {
                                    var target = jS.nearest($handle, trs).prev();
                                    if (target.length && target.position) {
                                        highlighter.height(target.position().top + target.height());
                                    }
                                },
                                stop:function (e, ui) {
                                    highlighter.remove();
                                    jS.setBusy(false);
                                    jS.setDirty(true);
                                    var target = jS.nearest($handle, trs);
                                    jS.obj.barHelper().remove();
                                    scrolledArea.end.row = actionUI.frozenAt.row = math.max(jS.getTdLocation(target.children(0)).row - 1, 0);
                                    jS.autoFillerHide();
                                    pane.actionUI.scrollStart('y', jS.sheetSize(pane.table));
                                },
                                containment:[offset.left, offset.top, offset.left, math.min(offset.top + pane.table.clientHeight, offset.top + pane.clientHeight - window.scrollBarSize.height)]
                            });

                            return true;
                        },

                        /**
                         * @memberOf jS.controlFactory.barHandleFreeze
                         */
                        corner:function () {
                        }
                    },

                    /**
                     *
                     * Creates menus for contextual menus and top bar button
                     * @param bar
                     * @param menuItems
                     * @returns {jQuery|HTMLElement}
                     * @memberOf jS.controlFactory
                     */
                    menu:function (bar, menuItems) {
                        var menu, buttons = $([]);

                        switch (bar) {
                            case "top":
                                menu = $(document.createElement('div'))
                                    .addClass(jS.cl.uiMenu + ' ' + jS.cl.tdMenu);
                                jS.controls.bar.x.menu[jS.i] = menu;
                                break;
                            case "left":
                                menu = $(document.createElement('div'))
                                    .addClass(jS.cl.uiMenu + ' ' + jS.cl.tdMenu);
                                jS.controls.bar.y.menu[jS.i] = menu;
                                break;
                            case "cell":
                                menu = $(document.createElement('div'))
                                    .addClass(jS.cl.uiMenu + ' ' + jS.cl.tdMenu);
                                jS.controls.tdMenu[jS.i] = menu;
                                break;
                        }

                        jS.controls.menus[jS.i] = jS.obj.menus().add(menu);

                        menu
                            .mouseleave(function () {
                                menu.hide();
                            })
                            .bind('contextmenu', function() {return false;})
                            .appendTo($body)
                            .hide()
                            .disableSelectionSpecial();

                        for (var msg in menuItems) {
                            if (menuItems[msg]) {
                                if ($.isFunction(menuItems[msg])) {
                                    buttons.pushStack(
                                        $(document.createElement('div'))
                                            .text(msg)
                                            .data('msg', msg)
                                            .click(function () {
                                                menuItems[$(this).data('msg')].call(this, jS);
                                                menu.hide();
                                                return false;
                                            })
                                            .appendTo(menu)
                                            .hover(function() {
                                                buttons.removeClass('ui-state-highlight');
                                                $(this).addClass('ui-state-highlight');
                                            }, function() {
                                                $(this).removeClass('ui-state-highlight');
                                            })
                                    );

                                } else if (menuItems[msg] == 'line') {
                                    $(document.createElement('hr')).appendTo(menu);
                                }
                            }
                        }

                        return menu;
                    },

                    /**
                     * Creates items within menus using jQuery.sheet.instance.msg
                     * @memberOf jS.controlFactory
                     * @namespace
                     */
                    barMenu:{

                        /**
                         * @param {Object} e jQuery event
                         * @param {Number} i column
                         * @param {jQuery|HTMLElement} target
                         * @returns {*}
                         * @memberOf jS.controlFactory.barMenu
                         */
                        top:function (e, i, target) {
                            if (jS.isBusy()) {
                                return false;
                            }
                            var menu = jS.obj.barMenuTop().hide();

                            if (!menu.length) {
                                menu = jS.controlFactory.menu('top', s.contextmenuTop);
                            }

                            jS.obj.menus().hide();

                            if (!target) {
                                menu
                                    .css('left', (e.pageX - 5) + 'px')
                                    .css('top', (e.pageY - 5) + 'px')
                                    .show();
                                return menu;
                            }

                            var barMenuParentTop = jS.obj.barMenuParentTop().hide();

                            if (!barMenuParentTop.length) {

                                barMenuParentTop = $(document.createElement('div'))
                                    .addClass(jS.cl.uiBarMenuTop + ' ' + jS.cl.barHelper + ' ' + jS.cl.barTopMenuButton)
                                    .append(
                                        $(document.createElement('span'))
                                            .addClass('ui-icon ui-icon-triangle-1-s')
                                    )
                                    .mousedown(function (e) {
                                        barMenuParentTop.parent()
                                            .mousedown()
                                            .mouseup();

                                        var offset = barMenuParentTop.offset();

                                        menu
                                            .css('left', (e.pageX - 5) + 'px')
                                            .css('top', (e.pageY - 5) + 'px')
                                            .show();
                                    })
                                    .blur(function () {
                                        if (menu) menu.hide();
                                    })
                                    .bind('destroy', function () {
                                        barMenuParentTop.remove();
                                        jS.controls.bar.x.menuParent[jS.i] = null;
                                    });

                                jS.controls.bar.x.menuParent[jS.i] = barMenuParentTop;
                            }

                            barMenuParentTop
                                .prependTo(target)
                                .show();
                        },

                        /**
                         *
                         * @param e
                         * @param i
                         * @returns {Boolean}
                         * @memberOf jS.controlFactory.barMenu
                         */
                        left:function (e, i) {
                            if (jS.isBusy()) {
                                return false;
                            }
                            jS.obj.barMenuLeft().hide();

                            if (i) {
                                jS.obj.barHandleFreezeLeft().remove();
                            }
                            var menu;

                            menu = jS.obj.barMenuLeft();

                            if (!menu.length) {
                                menu = jS.controlFactory.menu('left', s.contextmenuLeft);
                            }

                            jS.obj.menus().hide();

                            menu
                                .css('left', (e.pageX - 5) + 'px')
                                .css('top', (e.pageY - 5) + 'px')
                                .show();

                            return true;
                        },

                        /**
                         * @memberOf jS.controlFactory.barMenu
                         */
                        corner:function () {
                        }
                    },


                    /**
                     * Creates contextual menus for cells (a right click menu)
                     * @param {Object} e jQuery event
                     * @returns {Boolean}
                     * @memberOf jS.controlFactory
                     */
                    tdMenu:function (e) {
                        if (jS.isBusy()) {
                            return false;
                        }
                        jS.obj.tdMenu().hide();

                        var menu = jS.obj.tdMenu();

                        if (!menu.length) {
                            menu = jS.controlFactory.menu('cell', s.contextmenuCell);
                        }

                        jS.obj.menus().hide();

                        menu
                            .css('left', (e.pageX - 5) + 'px')
                            .css('top', (e.pageY - 5) + 'px')
                            .show();

                        return true;
                    },


                    /**
                     * Creates the control/container for everything above the spreadsheet, removes them if they already exist
                     * @memberOf jS.controlFactory
                     */
                    header:function () {
                        jS.obj.header().remove();
                        jS.obj.sheetAdder().remove();
                        jS.obj.tabContainer().remove();

                        var header = document.createElement('div'),
                            firstRow = document.createElement('table'),
                            firstRowTr = document.createElement('tr'),
                            secondRow,
                            secondRowTr,
                            title = document.createElement('td'),
                            label,
                            menuLeft,
                            menuRight,
                            formula,
                            formulaParent;

                        header.appendChild(firstRow);
                        firstRow.appendChild(firstRowTr);
                        header.className = jS.cl.header + ' ' + jS.cl.uiControl;

                        jS.controls.header = $(header);

                        if (s.title) {
                            if ($.isFunction(s.title)) {
                                s.title = jS.title(jS, I);
                            }

                            title.className = jS.cl.title;
                            jS.controls.title = $(title).html(s.title)
                        } else {
                            $(title).hide();
                        }
                        firstRowTr.appendChild(title);

                        //Sheet Menu Control
                        function makeMenu(menu) {
                            if ($.isFunction(menu)) {
                                menu = $(menu.call(jS));
                            } else {
                                menu = $(menu);
                            }

                            if (menu.is('ul')) {
                                menu
                                    .find("ul").hide()
                                    .addClass(jS.cl.uiMenuUl);

                                menu
                                    .find("li")
                                    .addClass(jS.cl.uiMenuLi)
                                    .hover(function () {
                                        $(this).find('ul:first')
                                            .hide()
                                            .show();
                                    }, function () {
                                        $(this).find('ul:first')
                                            .hide();
                                    });
                            }
                            return menu;
                        }

                        if (jS.isSheetEditable()) {
                            if (s.menuLeft) {
                                menuLeft = document.createElement('td');
                                menuLeft.className = jS.cl.menu + ' ' + jS.cl.menuFixed;
                                firstRowTr.insertBefore(menuLeft, title);

                                jS.controls.menuLeft[jS.i] = $(menuLeft)
                                    .append(makeMenu(s.menuLeft));

                                jS.controls.menuLeft[jS.i].find('img').load(function () {
                                    jS.sheetSyncSize();
                                });
                            }

                            if (s.menuRight) {
                                menuRight = document.createElement('td');
                                menuRight.className = jS.cl.menu + ' ' + jS.cl.menuFixed;
                                firstRowTr.appendChild(menuRight);

                                jS.controls.menuRight[jS.i] = $(menuRight)
                                    .append(makeMenu(s.menuRight));

                                jS.controls.menuRight[jS.i].find('img').load(function () {
                                    jS.sheetSyncSize();
                                });
                            }

                            label = document.createElement('td');
                            label.className = jS.cl.label;
                            jS.controls.label = $(label);

                            //Edit box menu
                            formula = document.createElement('textarea');
                            formula.className = jS.cl.formula;
                            formula.onkeydown = jS.evt.formula.keydown;
                            formula.onkeyup = function () {
                                jS.obj.inPlaceEdit().value = this.value;
                            };
                            formula.onchange = function () {
                                jS.obj.inPlaceEdit().value = this.value;
                            };
                            formula.onpaste = jS.evt.pasteOverCells;
                            formula.onfocus = function () {
                                jS.setNav(false);
                            };
                            formula.onfocusout = function () {
                                jS.setNav(true);
                            };
                            formula.onblur = function () {
                                jS.setNav(true);
                            };
                            jS.controls.formula = $(formula);

                            // resizable formula area - a bit hard to grab the handle but is there!
                            var formulaResize = document.createElement('span');
                            formulaResize.appendChild(formula);

                            secondRow = document.createElement('table');
                            secondRowTr = document.createElement('tr');
                            secondRow.appendChild(secondRowTr);

                            header.appendChild(secondRow);


                            formulaParent = document.createElement('td');
                            formulaParent.className = jS.cl.formulaParent;
                            formulaParent.appendChild(formulaResize);
                            secondRowTr.appendChild(label);
                            secondRowTr.appendChild(formulaParent);

                            jS.resizableSheet($(formulaResize), {
                                minHeight:jS.controls.formula.height(),
                                maxHeight:78,
                                handles:'s',
                                resize:function (e, ui) {
                                    jS.controls.formula.height(ui.size.height);
                                },
                                stop: function() {
                                    jS.sheetSyncSize();
                                }
                            });

                            var instance = $.sheet.instance;
                            for(var i = 0; i < instance.length; i++) {
                                (instance || {}).nav = false;
                            }

                            jS.setNav(true);

                            $(document).keydown(jS.evt.document.keydown);
                        }

                        return header;
                    },

                    /**
                     * Creates the user interface for spreadsheets
                     * @memberOf jS.controlFactory
                     */
                    ui:function () {
                        var ui = document.createElement('div');
                        ui.setAttribute('class', jS.cl.ui);
                        jS.obj.ui = ui;
                        return ui;
                    },

                    sheetAdder: function () {
                        var addSheet = document.createElement('span');
                        if (jS.isSheetEditable()) {
                            addSheet.setAttribute('class', jS.cl.sheetAdder + ' ' + jS.cl.tab + ' ' + jS.cl.uiTab + ' ui-corner-bottom');
                            addSheet.setAttribute('title', jS.msg.addSheet);
                            addSheet.innerHTML = '&nbsp;+&nbsp;';
                            addSheet.onmousedown = function () {
                                jS.addSheet();

                                return false;
                            };
                            addSheet.i = -1;
                        }
                        return jS.controls.sheetAdder = $(addSheet);
                    },

                    /**
                     * Creates the tab interface for spreadsheets
                     * @memberOf jS.controlFactory
                     */
                    tabContainer:function () {
                        var tabContainer = document.createElement('span'),
                            startPosition;
                        tabContainer.setAttribute('class', jS.cl.tabContainer);

                        tabContainer.onmousedown = function (e) {
                            e = e || window.event;

                            var i = (e.target || e.srcElement).i;
                            if (i >= 0) {
                                jS.trigger('sheetSwitch', [i]);
                            }
                            return false;
                        };
                        tabContainer.ondblclick = function (e) {
                            e = e || window.event;
                            var i = (e.target || e.srcElement).i;
                            if (i >= 0) {
                                jS.trigger('sheetRename', [i]);
                            }
                            return false;
                        };


                        if (jS.isSheetEditable() && $.fn.sortable) {
                            return jS.controls.tabContainer = $(tabContainer).sortable({
                                placeholder:'ui-state-highlight',
                                axis:'x',
                                forceHelperSize:true,
                                forcePlaceholderSize:true,
                                opacity:0.6,
                                start:function (e, ui) {
                                    startPosition = ui.item.index();
                                    jS.trigger('sheetTabSortStart', [e, ui]);
                                },
                                update:function (e, ui) {
                                    jS.trigger('sheetTabSortUpdate', [e, ui, startPosition]);
                                }
                            });
                        }

                        return jS.controls.tabContainer = $(tabContainer);
                    },

                    /**
                     * Creates the spreadsheet user interface
                     * @param {HTMLElement} ui raw user interface
                     * @param {HTMLElement} table raw table
                     * @param {Number} i the new count for spreadsheets in this instance
                     * @memberOf jS.controlFactory
                     */
                    sheetUI:function (ui, table, i) {
                        jS.i = i;

                        jS.tuneTableForSheetUse(table);

                        jS.readOnly[i] = (table.className || '').match(/\breadonly\b/i) != null;

                        var enclosure = jS.controlFactory.enclosure(table),
                            pane = enclosure.pane,
                            $pane = $(pane),
                            paneContextmenuEvent = function (e) {
                                e = e || window.event;
                                if (jS.isBusy()) {
                                    return false;
                                }
                                if (jS.isBar(e.target)) {
                                    var entity = e.target.entity,
                                        i = jS.getBarIndex[entity](e.target);

                                    if (i < 0) return false;

                                    if (jS.evt.barInteraction.first == jS.evt.barInteraction.last) {
                                        jS.controlFactory.barMenu[entity](e, i);
                                    }
                                } else {
                                    jS.controlFactory.tdMenu(e);
                                }
                                return false;
                            };

                        ui.appendChild(enclosure);

                        jS.controlFactory.barTop(table);
                        jS.controlFactory.barLeft(table);

                        pane.appendChild(table);

                        if (jS.isSheetEditable()) {
                            jS.controlFactory.autoFiller(pane);
                        }

                        if (jS.isSheetEditable()) {
                            var formula = jS.obj.formula(),
                                mouseDownEntity = "";

                            $pane.mousedown(function (e) {
                                jS.setNav(true);
                                if (jS.isBusy()) {
                                    return false;
                                }

                                if (jS.isCell(e.target)) {
                                    if (e.button == 2) {
                                        paneContextmenuEvent.call(this, e);
                                        jS.evt.cellOnMouseDown(e);
                                        return true;
                                    }
                                    jS.evt.cellOnMouseDown(e);
                                    return false;
                                }

                                if (jS.isBar(e.target)) { //possibly a bar
                                    if (e.button == 2) {
                                        paneContextmenuEvent.call(this, e);
                                    }
                                    mouseDownEntity = e.target.entity;
                                    jS.evt.barInteraction.select(e.target);
                                    return false;
                                }

                                return true;
                            });

                            pane.onmouseup = function() {
                                mouseDownEntity = "";
                            };

                            pane.onmouseover = function (e) {
                                e = e || window.event;

                                var target = e.target || e.srcElement;

                                //This manages bar resize, bar menu, and bar selection
                                if (jS.isBusy()) {
                                    return false;
                                }

                                if (!jS.isBar(target)) {
                                    return false;
                                }
                                var bar = $(target),
                                    entity = target.entity,
                                    i = jS.getBarIndex[entity](target);

                                if (i < 0) {
                                    return false;
                                }

                                if (jS.evt.barInteraction.selecting && entity == mouseDownEntity) {
                                    jS.evt.barInteraction.last = i;

                                    jS.cellSetActiveBar(entity, jS.evt.barInteraction.first, jS.evt.barInteraction.last);
                                } else {
                                    jS.resizeBar[entity](bar, i, pane, table);

                                    if (jS.isSheetEditable()) {
                                        jS.controlFactory.barHandleFreeze[entity](pane);

                                        if (entity == "top") {
                                            jS.controlFactory.barMenu[entity](e, i, bar);
                                        }
                                    }
                                }

                                return true;
                            };

                            pane.ondblclick = jS.evt.cellOnDblClick;

                            $pane
                                .bind('contextmenu', paneContextmenuEvent)
                                .disableSelectionSpecial()
                                .bind('cellEdit', jS.evt.cellEdit);
                        }

                        jS.themeRoller.start(table);

                        jS.createSpreadsheet(table, i);

                        jS.checkMinSize(table);

                        jS.controlFactory.tab();

                        var settings = jS.s,
                            hiddenRows = settings.hiddenRows,
                            hiddenColumns = settings.hiddenColumns;

                        if (!hiddenRows.length || !hiddenColumns.length) {
                            hiddenRows = table.attributes['data-hiddenrows'] || {value:''};
                            hiddenColumns = table.attributes['data-hiddencolumns'] || {value:''};

                            if (hiddenRows.value.length > 0)
                                hiddenRows = arrHelpers.toNumbers(hiddenRows.value.split(','));

                            if (hiddenColumns.value.length > 0)
                                hiddenColumns = arrHelpers.toNumbers(hiddenColumns.value.split(','));
                        }

                        enclosure.actionUI.hide(hiddenRows, hiddenColumns, jS.rows(table), jS.cols(table));

                        jS.setChanged(true);
                    },

                    /**
                     * The viewing console for spreadsheet
                     * @returns {*|jQuery|HTMLElement}
                     * @memberOf jS.controlFactory
                     */
                    enclosure:function (table) {
                        var pane = document.createElement('div'),
                            enclosure = document.createElement('div'),
                            $enclosure = $(enclosure),
                            actionUI = new Sheet.ActionUI(enclosure, pane, table, jS.cl.scroll, jS.s.frozenAt[jS.i], $.sheet.max),
                            scrollUI = actionUI.scrollUI;

                        table.size = function() { return jS.sheetSize(table); };

                        scrollUI.onscroll = function() {
                            if (!jS.isBusy()) {
                                var xUpdated = actionUI.scrollTo({axis:'x', pixel:this.scrollLeft}),
                                    yUpdated = actionUI.scrollTo({axis:'y', pixel:this.scrollTop});

                                if (xUpdated || yUpdated) {

                                    if (xUpdated) {
                                        setTimeout(function(){
                                            jS.calcVisibleCol(actionUI);
                                        }, 0);
                                    }

                                    if (yUpdated) {
                                        setTimeout(function() {
                                            jS.calcVisibleRow(actionUI);
                                        }, 0);
                                    }

                                    jS.obj.barHelper().remove();
                                    jS.autoFillerGoToTd();
                                    if (pane.inPlaceEdit) {
                                        pane.inPlaceEdit.goToTd();
                                    }
                                }
                            }
                        };
                        scrollUI.onmousedown = function() {
                            jS.obj.barHelper().remove();
                        };

                        enclosure.actionUI = pane.actionUI = actionUI;

                        enclosure.scrollUI = pane.scrollUI = scrollUI;
                        enclosure.appendChild(enclosure.scrollUI);

                        pane.setAttribute('class', jS.cl.pane + ' ' + jS.cl.uiPane);
                        enclosure.appendChild(pane);
                        enclosure.setAttribute('class', jS.cl.enclosure);

                        enclosure.pane = pane;
                        enclosure.table = table;

                        pane.table = table;
                        pane.enclosure = enclosure;
                        pane.$enclosure = $enclosure;

                        table.pane = pane;
                        table.enclosure = enclosure;
                        table.$enclosure = $enclosure;

                        jS.controls.pane[jS.i] = pane;
                        jS.controls.panes = jS.obj.panes().add(pane);
                        jS.controls.enclosure[jS.i] = $enclosure;
                        jS.controls.enclosures = jS.obj.enclosures().add(enclosure);

                        return enclosure;
                    },

                    /**
                     * Adds a tab for navigation to a spreadsheet
                     * @returns {Node|jQuery}
                     * @memberOf jS.controlFactory
                     */
                    tab:function () {
                        var tab = document.createElement('span'),
                            $tab = jS.controls.tab[jS.i] = $(tab).appendTo(jS.obj.tabContainer());

                        tab.setAttribute('class', jS.cl.tab);
                        jS.sheetTab(true, function(sheetTitle) {
                            tab.innerHTML = sheetTitle;
                        });

                        tab.i = jS.i;
                        tab.setAttribute('class',jS.cl.uiTab + ' ui-corner-bottom');
                        jS.controls.tabs = jS.obj.tabs().add($tab);

                        return tab;
                    },

                    /**
                     * Creates a textarea for a user to put a value in that floats on top of the current selected cell
                     * @param {jQuery|HTMLElement} td the td to be edited
                     * @param {Boolean} selected selects the text in the inline editor
                     * @memberOf jS.controlFactory
                     */
                    inPlaceEdit:function (td, selected) {
                        td = td || jS.obj.tdActive();

                        if (!td.length) {
                            td = $(jS.rowTds(null, 1)[1]);
                            jS.cellEdit(td);
                        }

                        if (!td.length) return;

                        (jS.obj.inPlaceEdit().destroy || emptyFN)();

                        var formula = jS.obj.formula(),
                            val = formula.val(),
                            textarea,
                            $textarea,
                            pane = jS.obj.pane();

                        if (!td[0].isHighlighted) return; //If the td is a dud, we do not want a textarea

                        textarea = document.createElement('textarea');
                        $textarea = $(textarea);
                        pane.inPlaceEdit = textarea;
                        textarea.i = jS.i;
                        textarea.className = jS.cl.inPlaceEdit + ' ' + jS.cl.uiInPlaceEdit;
                        textarea.td = td[0];
                        textarea.goToTd = function() {
                            this.offset = td.position();
                            if (!this.offset.left && !this.offset.right) {
                                $(textarea).hide();
                            } else {
                                this.setAttribute('style',
                                    'left:' + (this.offset.left - 1) + 'px;' +
                                        'top:' + (this.offset.top - 1) + 'px;' +
                                        'width:' + this.td.clientWidth + 'px;' +
                                        'height:' + this.td.clientHeight + 'px;' +
                                        'min-width:' + this.td.clientWidth + 'px;' +
                                        'min-height:' + this.td.clientHeight + 'px;');
                            }
                        };
                        textarea.goToTd();
                        textarea.onkeydown = jS.evt.inPlaceEdit.keydown;
                        textarea.onchange =
                            textarea.onkeyup =
                                function() { formula[0].value = textarea.value; };

                        textarea.onfocus = function () { jS.setNav(false); };

                        textarea.onblur =
                            textarea.onfocusout =
                                function () { jS.setNav(true); };

                        textarea.onpaste = jS.evt.pasteOverCells;

                        textarea.destroy = function () {
                            pane.inPlaceEdit = null;
                            jS.cellLast.isEdit = (textarea.value != val);
                            textarea.parentNode.removeChild(textarea);
                            jS.controls.inPlaceEdit[textarea.i] = false;
                        };

                        pane.appendChild(textarea);

                        textarea.onfocus();

                        jS.controls.inPlaceEdit[jS.i] = textarea;


                        //This is a little trick to get the cursor to the end of the textarea
                        $textarea
                            .focus()
                            .val('')
                            .val(formula[0].value);

                        if (selected) {
                            $textarea.select();
                        }

                        //Make the textarea resizable automatically
                        if ($.fn.elastic) {
                            $(textarea).elastic();
                        }
                    },

                    /**
                     * Created the autoFiller object
                     * @returns {*|jQuery|null}
                     * @memberOf jS.controlFactory
                     * @param {HTMLElement} pane
                     */
                    autoFiller:function (pane) {
                        if (!s.autoFiller) return false;

                        var autoFiller = document.createElement('div'),
                            handle = document.createElement('div'),
                            cover = document.createElement('div');

                        autoFiller.i = jS.i;

                        autoFiller.className = jS.cl.autoFiller + ' ' + jS.cl.uiAutoFiller;
                        handle.className = jS.cl.autoFillerHandle;
                        cover.className = jS.cl.autoFillerCover;

                        autoFiller.onmousedown = function () {
                            var td = jS.obj.tdActive();
                            if (td) {
                                var loc = jS.getTdLocation(td);
                                jS.cellSetActive(td, loc, true, jS.autoFillerNotGroup, function () {
                                    var highlighted = jS.highlighted(),
                                        hLoc = jS.getTdLocation(highlighted.last());
                                    jS.fillUpOrDown(hLoc.row < loc.row || hLoc.col < loc.col);
                                    jS.autoFillerGoToTd(td);
                                    jS.autoFillerNotGroup = false;
                                });
                            }

                            return false;
                        };

                        pane.autoFiller = jS.controls.autoFiller[jS.i] = $(autoFiller);
                        pane.appendChild(autoFiller);
                        return true;
                    }
                },

                /**
                 * Allows grouping of cells
                 * @memberOf jS
                 */
                autoFillerNotGroup:true,


                /**
                 * Sends tab delimited string into cells, usually a paste from external spreadsheet application
                 * @param [oldVal] what formula should be when this is done working with all the values
                 * @returns {Boolean}
                 * @memberOf jS
                 */
                updateCellsAfterPasteToFormula:function (oldVal) {
                    var newValCount = 0,
                        formula = jS.obj.formula(),
                        last = new Date();

                    oldVal = oldVal || formula.val();

                    var loc = {row:jS.cellLast.row, col:jS.cellLast.col},
                        val = formula.val(), //once ctrl+v is hit formula now has the data we need
                        firstValue = val;

                    //at this point we need to check if there is even a cell selected, if not, we can't save the information, so clear formula editor
                    if (loc.row == 0 && loc.col == 0) {
                        return false;
                    }

                    var row = tsv.parse(val);

                    //Single cell value
                    if (!$.isArray(row)) {
                        formula.val(row);
                        jS.fillUpOrDown(false, row);
                        return true;
                    }

                    //values that need put into multi cells
                    for (var i = 0; i < row.length; i++) {
                        jS.cellLast.isEdit = true;
                        var col = row[i];
                        for (var j = 0; j < col.length; j++) {
                            newValCount++;
                            var td = jS.getTd(jS.i, i + loc.row, j + loc.col);

                            td.row = loc.row;
                            td.col = loc.col;

                            if (td.length) {
                                if (!jS.spreadsheets[jS.i] || !jS.spreadsheets[jS.i][i + loc.row] || !jS.spreadsheets[jS.i][i + loc.row][j + loc.col]) continue;
                                var cell = jS.spreadsheets[jS.i][i + loc.row][j + loc.col];
                                if (cell) {
                                    s.parent.one('sheetPreCalculation', function () {
                                        if ((col[j] + '').charAt(0) == '=') { //we need to know if it's a formula here
                                            cell.formula = col[j].substring(1);
                                            cell.value = '';
                                            td.data('formula', col[j]);
                                        } else {
                                            cell.formula = '';
                                            cell.value = col[j];
                                            td.removeData('formula');
                                        }
                                    });
                                    jS.calcDependencies.call(cell);

                                    if (i == 0 && j == 0) { //we have to finish the current edit
                                        firstValue = col[j];
                                    }
                                }
                            }
                        }
                    }

                    if (val != firstValue) {
                        formula.val(firstValue);
                    }

                    jS.fillUpOrDown(false, firstValue);

                    jS.evt.cellEditDone(true);

                    return true;
                },

                /**
                 * Event handlers for instance
                 * @memberOf jS
                 * @namespace
                 */
                evt:{

                    inPlaceEdit:{
                        /**
                         *
                         * @param {Object} e jQuery event
                         * @returns {*}
                         * @memberOf jS.evt.inPlaceEdit
                         */
                        enter:function (e) {
                            if (e.shiftKey) {
                                return true;
                            }
                            return jS.evt.cellSetActiveFromKeyCode(e, true);
                        },

                        /**
                         *
                         * @param {Object} e jQuery event
                         * @returns {*}
                         * @memberOf jS.evt.inPlaceEdit
                         */
                        tab:function (e) {
                            if (e.shiftKey) {
                                return true;
                            }
                            return jS.evt.cellSetActiveFromKeyCode(e, true);
                        },
                        /**
                         * Edits the textarea that appears over cells for in place edit
                         * @param {Object} e jQuery event
                         * @returns {*}
                         * @memberOf jS.evt.inPlaceEdit
                         */
                        keydown:function (e) {
                            e = e || window.event;
                            jS.trigger('sheetFormulaKeydown', [true]);

                            switch (e.keyCode) {
                                case key.ENTER:
                                    return jS.evt.inPlaceEdit.enter(e);
                                    break;
                                case key.TAB:
                                    return jS.evt.inPlaceEdit.tab(e);
                                    break;
                                case key.ESCAPE:
                                    jS.evt.cellEditAbandon();
                                    return false;
                                    break;
                            }
                        }
                    },

                    formula:{
                        /**
                         *
                         * @param {Object} e jQuery event
                         * @returns {*}
                         * @memberOf jS.evt.formula
                         */
                        keydown:function (e) {
                            e = e || window.event;
                            if (jS.readOnly[jS.i]) return false;
                            if (jS.cellLast.row < 0 || jS.cellLast.col < 0) return false;

                            jS.trigger('sheetFormulaKeydown', [false]);

                            switch (e.keyCode) {
                                case key.C:
                                    if (e.ctrlKey) {
                                        return jS.evt.document.copy(e);
                                    }
                                case key.X:
                                    if (e.ctrlKey) {
                                        return jS.evt.document.cut(e);
                                    }
                                case key.Y:
                                    if (e.ctrlKey) {
                                        jS.evt.document.redo(e);
                                        return false;
                                    }
                                    break;
                                case key.Z:
                                    if (e.ctrlKey) {
                                        jS.evt.document.undo(e);
                                        return false;
                                    }
                                    break;
                                case key.ESCAPE:
                                    jS.evt.cellEditAbandon();
                                    return true;
                                    break;
                                case key.ENTER:
                                    jS.evt.cellSetActiveFromKeyCode(e, true);
                                    return false;
                                    break;
                            }

                            jS.cellLast.isEdit = true;
                        },

                        /**
                         * Helper for events
                         * @param {Boolean} ifTrue
                         * @param e {Object} jQuery event
                         * @returns {*}
                         * @memberOf jS.evt.keydownHandler
                         */
                        If:function (ifTrue, e) {
                            if (ifTrue) {
                                jS.obj.tdActive().dblclick();
                                return true;
                            }
                            return false;
                        }
                    },

                    /**
                     * Key down handlers
                     * @memberOf jS.evt
                     */
                    document:{
                        /**
                         *
                         * @param {Object} e jQuery event
                         * @returns {*}
                         * @memberOf jS.evt.document
                         */
                        enter:function (e) {
                            if (!jS.cellLast.isEdit && !e.ctrlKey) {
                                jS.obj.tdActive().dblclick();
                            }
                            return false;
                        },

                        /**
                         *
                         * @param {Object} e jQuery event
                         * @returns {*}
                         * @memberOf jS.evt.document
                         */
                        tab:function (e) {
                            jS.evt.cellSetActiveFromKeyCode(e);
                        },

                        /**
                         *
                         * @param {Object} e jQuery event
                         * @returns {*}
                         * @memberOf jS.evt.document
                         */
                        findCell:function (e) {
                            if (e.ctrlKey) {
                                jS.cellFind();
                                return false;
                            }
                            return true;
                        },

                        /**
                         *
                         * @param {Object} e jQuery event
                         * @returns {*}
                         * @memberOf jS.evt.document
                         */
                        redo:function (e) {
                            if (e.ctrlKey && !jS.cellLast.isEdit) {
                                jS.undo.manager.redo();
                                return false;
                            }
                            return true;
                        },

                        /**
                         *
                         * @param {Object} e jQuery event
                         * @returns {*}
                         * @memberOf jS.evt.document
                         */
                        undo:function (e) {
                            if (e.ctrlKey && !jS.cellLast.isEdit) {
                                jS.undo.manager.undo();
                                return false;
                            }
                            return true;
                        },

                        /**
                         * Copy what is in the highlighted tds
                         * @param [e]
                         * @param [clearValue]
                         * @returns {Boolean}
                         */
                        copy:function (e, clearValue) {
                            var tds = jS.highlighted(true),
                                formula = jS.obj.formula(),
                                oldValue = formula.val(),
                                cellsTsv = jS.toTsv(tds, clearValue);

                            formula
                                .val(cellsTsv)
                                .focus()
                                .select();

                            $document
                                .one('keyup', function () {
                                    if (clearValue) {
                                        formula.val('');
                                    } else {
                                        formula.val(oldValue);
                                    }
                                });

                            return true;
                        },

                        cut:function (e) {
                            return this.copy(e, true);
                        },

                        /**
                         * Manages the page up and down buttons
                         * @param {Boolean} [reverse] Go up or down
                         * @returns {Boolean}
                         * @memberOf jS.evt.document
                         */
                        pageUpDown:function (reverse) {
                            var size = jS.sheetSize(),
                                pane = jS.obj.pane(),
                                paneHeight = pane.clientHeight,
                                prevRowsHeights = 0,
                                thisRowHeight = 0,
                                td,
                                i;

                            if (reverse) { //go up
                                for (i = jS.cellLast.row; i > 0 && prevRowsHeights < paneHeight; i--) {
                                    td = jS.getTd(jS.i, i, 1);
                                    if (!td.data('hidden') && td.is(':hidden')) td.show();
                                    prevRowsHeights += td.parent().height();
                                }
                            } else { //go down
                                for (i = jS.cellLast.row; i < size.rows && prevRowsHeights < paneHeight; i++) {
                                    td = jS.getTd(jS.i, i, 1);
                                    prevRowsHeights += td.parent().height();
                                }
                            }
                            jS.cellEdit(td);

                            return false;
                        },

                        /**
                         *
                         * @param {Object} e jQuery event
                         * @returns {*}
                         * @memberOf jS.evt.document
                         */
                        keydown:function (e) {
                            e = e || window.event;
                            if (jS.readOnly[jS.i]) return false;
                            if (jS.cellLast.row < 0 || jS.cellLast.col < 0) return false;
                            var td = jS.cellLast.td;

                            if (jS.nav) {
                                //noinspection FallthroughInSwitchStatementJS
                                switch (e.keyCode) {
                                    case key.DELETE:
                                        jS.toTsv(null, true);
                                        jS.obj.formula().val('');
                                        break;
                                    case key.TAB:
                                        jS.evt.document.tab(e);
                                        break;
                                    case key.ENTER:
                                        jS.evt.cellSetActiveFromKeyCode(e);
                                        break;
                                    case key.LEFT:
                                    case key.UP:
                                    case key.RIGHT:
                                    case key.DOWN:
                                        (e.shiftKey ? jS.evt.cellSetHighlightFromKeyCode(e) : jS.evt.cellSetActiveFromKeyCode(e));
                                        break;
                                    case key.PAGE_UP:
                                        jS.evt.document.pageUpDown(true);
                                        break;
                                    case key.PAGE_DOWN:
                                        jS.evt.document.pageUpDown();
                                        break;
                                    case key.HOME:
                                    case key.END:
                                        jS.evt.cellSetActiveFromKeyCode(e);
                                        break;
                                    case key.V:
                                        if (e.ctrlKey) {
                                            return jS.evt.formula.If(!jS.evt.pasteOverCells(e), e);
                                        } else {
                                            td.trigger('cellEdit');
                                            return true;
                                        }
                                        break;
                                    case key.Y:
                                        if (e.ctrlKey) {
                                            jS.evt.document.redo(e);
                                            return false;
                                        } else {
                                            td.trigger('cellEdit');
                                            return true;
                                        }
                                        break;
                                    case key.Z:
                                        if (e.ctrlKey) {
                                            jS.evt.document.undo(e);
                                            return false;
                                        } else {
                                            td.trigger('cellEdit');
                                            return true;
                                        }
                                        break;
                                    case key.ESCAPE:
                                        jS.evt.cellEditAbandon();
                                        break;
                                    case key.F:
                                        if (e.ctrlKey) {
                                            return jS.evt.formula.If(jS.evt.document.findCell(e), e);
                                        } else {
                                            td.trigger('cellEdit');
                                            return true;
                                        }
                                        break;
                                    case key.CAPS_LOCK:
                                    case key.SHIFT:
                                    case key.ALT:
                                        break;
                                    case key.CONTROL: //we need to filter these to keep cell state
                                        jS.obj.formula().focus().select();
                                        return true;
                                        break;
                                    default:
                                        td.trigger('cellEdit');
                                        return true;
                                        break;
                                }
                                return false;
                            }
                        }
                    },

                    /**
                     * Used for pasting from other spreadsheets
                     * @param {Object} e jQuery event
                     * @returns {Boolean}
                     * @memberOf jS.evt
                     */
                    pasteOverCells:function (e) {
                        e = e || window.event;
                        if (e.ctrlKey || e.type == "paste") {
                            var fnAfter = function () {
                                jS.updateCellsAfterPasteToFormula();
                            };

                            var $doc = $document
                                .one('keyup', function () {
                                    fnAfter();
                                    fnAfter = function () {
                                    };
                                    $doc.mouseup();
                                })
                                .one('mouseup', function () {
                                    fnAfter();
                                    fnAfter = function () {
                                    };
                                    $doc.keyup();
                                });

                            jS.setDirty(true);
                            jS.setChanged(true);
                            return true;
                        }

                        return false;
                    },

                    /**
                     * Updates a cell after edit afterward event "sheetCellEdited" is called w/ params (td, row, col, spreadsheetIndex, sheetIndex)
                     * @param {Boolean} [force] if set to true forces a calculation of the selected sheet
                     * @memberOf jS.evt
                     */
                    cellEditDone:function (force) {
                        (jS.obj.inPlaceEdit().destroy || emptyFN)();
                        if (jS.cellLast.isEdit || force) {
                            var formula = jS.obj.formula(),
                                td = jS.obj.tdActive();

                            if (jS.isFormulaEditable(td)) {
                                //Lets ensure that the cell being edited is actually active
                                if (td && jS.cellLast.row > 0 && jS.cellLast.col > 0) {

                                    //This should return either a val from textbox or formula, but if fails it tries once more from formula.
                                    var v = formula.val(),
                                        cell = td[0].jSCell;

                                    if (!cell.edited) {
                                        cell.edited = true;
                                        jS.controls.cellsEdited[jS.i] = jS.obj.cellsEdited().add(cell);
                                    }

                                    s.parent.one('sheetPreCalculation', function () {
                                        if (v.charAt(0) == '=' && jS.formulaParser) {
                                            td.data('formula', v);
                                            cell.value = v;
                                            cell.formula = v;
                                        } else {
                                            td.removeData('formula');
                                            cell.value = v;
                                            cell.formula = '';
                                        }
                                    });
                                    jS.calcDependencies.call(cell);

                                    //formula.focus().select();
                                    jS.cellLast.isEdit = false;

                                    //perform final function call
                                    jS.trigger('sheetCellEdited', [cell]);
                                }
                            }
                        }
                    },

                    /**
                     * Abandons a cell edit
                     * @param {Boolean} [skipCalc] if set to true will skip sheet calculation;
                     * @memberOf jS.evt
                     */
                    cellEditAbandon:function (skipCalc) {
                        (jS.obj.inPlaceEdit().destroy || emptyFN)();
                        jS.themeRoller.bar.clearActive();
                        jS.themeRoller.cell.clearHighlighted(null, true);

                        if (!skipCalc) {
                            jS.calc();
                        }

                        jS.cellLast.td = $([]);
                        jS.cellLast.row = 0;
                        jS.cellLast.col = 0;
                        jS.rowLast = 0;
                        jS.colLast = 0;
                        jS.highlightedLast.start = {row:0,col:0};
                        jS.highlightedLast.end = {row:0,col:0};

                        jS.labelUpdate('', true);
                        jS.obj.formula()
                            .val('')
                            .blur();

                        jS.autoFillerHide();

                        return false;
                    },


                    /**
                     * Highlights a cell from a key code
                     * @param {Object} e jQuery event
                     * @returns {Boolean}
                     * @memberOf jS.evt
                     */
                    cellSetHighlightFromKeyCode:function (e) {
                        var grid = jS.highlightedLastOrdered(),
                            size = jS.sheetSize(),
                            td = jS.obj.tdActive(),
                            loc = jS.getTdLocation(td),
                            start = grid.start,
                            end = grid.end;

                        switch (e.keyCode) {
                            case key.UP:
                                if (start.row < loc.row) {
                                    start.row--;
                                    start.row = math.max(start.row, 1);
                                    break;
                                }

                                end.row--;
                                end.row = math.max(end.row, 1);

                                break;
                            case key.DOWN:
                                //just beginning the highlight
                                if (start.row === start.end) {
                                    start.row++;
                                    start.row = math.min(start.row, size.rows);
                                    break;
                                }

                                //if the highlight is above the active cell, then we have selected up and need to move down
                                if (start.row < loc.row) {
                                    start.row++;
                                    start.row = math.max(start.row, 1);
                                    break;
                                }

                                //otherwise we increment the row, and limit it to the size of the total grid
                                end.row++;
                                end.row = math.min(end.row, size.rows);

                                break;
                            case key.LEFT:
                                if (start.col < loc.col) {
                                    start.col--;
                                    start.col = math.max(start.col, 1);
                                    break;
                                }

                                end.col--;
                                end.col = math.max(end.col, 1);

                                break;
                            case key.RIGHT:
                                if (start.col < loc.col) {
                                    start.col++;
                                    start.col = math.min(start.col, size.cols);
                                    break;
                                }

                                end.col++;
                                end.col = math.min(end.col, size.cols);

                                break;
                        }

                        //highlight the cells
                        jS.highlightedLast.start = start;
                        jS.highlightedLast.end = end;

                        jS.cycleCellArea(function (o) {
                            jS.themeRoller.cell.setHighlighted(o.td);
                        }, start, end);

                        return false;
                    },


                    /**
                     * Activates a cell from a key code
                     * @param {Object} e jQuery event
                     * @param {Boolean} [skipMove]
                     * @returns {Boolean}
                     * @memberOf jS.evt
                     */
                    cellSetActiveFromKeyCode:function (e, skipMove) {
                        var loc = {
                                row: jS.cellLast.row,
                                col: jS.cellLast.col
                            },
                            overrideIsEdit = false,
                            highlighted,
                            doNotClearHighlighted = false;

                        switch (e.keyCode) {
                            case key.UP:
                                loc.row--;
                                break;
                            case key.DOWN:
                                loc.row++;
                                break;
                            case key.LEFT:
                                loc.col--;
                                break;
                            case key.RIGHT:
                                loc.col++;
                                break;
                            case key.ENTER:
                                loc = jS.evt.incrementAndStayInGrid(jS.highlightedLastOrdered(), loc, 'row', 'col', e.shiftKey);
                                overrideIsEdit = true;
                                highlighted = jS.highlighted();
                                if (highlighted.length > 1) {
                                    doNotClearHighlighted = true;
                                } else {
                                    if (!skipMove) {
                                        loc.row += (e.shiftKey ? -1 : 1);
                                    }
                                    if (s.autoAddCells && loc.row > jS.sheetSize().rows) {
                                        jS.controlFactory.addRow();
                                    }
                                }
                                break;
                            case key.TAB:
                                loc = jS.evt.incrementAndStayInGrid(jS.highlightedLastOrdered(), loc, 'col', 'row', e.shiftKey);
                                overrideIsEdit = true;
                                highlighted = jS.highlighted();
                                if (highlighted.length > 1) {
                                    doNotClearHighlighted = true;
                                } else {
                                    if (!skipMove) {
                                        loc.col += (e.shiftKey ? -1 : 1);
                                    }
                                    if (s.autoAddCells && loc.col > jS.sheetSize().cols) {
                                        jS.controlFactory.addColumn();
                                    }
                                }
                                break;
                            case key.HOME:
                                loc.col = 1;
                                break;
                            case key.END:
                                loc.col = jS.obj.tdActive().parent().children('td').length - 1;
                                break;
                        }

                        //we check here and make sure all values are above 0, so that we get a selected cell
                        loc.col = loc.col || 1;
                        loc.row = loc.row || 1;

                        //to get the td could possibly make keystrokes slow, we prevent it here so the user doesn't even know we are listening ;)
                        if (!jS.cellLast.isEdit || overrideIsEdit) {
                            //get the td that we want to go to
                            var td = jS.getTd(jS.i, loc.row, loc.col);

                            //if the td exists, lets go to it
                            if (td) {
                                jS.cellEdit(td, null, doNotClearHighlighted);
                                return false;
                            }
                        }
                        //default, can be overridden above
                        return true;
                    },

                    /**
                     * Calculate position for either horizontal movement or vertical movement within a grid, both forward and reverse
                     * @param {Object} grid
                     * @param {Object} loc
                     * @param {String} locA
                     * @param {String} locB
                     * @param {Boolean} reverse
                     * @returns {Object} loc
                     * @memberOf jS.evt
                     */
                    incrementAndStayInGrid: function (grid, loc, locA, locB, reverse) {
                        if (reverse) {
                            loc[locA]--;
                            if (loc[locA] < grid.start[locA]) {
                                loc[locA] = grid.end[locA];
                                loc[locB]--;
                            }
                            if (loc[locB] < grid.start[locB]) {
                                loc[locB] = grid.end[locB];
                            }
                        } else {
                            loc[locA]++;
                            if (loc[locA] > grid.end[locA]) {
                                loc[locA] = grid.start[locA];
                                loc[locB]++;
                            }
                            if (loc[locB] > grid.end[locB]) {
                                loc[locB] = grid.start[locB];
                            }
                        }

                        return loc;
                    },

                    /**
                     * Cell on mouse down
                     * @param {Object} e jQuery event
                     * @memberOf jS.evt
                     */
                    cellOnMouseDown:function (e) {


                        jS.obj.formula().blur();
                        if (e.shiftKey) {
                            jS.getTdRange(e, jS.obj.formula().val());
                        } else {
                            jS.cellEdit($(e.target), true);
                        }
                    },

                    /**
                     * Cell on double click
                     * @param {Object} e jQuery event
                     * @memberOf jS.evt
                     */
                    cellOnDblClick:function (e) {
                        if (jS.isBusy()) {
                            return false;
                        }

                        jS.controlFactory.inPlaceEdit();

                        return true;
                    },

                    cellEdit: function(e) {
                        if (jS.isBusy()) {
                            return false;
                        }

                        jS.controlFactory.inPlaceEdit(null, true);

                        return true;
                    },

                    /**
                     * Handles bar events, used for highlighting and activating
                     * @memberOf jS.evt
                     * @namespace
                     */
                    barInteraction:{

                        /**
                         * The first bar that received the event (mousedown)
                         * @memberOf jS.evt.barInteraction
                         */
                        first:0,

                        /**
                         * The last bar that received the event (mousedown)
                         * @memberOf jS.evt.barInteraction
                         */
                        last:0,

                        /**
                         * Tracks if we are in select mode
                         * @memberOf jS.evt.barInteraction
                         */
                        selecting:false,

                        /**
                         * Manages the bar selection
                         * @param {Object} o target
                         * @returns {*}
                         * @memberOf jS.evt.barInteraction
                         */
                        select:function (o) {
                            if (!o) return;
                            if (!o.type == 'bar') return;
                            var entity = o.entity, //returns "top" or "left";
                                i = jS.getBarIndex[entity](o);

                            if (i < 0) return false;

                            jS[entity + 'Last'] = i; //keep track of last column for inserting new columns
                            jS.evt.barInteraction.last = jS.evt.barInteraction.first = i;

                            jS.cellSetActiveBar(entity, jS.evt.barInteraction.first, jS.evt.barInteraction.last);
                            jS.evt.barInteraction.first = jS.evt.barInteraction.last = jS[entity + 'Last'] = i;

                            jS.evt.barInteraction.selecting = true;
                            $document
                                .one('mouseup', function () {
                                    jS.evt.barInteraction.selecting = false;
                                });

                            return false;
                        }
                    }
                },

                /**
                 *
                 * @param {Number} start index to start from
                 * @memberOf jS
                 */
                refreshColumnLabels:function (start) {
                    start = start || 0;

                    jS.obj.barMenuParentTop().trigger('destroy');

                    var tds = jS.controls.bar.x.td[jS.i];

                    if (!tds) return;

                    for (var i = start; i < tds.length; i++) {
                        if (i) {//greater than 1 (corner)
                            tds[i].text(jSE.columnLabelString(tds[i][0].cellIndex));
                        }
                    }
                },


                /**
                 *
                 * @param {Number} start index to start from
                 * @param {Number} [end] index to end at
                 * @memberOf jS
                 */
                refreshRowLabels:function (start, end) {
                    start = start || 0;

                    var tds = jS.controls.bar.y.td[jS.i];

                    if (!tds) return;

                    end = end || tds.length;

                    for (var i = start; i < end; i++) {
                        if (i) {
                            $(tds[i]).text(tds[i][0].parentNode.rowIndex);
                        }
                    }
                },

                /**
                 * Detects if an object is a td within a spreadsheet's table
                 * @param {jQuery|HTMLElement} o target
                 * @returns {Boolean}
                 * @memberOf jS
                 */
                isCell:function (o) {
                    if (o && o.tagName && o.tagName == 'TD' && o.type && o.type == 'cell') {
                        return true;
                    }
                    return false;
                },

                /**
                 * Detects if an object is a bar td within a spreadsheet's table
                 * @param {jQuery|HTMLElement} o target
                 * @returns {Boolean}
                 * @memberOf jS
                 */
                isBar:function (o) {
                    if (o && o.tagName && o.tagName == 'TD' && o.type && o.type == 'bar') {
                        return true;
                    }
                    return false;
                },

                /**
                 * Tracks read state of spreadsheet
                 * @memberOf jS
                 */
                readOnly:[],

                /**
                 * Detects read state of a spreadsheet
                 * @param {Number} [i] index of spreadsheet within instance
                 * @returns {Boolean}
                 * @memberOf jS
                 */
                isSheetEditable:function (i) {
                    i = i || jS.i;
                    return (
                        s.editable == true && !jS.readOnly[i]
                        );
                },

                /**
                 * Detects read state of formula of an object
                 * @param {jQuery|HTMLElement} o target
                 * @returns {Boolean}
                 * @memberOf jS
                 */
                isFormulaEditable:function (o) {
                    if (s.lockFormulas) {
                        if (o.data('formula') !== u) {
                            return false;
                        }
                    }
                    return true;
                },

                /**
                 * Toggles full screen mode
                 * @memberOf jS
                 */
                toggleFullScreen:function () {
                    if (!jS) return;
                    jS.evt.cellEditDone();
                    var fullScreen = jS.obj.fullScreen(),
                        pane = jS.obj.pane();
                    if (fullScreen.is(':visible')) {
                        $window.unbind('jSResize');
                        $body.removeClass('bodyNoScroll');
                        s.parent = fullScreen[0].origParent;

                        s.parent.prepend(fullScreen.children());

                        fullScreen.remove();

                        jS.sheetSyncSize();
                        pane.resizeScroll();
                        jS.trigger('sheetFullScreen', [false]);
                    } else { //here we make a full screen
                        $body.addClass('bodyNoScroll');

                        var parent = $(s.parent),
                            fullScreen = document.createElement('div'),
                            events = $._data(s.parent[0], 'events');

                        fullScreen.className = jS.cl.fullScreen + ' ' + jS.cl.uiFullScreen + ' ' + jS.cl.parent;

                        fullScreen.origParent = parent;
                        s.parent = jS.controls.fullScreen = $(fullScreen)
                            .append(parent.children())
                            .appendTo($body);

                        $window
                            .bind('resize', function() {
                                $window.trigger('jSResize');
                            })
                            .bind('jSResize', function () {
                                this.w = $window.width();
                                this.h = $window.height();
                                s.parent
                                    .width(this.w)
                                    .height(this.h);

                                jS.sheetSyncSize();
                                pane.resizeScroll();
                            })
                            .trigger('jSResize');


                        parent.trigger('sheetFullScreen', [true]);

                        for (var event in events) {
                            for (var i = 0; i < events[event].length; i++) {
                                s.parent.bind(event, events[event][i].handler);
                            }
                        }
                    }
                },

                /**
                 * Assists in rename of spreadsheet
                 * @memberOf jS
                 */
                renameSheet:function (i) {
                    if (n(i)) {
                        return false;
                    }

                    if (i > -1) {
                        jS.sheetTab();
                    }

                    return true;
                },

                /**
                 * Switches spreadsheet
                 * @param {Number} i index of spreadsheet within instance
                 * @memberOf jS
                 */
                switchSheet:function (i) {
                    if (n(i)) {
                        return false;
                    }

                    if (i == -1) {
                        jS.addSheet();
                    } else if (i != jS.i) {
                        jS.setActiveSheet(i);
                        jS.calc(i);
                    }

                    return true;
                },

                /**
                 * Makes table object usable by sheet
                 * @param {jQuery|HTMLElement} table
                 * @returns {*}
                 * @memberOf jS
                 */
                tuneTableForSheetUse:function (table) {
                    var $table = $(table);
                    jS.controls.table[jS.i] = $table
                        .addClass(jS.cl.table)
                        .addClass(jS.cl.uiTable)
                        .attr('id', jS.id + jS.i)
                        .attr('border', '1px')
                        .attr('cellpadding', '0')
                        .attr('cellspacing', '0');

                    jS.formatTable(table);
                    jS.sheetDecorateRemove(false, $table);

                    jS.controls.tables = jS.obj.tables().add(table);

                    //override frozenAt settings with table's data-frozenatrow and data-frozenatcol
                    var frozenAtRow = $table.attr('data-frozenatrow') * 1,
                        frozenAtCol = $table.attr('data-frozenatcol') * 1;

                    if (!jS.s.frozenAt[jS.i]) jS.s.frozenAt[jS.i] = {row:0, col:0};
                    if (frozenAtRow) jS.s.frozenAt[jS.i].row = frozenAtRow;
                    if (frozenAtCol) jS.s.frozenAt[jS.i].col = frozenAtCol;
                },

                /**
                 * Cycles through all the td's and turns table into spreadsheet
                 * @param {HTMLElement} table spreadsheet
                 * @param {Number} i spreadsheet index within instance
                 * @memberOf jS
                 */
                createSpreadsheet:function (table, i) {
                    table.spreadsheet = jS.spreadsheets[i] = []; //reset the sheet's spreadsheet

                    var rows = jS.rows(table),
                        row = rows.length - 1,
                        col;
                    if (row < 0) return;
                    do {
                        col = rows[row].children.length - 1;
                        if (col < 0) return;
                        do {
                            var td = rows[row].children[col];
                            if (row > 0 && col > 0) {
                                jS.createCell(i, row, col);
                            } else {
                                if (col == 0 && row > 0) { //barleft
                                    td.type = 'bar';
                                    td.entity = 'left';
                                    td.innerHTML = row;
                                    td.className = jS.cl.barLeft + ' ' + jS.cl.barLeft + '_' + jS.i + ' ' + jS.cl.uiBar;
                                    td.setAttribute('style', 'height:' + td.nextSibling.style.height); //This element is generated and needs to track the height of the item just before it
                                }

                                if (row == 0 && col > 0) { //bartop
                                    td.type = 'bar';
                                    td.entity = 'top';
                                    td.innerHTML = jSE.columnLabelString(col);
                                    td.className = jS.cl.barTop + ' ' + jS.cl.barTop + '_' + jS.i + ' ' + jS.cl.uiBar;
                                }

                                if (row == 0 && col == 0) { //corner
                                    td.type = 'bar';
                                    td.entity = 'corner';
                                    td.className = jS.cl.uiBar + ' ' + ' ' + jS.cl.barCorner;
                                    jS.controls.bar.corner[jS.i] = td;
                                }
                            }
                        } while (col--);
                    } while (row--);
                },

                toggleHideRow: function(i) {
                    i = i || jS.rowLast;
                    if (!i) return;

                    var row = jS.rows()[i],
                        actionUI = jS.obj.pane().actionUI;

                    actionUI.toggleHideRow(row, i);
                    jS.autoFillerGoToTd();
                },
                toggleHideColumn: function(i) {
                    i = i || jS.colLast;
                    if (!i) return;

                    var col = jS.cols()[i],
                        actionUI = jS.obj.pane().actionUI;

                    actionUI.toggleHideColumn(col, i);
                    jS.autoFillerGoToTd();
                },
                rowShowAll: function() {
                    jS.obj.pane().actionUI.rowShowAll();
                },
                columnShowAll: function() {
                    jS.obj.pane().actionUI.columnShowAll();
                },
                /**
                 * Merges cells together
                 * @param {Object} [tds]
                 * @memberOf jS
                 */
                merge:function (tds) {
                    tds = tds || jS.highlighted();
                    if (!tds.length) {
                        return;
                    }
                    var
                        cellsValue = [],
                        firstTd = tds[0],
                        lastTd = tds[tds.length - 1],
                        firstLocRaw = jS.getTdLocation(firstTd),
                        lastLocRaw = jS.getTdLocation(lastTd),
                        firstLoc = {},
                        lastLoc = {},
                        colSpan = 0,
                        rowSpan = 0,
                        last = new Date(),
                        i = tds.length - 1,
                        cell,
                        _td,
                        td,
                        $td,
                        loc;

                    if (firstLocRaw.row) {
                        jS.setDirty(true);
                        jS.setChanged(true);

                        if (firstLocRaw.row < lastLocRaw.row) {
                            firstLoc.row = firstLocRaw.row;
                            lastLoc.row = lastLocRaw.row;
                            td = firstTd;
                        } else {
                            firstLoc.row = lastLocRaw.row;
                            lastLoc.row = firstLocRaw.row;
                            td = lastTd;
                        }

                        if (td.getAttribute('rowSpan') || td.getAttribute('colSpan')) {
                            return false;
                        }

                        $td = $(td);

                        if (firstLocRaw.col < lastLocRaw.col) {
                            firstLoc.col = firstLocRaw.col;
                            lastLoc.col = lastLocRaw.col;
                        } else {
                            firstLoc.col = lastLocRaw.col;
                            lastLoc.col = firstLocRaw.col;
                        }

                        rowSpan = (lastLoc.row - firstLoc.row) + 1;
                        colSpan = (lastLoc.col - firstLoc.col) + 1;

                        loc = jS.getTdLocation(td);

                        do {
                            _td = tds[i];
                            cell = _td.jSCell;
                            if (cell.formula || cell.value) {
                                cellsValue.unshift(cell.formula ? "(" + cell.formula.substring(1) + ")" : cell.value);
                            }
                            s.parent.one('sheetPreCalculation', function () {
                                if (_td.cellIndex != loc.col || _td.parentNode.rowIndex != loc.row) {
                                    cell.formula = null;
                                    cell.value = '';
                                    cell.html = '';
                                    cell.defer = td.jSCell;
                                    cell.calcLast = last;

                                    _td.removeAttribute('data-formula');
                                    _td.removeAttribute('data-celltype')
                                    _td.innerHTML = '';
                                    _td.style.display = 'none';
                                    _td.colSpan = colSpan - (_td.cellIndex - td.cellIndex);
                                    _td.rowSpan = rowSpan - (_td.parentNode.rowIndex - td.parentNode.rowIndex);
                                }
                            });

                            jS.calcDependencies.call(cell);
                        } while(i--);

                        td.jSCell.value = cellsValue.join(' ');
                        td.jSCell.formula = (td.jSCell.formula ? cellsValue.join(' ') : '');
                        td.jSCell.calcLast = last;

                        td.style.display = '';
                        td.setAttribute('rowSpan', rowSpan);
                        td.setAttribute('colSpan', colSpan);

                        jS.calcDependencies.call(td.jSCell);
                        jS.evt.cellEditDone();
                        jS.autoFillerGoToTd($td);
                        jS.cellSetActive($td, loc);
                    }
                    return true;
                },

                /**
                 * Unmerges cells together
                 * @param {jQuery} [$td]
                 * @memberOf jS
                 */
                unmerge:function ($td) {
                    $td = $td || jS.highlighted();
                    if (!$td) {
                        return;
                    }
                    var td = $td[0],
                        loc = jS.getTdLocation(td),
                        last = new Date(),
                        row = math.max(td.getAttribute('rowSpan') * 1, 1) - 1,
                        col = math.max(td.getAttribute('colSpan') * 1, 1) - 1,
                        i = row + loc.row,
                        j,
                        _td,
                        tds = [];

                    if (row == 0 && col == 0) {
                        return false;
                    }

                    do {
                        j = loc.col + col;
                        do {
                            _td = jS.getTd(jS.i, i, j)[0];
                            _td.style.display = '';
                            _td.removeAttribute('colSpan');
                            _td.removeAttribute('rowSpan');
                            _td.jSCell.defer = null;

                            jS.calcDependencies.call(_td.jSCell, last);

                            tds.push(_td);
                        } while (j-- > loc.col);
                    } while (i-- > loc.row);

                    jS.evt.cellEditDone();
                    jS.autoFillerGoToTd($td);
                    jS.cellSetActive($td, loc);
                    jS.themeRoller.cell.setHighlighted($(tds));
                    return true;
                },

                /**
                 * Fills values down or up to highlighted cells from active cell;
                 * @param {Boolean} [goUp] default is down, when set to true value are filled from bottom, up;
                 * @param {String} [v] the value to set cells to, if not set, formula will be used;
                 * @param {Object} [cells]
                 * @memberOf jS
                 * @returns {Boolean}
                 */
                fillUpOrDown:function (goUp, v, cells) {
                    jS.evt.cellEditDone();
                    cells = cells || jS.highlighted(true);

                    if (cells.length < 1) {
                        return false;
                    }

                    var activeTd = jS.obj.tdActive(),
                        last = new Date();

                    if (cells.length < 1) {
                        return false;
                    }

                    var startLoc = jS.getTdLocation(cells[0].td),
                        endLoc = jS.getTdLocation(cells[cells.length - 1].td),
                        relativeLoc = jS.getTdLocation(activeTd),
                        offset = {
                            row:0,
                            col:0
                        },
                        newV = v || activeTd[0].jSCell.value,
                        isNumber = false,
                        i = cells.length - 1,
                        fn = function() {};

                    v = v || activeTd[0].jSCell.value;

                    if (i >= 0) {
                        if (v.charAt && v.charAt(0) == '=') {
                            if (i >= 0) {
                                do {
                                    if (!goUp) {
                                        offset.row = relativeLoc.row - endLoc.row;
                                        offset.col = relativeLoc.col - endLoc.col;
                                    } else {
                                        offset.row = relativeLoc.row - startLoc.row;
                                        offset.col = relativeLoc.col - startLoc.col;
                                    }

                                    newV = jS.reparseFormula(v, offset);

                                    s.parent.one('sheetPreCalculation', function () {
                                        cells[i].formula = newV;
                                        cells[i].value = '';
                                        cells[i].td.data('formula', newV);
                                    });

                                    jS.calcDependencies.call(cells[i], last);
                                } while (i--);
                                return true;
                            }
                        } else {
                            if ((isNumber = !n(newV)) || newV.length > 0) {
                                if (isNumber && newV != '') {
                                    newV *= 1;

                                    if (goUp) {
                                        newV -= cells.length - 1;
                                    }
                                    fn = function() {
                                        newV++;
                                    };
                                }
                            }

                            do {
                                s.parent.one('sheetPreCalculation', function () {
                                    cells[i].formula = '';
                                    cells[i].value = newV + '';
                                    cells[i].td.removeData('formula');
                                });

                                jS.calcDependencies.call(cells[i], last);

                                fn();
                            } while (i--);
                            return true;
                        }
                    }

                    return false;
                },

                /**
                 * Turns values into a tab separated value
                 * @param {Object} [cells]
                 * @param {String} [clearValue]
                 * @param {Object} [fnEach]
                 * @memberOf jS
                 * @returns {String}
                 */
                toTsv:function (cells, clearValue, fnEach) {
                    cells = cells || jS.highlighted(true);
                    if (cells.type) {
                        cells = [cells];
                    }
                    fnEach = fnEach || function (loc, cell) {
                        if (clearValue) {
                            s.parent.one('sheetPreCalculation', function () {
                                cell.formula = '';
                                cell.value = '';
                            });
                            jS.calcDependencies.call(cell, last);
                        }
                    };
                    var cellValues = [],
                        firstLoc,
                        lastLoc,
                        minLoc = {},
                        last = new Date(),
                        i = cells.length - 1,
                        row,
                        col;

                    if (i >= 0) {
                        firstLoc = jS.getTdLocation(cells[0].td);
                        lastLoc = jS.getTdLocation(cells[cells.length - 1].td);
                        minLoc.row = math.min(firstLoc.row, lastLoc.row);
                        minLoc.col = math.min(firstLoc.col, lastLoc.col);
                        do {
                            var loc = jS.getTdLocation(cells[i].td),
                                value = (cells[i].formula ? '=' + cells[i].formula : cells[i].value);

                            row = math.abs(loc.row - minLoc.row);
                            col = math.abs(loc.col - minLoc.col);

                            if (!cellValues[row]) cellValues[row] = [];

                            if ((value += '').match(/\n/)) {
                                value = '"' + value + '"';
                            }

                            cellValues[row][col] = (value || '');

                            fnEach.call(cells[i].td, loc, cells[i]);
                        } while (i-- > 0);


                        i = cellValues.length - 1;
                        do {
                            cellValues[i] = cellValues[i].join('\t');
                        } while (i-- > 0);

                        return cellValues.join('\n');
                    }
                    return '';
                },

                /**
                 * Makes cell formulas increment within a range
                 * @param {Object} loc expects keys row,col
                 * @param {Object} offset expects keys row,col, offsets increment
                 * @param {Boolean} [isBefore] inserted before location
                 * @param {Boolean} [wasDeleted]
                 * @memberOf jS
                 */
                offsetFormulas:function (loc, offset, isBefore, wasDeleted) {
                    var size = jS.sheetSize(),
                    //effected range is the entire spreadsheet
                        affectedRange = {
                            first:{
                                row:1,
                                col:1
                            },
                            last:{
                                row:size.rows,
                                col:size.cols
                            }
                        },
                        last = new Date(),
                        cellStack = [];



                    jS.cycleCells(function () {
                        var cell = this;
                        if (this.formula && typeof this.formula == 'string' && jS.isFormulaEditable(this.td)) {
                            this.formula = jS.reparseFormula(this.formula, offset, loc, isBefore, wasDeleted);

                            this.td.data('formula', '=' + this.formula);
                        }

                        cellStack.push(function() {
                            jS.calcDependencies.call(cell, last, true);
                        });

                    }, affectedRange.first, affectedRange.last);

                    while (cellStack.length) {
                        cellStack.pop()();
                    }

                    jS.evt.cellEditDone();
                },

                /**
                 * Re-parses a formula
                 * @param formula
                 * @param {Object} offset expects keys row,col, offsets increment
                 * @param {Object} [loc]
                 * @param {Boolean} [isBefore]
                 * @param {Boolean} [wasDeleted]
                 * @returns {String}
                 * @memberOf jS
                 */
                reparseFormula:function (formula, offset, loc, isBefore, wasDeleted) {
                    return formula.replace(jSE.regEx.cell, function (ignored, col, row, pos) {
                        if (col == "SHEET") return ignored;
                        offset = offset || {loc: 0, row: 0};

                        var oldLoc = {
                                row:row * 1,
                                col:jSE.columnLabelIndex(col)
                            },
                            moveCol,
                            moveRow,
                            override = {
                                row: row,
                                col: col,
                                use: false
                            };

                        if (loc) {
                            if (wasDeleted) {
                                if (isBefore) {
                                    if (oldLoc.col && oldLoc.col == loc.col - 1) {
                                        override.col = '#REF!';
                                        override.use = true;
                                    }
                                    if (oldLoc.row && oldLoc.row == loc.row - 1) {
                                        override.row = '#REF!';
                                        override.use = true;
                                    }

                                    if (oldLoc.col >= loc.col) {
                                        moveCol = true;
                                    }
                                    if (oldLoc.row >= loc.row) {
                                        moveRow = true;
                                    }
                                } else {
                                    if (loc.col && oldLoc.col == loc.col) {
                                        override.col = '#REF!';
                                        override.use = true;
                                    }
                                    if (loc.row && oldLoc.row == loc.row) {
                                        override.row = '#REF!';
                                        override.use = true;
                                    }

                                    if (loc.col && oldLoc.col > loc.col) {
                                        moveCol = true;
                                    }
                                    if (loc.row && oldLoc.row > loc.row) {
                                        moveRow = true;
                                    }
                                }

                                if (override.use) {
                                    return override.col + override.row;
                                }

                                if (moveCol) {
                                    oldLoc.col += offset.col;
                                    return jS.makeFormula(oldLoc);
                                }

                                if (moveRow) {
                                    oldLoc.row += offset.row;
                                    return jS.makeFormula(oldLoc);
                                }
                            } else {
                                if (isBefore) {
                                    if (loc.col && oldLoc.col >= loc.col) {
                                        moveCol = true;
                                    }
                                    if (loc.row && oldLoc.row >= loc.row) {
                                        moveRow = true;
                                    }
                                } else {
                                    if (loc.col && oldLoc.col > loc.col) {
                                        moveCol = true;
                                    }
                                    if (loc.row && oldLoc.row > loc.row) {
                                        moveRow = true;
                                    }
                                }

                                if (moveCol) {
                                    oldLoc.col += offset.col;
                                    return jS.makeFormula(oldLoc);
                                }

                                if (moveRow) {
                                    oldLoc.row += offset.row;
                                    return jS.makeFormula(oldLoc);
                                }
                            }
                        } else {
                            return jS.makeFormula(oldLoc, offset);
                        }

                        return ignored;
                    });
                },


                /**
                 * Reconstructs a formula
                 * @param {Object} loc expects keys row,col
                 * @param {Object} [offset] expects keys row,col
                 * @returns {String}
                 * @memberOf jS
                 */
                makeFormula:function (loc, offset) {
                    offset = $.extend({row:0, col:0}, offset);

                    //set offsets
                    loc.col += offset.col;
                    loc.row += offset.row;

                    //0 based now
                    if (loc.col < 0) loc.col = 0;
                    if (loc.row < 0) loc.row = 0;

                    return jSE.parseCellName(loc.col, loc.row);
                },

                /**
                 * Cycles through a certain group of td objects in a spreadsheet table and applies a function to them
                 * @param {Function} fn the function to apply to a cell
                 * @param {Object} [firstLoc] expects keys row,col, the cell to start at
                 * @param {Object} [lastLoc] expects keys row,col, the cell to end at
                 * @param {Number} [i] spreadsheet index within instance
                 * @memberOf jS
                 */
                cycleCells:function (fn, firstLoc, lastLoc, i) {
                    i = i || jS.i;
                    firstLoc = firstLoc || {row:1, col:1};

                    if (!lastLoc) {
                        var size = jS.sheetSize();
                        lastLoc = {row:size.rows, col:size.cols};
                    }

                    var row = lastLoc.row, col;
                    if (row < firstLoc.row) return;
                    do {
                        col = lastLoc.col;
                        do {
                            fn.call(jS.spreadsheets[i][row][col], i, row, col);
                        } while (col-- > firstLoc.col);
                    } while (row-- > firstLoc.row);
                },

                /**
                 * Cycles through all td objects in a spreadsheet table and applies a function to them
                 * @param fn
                 * @memberOf jS
                 */
                cycleCellsAll:function (fn) {
                    var jSI = jS.i, i,size,endLoc;
                    for (i = 0; i <= jS.sheetCount; i++) {
                        jS.i = i;
                        size = jS.sheetSize();
                        endLoc = {row:size.rows, col:size.cols};
                        jS.cycleCells(fn, {row:0, col:0}, endLoc, i);
                    }
                    jS.i = jSI;
                },

                /**
                 * Cycles through a certain group of td objects in a spreadsheet table and applies a function to them, firstLoc can be bigger then lastLoc, this is more dynamic
                 * @param {Function} fn the function to apply to a cell
                 * @param {Object} firstLoc expects keys row,col, the cell to start at
                 * @param {Object} lastLoc expects keys row,col, the cell to end at
                 * @param {Boolean} [ordered] is what you are sending to this method already sorted?
                 * @param {Boolean} [increment] use increment rather than decrement, which is a bit slower
                 * @memberOf jS
                 */
                cycleCellArea:function (fn, firstLoc, lastLoc, ordered, increment) {
                    var grid = {start:{}, end: {}},
                        rowIndex,
                        colIndex,
                        row,
                        cell,
                        i = jS.i,
                        o = {cell: [], td: []},
                        sheet = jS.spreadsheets[i],
                        arrayMethod = (increment ? 'unshift' : 'push');

                    if (ordered) {
                        grid.start = firstLoc;
                        grid.end = lastLoc;
                    } else {
                        grid.start.row = math.max(math.min(firstLoc.row, lastLoc.row), 1);
                        grid.start.col = math.max(math.min(firstLoc.col, lastLoc.col), 1);
                        grid.end.row = math.max(firstLoc.row, lastLoc.row, 1);
                        grid.end.col = math.max(firstLoc.col, lastLoc.col, 1);
                    }

                    rowIndex = grid.end.row;

                    do {
                        colIndex = grid.end.col;
                        row = sheet[rowIndex] || null;
                        do {
                            if (row) {
                                cell = row[colIndex] || null;
                                if (cell) {
                                    o.cell[arrayMethod](cell);
                                    o.td[arrayMethod](cell.td[0]);
                                }
                            }
                        } while (colIndex-- > grid.start.col);
                    } while (rowIndex-- > grid.start.row);

                    if (fn) {
                        fn(o);
                    }
                },


                /**
                 * Adds tBody, colGroup, heights and widths to different parts of a spreadsheet
                 * @param {HTMLElement} table table object
                 * @memberOf jS
                 */
                formatTable:function (table) {
                    var w = s.newColumnWidth,
                        h = s.colMargin,
                        children = table.children,
                        i = children.length - 1,
                        j,
                        col,
                        tBody,
                        colGroup,
                        firstTr,
                        hasTBody,
                        hasColGroup;

                    if (i > -1) {
                        do {
                            switch (children[i].nodeName) {
                                case 'TBODY':
                                    hasTBody = true;
                                    tBody = children[i];
                                    break;
                                case 'COLGROUP':
                                    hasColGroup = true;
                                    colGroup = children[i];
                                    break;
                            }
                        } while (i--);
                    } else {
                        var child = document.createElement('tr');
                        //if there aren't any children, give it at least 1
                        child.appendChild(document.createElement('td'));
                        table.appendChild(child);
                        children = table.children;
                    }

                    if (!tBody) {
                        tBody = document.createElement('tbody');
                        do {
                            tBody.appendChild(children[0]);
                        } while (children.length);
                    }

                    if (!colGroup || colGroup.children.length < 1) {
                        colGroup = document.createElement('colgroup');

                        table.appendChild(colGroup);
                        table.appendChild(tBody);

                        firstTr = tBody.children[0];

                        for (i = 0, j = firstTr.children.length; i < j; i++) {
                            col = document.createElement('col');
                            colGroup.appendChild(col);
                            col.style.width = w + 'px';
                        }
                        for (i = 0, j = tBody.children.length; i < j; i++) {
                            tBody.children[i].style.height = h + 'px';
                        }
                    }

                    table.tBody = tBody;
                    table.colGroup = colGroup;
                    table.removeAttribute('width');
                    table.style.width = '';
                },

                /**
                 * Ensure sheet minimums have been met, if not add columns and rows
                 * @param {jQuery|HTMLElement} o table object
                 * @memberOf jS
                 */
                checkMinSize:function (o) {
                    var size = jS.sheetSize(o),
                        addRows = s.minSize.rows || 0,
                        addCols = s.minSize.cols || 0,
                        actionUI = jS.obj.pane().actionUI,
                        frozenAt = actionUI.frozenAt;

                    addRows = (frozenAt.row > addRows ? frozenAt.row + 1 : addRows);
                    addCols = (frozenAt.col > addCols ? frozenAt.col + 1 : addCols);

                    if (size.cols < addCols) {
                        jS.controlFactory.addColumnMulti(null, addCols - size.cols);
                    }

                    if (size.rows < addRows) {
                        jS.controlFactory.addRowMulti(null, addRows - size.rows);
                    }
                },

                /**
                 * jQuery ui Themeroller integration
                 * @memberOf jS
                 * @namespace
                 */
                themeRoller:{

                    /**
                     * Starts themeroller integration
                     * @param {jQuery|HTMLElement} sheet spreadsheet table
                     * @memberOf jS.themeRoller
                     */
                    start:function (sheet) {
                        jS.obj.header().addClass(jS.cl.uiControl);
                        jS.obj.label().addClass(jS.cl.uiControl);
                        jS.obj.formula().addClass(jS.cl.uiControlTextBox);
                    },

                    /**
                     * Themeroller cell interactions
                     * @memberOf jS.themeRoller
                     * @namespace
                     */
                    cell:{

                        /**
                         * Highlights object
                         * @param {jQuery|HTMLElement} [obj] td object
                         * @memberOf jS.themeRoller.cell
                         */
                        setHighlighted:function (obj) {
                            obj = obj || $([]);

                            var i,
                                oldObjects = jS.highlightedLast.obj,
                                actionUI = jS.obj.pane().actionUI;

                            //_obj is the old selected items
                            if (oldObjects && oldObjects.length > 0) {
                                i = oldObjects.length - 1;
                                do {
                                    oldObjects[i].isHighlighted = false;
                                } while (i-- > 0);
                            }

                            if (obj.length > 0) {
                                i = obj.length - 1;
                                do {
                                    if (!obj[i].isHighlighted) {
                                        obj[i].isHighlighted = true;
                                        if (!obj[i].className.match(jS.cl.uiTdHighlighted)) {
                                            obj[i].className += ' ' + jS.cl.uiTdHighlighted;
                                        }
                                    }
                                } while (i-- > 0);
                            }

                            jS.themeRoller.cell.clearHighlighted.call(obj, oldObjects);

                            //Chrome has a hard time rendering table col elements when they change style, this triggers the table to be re-rendered
                            actionUI.redraw();
                        },

                        /**
                         * Detects if there is a cell highlighted
                         * @returns {Boolean}
                         * @memberOf jS.themeRoller.cell
                         */
                        isHighlighted:function () {
                            return (jS.highlightedLast.obj.length ? true : false);
                        },

                        /**
                         * Clears highlighted cells
                         * @param {Object} [obj]
                         * @param {Boolean} [force]
                         * @memberOf jS.themeRoller.cell
                         */
                        clearHighlighted:function (obj, force) {
                            if (jS.themeRoller.cell.isHighlighted()) {
                                obj = obj || jS.highlightedLast.obj;

                                if (obj && obj.length) {
                                    var i = obj.length - 1;
                                    do {
                                        if (!obj[i].isHighlighted || force) {
                                            obj[i].className = obj[i].className.replace(jS.cl.uiTdHighlighted, '');
                                            obj[i].isHighlighted = false;
                                        }
                                    } while (i-- > 0);
                                }
                            }

                            if (this.length) {
                                jS.highlightedLast.obj = this;
                            } else {
                                jS.highlightedLast.obj = $([]);
                            }
                        }
                    },

                    /**
                     * Themeroller bar interactions
                     * @memberOf jS.themeRoller
                     * @namespace
                     */
                    bar:{

                        /**
                         * Adds initial style to bar
                         * @param {jQuery|HTMLElement} o bar object
                         * @memberOf jS.themeRoller.bar
                         */
                        style:function (o) {
                            $(o).addClass(jS.cl.uiBar);
                        },

                        /**
                         * Sets a bar to be active
                         * @param {String} direction left or top
                         * @param {HTMLElement} td index of bar
                         * @memberOf jS.themeRoller.bar
                         */
                        setActive:function (direction, td) {
                            switch (direction) {
                                case 'top':
                                    jS.highlightedLast.barTop
                                        .removeClass(jS.cl.uiBarHighlight);
                                    jS.highlightedLast.barTop = $(td).addClass(jS.cl.uiBarHighlight);
                                    break;
                                case 'left':
                                    jS.highlightedLast.barLeft
                                        .removeClass(jS.cl.uiBarHighlight);
                                    jS.highlightedLast.barLeft = $(td).addClass(jS.cl.uiBarHighlight);
                                    break;
                            }
                        },

                        /**
                         * Clears bars from being active
                         * @memberOf jS.themeRoller.bar
                         */
                        clearActive:function () {
                            jS.highlightedLast.barLeft
                                .removeClass(jS.cl.uiBarHighlight);
                            jS.highlightedLast.barLeft = $([]);

                            jS.highlightedLast.barTop
                                .removeClass(jS.cl.uiBarHighlight);
                            jS.highlightedLast.barTop = $([]);
                        }
                    },

                    /**
                     * Themeroller tab interactions
                     * @memberOf jS.themeRoller
                     * @namespace
                     */
                    tab:{

                        /**
                         * Sets a tab to be active
                         * @memberOf jS.themeRoller.tab
                         */
                        setActive:function () {
                            this.clearActive();
                            jS.obj.tab().addClass(jS.cl.uiTabActive);
                        },

                        /**
                         * Clears a tab from being active
                         * @memberOf jS.themeRoller.tab
                         */
                        clearActive:function () {
                            jS.obj.tabContainer().find('span.' + jS.cl.uiTabActive)
                                .removeClass(jS.cl.uiTabActive);
                        }
                    }
                },

                /**
                 * jQuery ui resizeable integration
                 * @param {jQuery|HTMLElement} o To set resizable
                 * @param {Object} settings the settings used with jQuery ui resizable
                 * @memberOf jS
                 */
                resizable:function (o, settings) {
                    if (!o.data('resizable')) {
                        o.resizable(settings);
                    }
                },

                /**
                 * instance busy state
                 * @memberOf jS
                 */
                busy:[],


                /**
                 * Set the spreadsheet busy status
                 * @param {Boolean} busy
                 * @memberOf jS
                 */
                setBusy:function (busy) {
                    if (busy) {
                        jS.busy.push(busy);
                    } else {
                        jS.busy.pop();
                    }
                },

                /**
                 * get the spreadsheet busy status
                 * @memberOf jS
                 * @returns {Boolean}
                 */
                isBusy:function () {
                    return (jS.busy.length > 0);
                },

                /**
                 * jQuery ui draggable integration
                 * @param {jQuery|HTMLElement} o To set resizable
                 * @param {Object} settings the settings used with jQuery ui resizable
                 * @memberOf jS
                 */
                draggable:function (o, settings) {
                    if (!o.data('jSdraggable')) {
                        o
                            .data('jSdraggable', true)
                            .draggable(settings);
                    }
                },

                /**
                 * jQuery nearest integration
                 * @param o
                 * @param settings
                 * @memberOf jS
                 */
                nearest:function (o, settings) {
                    return $(o).nearest(settings);
                },

                /**
                 * Bar resizing
                 * @memberOf jS
                 * @namespace
                 */
                resizeBar:{

                    /**
                     * Provides the top bar with ability to resize
                     * @param {jQuery|HTMLElement} $bar td bar object
                     * @param {Number} i index of bar
                     * @param {jQuery|HTMLElement} pane spreadsheet pane
                     * @param {jQuery|HTMLElement} sheet spreadsheet table
                     * @memberOf jS.resizeBar
                     */
                    top:function ($bar, i, pane, sheet) {
                        jS.obj.barTopControls().remove();
                        var barController = document.createElement('div'),
                            $barController = $(barController)
                                .addClass(jS.cl.barController + ' ui-state-highlight')
                                .width($bar.width())
                                .height(0)
                                .prependTo($bar),
                            col,
                            handle;

                        jS.controls.bar.x.controls[jS.i] = jS.obj.barTopControls().add($barController);

                        jS.resizableCells($barController, {
                            handles:'e',
                            start:function (e, ui) {
                                jS.autoFillerHide();
                                jS.setBusy(true);
                                col = jS.col(sheet, i);
                                if (pane.freezeHandleTop) {
                                    pane.freezeHandleTop.remove();
                                }
                                col.removeAttribute('width');
                            },
                            resize:function (e, ui) {
                                col.style.width = ui.size.width + 'px';

                                if (pane.inPlaceEdit) {
                                    pane.inPlaceEdit.goToTd();
                                }
                            },
                            stop:function (e, ui) {
                                jS.setBusy(false);
                                pane.resizeScroll();
                                jS.followMe();
                                jS.setDirty(true);
                            },
                            minWidth: 32
                        });

                        handle = barController.children[0];
                        handle.style.height = $bar.outerHeight() + 'px';
                        handle.style.position = 'absolute';
                    },

                    /**
                     * Provides the left bar with ability to resize
                     * @param {jQuery|HTMLElement} $bar td bar object
                     * @param {Number} i index of bar
                     * @param {jQuery|HTMLElement} pane spreadsheet pane
                     * @param {jQuery|HTMLElement} sheet spreadsheet table
                     * @memberOf jS.resizeBar
                     */
                    left:function ($bar, i, pane, sheet) {
                        jS.obj.barLeftControls().remove();
                        var offset = $bar.offset(),
                            barController = document.createElement('div'),
                            $barController = $(barController)
                                .addClass(jS.cl.barController + ' ui-state-highlight')
                                .prependTo($bar)
                                .offset({
                                    top:offset.top,
                                    left:offset.left
                                }),
                            bar = $bar[0],
                            td = $bar.next()[0],
                            parent = td.parentNode,
                            child = document.createElement('div'),
                            $child = $(child)
                                .addClass('jSBarControllerChild')
                                .height($bar.height())
                                .prependTo($barController),
                            handle;

                        jS.controls.bar.y.controls[jS.i] = jS.obj.barLeftControls().add($barController);

                        jS.resizableCells($child, {
                            handles:'s',
                            start:function () {
                                jS.autoFillerHide();
                                jS.setBusy(true);
                                if (pane.freezeHandleLeft) {
                                    pane.freezeHandleLeft.remove();
                                }
                                parent.removeAttribute('height');
                                bar.removeAttribute('height');
                                td.removeAttribute('height');
                            },
                            resize:function (e, ui) {
                                barController.style.height =
                                    td.style.height =
                                        bar.style.height =
                                            parent.style.height =
                                                ui.size.height + 'px';

                                if (pane.inPlaceEdit) {
                                    pane.inPlaceEdit.goToTd();
                                }
                            },
                            stop:function (e, ui) {
                                jS.setBusy(false);
                                pane.resizeScroll();
                                jS.followMe();
                                jS.setDirty(true);
                            },
                            minHeight: 15
                        });

                        handle = child.children[0];
                        handle.style.width = $bar.outerWidth() + 'px';
                        handle.style.position = 'absolute';
                    },

                    /**
                     * Provides the corner bar, just a place holder, needed for auto events
                     * @memberOf jS.resizeBar
                     */
                    corner:function () {
                    }
                },

                /**
                 * Removes sheet decorations
                 * @param {Boolean} makeClone creates a clone rather than the actual object
                 * @param {jQuery|HTMLElement} sheets spreadsheet table object to remove decorations from
                 * @returns {jQuery|HTMLElement}
                 * @memberOf jS
                 */
                sheetDecorateRemove:function (makeClone, sheets) {
                    sheets = sheets || jS.obj.tables();
                    sheets = (makeClone ? sheets.clone() : sheets);

                    //Get rid of highlighted cells and active cells
                    sheets.find('td.' + jS.cl.uiTdActive)
                        .removeClass(jS.cl.uiTdActive);

                    sheets.find('td.' + jS.cl.uiTdHighlighted)
                        .removeClass(jS.cl.uiTdHighlighted);
                    return sheets;
                },

                /**
                 * Updates the label so that the user knows where they are currently positioned
                 * @param {String|Object} v Value to update to, if object {col, row}
                 * @param {Boolean} [setDirect]
                 * @memberOf jS
                 */
                labelUpdate:function (v, setDirect) {
                    if (!setDirect) {
                        v = jSE.parseCellName(v.col, v.row);
                        if (v) jS.obj.label().text(v);
                    } else {
                        jS.obj.label().text(v);
                    }
                },

                /**
                 * Starts td to be edited
                 * @param {jQuery|HTMLElement} td
                 * @param {Boolean} [isDrag] should be determined by if the user is dragging their mouse around setting cells
                 * @param {Boolean} [doNotClearHighlighted]
                 */
                cellEdit:function (td, isDrag, doNotClearHighlighted) {
                    if (jS.cellLast.td.length < 1) {
                        //doNotClearHighlighted = false;
                    }

                    jS.autoFillerNotGroup = true; //make autoFiller directional again.
                    //This finished up the edit of the last cell
                    jS.evt.cellEditDone();

                    if (!td.length) return;

                    var loc = jS.getTdLocation(td),
                        cell = td[0].jSCell,
                        v;

                    if (!cell) return;
                    if (cell.uneditable) return;

                    jS.trigger('sheetCellEdit', [cell]);

                    if (!td.is(jS.cellLast.td)) {
                        jS.followMe(td);
                    } else {
                        jS.autoFillerGoToTd(td);
                    }

                    //Show where we are to the user
                    jS.labelUpdate(loc);

                    if (cell.formula) {
                        v = '=' + cell.formula;
                    } else {
                        v = cell.value;
                    }

                    var formula = jS.obj.formula()
                        .val(v)
                        .blur();

                    jS.cellSetActive(td, loc, isDrag, false, null, doNotClearHighlighted);
                },

                /**
                 * sets cell active to sheet, and highlights it for the user, shouldn't be called directly, should use cellEdit
                 * @param {jQuery|HTMLElement} td
                 * @param {Object} loc {col, row}
                 * @param {Boolean} [isDrag] should be determined by if the user is dragging their mouse around setting cells
                 * @param {Boolean} [directional] makes highlighting directional, only left/right or only up/down
                 * @param {Function} [fnDone] called after the cells are set active
                 * @param {Boolean} [doNotClearHighlighted]
                 * @memberOf jS
                 */
                cellSetActive:function (td, loc, isDrag, directional, fnDone, doNotClearHighlighted) {
                    loc = loc || jS.getTdLocation(td);
                    if (loc.col != u) {
                        jS.cellLast.td = td; //save the current cell/td

                        jS.cellLast.row = jS.rowLast = loc.row;
                        jS.cellLast.col = jS.colLast = loc.col;

                        if (!doNotClearHighlighted) {
                            jS.themeRoller.cell.setHighlighted(td); //themeroll the cell and bars
                            jS.highlightedLast.start = loc;
                            jS.highlightedLast.end = loc;
                        }

                        if (!td.length) return;

                        jS.themeRoller.bar.setActive('left', td[0].barLeft);
                        jS.themeRoller.bar.setActive('top', td[0].barTop);

                        var selectModel,
                            clearHighlightedModel;

                        switch (s.cellSelectModel) {
                            case 'excel':
                            case 'gdrive':
                                selectModel = function () {};
                                clearHighlightedModel = function() {};//jS.themeRoller.cell.clearHighlighted;
                                break;
                            case 'oo':
                                selectModel = function (target) {
                                    if (jS.isCell(target)) {
                                        jS.cellEdit($(target));
                                    }
                                };
                                clearHighlightedModel = function () {};
                                break;
                        }

                        if (isDrag) {
                            var locTrack = {},
                                pane = jS.obj.pane();

                            locTrack.last = loc;//we keep track of the most recent location because we don't want tons of recursion here

                            pane.onmousemove = function (e) {
                                e = e || window.event;

                                var target = e.target || e.srcElement;

                                if (jS.isBusy()) {
                                    return false;
                                }

                                var locEnd = jS.highlightedLast.end = jS.getTdLocation(target),
                                    ok = true;

                                //bar
                                if (
                                    locEnd.col < 1
                                        || locEnd.row < 1
                                        || locEnd.col == nAN
                                        || locEnd.row == nAN
                                    ) {
                                    return false;
                                }

                                if (directional) {
                                    ok = false;
                                    if (loc.col == locEnd.col || loc.row == locEnd.row) {
                                        ok = true;
                                    }
                                }

                                if ((locTrack.last.col != locEnd.col || locTrack.last.row != locEnd.row) && ok) { //this prevents this method from firing too much
                                    //select active cell if needed
                                    selectModel(target);

                                    //highlight the cells
                                    jS.cycleCellArea(function (o) {
                                        jS.themeRoller.cell.setHighlighted(o.td);
                                    }, loc, locEnd, false, true);
                                }
                                jS.followMe($(target));
                                var mouseY = e.clientY,
                                    mouseX = e.clientX,
                                    offset = pane.$enclosure.offset(),
                                    cellLoc = jS.getTdLocation(target),
                                    up = cellLoc.row,
                                    left = cellLoc.col,
                                    move = false,
                                    previous;

                                if (n(up) || n(left)) {
                                    return false;
                                }

                                if(mouseY > offset.top){
                                    move = true;
                                    up--
                                }
                                if(mouseX > offset.left){
                                    move = true;
                                    left--
                                }
                                if(move){
                                    if (up < 1 || left < 1) {
                                        return false;
                                    }
                                    previous = jS.spreadsheets[jS.i][up][left];
                                    jS.followMe($(previous.td), true);
                                }

                                locTrack.last = locEnd;
                                return true;
                            };

                            document.onmouseup = function() {
                                pane.onmousemove = null;
                                pane.onmousemove = null;
                                pane.onmouseup = null;
                                document.onmouseup = null;

                                if (fnDone) {
                                    fnDone();
                                }
                            };
                        }
                    }
                },

                /**
                 * the most recent used column
                 * @memberOf jS
                 */
                colLast:0,

                /**
                 * the most recent used row
                 * @memberOf jS
                 */
                rowLast:0,

                /**
                 * the most recent used cell, {td, row, col, isEdit}
                 * @memberOf jS
                 * @type {Object}
                 */
                cellLast:{
                    td:$([]), //this is a dud td, so that we don't get errors
                    row:0,
                    col:0,
                    isEdit:false
                },

                /**
                 * the most recent highlighted cells {td, rowStart, colStart, rowEnd, colEnd}
                 * @memberOf jS
                 * @type {Object}
                 */
                highlightedLast:{
                    obj:$([]),
                    start: {row: 0, col: 0},
                    end: {row: 0, col: 0},
                    barLeft: $([]),
                    barTop: $([])
                },

                /**
                 * the most recent highlighted cells {td, rowStart, colStart, rowEnd, colEnd}, in order
                 * @memberOf jS
                 * @type {Object}
                 */
                highlightedLastOrdered: function() {
                    var grid = this.highlightedLast,
                        _grid = {start:{}, end:{}};

                    _grid.start.row = Math.min(grid.start.row, grid.end.row);
                    _grid.start.col = Math.min(grid.start.col, grid.end.col);
                    _grid.end.row = Math.max(grid.start.row, grid.end.row);
                    _grid.end.col = Math.max(grid.start.col, grid.end.col);

                    return _grid;
                },

                /**
                 * sets cell(s) class for styling
                 * @param {String} setClass class(es) to set cells to
                 * @param {String} [removeClass] class(es) to remove from cell if the setClass would conflict with
                 * @param {Object} [tds]
                 * @returns {Boolean}
                 * @memberOf jS
                 */
                cellStyleToggle:function (setClass, removeClass, tds) {
                    tds = tds || jS.highlighted();
                    if (tds.length < 1) {
                        return false;
                    }
                    jS.setDirty(true);
                    //Lets check to remove any style classes
                    var td,
                        $td,
                        i = tds.length - 1,
                        cells = jS.obj.cellsEdited(),
                        cellsEdited = jS.controls.cellsEdited[jS.i],
                        hasClass;

                    //TODO: use calcDependencies and sheetPreCalculation to set undo redo data

                    if (i >= 0) {
                        hasClass = tds[0].className.match(setClass); //go by first element in set
                        do {
                            td = tds[i];
                            $td = $(td);

                            if (removeClass) {//If there is a class that conflicts with this one, we remove it first
                                $td.removeClass(removeClass);
                            }

                            //Now lets add some style
                            if (hasClass) {
                                $td.removeClass(setClass);
                            } else {
                                $td.addClass(setClass);
                            }

                            if (!td.jSCell.edited) {
                                td.jSCell.edited = true;
                                cellsEdited = cells.add(td.jSCell);
                            }

                        } while (i--);

                        return true;
                    }

                    return false;
                },

                /**
                 * sets cell(s) type
                 * @param {String} [type] cell type
                 * @param {Object} [cells]
                 * @returns {Boolean}
                 * @memberOf jS
                 */
                cellTypeToggle:function(type, cells) {
                    cells = cells || jS.highlighted(true);

                    if (cells.length < 1) {
                        return;
                    }

                    var i = cells.length - 1,
                        remove = cells[i].cellType == type;

                    if (i >= 0) {
                        do {
                            if (remove) {
                                cells[i].cellType = null;
                            } else {
                                cells[i].cellType = type;
                            }
                            cells[i].calcLast = 0;
                            jS.updateCellValue.call(cells[i]);
                        } while(i--);
                    }
                },

                /**
                 * Resize fonts in a cell by 1 pixel
                 * @param {String} direction "up" or "down"
                 * @param {Object} [tds]
                 * @memberOf jS
                 * @returns {Boolean}
                 */
                fontReSize:function (direction, tds) {
                    tds = tds || jS.highlighted();
                    if (tds.length < 1) {
                        return false;
                    }

                    var resize = 0;
                    switch (direction) {
                        case 'up':
                            resize = 1;
                            break;
                        case 'down':
                            resize = -1;
                            break;
                    }

                    //Lets check to remove any style classes
                    var td,
                        $td,
                        i = tds.length - 1,
                        size,
                        cells = jS.obj.cellsEdited(),
                        cellsEdited = jS.controls.cellsEdited[jS.i];

                    //TODO: use calcDependencies and sheetPreCalculation to set undo redo data

                    if (i >= 0) {
                        do {
                            td = tds[i];
                            $td = $(td);
                            size = ($td.css("font-size") + '').replace("px", "") * 1;
                            $td.css("font-size", ((size || 10) + resize) + "px");

                            if (!td.jSCell.edited) {
                                td.jSCell.edited = true;
                                cellsEdited = cells.add(td.jSCell);
                            }
                        } while(i--);
                        return true;
                    }
                    return false;
                },

                /**
                 * Current number of cells being parsed
                 * @type {Number}
                 * @memberOf jS
                 */
                callStack:0,

                /**
                 * Ignites calculation with cell, is recursively called if cell uses value from another cell, can be sent indexes, or be called via .apply(cell)
                 * @param {Number} [sheetIndex] sheet index within instance
                 * @param {Number} [rowIndex] row index
                 * @param {Number} [colIndex] col index
                 * @returns {*} cell value after calculated
                 * @memberOf jS
                 */
                updateCellValue:function (sheetIndex, rowIndex, colIndex) {
                    var sheet, row, cell, fn;

                    sheetIndex = sheetIndex || 0;
                    rowIndex = rowIndex || -1;
                    colIndex = colIndex || -1;

                    if (rowIndex > -1) {
                        //first detect if the cell exists if not return nothing
                        if (!(sheet = jS.spreadsheets[sheetIndex])) return s.error({error:jS.msg.notFoundSheet});
                        if (!(row = sheet[rowIndex])) return s.error({error:jS.msg.notFoundRow});
                        if (!(cell = row[colIndex])) return s.error({error:jS.msg.notFoundColumn});
                    } else {
                        cell = this;
                    }

                    if (cell === undefined) {
                        throw new Error("cell doesn't exist");
                    }

                    cell.oldValue = cell.value; //we detect the last value, so that we don't have to update all cell, thus saving resources

                    if (cell.result) { //unset the last result if it is set
                        delete cell.result;
                    }

                    switch (cell.state[cell.state.length - 1]) {
                        case 'updating':
                            return s.error({error:jS.msg.loopDetected});
                        case 'updatingDependencies':
                            return (cell.valueOverride != u ? cell.valueOverride : cell.value);
                    }

                    if (cell.defer) {//merging creates a defer property, which points the cell to another location to get the other value
                        return jS.updateCellValue.call(cell.defer);
                    }

                    cell.state.push('updating');
                    cell.fnCount = 0;
                    cell.result = null;

                    if (cell.calcLast != jS.calcLast || cell.calcDependenciesLast != jS.calcDependenciesLast) {
                        cell.valueOverride = null;
                        cell.calcLast = jS.calcLast;
                        cell.calcDependenciesLast = jS.calcDependenciesLast;
                        cell.needsUpdated = true;

                        cell.calcCount++;
                        if (cell.formula) {
                            try {
                                if (cell.formula.charAt(0) == '=') {
                                    cell.formula = cell.formula.substring(1);
                                }

                                var formulaParser;
                                if (jS.callStack) { //we prevent parsers from overwriting each other
                                    if (!cell.formulaParser) { //cut down on un-needed parser creation
                                        cell.formulaParser = window.Formula(jS.cellHandler);
                                    }
                                    formulaParser = cell.formulaParser
                                } else {//use the sheet's parser if there aren't many calls in the callStack
                                    formulaParser = jS.formulaParser;
                                }

                                jS.callStack++;
                                formulaParser.setObj(cell);
                                cell.result = formulaParser.parse(cell.formula);
                            } catch (e) {
                                cell.result = e.toString();
                            }
                            jS.callStack--;

                            if (cell.result && cell.cellType && s.cellTypeHandlers[cell.cellType]) {
                                cell.result = s.cellTypeHandlers[cell.cellType].call(cell, cell.result);
                            }
                            jS.filterValue.call(cell);
                        } else if (cell.value && cell.cellType && s.cellTypeHandlers[cell.cellType]) {
                            cell.result = s.cellTypeHandlers[cell.cellType].call(cell, cell.value);
                            jS.filterValue.call(cell);
                        } else {
                            if (cell.value != u && cell.value.charAt) {
                                fn = jS.s.cellStartingHandlers[cell.value.charAt(0)];
                                if (fn) {
                                    cell.valueOverride = fn.call(cell, cell.value);
                                } else {
                                    fn = jS.s.cellEndHandlers[cell.value.charAt(cell.value.length - 1)];
                                    if (fn) {
                                        cell.valueOverride = fn.call(cell, cell.value);
                                    }
                                }
                            }
                            jS.filterValue.call(cell);
                        }
                    }
                    cell.needsUpdated = false;
                    cell.state.pop();
                    return (cell.valueOverride != u ? cell.valueOverride : cell.value);
                },

                /**
                 * Ignites calculation with dependent cells is recursively called if cell uses value from another cell, also adds dependent cells to the dependencies attribute of cell
                 * @memberOf jS
                 */
                updateCellDependencies:function () {
                    if ((this.state || (this.state = [])).length) return;
                    this.state.push('updatingDependencies');
                    var dependencies = this.dependencies || []; //just in case it was never set
                    this.dependencies = [];
                    var i = dependencies.length - 1;

                    if (i > -1) {
                        do {
                            var dependantCell = dependencies[i],
                                dependantCellLoc = jS.getTdLocation(dependantCell.td);

                            dependantCell.calcDependenciesLast = 0;

                            jS.updateCellValue.call(dependantCell);
                            if (dependantCellLoc.row > 0 && dependantCellLoc.col > 0) {
                                jS.updateCellDependencies.call(dependantCell);
                            }
                        } while (i--);
                    }

                    this.state.pop();
                },

                /**
                 * Filters cell's value so correct entity is displayed, use apply on cell object
                 * @memberOf jS
                 */
                filterValue:function () {
                    var encodedValue, html;
                    if (this.result != u) {
                        this.value = this.result.valueOf();
                        html = this.result.html;
                    } else if (typeof this.value == 'string' && this.value.length > 0) {
                        encodedValue = s.encode(this.value);
                    }

                    this.td.html(html || encodedValue || this.value);
                },

                /**
                 * Object handler for formulaParser
                 * @memberOf jS
                 * @namespace
                 */
                cellHandler:{

                    /**
                     * Variable handler for formulaParser, arguments are the variable split by '.'.  Expose variables by using jQuery.sheet setting formulaVariables
                     * @returns {*}
                     * @memberOf jS.cellHandler
                     */
                    variable:function () {
                        if (arguments.length) {
                            var name = arguments[0],
                                attr = arguments[1],
                                formulaVariables = jS.s.formulaVariables,
                                formulaVariable,
                                result;

                            switch (name.toLowerCase()) {
                                case 'true':
                                    result = new Boolean(true);
                                    result.html = 'TRUE';
                                    return result;
                                case 'false':
                                    result = new Boolean(false);
                                    result.html = 'FALSE';
                                    return result;
                            }

                            if (formulaVariable = formulaVariables[name] && !attr) {
                                return formulaVariable;
                            } else if (formulaVariable && attr) {
                                return formulaVariable[attr];
                            } else {
                                return '';
                            }
                        }
                    },

                    /**
                     * time to fraction of day 1 / 0-24
                     * @param {String} time
                     * @param {Boolean} isAmPm
                     * @returns {*}
                     * @memberOf jS.cellHandler
                     */
                    time:function (time, isAmPm) {
                        return times.fromString(time, isAmPm);
                    },

                    /**
                     * get a number from variable
                     * @param {*} num
                     * @returns {Number}
                     * @memberOf jS.cellHandler
                     */
                    number:function (num) {
                        switch (typeof num) {
                            case 'number':
                                return num;
                            case 'string':
                                if (!n(num)) {
                                    return num * 1;
                                }
                            case 'object':
                                if (num.getMonth) {
                                    return dates.toCentury(num);
                                }
                        }
                        return num;
                    },

                    /**
                     * get a number from variable
                     * @param {*} _num
                     * @returns {Number}
                     * @memberOf jS.cellHandler
                     */
                    numberInverted: function(_num) {
                        var num = jS.cellHandler.number(_num),
                            inverted = new Number(num.valueOf() * -1);
                        if (num.html) {
                            inverted.html = num.html;
                        }
                        return inverted;
                    },

                    /**
                     * Perform math internally for parser
                     * @param {String} mathType
                     * @param {*} num1
                     * @param {*} num2
                     * @returns {*}
                     */
                    performMath: function (mathType, num1, num2) {
                        var type1,
                            type2,
                            type1IsNumber = true,
                            type2IsNumber = true,
                            errors = [],
                            value,
                            output = function(val) {return val;};

                        switch (type1 = (typeof num1.valueOf())) {
                            case 'number':break;
                            case 'string':
                                if (!n(num1)) {
                                    num1 *= 1;
                                } else {
                                    type1IsNumber = false;
                                }
                                break;
                            case 'object':
                                if (num1.getMonth) {
                                    num1 = dates.toCentury(num1);
                                    output = dates.get;
                                } else {
                                    type1IsNumber = false;
                                }
                                break;
                            default:
                                type1IsNumber = false;
                        }

                        switch (type2 = (typeof num2.valueOf())) {
                            case 'number':break;
                            case 'string':
                                if (!n(num2)) {
                                    num2 *= 1;
                                } else {
                                    type2IsNumber = false;
                                }
                                break;
                            case 'object':
                                if (num2.getMonth) {
                                    num2 = dates.toCentury(num2);
                                } else {
                                    type2IsNumber = false;
                                }
                                break;
                            default:
                                type2IsNumber = false;
                        }

                        if (!type1IsNumber) {
                            errors.push('not a number: ' + num1);
                            num1 = 0;
                        }

                        if (!type2IsNumber) {
                            errors.push('not a number: ' + num2);
                            num2 = 0;
                        }

                        if (errors.length) {
                            //throw new Error(errors.join(';') + ';');
                        }

                        switch (mathType) {
                            case '+':
                                value = num1 + num2;
                                break;
                            case '-':
                                value = num1 - num2;
                                break;
                            case '/':
                                value = num1 / num2;
                                if (value == Infinity || value == nAN) {
                                    value = 0;
                                }
                                break;
                            case '*':
                                value = num1 * num2;
                                break;
                            case '^':
                                value = math.pow(num1, num2);
                                break;
                        }

                        return output(value);
                    },

                    /**
                     * Get cell value
                     * @param {String} id example "A1"
                     * @returns {*}
                     * @memberOf jS.cellHandler
                     */
                    cellValue:function (id) { //Example: A1
                        var loc = jSE.parseLocation(id), cell;

                        cell = jS.cellHandler.createDependency.call(this, this.sheet, loc);

                        jS.updateCellValue.call(cell);
                        return (cell.valueOverride != u ? cell.valueOverride : cell.value);
                    },

                    /**
                     * Creates a relationship between 2 cells, where the formula originates and the cell that is required to supply a value to
                     * @param {Number} sheetIndex
                     * @param {Object} loc {row, col}
                     * @returns {Object}
                     */
                    createDependency:function (sheetIndex, loc) {
                        var sheet, row, cell;
                        if (!(sheet = jS.spreadsheets[sheetIndex])) return null;
                        if (!(row = sheet[loc.row])) return null;
                        if (!(cell = row[loc.col])) return null;

                        if (!cell.dependencies) cell.dependencies = [];

                        if ($.inArray(this, cell.dependencies) < 0) {
                            cell.dependencies.push(this);
                        }

                        return cell;
                    },

                    /**
                     * Get cell values as an array
                     * @param {String} start example "A1"
                     * @param {String} end example "B1"
                     * @returns {Array}
                     * @memberOf jS.cellHandler
                     */
                    cellRangeValue:function (start, end) {//Example: A1:B1
                        start = jSE.parseLocation(start);
                        end = jSE.parseLocation(end);
                        var result = [],
                            i = math.max(start.row, end.row),
                            iEnd = math.min(start.row, end.row),
                            j = math.max(start.col, end.col),
                            jStart = j,
                            jEnd = math.min(start.col, end.col),
                            cell;

                        if (i >= iEnd || j >= jEnd) {
                            do {
                                j = jStart;
                                do {
                                    cell = jS.spreadsheets[this.sheet][i][j];
                                    jS.cellHandler.createDependency.call(this, this.sheet, {row:i, col:j});
                                    result.unshift(jS.updateCellValue.call(cell));
                                } while(j-- > jEnd);
                            } while (i-- > iEnd);
                            return result;
                        }

                        return result;
                    },

                    /**
                     * Get cell value
                     * @param {String} id example "$A$1"
                     * @returns {*}
                     * @memberOf jS.cellHandler
                     */
                    fixedCellValue:function (id) {
                        id = id.replace(/\$/g, '');
                        return jS.cellHandler.cellValue.call(this, id);
                    },

                    /**
                     * Get cell values as an array
                     * @param {String} start example "$A$1"
                     * @param {String} end example "$B$1"
                     * @returns {Array}
                     * @memberOf jS.cellHandler
                     */
                    fixedCellRangeValue:function (start, end) {
                        start = start.replace(/\$/g, '');
                        end = end.replace(/\$/g, '');
                        return jS.cellHandler.cellRangeValue.call(this, start, end);
                    },

                    /**
                     * Get cell value from a different sheet within an instance
                     * @param {String} sheet example "SHEET1"
                     * @param {String} id example "A1"
                     * @returns {*}
                     * @memberOf jS.cellHandler
                     */
                    remoteCellValue:function (sheet, id) {//Example: SHEET1:A1
                        var loc = jSE.parseLocation(id),
                            sheetIndex = jSE.parseSheetLocation(sheet);

                        jS.cellHandler.createDependency.call(this, sheetIndex, loc);

                        return jS.updateCellValue(sheetIndex, loc.row, loc.col);
                    },

                    /**
                     * Get cell values as an array from a different sheet within an instance
                     * @param {String} sheet example "SHEET1"
                     * @param {String} start example "A1"
                     * @param {String} end example "B1"
                     * @returns {Array}
                     * @memberOf jS.cellHandler
                     */
                    remoteCellRangeValue:function (sheet, start, end) {//Example: SHEET1:A1:B2
                        sheet = jSE.parseSheetLocation(sheet);
                        start = jSE.parseLocation(start);
                        end = jSE.parseLocation(end);

                        var result = [];

                        for (var i = start.row; i <= end.row; i++) {
                            for (var j = start.col; j <= end.col; j++) {
                                result.push(jS.updateCellValue(sheet, i, j));
                            }
                        }

                        return [result];
                    },

                    /**
                     * Calls a function either from jQuery.sheet.engine or defined in jQuery sheet setting formulaFunctions.  When calling a function the cell being called from is "this".
                     * @param {String} fn function name (Will be converted to upper case)
                     * @param {Array} [args] arguments needing to be sent to function
                     * @returns {*}
                     * @memberOf jS.cellHandler
                     */
                    callFunction:function (fn, args) {
                        fn = fn.toUpperCase();
                        args = args || [];

                        if ($.sheet.fn[fn]) {
                            this.fnCount++;
                            var result = $.sheet.fn[fn].apply(this, args);
                            return result;
                        } else {
                            return s.error({error:"Function " + fn + " Not Found"});
                        }
                    }
                },

                /**
                 * Cell lookup handlers
                 * @memberOf jS
                 * @namespace
                 */
                cellLookupHandlers:{

                    /**
                     * @param {String} id example "$A$1"
                     * @returns {Array} [sheet, startCell, endCell]
                     * @memberOf jS.cellLookupHandlers
                     */
                    fixedCellValue:function (id) {
                        return [jS.sheet, jSE.parseLocation(id), jSE.parseLocation(id)];
                    },

                    /**
                     * @param {String} sheet example "SHEET1"
                     * @param {String} start example "$A$1"
                     * @param {String} end example "$B$1"
                     * @returns {Array} [sheet, startCell, endCell]
                     * @memberOf jS.cellLookupHandlers
                     */
                    fixedCellRangeValue:function (sheet, start, end) {
                        return [jSE.parseSheetLocation(sheet), jSE.parseLocation(start), jSE.parseLocation(end)];
                    },

                    /**
                     * doesn't do anything right now
                     * @param id
                     * @memberOf jS.cellLookupHandlers
                     */
                    cellValue:function (id) {

                    },

                    /**
                     * @param {String} sheet example "SHEET1"
                     * @param {String} start example "A1"
                     * @param {String} end example "B1"
                     * @returns {Array} [sheet, startCell, endCell]
                     * @memberOf jS.cellLookupHandlers
                     */
                    cellRangeValue:function (sheet, start, end) {
                        return [jS.sheet, jSE.parseLocation(start), jSE.parseLocation(end)];
                    },

                    /**
                     * @param {String} sheet example "SHEET1"
                     * @param {String} id example "A1"
                     * @returns {Array} [sheet, startCell, endCell]
                     * @memberOf jS.cellLookupHandlers
                     */
                    remoteCellValue:function (sheet, id) {
                        return [jS.sheet, jSE.parseLocation(id), jSE.parseLocation(id)];
                    },

                    /**
                     *
                     * @param {String} sheet example "SHEET1"
                     * @param {String} start example "A1"
                     * @param {String} end example "B1"
                     * @returns {Array} [sheet, startCell, endCell]
                     * @memberOf jS.cellLookupHandlers
                     */
                    remoteCellRangeValue:function (sheet, start, end) {
                        return [jSE.parseSheetLocation(sheet), jSE.parseLocation(start), jSE.parseLocation(end)];
                    },

                    /**
                     * @returns {*}
                     * @memberOf jS.cellLookupHandlers
                     */
                    callFunction:function () {
                        if (arguments[0] == "VLOOKUP" || arguments[0] == "HLOOKUP" && arguments[1]) {
                            if (arguments[1] && arguments[1][1]) {
                                return arguments[1][1];
                            }
                            return [];
                        }
                    }
                },

                /**
                 * Looks up cell using jS.cellLookupHandlers
                 * @returns {Array}
                 * @memberOf jS
                 */
                cellLookup:function () {
                    var formulaParser = Formula($.extend({}, jS.cellHandler, jS.cellLookupHandlers));
                    formulaParser.setObj(this);

                    var args = formulaParser.parse(this.formula),
                        lookupTable = [];

                    for (var row = args[1].row; row <= args[2].row; row++) {
                        for (var col = args[1].col; col <= args[2].col; col++) {
                            lookupTable.push(jS.getCell(row, col, args[0]));
                        }
                    }

                    return lookupTable;
                },

                /**
                 * Date of last calculation
                 * @memberOf jS
                 */
                calcLast:0,

                /**
                 * @memberOf jS
                 */
                calcDependenciesLast:0,

                /**
                 * Where jS.spreadsheets are calculated, and returned to their td counterpart
                 * @param {Number} [sheetIndex] table index
                 * @param {Boolean} [refreshCalculations]
                 * @memberOf jS
                 */
                calc:function (sheetIndex, refreshCalculations) {
                    sheetIndex = sheetIndex || jS.i;
                    if (
                        jS.readOnly[sheetIndex]
                            || jS.isChanged(sheetIndex) === false
                            && !refreshCalculations
                            || !jS.formulaParser
                        ) {
                        return false;
                    } //readonly is no calc at all

                    jS.calcLast = new Date();

                    var sheet = jS.spreadsheetToArray(null, sheetIndex);
                    jSE.calc(sheetIndex, sheet, jS.updateCellValue);
                    jS.trigger('sheetCalculation', [
                        {which:'spreadsheet', sheet:sheet, index:sheetIndex}
                    ]);
                    jS.setChanged(false);
                    return true;
                },

                calcVisiblePos: {
                    row: -1,
                    col: -1
                },
                calcVisibleInit: function(sheetIndex) {
                    sheetIndex = sheetIndex || jS.i;

                    jS.calcLast = jS.calcLast || new Date();

                    var spreadsheet = jS.spreadsheetToArray(null, sheetIndex) || [],
                        ignite = jS.updateCellValue,
                        min = Math.min,
                        initRows = s.initCalcRows,
                        initCols = s.initCalcCols,
                        rowIndex = min(spreadsheet.length - 1, initRows),
                        row,
                        colIndex,
                        pos = {row: -1, col: -1};


                    if (rowIndex > 0) {
                        pos.row = rowIndex;
                        do {
                            if (rowIndex > 0 && (row = spreadsheet[rowIndex]) !== undefined) {
                                pos.col = colIndex = min(row.length - 1, initCols);
                                if (colIndex > 0) {
                                    do {
                                        ignite.apply(row[colIndex]);
                                    } while (colIndex-- > 1);
                                }
                            }
                        } while(rowIndex-- > 1);
                    }

                    this.calcVisiblePos = pos;
                    jS.trigger('sheetCalculation', [
                        {which:'spreadsheet', sheet:spreadsheet, index:sheetIndex}
                    ]);
                    jS.setChanged(false);
                },
                calcVisibleRow: function(actionUI, sheetIndex) {
                    sheetIndex = sheetIndex || jS.i;

                    var spreadsheet = jS.spreadsheetToArray(null, sheetIndex) || [],
                        endScrolledArea = actionUI.scrolledArea.end,
                        ignite = jS.updateCellValue,
                        min = Math.min,
                        initRows = s.initCalcRows,
                        initCols = s.initCalcCols,
                        targetRow = endScrolledArea.row + initRows,
                        targetCol = endScrolledArea.col + initCols,
                        rowIndex = min(spreadsheet.length - 1, targetRow),
                        row,
                        colIndex,
                        oldPos = this.calcVisiblePos,
                        newPos = {row: rowIndex, col: oldPos.col};


                    if (rowIndex > 0) {
                        do {
                            if ((row = spreadsheet[rowIndex]) !== undefined) {
                                colIndex = min(row.length - 1, targetCol);
                                if (colIndex > 0) {
                                    do {
                                        ignite.apply(row[colIndex]);
                                    } while (colIndex-- > 1);
                                }
                            }
                        } while (rowIndex-- > oldPos.row);

                    }

                    this.calcVisiblePos = newPos;

                    jS.trigger('sheetCalculation', [
                        {which:'spreadsheet', sheet:spreadsheet, index:sheetIndex}
                    ]);
                    jS.setChanged(false);
                },
                calcVisibleCol: function(actionUI, sheetIndex) {
                    sheetIndex = sheetIndex || jS.i;

                    var spreadsheet = jS.spreadsheetToArray(null, sheetIndex) || [],
                        endScrolledArea = actionUI.scrolledArea.end,
                        ignite = jS.updateCellValue,
                        min = Math.min,
                        initRows = s.initCalcRows,
                        initCols = s.initCalcCols,
                        targetRow = endScrolledArea.row + initRows,
                        targetCol = endScrolledArea.col + initCols,
                        rowIndex = min(spreadsheet.length - 1, targetRow),
                        row,
                        colIndex,
                        cell,
                        oldPos = this.calcVisiblePos,
                        newPos = {row: oldPos.row, col: oldPos.col};


                    if (rowIndex > 0) {
                        do {
                            if (rowIndex > 0 && (row = spreadsheet[rowIndex]) !== undefined) {
                                colIndex = min(row.length - 1, targetCol);
                                if ((cell = row[colIndex]) !== undefined) {
                                    ignite.apply(cell);
                                }
                            }
                        } while(rowIndex-- > 1);
                    }

                    this.calcVisiblePos = newPos;

                    jS.trigger('sheetCalculation', [
                        {which:'spreadsheet', sheet:spreadsheet, index:sheetIndex}
                    ]);
                    jS.setChanged(false);
                },

                /**
                 * Calculates just the dependencies of a single cell, and their dependencies recursively
                 * @param {Date} last
                 * @param {Boolean} skipUndoable
                 * @memberOf jS
                 */
                calcDependencies:function (last, skipUndoable) {
                    last = last || new Date();
                    jS.calcDependenciesLast = last;

                    if (!skipUndoable) {
                        var cell = this;
                        jS.undo.createCells([this], function(cells) {
                            jS.trigger('sheetPreCalculation', [
                                {which:'cell', cell:cell}
                            ]);

                            jS.setDirty(true);
                            jS.setChanged(true);
                            jS.updateCellValue.call(cell);
                            jS.updateCellDependencies.call(cell);
                            jS.trigger('sheetCalculation', [
                                {which:'cell', cell: cell}
                            ]);

                            return cells;
                        });
                    } else {
                        jS.trigger('sheetPreCalculation', [
                            {which:'cell', cell:this}
                        ]);

                        jS.setDirty(true);
                        jS.setChanged(true);
                        jS.updateCellValue.call(this);
                        jS.updateCellDependencies.call(this);
                        jS.trigger('sheetCalculation', [
                            {which:'cell', cell: this}
                        ]);
                    }
                },

                /**
                 * adds a spreadsheet table
                 * @param {Object} [size]
                 * @memberOf jS
                 */
                addSheet:function (size) {
                    size = size || {rows: 25, cols: 10};
                    if (size) {
                        jS.evt.cellEditAbandon();
                        jS.setDirty(true);
                        jS.controlFactory.sheetUI(jS.obj.ui, $.sheet.makeTable(size), jS.sheetCount);

                        jS.setActiveSheet(jS.sheetCount);

                        jS.sheetCount++;

                        jS.sheetSyncSize();

                        jS.obj.pane().resizeScroll();

                        jS.trigger('sheetAdd', [jS.i]);
                    }
                },

                /**
                 * deletes a spreadsheet table
                 * @param {Number} [i] spreadsheet index within instance
                 * @memberOf jS
                 */
                deleteSheet:function (i) {
                    var oldI = i || jS.i,
                        enclosureArray =jS.controls.enclosures.toArray(),
                        tabIndex;

                    enclosureArray.splice(oldI,1)

                    jS.obj.barHelper().remove();

                    jS.obj.enclosure().remove();
                    //BUG Found:
                    //The enclosure will not be removed correctly while you delete the sheet.You may find all the enclosure will be hidden after you add a sheet and delete it.
                    //The reason is that "jS.controls.enclosures" is a jQuery selector object( "$([])" ) which can't not remove element like an array.All enclosure are reserved after sheet has been deleted.
                    //Here I remove the element by creating the selector object again.
                    jS.controls.enclosures = $(enclosureArray);
                    jS.obj.menus().remove();
                    jS.obj.tabContainer().children().eq(jS.i).remove();
                    jS.spreadsheets.splice(oldI, 1);
                    jS.controls.autoFiller.splice(oldI, 1);
                    jS.controls.bar.helper.splice(oldI, 1);
                    jS.controls.bar.corner.splice(oldI, 1);
                    jS.controls.bar.x.controls.splice(oldI, 1);
                    jS.controls.bar.x.handleFreeze.splice(oldI, 1);
                    jS.controls.bar.x.controls.splice(oldI, 1);
                    jS.controls.bar.x.menu.splice(oldI, 1);
                    if (jS.controls.bar.x.menuParent && jS.controls.bar.x.menuParent[oldI]) {
                        jS.controls.bar.x.menuParent.splice(oldI, 1);
                    }
                    jS.controls.bar.x.parent.splice(oldI, 1);
                    jS.controls.bar.x.td.splice(oldI, 1);
                    jS.controls.bar.y.controls.splice(oldI, 1);
                    jS.controls.bar.y.handleFreeze.splice(oldI, 1);
                    jS.controls.bar.y.controls.splice(oldI, 1);
                    jS.controls.bar.y.menu.splice(oldI, 1);
                    if (jS.controls.bar.y.menuParent && jS.controls.bar.y.menuParent[oldI]) {
                        jS.controls.bar.y.menuParent.splice(oldI, 1);
                    }
                    jS.controls.bar.y.parent.splice(oldI, 1);
                    jS.controls.bar.y.td.splice(oldI, 1);
                    jS.controls.barMenuLeft.splice(oldI, 1);
                    jS.controls.barMenuTop.splice(oldI, 1);
                    jS.controls.barLeft.splice(oldI, 1);
                    jS.controls.barTop.splice(oldI, 1);
                    jS.controls.barTopParent.splice(oldI, 1);
                    jS.controls.chart.splice(oldI, 1);
                    jS.controls.tdMenu.splice(oldI, 1);
                    jS.controls.enclosure.splice(oldI, 1);
                    jS.controls.fullScreen = null;
                    jS.controls.inPlaceEdit.splice(oldI, 1);
                    jS.controls.menus.splice(oldI, 1);
                    jS.controls.menuLeft.splice(oldI, 1);
                    jS.controls.menuRight.splice(oldI, 1);
                    jS.controls.pane.splice(oldI, 1);
                    jS.controls.tables.splice(oldI, 1);
                    jS.controls.table.splice(oldI, 1);
                    //BUGFIX - After removing of sheet, we need update the tab.i property - start from removed sheet's position.
                    for (tabIndex = oldI+1; tabIndex < jS.controls.tab.length; ++tabIndex) {
                        var tab = jS.controls.tab[tabIndex].get(0);
                        tab.i--;
                    }
                    jS.controls.tab.splice(oldI, 1);
                    jS.controls.toggleHide.x.splice(oldI, 1);
                    jS.controls.toggleHide.y.splice(oldI, 1);
                    jS.readOnly.splice(oldI, 1);
                    jS.i = 0;
                    jS.sheetCount--;
                    jS.sheetCount = math.max(jS.sheetCount, 0);

                    if (jS.sheetCount == 0) {
                        jS.addSheet();
                    }

                    jS.setActiveSheet(jS.i);
                    jS.setDirty(true);
                    jS.setChanged(true);

                    jS.trigger('sheetDelete', [oldI]);
                },

                /**
                 * removes the currently selected row
                 * @param {Number} row
                 * @param {Boolean} skipCalc
                 * @memberOf jS
                 */
                deleteRow:function (row, skipCalc) {
                    var i, start, end, qty, size = jS.sheetSize();

                    if (row) {
                        start = end = row;
                    } else {
                        start = math.min(jS.highlightedLast.start.row, jS.highlightedLast.end.row);
                        end = math.max(jS.highlightedLast.start.row, jS.highlightedLast.end.row);
                    }

                    qty = (end - start) + 1;

                    if (start < 1 || size.cols < 2 || qty >= size.rows) {
                        return;
                    }

                    i = end;

                    do {
                        //remove tr's first
                        jS.getTd(jS.i, i, 1).parent().remove();
                    } while (start < i--);

                    //now remove bar
                    jS.controls.bar.y.td[jS.i].splice(start, qty);

                    //now remove cells
                    jS.spreadsheets[jS.i].splice(start, qty);

                    jS.refreshRowLabels(start);

                    jS.setChanged(true);

                    jS.offsetFormulas({
                            row:start,
                            col:0
                        }, {
                            row:-qty,
                            col:0
                        },
                        false,
                        true
                    );

                    jS.setDirty(true);

                    jS.evt.cellEditAbandon();

                    jS.obj.pane().resizeScroll();

                    jS.trigger('sheetDeleteRow', i);
                },

                /**
                 * removes the columns associated with highlighted cells
                 * @param {Number} [i]
                 * @memberOf jS
                 */
                deleteColumn:function (i) {
                    var j, start, end, qty, size = jS.sheetSize();

                    if (i) {
                        start = end = i;
                    } else {
                        start = math.min(jS.highlightedLast.start.col, jS.highlightedLast.end.col);
                        end = math.max(jS.highlightedLast.start.col, jS.highlightedLast.end.col);
                    }

                    qty = (end - start) + 1;

                    if (
                        start < 1
                            || size.cols < 2
                            || qty >= size.cols
                        ) {
                        return;
                    }

                    j = end;

                    jS.obj.barHelper().remove();
                    do {
                        var table = jS.obj.table(),
                            col = jS.col(table[0], j),
                            bar = jS.obj.barTop(j).remove(),
                            tds = col.tds,
                            k = tds.length - 1;

                        //remove tds first
                        do {
                            $(tds[k]).remove();
                        } while (k--);

                        //now remove bar
                        jS.obj.barTop(j).remove();

                        //now remove col
                        $(col).remove();
                    } while (start < j--);

                    //remove column
                    jS.controls.bar.x.td[jS.i].splice(start, qty);

                    //remove cells
                    k = jS.spreadsheets[jS.i].length - qty;
                    do {
                        jS.spreadsheets[jS.i][k].splice(start, qty);
                    } while (k-- > 1);

                    //refresh labels
                    jS.refreshColumnLabels(start);

                    jS.setChanged(true);

                    jS.offsetFormulas({
                            row:0,
                            col:start
                        }, {
                            row:0,
                            col:-qty
                        },
                        false,
                        true
                    );

                    jS.setDirty(true);

                    jS.evt.cellEditAbandon();

                    jS.obj.pane().resizeScroll();

                    jS.trigger('sheetDeleteColumn', j);
                },

                /**
                 * manages a tabs inner value
                 * @param {Boolean} [get] makes return the current value of the tab
                 * @param {Function} [callback]
                 * @returns {String}
                 * @memberOf jS
                 */
                sheetTab:function (get, callback) {
                    var sheetTab = '';
                    if (get) {
                        sheetTab = jS.obj.table().attr('title') || jS.msg.sheetTitleDefault.replace(/[{]index[}]/gi, jS.i + 1);
                        if (callback) {
                            callback(sheetTab);
                        }
                        return sheetTab;
                    } else if (jS.isSheetEditable() && s.editableNames) { //ensure that the sheet is editable, then let them change the sheet's name
                        s.prompt(
                            jS.msg.newSheetTitle,
                            function(newTitle) {
                                if (!newTitle) { //The user didn't set the new tab name
                                    sheetTab = jS.obj.table().attr('title');
                                    newTitle = (sheetTab ? sheetTab : jS.msg.sheetTitleDefault.replace(/[{]index[}]/gi, jS.i + 1));
                                } else {
                                    jS.setDirty(true);
                                    jS.obj.table().attr('title', newTitle);
                                    jS.obj.tab().html(newTitle);

                                    sheetTab = newTitle;
                                }

                                if (callback) {
                                    callback($(document.createElement('div')).text(sheetTab).html());
                                }
                            },
                            jS.sheetTab(true)
                        );
                        return null;
                    }
                },

                /**
                 * detects if a td is not visible
                 * @param {jQuery|HTMLElement} $td
                 * @memberOf jS
                 * @returns {Boolean|Object}
                 */
                tdNotVisible:function($td) {
                    var pane = jS.obj.pane(),
                        td = $td[0],
                        visibleFold = {
                            top:0,
                            bottom:pane.clientHeight,
                            left:0,
                            right:pane.clientWidth
                        },

                        tdWidth = $td.width(),
                        tdHeight = $td.height(),
                        tdLocation = {
                            top:td.offsetTop,
                            bottom:td.offsetTop + tdHeight,
                            left:td.offsetLeft,
                            right:td.offsetLeft + tdWidth
                        },
                        $tdParent = $td.parent();

                    if (!td.col) {
                        return false;
                    }

                    var xHidden = $(td.barTop).is(':hidden'),
                        yHidden = $tdParent.is(':hidden'),
                        hidden = {
                            up:yHidden,
                            down:tdLocation.bottom > visibleFold.bottom && tdHeight <= pane.clientHeight,
                            left:xHidden,
                            right:tdLocation.right > visibleFold.right && tdWidth <= pane.clientWidth
                        };

                    if (hidden.up || hidden.down || hidden.left || hidden.right) {
                        return hidden;
                    }

                    return false;
                },

                /**
                 * scrolls the sheet to the selected cell
                 * @param {jQuery|HTMLElement} [$td] default is tdActive
                 * @param {boolean} [dontMoveAutoFiller] keeps autoFillerHandler in default position
                 * @memberOf jS
                 */
                followMe:function ($td, dontMoveAutoFiller) {
                    $td = $td || jS.obj.tdActive();
                    if (!$td.length) return;

                    var pane = jS.obj.pane(),
                        actionUI = pane.actionUI;

                    jS.setBusy(true);

                    actionUI.putTdInView($td[0]);

                    jS.setBusy(false);

                    if(!dontMoveAutoFiller){
                        jS.autoFillerGoToTd($td);
                    }
                },

                /**
                 * moves autoFiller to a selected cell if it is enabled in settings
                 * @param {jQuery|HTMLElement} [$td] default is tdActive
                 * @param {Number} [h] height of a td object
                 * @param {Number} [w] width of a td object
                 * @memberOf jS
                 */
                autoFillerGoToTd:function ($td, h, w) {
                    if (!s.autoFiller) return;

                    $td = $td || jS.obj.tdActive();
                    var td = $td[0];

                    if (td && td.type == 'cell') { //ensure that it is a usable cell
                        h = h || td.clientHeight;
                        w = w || td.clientWidth;
                        if (!td.offsetHeight || !td.offsetWidth || !td.clientHeight || !td.clientWidth) {
                            jS.autoFillerHide();
                            return;
                        }

                        var tdPos = $td.position();

                        jS.obj.autoFiller()
                            .show()
                            .css('top', ((tdPos.top + (h || $td.height()) - 3) + 'px'))
                            .css('left', ((tdPos.left + (w || $td.width()) - 3) + 'px'));
                    }
                },

                /**
                 * hides the auto filler if it is enabled in settings
                 * @memberOf jS
                 */
                autoFillerHide:function () {
                    if (!s.autoFiller) return;

                    jS.obj.autoFiller().hide();
                },

                /**
                 * sets active a spreadsheet inside of a sheet instance
                 * @param {Number} [i] a sheet integer desired to show, default 0
                 * @memberOf jS
                 */
                setActiveSheet:function (i) {
                    i = i || 0;

                    if (jS.cellLast.row > 0 || jS.cellLast.col > 0) {
                        jS.evt.cellEditDone();
                        jS.obj.formula().val('');
                    }

                    //the below use of _scrollLeft and _scrollTop are protected from IE, which makes those attributes go away after something is hidden, thus forgetting where you are scrolled to when you change sheets
                    //IE, stop flossin' me
                    var enclosures = jS.obj.enclosures(),
                        j = enclosures.length - 1,
                        enclosure;

                    jS.autoFillerHide();

                    if (j > 0) {
                        do {
                            if (i != j) {
                                enclosure = enclosures[j];
                                enclosure._scrollLeft = enclosure._scrollLeft || enclosure.scrollUI.scrollLeft;
                                enclosure._scrollTop = enclosure._scrollTop || enclosure.scrollUI.scrollTop;
                                enclosure.style.display = "none";
                            }
                        } while (j-- > 0);
                    }

                    jS.i = i;

                    enclosure = enclosures[i];

                    enclosure.style.display = "";

                    jS.themeRoller.tab.setActive();

                    jS.readOnly[i] = (enclosure.table.className || '').match(/\breadonly\b/i) != null;

                    enclosure.pane.resizeScroll(true);

                    enclosure.scrollUI.scrollLeft = enclosure._scrollLeft || enclosure.scrollUI.scrollLeft;
                    enclosure.scrollUI.scrollTop = enclosure._scrollTop || enclosure.scrollUI.scrollTop;
                    enclosure._scrollLeft = enclosure._scrollTop = null;
                    enclosure.scrollUI.onscroll();
                },

                /**
                 * opens a spreadsheet into the active sheet instance
                 * @param {jQuery|HTMLCollection} tables
                 * @memberOf jS
                 */
                openSheet:function (tables) {
                    var lastIndex = tables.length - 1,
                        stack = [],
                        open = function() {
                            jS.setBusy(true);
                            var header = jS.controlFactory.header(),
                                ui = jS.controlFactory.ui(),
                                sheetAdder = jS.controlFactory.sheetAdder(),
                                tabContainer = jS.controlFactory.tabContainer(),
                                i;

                            jS.sheetCount = tables.length - 1;

                            header.ui = ui;
                            header.tabContainer = tabContainer;

                            ui.header = header;
                            ui.sheetAdder = sheetAdder;
                            ui.tabContainer = tabContainer;

                            tabContainer.header = header;
                            tabContainer.ui = ui;

                            s.parent
                                .append(header)
                                .append(ui)
                                .append(sheetAdder)
                                .append(tabContainer);

                            for (i = 0; i < tables.length; i++) {
                                new Loader(jS, i, ui, tables[i]);
                            }
                        },
                        Loader = function(jS, i, ui, table) {
                            var me = this;
                            this.i = i;
                            this.ui = ui;
                            this.table = table;
                            this.isLast = (i == lastIndex);
                            this.jS = jS;

                            if ($.sheet.max) {
                                var size = jS.tableSize(table);
                                if (size.rows > $.sheet.max || size.cols > $.sheet.max) {
                                    jS.trigger('sheetMaxSize', [table, i]);
                                    s.confirm(
                                        jS.msg.maxSizeBrowserLimitationOnOpen,
                                        function() {me.load();}
                                    );
                                } else {
                                    this.load();
                                }
                            } else {
                                this.load();
                            }
                        };

                    Loader.prototype = {
                        load: function() {
                            jS.controlFactory.sheetUI(this.ui, this.table, this.i);
                            jS.sheetCount++;

                            stack.push(this.i);

                            jS.trigger('sheetOpened', [this.i]);

                            if (this.isLast) {
                                this.loaded();
                            }
                        },
                        loaded: function() {
                            var jS = this.jS,
                                ui = this.ui;

                            // resizable container div
                            jS.resizableSheet(s.parent, {
                                minWidth:s.parent.width() * 0.1,
                                minHeight:s.parent.height() * 0.1,
                                start:function () {
                                    jS.setBusy(true);
                                    jS.obj.enclosure().hide();
                                    ui.sheetAdder.hide();
                                    ui.tabContainer.hide();
                                },
                                stop:function () {
                                    jS.obj.enclosure().show();
                                    ui.sheetAdder.show();
                                    ui.tabContainer.show();
                                    jS.setBusy(false);
                                    jS.sheetSyncSize();
                                    jS.obj.pane().resizeScroll();
                                }
                            });

                            jS.sheetSyncSize();

                            jS.setActiveSheet(0);

                            jS.setDirty(false);
                            jS.setBusy(false);

                            while (stack.length) {
                                jS.calcVisibleInit(jS.i = stack.pop());
                            }

                            jS.trigger('sheetAllOpened');
                        }
                    };

                    if (jS.isDirty) {
                        s.confirm(
                            jS.msg.openSheet,
                            open
                        );
                    } else {
                        open();
                    }
                },

                /**
                 * creates a new sheet from size from prompt
                 * @memberOf jS
                 */
                newSheet:function () {
                    s.parent
                        .html($.sheet.makeTable())
                        .sheet(s);
                },


                /**
                 * synchronizes the called parent's controls so that the controls fit correctly within the parent
                 * @function sheetSyncSize
                 * @memberOf jS
                 */
                sheetSyncSize:function () {
                    var h = s.parent[0].clientHeight;
                    if (!h) {
                        h = 400; //Height really needs to be set by the parent
                        s.parent.height(h);
                    } else if (h < 200) {
                        h = 200;
                        s.parent.height(h);
                    }

                    h -= jS.obj.header().outerHeight();
                    h -= jS.obj.tabContainer().outerHeight() + jS.s.boxModelCorrection;

                    var w = s.parent[0].clientWidth,
                        uiStyle = jS.obj.ui.style,
                        paneHeight = (h - window.scrollBarSize.height - s.boxModelCorrection) + 'px',
                        paneWidth = (w - window.scrollBarSize.width) + 'px',
                        standardHeight = h + 'px',
                        standardWidth = w + 'px';

                    jS.obj.panes().each(function() {
                        var style = this.style,
                            scrollStyle = this.actionUI.scrollUI.style,
                            enclosureStyle = this.enclosure.style;

                        style.height = paneHeight;
                        style.width = paneWidth;

                        enclosureStyle.height = scrollStyle.height = standardHeight;
                        enclosureStyle.width = scrollStyle.width = standardWidth;
                    });


                    uiStyle.height = standardHeight;
                    uiStyle.width = standardWidth;
                },

                /**
                 * changes a cell's style and makes it undoable/redoable
                 * @param style
                 * @param value
                 * @param cells
                 */
                cellChangeStyle:function (style, value, cells) {
                    cells = cells || jS.highlighted(true);
                    if (cells.length < 1) {
                        return false;
                    }

                    jS.setDirty(this);
                    var i = cells.length - 1;

                    if ( i >= 0) {
                        jS.undo.createCells(cells, function(cells) { //save state, make it undoable
                            do {
                                cells[i].td.css(style, value);
                            } while(i--);

                            return cells;
                        });
                        return true;
                    }

                    return false;
                },

                /**
                 * Finds a cell in a sheet from a value
                 * @param {String} [v] value to look for within a cell, if not supplied, a prompt is given
                 * @memberOf jS
                 */
                cellFind:function (v) {
                    function find (v) {
                        var trs = jS.obj.table()
                            .children('tbody')
                            .children('tr');

                        if (v) {//We just do a simple uppercase/lowercase search.
                            var o = trs.children('td:contains("' + v + '")');

                            if (o.length < 1) {
                                o = trs.children('td:contains("' + v.toLowerCase() + '")');
                            }

                            if (o.length < 1) {
                                o = trs.children('td:contains("' + v.toUpperCase() + '")');
                            }

                            o = o.eq(0);
                            if (o.length > 0) {
                                jS.cellEdit(o);
                            } else {
                                s.alert(jS.msg.cellNoFind);
                            }                           }
                    }
                    if (!v) {
                        s.prompt(
                            jS.msg.cellFind,
                            find
                        );
                    } else {
                        find(v);
                    }

                },

                /**
                 * Sets active bar
                 * @param {String} type "col" || "row" || "all"
                 * @param {Number} begin start highlighting from
                 * @param {Number} end end highlighting to
                 * @memberOf jS
                 */
                cellSetActiveBar:function (type, begin, end) {
                    var size = jS.sheetSize(),
                        first = math.min(begin, end),
                        last = math.max(begin, end),
                        start = {},
                        stop = {},

                        /**
                         * Sets active bar
                         * @param {Boolean} [before]
                         */
                            SetActive = function (before) {
                            switch (s.cellSelectModel) {
                                case 'oo': //follow cursor behavior
                                    this.row = (before ? start.row : stop.row);
                                    this.col = (before ? start.col : stop.col);
                                    this.td = jS.getTd(jS.i, this.row, this.col);
                                    if (!this.td.is(jS.cellLast.td)) {
                                        jS.cellEdit(this.td, false, true);
                                    }
                                    break;
                                default: //stay at initial cell
                                    this.row = (before ? stop.row : start.row);
                                    this.col = (before ? stop.col : start.col);
                                    this.td = jS.getTd(jS.i, this.row, this.col);
                                    if (!this.td.is(jS.cellLast.td[0])) {
                                        jS.cellEdit(this.td, false, true);
                                    }
                                    break;
                            }
                        },
                        obj = [],
                        scrolledArea  = jS.obj.pane().actionUI.scrolledArea,
                        sheet = jS.obj.table(),
                        col,
                        row;

                    switch (type) {
                        case 'top':
                            start.row = scrolledArea.end.row;
                            start.col = first;
                            stop.row = scrolledArea.end.row;
                            stop.col = last;

                            jS.highlightedLast.start.row = 1;
                            jS.highlightedLast.start.col = first;
                            jS.highlightedLast.end.row = size.rows;
                            jS.highlightedLast.end.col = last;

                            col = last;

                            do {
                                obj.push(jS.col(sheet[0], col));
                            } while(col-- > first);
                            break;
                        case 'left':
                            start.row = first;
                            start.col = scrolledArea.end.col;
                            stop.row = last;
                            stop.col = scrolledArea.end.col;

                            jS.highlightedLast.start.row = first;
                            jS.highlightedLast.start.col = 1;
                            jS.highlightedLast.end.row = last;
                            jS.highlightedLast.end.col = size.cols;

                            row = last;

                            do {
                                obj.push(jS.getTd(jS.i, row, 1)[0].parentNode);
                            } while(row-- > first);
                            break;
                        case 'corner': //all
                            start.row = 1;
                            start.col = 1;
                            stop.col = size.cols;
                            stop.row = size.rows;

                            obj.push(sheet[0]);
                            break;
                    }

                    new SetActive(begin > end);

                    jS.themeRoller.cell.setHighlighted($(obj));
                },

                /**
                 * gets a range of selected cells, then returns it
                 * @param {Object} [e] jQuery event, when in use, is during mouse down
                 * @param {String} v Value to preserve and return
                 * @param {String} [newFn]
                 * @param {Boolean} [notSetFormula]
                 * @returns {String}
                 * @memberOf jS
                 */
                getTdRange:function (e, v, newFn, notSetFormula) {
                    jS.cellLast.isEdit = true;

                    var range = function (loc) {
                            if (loc.first.col > loc.last.col ||
                                loc.first.row > loc.last.row
                                ) {
                                return {
                                    first:jSE.parseCellName(loc.last.col, loc.last.row),
                                    last:jSE.parseCellName(loc.first.col, loc.first.row)
                                };
                            } else {
                                return {
                                    first:jSE.parseCellName(loc.first.col, loc.first.row),
                                    last:jSE.parseCellName(loc.last.col, loc.last.row)
                                };
                            }
                        },
                        label = function (loc) {
                            var rangeLabel = range(loc),
                                v2 = v + '';
                            v2 = (v2.match(/=/) ? v2 : '=' + v2); //make sure we can use this value as a formula

                            if (newFn || v2.charAt(v2.length - 1) != '(') { //if a function is being sent, make sure it can be called by wrapping it in ()
                                v2 = v2 + (newFn ? newFn : '') + '(';
                            }

                            var formula,
                                lastChar = '';
                            if (rangeLabel.first != rangeLabel.last) {
                                formula = rangeLabel.first + ':' + rangeLabel.last;
                            } else {
                                formula = rangeLabel.first;
                            }

                            if (v2.charAt(v2.length - 1) == '(') {
                                lastChar = ')';
                            }

                            return v2 + formula + lastChar;
                        },
                        newVal = '',
                        loc,
                        sheet,
                        cells;

                    if (e) { //if from an event, we use mousemove method
                        loc = {
                            first:jS.getTdLocation([e.target])
                        };

                        sheet = jS.obj.table().mousemove(function (e) {
                            loc.last = jS.getTdLocation([e.target]);

                            newVal = label(loc);

                            if (!notSetFormula) {
                                jS.obj.formula().val(newVal);
                                jS.obj.inPlaceEdit().val(newVal);
                            }
                        });

                        $document.one('mouseup', function () {
                            sheet.unbind('mousemove');
                            return newVal;
                        });
                    } else {
                        cells = jS.highlighted().not(jS.obj.tdActive());

                        if (cells.length) {
                            loc = { //tr/td column and row index
                                first:jS.getTdLocation(cells.first()),
                                last:jS.getTdLocation(cells.last())
                            };

                            newVal = label(loc);

                            if (!notSetFormula) {
                                jS.obj.formula().val(newVal);
                                jS.obj.inPlaceEdit().val(newVal);
                            }

                            return newVal;
                        } else {
                            return '';
                        }
                    }
                    return '';
                },

                /**
                 * Gets the td element within a spreadsheet instance
                 * @param {Number} tableIndex table index
                 * @param {Number} rowIndex row index
                 * @param {Number} colIndex column index
                 * @returns {jQuery|HTMLElement}
                 * @memberOf jS
                 */
                getTd:function (tableIndex, rowIndex, colIndex) {
                    var table = jS.obj.tables()[tableIndex],
                        tBody,
                        row,
                        td;

                    if (
                        !table
                            || !(tBody = table.tBody)
                            || !(row = tBody.children[rowIndex])
                            || !(td = row.children[colIndex])
                        ) {
                        td = document.createElement('td');
                    }

                    return $(td);
                },

                /**
                 * Gets the td row and column index as an object {row, col}
                 * @param {Object} td
                 * @returns {Object}
                 * @memberOf jS
                 */
                getTdLocation:function (td) {
                    var result = {col:0, row:0};
                    td = td || '';
                    if (!td && !td[0]) return result;

                    td = td[0] || td;

                    if (td.parentNode == u || td.parentNode.rowIndex < 0) {
                        return result;
                    }

                    return {
                        col:parseInt(td.cellIndex),
                        row:parseInt(td.parentNode.rowIndex)
                    };
                },

                /**
                 * Get the bar index from an Element
                 * @memberOf jS
                 * @namespace
                 */
                getBarIndex:{

                    /**
                     * get index from bar left element
                     * @param [td] if null, will return -1
                     * @returns {Number}
                     * @memberOf jS.getBarIndex
                     */
                    left:function (td) {
                        td = td || {};
                        if (!td.parentNode || n(td.parentNode.rowIndex)) {
                            return -1;
                        } else {
                            return td.parentNode.rowIndex;
                        }
                    },

                    /**
                     * get index from bar top element
                     * @param [td] if null, will return -1
                     * @returns {Number} cellIndex
                     * @memberOf hS.getBarIndex
                     */
                    top:function (td) {
                        td = td || {};
                        if (n(td.cellIndex)) {
                            return -1;
                        } else {
                            return td.cellIndex;
                        }
                    },
                    corner:function () {
                        return 0;
                    }
                },

                /**
                 * Time manager for measuring execution speed
                 * @namespace
                 * @memberOf jS
                 */
                time:{
                    now:new Date(),
                    last:new Date(),
                    diff:function () {
                        return math.abs(math.ceil(this.last.getTime() - this.now.getTime()) / 1000).toFixed(5);
                    },
                    set:function () {
                        this.last = this.now;
                        this.now = new Date();
                    },
                    get:function () {
                        return this.now.getHours() + ':' + this.now.getMinutes() + ':' + this.now.getSeconds();
                    }
                },

                /**
                 * Changed tracker per sheet
                 * @memberOf jS
                 */
                changed:[],

                /**
                 * Changed = needs to be calculated
                 * @memberOf jS
                 * @param tableIndex
                 */
                isChanged:function (tableIndex) {
                    return jS.changed[tableIndex || jS.i];
                },

                /**
                 * Sets changed
                 * @param {Boolean} changed changed state
                 * @memberOf jS
                 */
                setChanged:function (changed) {
                    jS.changed[jS.i] = changed;
                },

                /**
                 * Dirty = changed needs saved
                 * @memberOf jS
                 */
                isDirty:false,

                /**
                 * Dirty manager
                 * @param dirty
                 * @memberOf jS
                 */
                setDirty:function (dirty) {
                    jS.dirty = dirty;
                },

                /**
                 * @param v
                 * @memberOf jS
                 */
                appendToFormula:function (v) {
                    var formula = jS.obj.formula(),
                        fV = formula.val();

                    if (fV.charAt(0) != '=') {
                        fV = '=' + fV;
                    }

                    formula.val(fV + v);
                },

                /**
                 * undo manager integration
                 * @memberOf jS
                 * @namespace
                 */
                undo:{
                    manager:(
                        window.UndoManager
                            ? new UndoManager()
                            : {
                            undo: emptyFN,
                            redo: emptyFN,
                            register: emptyFN,
                            notLoaded: true
                        }
                        ),
                    cells:[],
                    id:0,
                    createCells: function(cells, fn, id) {
                        if (jS.undo.manager.notLoaded) {
                            this.createCells = function(cells, fn, id) {
                                return fn(cells);
                            };
                            return this.createCells(cells, fn, id);
                        }
                        if (id == u) {
                            jS.undo.id++;
                            id = jS.undo.id;
                        }

                        var before = new jSCellRange(cells).clone().cells,
                            after = (fn ? new jSCellRange(fn(cells)).clone().cells : before);

                        before.id = id;
                        after.id = id;

                        jS.undo.manager.register(u, jS.undo.removeCells, [before, id], 'Remove Cells', u, jS.undo.createCells, [after, null, id], 'Create Cells');

                        if (id != jS.undo.id || !fn) {
                            jS.calcLast = new Date();
                            jS.undo.draw(after);
                        }

                        return true;
                    },
                    removeCells: function(cells, id) {
                        var i = 0, index = -1;
                        if (cells.id === id) {
                            index = i;
                        }

                        if (index !== -1) {
                            //jS.undo.cells.splice(index, 1);
                        }
                        jS.undo.draw(cells);
                    },
                    draw: function(clones) {
                        var i;
                        for (i = 0; i < clones.length; i++) {
                            var clone = clones[i],
                                loc = jS.getTdLocation(clone.td),
                                cell = jS.spreadsheets[clone.sheet][loc.row][loc.col];

                            cell.value = clone.value;
                            cell.formula = clone.formula;
                            cell.td = clone.td;
                            cell.dependencies = clone.dependencies;
                            cell.needsUpdated = clone.needsUpdated;
                            cell.calcCount = clone.calcCount;
                            cell.sheet = clone.sheet;
                            cell.calcLast = clone.calcLast;
                            cell.html = clone.html;
                            cell.state = clone.state;
                            cell.jS = clone.jS;
                            cell.calcDependenciesLast = clone.calcDependenciesLast;
                            cell.td.attr('style', clone.style);
                            cell.td.attr('class', clone.cl);

                            jS.updateCellValue.call(cell);
                        }
                    }
                },

                /**
                 * get cols associated with a sheet/table within an instance
                 * @param {jQuery|HTMLElement} [table]
                 * @returns {HTMLCollection|Array}
                 * @memberOf jS
                 */
                cols:function (table) {
                    table = table || jS.obj.table()[0];

                    //table / colGroup / col
                    if (!table) return [];
                    if (!table.colGroup) return [];
                    if (!table.colGroup.children) return [];

                    return table.colGroup.children
                },

                /**
                 * clone tables associated with sheet, and return them free of decorations and enclosure/pane etc.
                 * @param {jQuery|HTMLElement} [tables]
                 * @param {Boolean} [useActualTables]
                 * @returns {jQuery|Element}
                 * @memberOf jS
                 */
                tables:function (tables, useActualTables) {
                    tables = tables || jS.obj.tables();
                    var clonedTables = [],
                        i = tables.length - 1,
                        j,
                        table,
                        tBody,
                        colGroup,
                        colLeft,
                        tdLeft,
                        trTop,
                        trs,
                        tr;

                    do {
                        table = (useActualTables ? document.body.removeChild(tables[i]) : tables[i].cloneNode(true));

                        if (
                            (colGroup = table.children[0])
                                && (colLeft = colGroup.children[0])
                            ) {
                            colGroup.removeChild(colLeft);
                        }

                        if (tBody = table.children[1]) {
                            trs = tBody.children;
                            trTop = trs[0];
                            tBody.removeChild(trTop);
                            j = trs.length - 1;
                            do {
                                tr = trs[j];
                                tdLeft = tr.children[0];
                                tr.removeChild(tdLeft);
                            } while ( j-- > 0 ); //1 because trTop still exists in the array
                        }
                        clonedTables[i] = table;
                    } while (i-- > 0);

                    return jS.sheetDecorateRemove(false, $(clonedTables));
                },

                /**
                 * get col associated with a sheet/table within an instance
                 * @param {jQuery|HTMLElement} table
                 * @param {Number} [i] Index of column, default is last
                 * @returns {Element}
                 * @memberOf jS
                 */
                col:function (table, i) {
                    table = table || jS.obj.table()[0];

                    var cols = jS.cols(table);

                    if (i === u) {
                        i = cols.length - 1;
                    }

                    return cols[i];
                },

                /**
                 * get cells of a table row
                 * @param {HTMLElement} [table]
                 * @param {Number} [i] Index of row, default is last
                 * @returns {HTMLCollection|Array}
                 * @memberOf jS
                 */
                rowTds:function (table, i) {
                    table = table || jS.obj.table();

                    var rows = jS.rows(table);

                    if (!rows.length) {
                        return [];
                    }

                    if (i == u) {
                        i = rows.length - 1;
                    }


                    if (!rows[i]) return []; //tr
                    if (!rows[i].children) return []; //td

                    return rows[i].children;
                },

                /**
                 * Get rows of a sheet/table
                 * @param {HTMLElement} table
                 * @returns {HTMLCollection|Array}
                 * @memberOf jS
                 */
                rows:function (table) {
                    table = table || jS.obj.table()[0];
                    if (!table) return []; //table
                    if (!table.tBody) return []; //tBody
                    if (!table.tBody.children) return []; //tr

                    return table.tBody.children;
                },

                /**
                 * Get all the td objects that are currently highlighted
                 * @param {Boolean} [cells] will return cell objects rather than HTMLElement
                 * @returns {jQuery|HTMLElement|Array}
                 */
                highlighted:function(cells) {
                    var highlighted = jS.highlightedLast.obj || $([]),
                        obj = [],
                        tag,
                        i;

                    if (!(tag = highlighted) || !highlighted.length || !(tag = tag[0]) || !tag.tagName) {
                        return cells ? obj : $(obj);
                    }

                    switch (tag.tagName) {
                        case 'TD':
                            i = highlighted.length - 1;
                            do {
                                obj.unshift(cells ? highlighted[i].jSCell : highlighted[i]);
                            } while (i-- > 0);
                            break;
                        case 'TR':
                            i = highlighted.length - 1;
                            do {
                                if (highlighted[i].tds) {
                                    obj = obj.concat(cells ? highlighted[i].jSCells : highlighted[i].tds);
                                }
                            } while(i-- > 0);
                            break;
                        case 'COL':
                            highlighted = highlighted.filter('col');
                            i = highlighted.length - 1;
                            do {
                                if (highlighted[i].tds) {
                                    obj = obj.concat(cells ? highlighted[i].jSCells : highlighted[i].tds);
                                }
                            } while(i-- > 0);
                            break;
                        case 'TABLE':
                            obj = (cells ? tag.jSCells : tag.tds);
                            break;
                    }

                    return cells ? obj : $(obj);
                },

                /**
                 *
                 * @param {jQuery|HTMLElement} [table]
                 * @returns {Object} {cols, rows}
                 * @memberOf jS
                 */
                sheetSize:function (table) {
                    table = table || jS.obj.table()[0];
                    //table / tBody / tr / td

                    var lastRow = jS.rowTds(table),
                        loc = jS.getTdLocation(lastRow[lastRow.length - 1]);
                    return {
                        cols:loc.col,
                        rows:loc.row
                    };
                },

                test: function() {
                    var s = jS.highlighted(true);
//                        console.log(s);
                    console.log(jS.obj.enclosure())
                },

                sortVerticalSelectAscending:function() {
                    if (confirm('Do you want to extend your selection?')) {
                        jS.sortVertical(); return true;
                    } else {
                        jS.sortVerticalSingle(false); return true
                    }
                },

                sortVerticalSelectDescending:function() {
                    if (confirm('Do you want to extend your selection?')) {
                        jS.sortVertical(); return false;
                    } else {
                        jS.sortVerticalSingle(true); return false
                    }
                },


                /**
                 * Sorts what is highlighted vertically, and updates accordingly
                 * @param {Boolean} [reversed]
                 * @memberOf jS
                 */
                sortVertical:function (reversed) {

                    var selected = jS.highlighted(true),
                        trSibling = selected[0].td.parent().prev(),
                        length = selected.length,
                        date = new Date(),
                        isNum = true,
                        vals = [],
                        row = [],
                        offset,
                        i = 0,
                        cell,
                        val,
                        td;

                    while(i<length){
                        cell = selected[i];
                        td = cell.td[0];
                        if(!isNaN(cell.value)){
                            val = (new Number(cell.value.valueOf()));
                        }
                        else{
                            isNum = false;
                            val = (new String(cell.value.valueOf()));
                        }
                        val.loc = jS.getTdLocation(td);
                        val.row = td.parentNode;
                        val.col = td;
                        val.cell = cell;
                        vals.push(val);
                        i++;
                    }


                    if(reversed){
                        if(isNum == false){
                            vals.sort(function(a,b){return b-a});
                        }
                        else{
                            vals.sort();
                            vals.reverse();
                        }
                    }

                    else
                    {
                        if(isNum == true){
                            vals.sort(function(a,b){return a-b});
                        }
                        else{
                            vals.sort();
                        }
                    }

                    jS.undo.createCells(selected);
                    while(offset = vals.length)                            {
                        val = vals.pop();
                        row = jS.spreadsheets[jS.i].splice(val.row.rowIndex, 1);
                        cell = val.cell;
                        cell.value = val.valueOf();
                        cell.calcLast = 0;
                        val.row.parentNode.removeChild(val.row);
                        trSibling.after(val.row);
                        val.row.children[0].innerHTML = trSibling[0].rowIndex + offset;
                        jS.spreadsheets[jS.i].splice(trSibling[0].rowIndex + 1, 0, row[0]);
                        jS.calcDependencies.call(cell, date, true);
                    }

                    jS.undo.createCells(selected);
                },

                /**
                 * Sorts a single column
                 * @param reversed
                 */
                sortVerticalSingle: function (reversed) {
                    var selected = jS.highlighted(true),
                        length = selected.length,
                        i =  0,
                        num = [],
                        cell;

                    while(i<length){
                        num.push(selected[i].value);
                        i++
                    }
                    if(reversed){
                        num.sort(function(a,b){return b-a});
                    }
                    else{
                        num.sort(function(a,b){return a-b});
                    }
                    while(selected.length){
                        cell = selected.pop();
                        cell.value = num[selected.length];
                        cell.calcLast = 0;
                        jS.updateCellValue.call(cell);
                        jS.updateCellDependencies.call(cell);
                    }
                },

                sortHorizontalSelectAscending:function() {
                    if (confirm('Do you want to extend your selection?')) {
                        jS.sortHorizontal(); return true;
                    } else {
                        jS.sortHorizontalSingle(false); return true;
                    }
                },

                sortHorizontalSelectDescending:function() {
                    if (confirm('Do you want to extend your selection?')) {
                        jS.sortHorizontal(); return false;
                    } else {
                        jS.sortHorizontalSingle(true); return false;
                    }
                },

                /**
                 * Sorts what is highlighted horizontally, and updates accordingly
                 * @param {Boolean} [reversed]
                 * @memberOf jS
                 */
                sortHorizontal:function (reversed) {

                    var selected = jS.highlighted(true),
                        tdSibling = selected[0].td[0],
                        tdSiblingIndex = tdSibling.cellIndex,
                        colGroup = tdSibling.table.colGroup,
                        size = jS.sheetSize().rows,
                        length = selected.length,
                        date = new Date(),
                        isNum = true,
                        vals = [],
                        offset,
                        i = 0,
                        x,
                        cell,
                        val,
                        tr,
                        td;

                    while(i<length){
                        x = 0;
                        cell = selected[i];
                        td = cell.td[0];
                        if(!isNaN(cell.value)){
                            val = new Number(cell.value.valueOf());
                        }
                        else{
                            isNum = false;
                            val = new String(cell.value.valueOf());
                        }
                        val.tds = [];
                        val.loc = jS.getTdLocation(td);
                        val.tr = td.parentNode;
                        val.td = td;
                        val.cell = cell;
                        while(x <= size){
                            val.tds.push(jS.obj.pane().table.children[1].children[x].children[td.cellIndex]);
                            x++;
                        }
                        vals.push(val);
                        i++;

                    }


                    if(reversed){
                        if(isNum == false){
                            vals.sort(function(a,b){return b-a});
                        }
                        else{
                            vals.sort();
                            vals.reverse();
                        }
                    }

                    else
                    {
                        if(isNum == true){
                            vals.sort(function(a,b){return a-b});
                        }
                        else{
                            vals.sort();
                        }
                    }

                    jS.undo.createCells(selected);
                    while(vals.length){
                        val = vals.pop();
                        while(val.tds.length > 1){
                            td = val.tds.pop();
                            tr = td.parentNode;
                            cell = jS.spreadsheets[jS.i][tr.rowIndex].splice(td.cellIndex, 1);
                            tr.insertBefore(td, tr.children[tdSiblingIndex]);
                            td.col = colGroup.children[vals.length + td.cellIndex - 1];
                            td.barTop = td.col.bar;
                            cell.value = td.jSCell.value;
                            cell.calcLast = date;
                            jS.spreadsheets[jS.i][tr.rowIndex].splice(td.cellIndex, 0, cell[0]);
                            jS.calcDependencies.call(cell, date, true);
                        }
                    }
                    jS.undo.createCells(selected);
                },

                /**
                 * Sorts a single row
                 * @param reversed
                 */
                sortHorizontalSingle: function (reversed) {
                    var selected = jS.highlighted(true),
                        length = selected.length,
                        i =  0,
                        num = [],
                        cell;

                    while(i<length){
                        num.push(selected[i].value);
                        i++
                    }
                    if(reversed){
                        num.sort(function(a,b){return b-a});
                    }
                    else{
                        num.sort(function(a,b){return a-b});
                    }
                    while(selected.length){
                        cell = selected.pop();
                        cell.value = num[selected.length];
                        cell.calcLast = 0;
                        jS.updateCellValue.call(cell);
                        jS.updateCellDependencies.call(cell);
                    }
                },

                /**
                 *
                 * @param {jQuery|HTMLElement} [table]
                 * @returns {Object} {cols, rows}
                 * @memberOf jS
                 */
                tableSize:function (table) {
                    table = table || jS.obj.table()[0];
                    //table / tBody / tr / td

                    var trs = (table.children[0].nodeName != "TBODY" ? table.children[1] : table.children[0]).children,
                        tds = trs[trs.length - 1].children,
                        td = tds[tds.length - 1],
                        loc = jS.getTdLocation(td);

                    return {
                        cols:loc.col,
                        rows:loc.row
                    };
                },

                /**
                 * Toggles from editable to viewable and back
                 * @param replacementTables
                 * @memberOf jS
                 */
                toggleState:function (replacementTables) {
                    if (s.allowToggleState) {
                        var tables = replacementTables || jS.tables();
                        if (s.editable) {
                            jS.evt.cellEditAbandon();
                            jS.trigger('sheetSave', [tables]);
                        }
                        jS.setDirty(false);
                        jS.setChanged(true);
                        s.editable = !s.editable;

                        jS.kill();


                        s.parent
                            .html(tables)
                            .sheet(s);
                        s = null;
                    }
                },

                /**
                 * Turns a cell into a formula variable so it can be accessed by a name
                 * @param ref
                 * @memberOf jS
                 */
                setCellRef:function (ref) {
                    function setRef(ref) {
                        if (ref) { //TODO: need to update value when cell is updated
                            jS.s.formulaVariables[ref] = jS.updateCellValue(jS.i, loc.row, loc.col);
                        }
                    }

                    var td = jS.obj.tdActive(),
                        loc = jS.getTdLocation(td);

                    if (ref) {
                        setRef(ref);
                    } else {
                        s.prompt(
                            jS.msg.setCellRef,
                            setRef
                        );
                    }
                },

                /**
                 * @memberOf jS
                 */
                formulaParser: null
            };
        jS.setBusy(true);
        s.parent[0].jS = jS;

        //got tired of ie crashing when console not available
        if (!window.console) window.console = {log:function () {}};

        if (!window.scrollBarSize) {
            window.scrollBarSize = $.sheet.getScrollBarSize();
        }

        //ready the sheet's parser;
        if (window.Formula) {
            jS.formulaParser = window.Formula(jS.cellHandler);
        }

        //We need to take the sheet out of the parent in order to get an accurate reading of it's height and width
        //$(this).html(s.loading);
        s.origHtml = s.parent.children().detach();

        s.parent.addClass(jS.cl.parent);

        s.parent
            .bind('sheetSwitch', function (e, js, i) {
                jS.switchSheet(i);
            })
            .bind('sheetRename', function (e, js, i) {
                jS.renameSheet(i);
            });

        //Use the setting height/width if they are there, otherwise use parent's
        s.width = (s.width ? s.width : s.parent.width());
        s.height = (s.height ? s.height : s.parent.height());


        // Drop functions if they are not needed & save time in recursion
        if (!$.nearest) {
            jS.nearest = emptyFN;
        }

        jS.resizableCells = jS.resizableSheet = jS.resizable;
        if (!$.ui) {
            jS.resizable = jS.resizableCells = jS.resizableSheet = jS.draggable = emptyFN;
        } else {
            if (!s.resizableCells) jS.resizableCells = emptyFN;
            if (!s.resizableSheet) jS.resizableSheet = emptyFN;
        }

        if (!$.support.boxModel) {
            s.boxModelCorrection = 0;
        }

        if (!s.barMenus) {
            jS.controlFactory.barMenuTop = jS.controlFactory.barMenuLeft = emptyFN;
        }

        if (!s.freezableCells) {
            jS.controlFactory.barHandleFreeze.top = jS.controlFactory.barHandleFreeze.left = emptyFN;
        }

        if (s.calcOff) {
            jS.calc = emptyFN;
        }

        if (!window.Raphael) {
            jSE.chart = emptyFN;
        }

        $window
            .resize(function () {
                if (jS && !jS.isBusy()) { //We check because jS might have been killed
                    s.width = s.parent.width();
                    s.height = s.parent.height();
                    jS.sheetSyncSize();
                }
            });


        if ($.sheet.fn) { //If the new calculations engine is alive, fill it too, we will remove above when no longer needed.
            //Extend the calculation engine plugins
            $.sheet.fn = $.extend($.sheet.fn, s.formulaFunctions);

            //Extend the calculation engine with advanced functions
            if ($.sheet.advancedfn) {
                $.sheet.fn = $.extend($.sheet.fn, $.sheet.advancedfn);
            }

            //Extend the calculation engine with finance functions
            if ($.sheet.financefn) {
                $.sheet.fn = $.extend($.sheet.fn, $.sheet.financefn);
            }
        }

        s.title = s.title || s.parent.attr('title') || '';

        jS.s = s;

        s.parent.addClass(jS.cl.uiParent);

        if (s.origHtml.length) {
            jS.openSheet(s.origHtml);
        } else {
            jS.openSheet($(document.createElement('table')));
        }

        jS.setBusy(false);

        return jS;
    },

    /**
     * Creates an HTMLElement from a size given
     * @memberOf jQuery.sheet
     * @param {Object} [size] expects keys rows,cols,
     * @param {Number} [columnWidth] column width as number
     * @param {Number} [rowHeight] row height as number
     * @returns {HTMLElement}
     */
    makeTable:function (size, columnWidth, rowHeight) {
        var doc = document;
        //set defaults;
        size = size || {rows:25, cols:10};
        columnWidth = columnWidth || 120;
        rowHeight = rowHeight || 15;

        //Create elements before loop to make it faster.
        var table = document.createElement('table'),
            colGroup = document.createElement('colgroup'),
            tBody = document.createElement('tbody'),
            colStyle = 'width:' + columnWidth + 'px;',
            rowStyle = 'height:' + rowHeight + 'px;',
            tr,
            col,
            i,
            j;

        i = size.cols;

        do {
            col = document.createElement('col');
            col.setAttribute('style', colStyle);
            colGroup.appendChild(col);
        } while (i-- > 1);

        i = size.rows;
        do {
            tr = document.createElement('tr');
            tr.setAttribute('style', rowStyle);

            j = size.cols;
            do {
                tr.appendChild(document.createElement('td'));
            } while (j-- > 1);

            tBody.appendChild(tr);
        } while (i-- > 1);

        table.appendChild(colGroup);
        table.appendChild(tBody);

        return table;

    },

    /**
     * Destroy all spreadsheets loaded
     * @memberOf jQuery.sheet
     */
    killAll:function () {
        if ($.sheet) {
            var instance = $.sheet.instance;
            if (instance) {
                for (var i = 0; i< instance.length; i++) {
                    if (instance[i] && instance[i].kill) {
                        instance[i].kill();
                    }
                }
                $.sheet.instance = [];
            }
        }
    },

    /**
     * Make 2 or more spreadsheets scroll to together, useful for history viewing or spreadsheet comparison
     * @param {Number} I instance index
     * @memberOf jQuery.sheet
     */
    scrollLocker:function (I) {
        var instance = $.sheet.instance;
        instance[I].obj.panes().each(function (i) {
            var me;
            $(me = this.scrollUI).scroll(function (e) {
                var j = instance.length - 1,
                    scrollUI;
                if (j > -1) {
                    do {
                        scrollUI = instance[j].controls.enclosure[i][0].scrollUI;

                        if (this !== scrollUI) {
                            scrollUI.scrollLeft = me.scrollLeft;
                            scrollUI.scrollTop = me.scrollTop;
                        }
                    } while (j--);
                }
            });
        });
    },

    /**
     * Make 2 or more spreadsheets switch together, just like clicking their tabs at the same time
     * @param {Number} I instance index
     * @memberOf jQuery.sheet
     */
    switchSheetLocker:function (I) {
        $.each($.sheet.instance, function () {
            this.s.parent.bind('sheetSwitch', function (e, jS, i) {
                $.each($.sheet.instance, function () {
                    this.setActiveSheet(i);
                });
            });
        });
    },

    /**
     * Get current instance count
     * @returns {Number}
     * @memberOf jQuery.sheet
     */
    I:function () {
        var I = 0;
        if (this.instance) {
            I = (this.instance.length === 0 ? 0 : this.instance.length - 1); //we use length here because we havent yet created sheet, it will append 1 to this number thus making this the effective instance number
        } else {
            this.instance = [];
        }
        return I;
    },

    /**
     * Get scrollBar size
     * @returns {Object} {height: int, width: int}
     * @memberOf jQuery.sheet
     */
    getScrollBarSize:function () {
        var doc = document,
            inner = $(document.createElement('p')).css({
                width:'100%',
                height:'100%'
            }),
            outer = $(document.createElement('div')).css({
                position:'absolute',
                width:'100px',
                height:'100px',
                top:'0',
                left:'0',
                visibility:'hidden',
                overflow:'hidden'
            }).append(inner);

        $(document.body).append(outer);

        var w1 = inner.width(),
            h1 = inner.height();

        outer.css('overflow', 'scroll');

        var w2 = inner.width(),
            h2 = inner.height();

        if (w1 == w2 && outer[0].clientWidth) {
            w2 = outer[0].clientWidth;
        }
        if (h1 == h2 && outer[0].clientHeight) {
            h2 = outer[0].clientHeight;
        }

        outer.detach();

        var w = w1 - w2, h = h1 - h2;

        return {
            width: w || 15,
            height: h || 15
        };
    },

    debugPositionBox:function (x, y, box, color, which) {
        color = color || '#' + Math.floor(Math.random() * 16777215).toString(16);
        if (box) {
            var $box = $([]);
            $box = $box.add(this.debugPositionBox(box.left, box.top, null, color, 'top-left'));
            $box = $box.add(this.debugPositionBox(box.right, box.top, null, color, 'top-right'));
            $box = $box.add(this.debugPositionBox(box.left, box.bottom, null, color, 'bottom-left'));
            $box = $box.add(this.debugPositionBox(box.right, box.bottom, null, color, 'bottom-right'));
            return $box;
        }
        return $('<div style="width: 10px; height: 10px; position: absolute;"></div>')
            .css('top', (y - 5) + 'px')
            .css('left', (x + 5) + 'px')
            .css('background-color', color)
            .click(function () {
                console.log(which || 'none');
            })
            .appendTo('body');
    }
};/**
 * jQuery.sheet's default formula engine
 * @namespace
 * @memberOf jQuery.sheet
 * @alias jQuery.sheet.engine
 */
var jSE = $.sheet.engine = {
    /**
     * Calculate a spreadsheet
     * @param {Number} sheet
     * @param {Array} spreadsheet [row][cell], [1][1] = SHEET1!A1
     * @param {Function} ignite function to run on every cell
     * @memberOf jSE
     */
    calc:function (sheet, spreadsheet, ignite) {
        spreadsheet = spreadsheet || [];

        var row = spreadsheet.length - 1, col;
        if (row > 0) {
            do {
                if (row > 0 && spreadsheet[row]) {
                    col = spreadsheet[row].length - 1;
                    if (col > 0) {
                        do {
                            ignite(sheet, row, col);
                        } while (col--);
                    }
                }
            } while(row--);
        }
    },

    /**
     * Parse a cell name to it's location
     * @param {String} locStr "A1" = {row: 1, col: 1}
     * @returns {Object} {row: 1, col: 1}
     * @memberOf jQuery.sheet.engine
     */
    parseLocation:function (locStr) {
        for (var firstNum = 0; firstNum < locStr.length; firstNum++) {
            if (locStr.charCodeAt(firstNum) <= 57) {// 57 == '9'
                break;
            }
        }
        return {
            row:parseInt(locStr.substring(firstNum)),
            col:jSE.columnLabelIndex(locStr.substring(0, firstNum))
        };
    },

    /**
     * Parse a sheet name to it's index
     * @param {String} locStr SHEET1 = 0
     * @returns {Number}
     * @memberOf jQuery.sheet.engine
     */
    parseSheetLocation:function (locStr) {
        return ((locStr + '').replace('SHEET', '') * 1) - 1;
    },

    /**
     *
     * @param {Number} col 1 = A
     * @param {Number} row 1 = 1
     * @returns {String}
     * @memberOf jQuery.sheet.engine
     */
    parseCellName:function (col, row) {
        return jSE.columnLabelString(col) + (row || '');
    },

    /**
     * Available labels, used for their index
     * @memberOf jQuery.sheet.engine
     */
    alphabet: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split(''),
    /**
     * Available labels, used for their index
     * @memberOf jQuery.sheet.engine
     */
    columnLabels: {},
    /**
     * Get index of a column label
     * @param {String} str A to 1, B to 2, Z to 26, AA to 27
     * @returns {Number}
     * @memberOf jQuery.sheet.engine
     */
    columnLabelIndex:function (str) {
        return this.columnLabels[str.toUpperCase()];
    },

    /**
     * Available indexes, used for their labels
     * @memberOf jQuery.sheet.engine
     */
    columnIndexes:[],

    /**
     * Get label of a column index
     * @param {Number} index 1 = A, 2 = B, 26 = Z, 27 = AA
     * @returns {String}
     * @memberOf jQuery.sheet.engine
     */
    columnLabelString:function (index) {
        if (!this.columnIndexes.length) { //cache the indexes to save on processing
            var s = '', i, j, k, l;
            i = j = k = -1;
            for (l = 1; l < 16385; ++l) {
                s = '';
                ++k;
                if (k == 26) {
                    k = 0;
                    ++j;
                    if (j == 26) {
                        j = 0;
                        ++i;
                    }
                }
                if (i >= 0) s += this.alphabet[i];
                if (j >= 0) s += this.alphabet[j];
                if (k >= 0) s += this.alphabet[k];
                this.columnIndexes[l] = s;
                this.columnLabels[s] = l;
            }
        }
        return this.columnIndexes[index] || '';
    },

    /**
     * Regular expressions cache
     * @memberOf jQuery.sheet.engine
     */
    regEx: {
        n: 			    /[\$,\s]/g,
        cell: 			/\$?([a-zA-Z]+|[#]REF[!])\$?([0-9]+|[#]REF[!])/gi, //a1
        range: 			/\$?([a-zA-Z]+)\$?([0-9]+):\$?([a-zA-Z]+)\$?([0-9]+)/gi, //a1:a4
        remoteCell:		/\$?(SHEET+)\$?([0-9]+)[:!]\$?([a-zA-Z]+)\$?([0-9]+)/gi, //sheet1:a1
        remoteCellRange:/\$?(SHEET+)\$?([0-9]+)[:!]\$?([a-zA-Z]+)\$?([0-9]+):\$?([a-zA-Z]+)\$?([0-9]+)/gi, //sheet1:a1:b4
        sheet:			/SHEET/i,
        amp: 			/&/g,
        gt: 			/</g,
        lt: 			/>/g,
        nbsp: 			/&nbsp;/g
    },

    /**
     * Creates a chart, piggybacks g Raphael JS
     * @param {Object} o options
     * x: { legend: "", data: [0]}, //x data
     * y: { legend: "", data: [0]}, //y data
     * title: "",
     * data: [0], //chart data
     * legend: "",
     * td: jS.getTd(this.sheet, this.row, this.col), //td container for cell
     * chart: jQuery('<div class="' + jS.cl.chart + '" />') //chart
     * @returns {jQuery|HTMLElement}
     */
    chart:function (o) {
        var jS = this.jS,
            owner = this;

        function sanitize(v, toNum) {
            if (!v) {
                if (toNum) {
                    v = 0;
                } else {
                    v = "";
                }
            } else {
                if (toNum) {
                    v = arrHelpers.toNumbers(v);
                } else {
                    v = arrHelpers.flatten(v);
                }
            }
            return v;
        }

        o = $.extend({
            x:{ legend:"", data:[0]},
            y:{ legend:"", data:[0]},
            title:"",
            data:[0],
            legend:"",
            td:this.td,
            chart:$(document.createElement('div'))
                .addClass(jS.cl.chart)
                .mousedown(function () {
                    o.td.mousedown();
                }),
            gR:{}
        }, o);

        jS.controls.chart[jS.i] = jS.obj.chart().add(o.chart);

        o.data = sanitize(o.data, true);
        o.x.data = sanitize(o.x.data, true);
        o.y.data = sanitize(o.y.data, true);
        o.legend = sanitize(o.legend);
        o.x.legend = sanitize(o.x.legend);
        o.y.legend = sanitize(o.y.legend);

        o.legend = (o.legend ? o.legend : o.data);

        jS.s.parent.one('sheetCalculation', function () {
            var width = o.chart.width(),
                height = o.chart.height(),
                r = Raphael(o.chart[0]);

            if (o.title) r.text(width / 2, 10, o.title).attr({"font-size":20});
            switch (o.type) {
                case "bar":
                    o.gR = r.barchart(width / 8, height / 8, width * 0.8, height * 0.8, o.data, o.legend)
                        .hover(function () {
                            this.flag = r.popup(
                                this.bar.x,
                                this.bar.y,
                                this.bar.value || "0"
                            ).insertBefore(this);
                        }, function () {
                            this.flag.animate({
                                    opacity:0
                                }, 300,

                                function () {
                                    this.remove();
                                }
                            );
                        });
                    break;
                case "hbar":
                    o.gR = r.hbarchart(width / 8, height / 8, width * 0.8, height * 0.8, o.data, o.legend)
                        .hover(function () {
                            this.flag = r.popup(this.bar.x, this.bar.y, this.bar.value || "0").insertBefore(this);
                        }, function () {
                            this.flag.animate({
                                    opacity:0
                                }, 300,
                                function () {
                                    this.remove();
                                }
                            );
                        });
                    break;
                case "line":
                    o.gR = r.linechart(width / 8, height / 8, width * 0.8, height * 0.8, o.x.data, o.y.data, {
                        nostroke:false,
                        axis:"0 0 1 1",
                        symbol:"circle",
                        smooth:true
                    })
                        .hoverColumn(function () {
                            this.tags = r.set();
                            if (this.symbols.length) {
                                for (var i = 0, ii = this.y.length; i < ii; i++) {
                                    this.tags.push(
                                        r
                                            .tag(this.x, this.y[i], this.values[i], 160, 10)
                                            .insertBefore(this)
                                            .attr([
                                                { fill:"#fff" },
                                                { fill:this.symbols[i].attr("fill") }
                                            ])
                                    );
                                }
                            }
                        }, function () {
                            this.tags && this.tags.remove();
                        });

                    break;
                case "pie":
                    o.gR = r.piechart(width / 2, height / 2, (width < height ? width : height) / 2, o.data, {legend:o.legend})
                        .hover(function () {
                            this.sector.stop();
                            this.sector.scale(1.1, 1.1, this.cx, this.cy);

                            if (this.label) {
                                this.label[0].stop();
                                this.label[0].attr({ r:7.5 });
                                this.label[1].attr({ "font-weight":800 });
                            }
                        }, function () {
                            this.sector.animate({ transform:'s1 1 ' + this.cx + ' ' + this.cy }, 500, "bounce");

                            if (this.label) {
                                this.label[0].animate({ r:5 }, 500, "bounce");
                                this.label[1].attr({ "font-weight":400 });
                            }
                        });
                    break;
                case "dot":
                    o.gR = r.dotchart(width / 8, height / 8, width * 0.8, height * 0.8, o.x.data, o.y.data, o.data, {
                        symbol:"o",
                        max:10,
                        heat:true,
                        axis:"0 0 1 1",
                        axisxstep:o.x.data.length - 1,
                        axisystep:o.y.data.length - 1,
                        axisxlabels:(o.x.legend ? o.x.legend : o.x.data),
                        axisylabels:(o.y.legend ? o.y.legend : o.y.data),
                        axisxtype:" ",
                        axisytype:" "
                    })
                        .hover(function () {
                            this.marker = this.marker || r.tag(this.x, this.y, this.value, 0, this.r + 2).insertBefore(this);
                            this.marker.show();
                        }, function () {
                            this.marker && this.marker.hide();
                        });

                    break;
            }

            o.gR
                .mousedown(function () {
                    o.td.mousedown().mouseup();
                });

            o.chart.mousemove(function () {
                o.td.mousemove();
                return false;
            });

        });

        return o.chart;
    }
};/**
 * The functions container of all functions used in jQuery.sheet
 * @namespace
 * @alias jQuery.sheet.fn
 * @name jFN
 */
var jFN = $.sheet.fn = {
    /**
     * information function
     * @param v
     * @returns {Boolean}
     * @this jSCell
     * @memberOf jFN
     */
    ISNUMBER:function (v) {
        var result;
        if (!isNaN(v.valueOf())) {
            result = new Boolean(true);
            result.html = 'TRUE';
            return result;
        }
        result = new Boolean(false);
        result.html = 'FALSE'
        return result;
    },
    /**
     * information function
     * @param v
     * @memberOf jFN
     * @returns {*}
     */
    N:function (v) {
        if (v == null) {
            return 0;
        }
        if (v instanceof Date) {
            return v.getTime();
        }
        if (typeof(v) == 'object') {
            v = v.toString();
        }
        if (typeof(v) == 'string') {
            v = parseFloat(v.replace(jSE.regEx.n, ''));
        }
        if (isNaN(v)) {
            return 0;
        }
        if (typeof(v) == 'number') {
            return v;
        }
        if (v == true) {
            return 1;
        }
        return 0;
    },

    /**
     * information function
     * @returns {*|string}
     * @memberOf jFN
     */
    VERSION:function () {
        return this.jS.version;
    },

    /**
     * math function
     * @param v
     * @returns {number}
     * @memberOf jFN
     */
    ABS:function (v) {
        return Math.abs(jFN.N(v));
    },

    /**
     * math function
     * @param value
     * @param significance
     * @returns {number}
     * @memberOf jFN
     */
    CEILING:function (value, significance) {
        significance = significance || 1;
        return (parseInt(value / significance) * significance) + significance;
    },

    /**
     * math function
     * @param v
     * @returns {number}
     * @memberOf jFN
     */
    EVEN:function (v) {
        v = Math.round(v);
        var even = (v % 2 == 0);
        if (!even) {
            if (v > 0) {
                v++;
            } else {
                v--;
            }
        }
        return v;
    },

    /**
     * math function
     * @param v
     * @returns {number}
     * @memberOf jFN
     */
    EXP:function (v) {
        return Math.exp(v);
    },

    /**
     * math function
     * @param value
     * @param significance
     * @returns {*}
     * @memberOf jFN
     */
    FLOOR:function (value, significance) {
        significance = significance || 1;
        if (
            (value < 0 && significance > 0 )
                || (value > 0 && significance < 0 )
            ) {
            var result = new Number(0);
            result.html = '#NUM';
            return result;
        }
        if (value >= 0) {
            return Math.floor(value / significance) * significance;
        } else {
            return Math.ceil(value / significance) * significance;
        }
    },

    /**
     * math function
     * @param v
     * @returns {number}
     * @memberOf jFN
     */
    INT:function (v) {
        return Math.floor(jFN.N(v));
    },

    /**
     * math function
     * @param v
     * @returns {number}
     * @memberOf jFN
     */
    LN:function (v) {
        return Math.log(v);
    },

    /**
     * math function
     * @param v
     * @param n
     * @returns {number}
     * @memberOf jFN
     */
    LOG:function (v, n) {
        n = n || 10;
        return Math.log(v) / Math.log(n);
    },

    /**
     * math function
     * @param v
     * @returns {*}
     * @memberOf jFN
     */
    LOG10:function (v) {
        return jFN.LOG(v);
    },

    /**
     * math function
     * @param x
     * @param y
     * @returns {number}
     * @memberOf jFN
     */
    MOD:function (x, y) {
        var modulus = x % y;
        if (y < 0) {
            modulus *= -1;
        }
        return modulus;
    },

    /**
     * math function
     * @param v
     * @returns {number}
     * @memberOf jFN
     */
    ODD:function (v) {
        var gTZ = false;
        if (v > 0) {
            v = Math.floor(Math.round(v));
            gTZ = true;
        } else {
            v = Math.ceil(v);
        }

        var vTemp = Math.abs(v);
        if ((vTemp % 2) == 0) { //even
            vTemp++;
        }

        if (gTZ) {
            return vTemp;
        } else {
            return -vTemp;
        }
    },

    /**
     * math function
     * @returns {number}
     * @memberOf jFN
     */
    PI:function () {
        return Math.PI;
    },

    /**
     * math function
     * @param x
     * @param y
     * @returns {number}
     * @memberOf jFN
     */
    POWER:function (x, y) {
        return Math.pow(x, y);
    },

    /**
     * math function
     * @param v
     * @returns {number}
     * @memberOf jFN
     */
    SQRT:function (v) {
        return Math.sqrt(v);
    },

    /**
     * math function
     * @returns {number}
     * @memberOf jFN
     */
    RAND:function () {
        return Math.random();
    },

    /**
     * math function
     * @returns {number}
     * @memberOf jFN
     */
    RND:function () {
        return Math.random();
    },

    /**
     * math function
     * @param v
     * @param decimals
     * @returns {number}
     * @memberOf jFN
     */
    ROUND:function (v, decimals) {
        var shift = Math.pow(10, decimals || 0);
        return Math.round(v * shift) / shift;
    },

    /**
     * math function
     * @param v
     * @param decimals
     * @returns {number}
     * @memberOf jFN
     */
    ROUNDDOWN:function (v, decimals) {
        var neg = (v < 0);
        v = Math.abs(v);
        decimals = decimals || 0;
        v = Math.floor(v * Math.pow(10, decimals)) / Math.pow(10, decimals);
        return (neg ? -v : v);
    },

    /**
     * math function
     * @param v
     * @param decimals
     * @returns {number}
     * @memberOf jFN
     */
    ROUNDUP:function (v, decimals) {
        var neg = (v < 0);
        v = Math.abs(v);
        decimals = decimals || 0;
        v = Math.ceil(v * Math.pow(10, decimals)) / Math.pow(10, decimals);
        return (neg ? -v : v);
    },

    /**
     * math function
     * @returns {number}
     * @memberOf jFN
     */
    SUM:function () {
        var sum = 0,
            v = arrHelpers.toNumbers(arguments),
            i = v.length - 1;

        if (i < 0) {
            return 0;
        }

        do {
            sum += v[i] * 1;
        } while (i--);

        return sum;
    },

    /**
     * math function
     * @param number
     * @param digits
     * @returns {*}
     * @memberOf jFN
     */
    TRUNC:function (number, digits) {
        digits = digits || 0;
        number = number + '';

        if (digits == 0) {
            return number.split('.').shift();
        }

        if (number.match('.')) {
            if (digits == 1) {
                number = number.substr(0, number.length - 1);
            } else if (digits == -1) {
                number = number.split('.').shift();
                number = number.substr(0, number.length - 1) + '0';
            }
        }

        return number;
    },


    /**
     * statistical function
     * @param v
     * @returns {number}
     * @memberOf jFN
     */
    AVERAGE:function (v) {
        return jFN.SUM(arguments) / jFN.COUNT(arguments);
    },

    /**
     * statistical function
     * @param v
     * @returns {*}
     * @memberOf jFN
     */
    AVG:function (v) {
        return jFN.AVERAGE(v);
    },

    /**
     * statistical function
     * @returns {number}
     * @memberOf jFN
     */
    COUNT:function () {
        var count = 0,
            v = arrHelpers.toNumbers(arguments),
            i = v.length - 1;

        if (i < 0) {
            return count;
        }

        do {
            if (v[i] !== null) {
                count++;
            }
        } while (i--);

        return count;
    },

    /**
     * statistical function
     * @returns {number}
     * @memberOf jFN
     */
    COUNTA:function () {
        var count = 0,
            v = arrHelpers.flatten(arguments),
            i = v.length - 1;

        if (i < 0) {
            return count;
        }

        do {
            if (v[i]) {
                count++;
            }
        } while (i--);

        return count;
    },

    /**
     * statistical function
     * @returns {*}
     * @memberOf jFN
     */
    MAX:function () {
        var v = arrHelpers.toNumbers(arguments),
            max = v[0],
            i = v.length - 1;

        if (i < 0) {
            return 0;
        }

        do {
            max = (v[i] > max ? v[i] : max);
        } while (i--);

        return max;
    },

    /**
     * statistical function
     * @returns {*}
     * @memberOf jFN
     */
    MIN:function () {
        var v = arrHelpers.toNumbers(arguments),
            min = v[0],
            i = v.length - 1;

        if (i < 0) {
            return 0;
        }

        do {
            min = (v[i] < min ? v[i] : min);
        } while (i--);

        return min;
    },

    /**
     * string function
     * @param v
     * @returns {Number}
     * @memberOf jFN
     */
    ASC:function (v) {
        return v.charCodeAt(0);
    },
    /**
     * string function
     * @param v
     * @returns {string}
     * @memberOf jFN
     */
    CHAR:function (v) {
        return String.fromCharCode(v);
    },
    /**
     * string function
     * @param v
     * @returns {String}
     * @memberOf jFN
     */
    CLEAN:function (v) {
        var exp = new RegExp("[\cG\x1B\cL\cJ\cM\cI\cK\x07\x1B\f\n\r\t\v]","g");
        return v.replace(exp, '');
    },
    /**
     * string function
     * @param v
     * @returns {*}
     * @memberOf jFN
     */
    CODE:function (v) {
        return jFN.ASC(v);
    },
    /**
     * string function
     * @returns {String}
     * @memberOf jFN
     */
    CONCATENATE:function () {
        var arr = arrHelpers.flatten(arguments),
            result = '',
            cell = this;
        jQuery.each(arr, function (i) {
            result += arr[i];
        });
        return result;
    },
    /**
     * string function
     * @param v
     * @param decimals
     * @param symbol
     * @returns {Number}
     * @memberOf jFN
     */
    DOLLAR:function (v, decimals, symbol) {
        decimals = decimals || 2;
        symbol = symbol || '$';

        var result = new Number(v),
            r = jFN.FIXED(v, decimals, false);

        if (v >= 0) {
            result.html = symbol + r;
        } else {
            result.html = '(' + symbol + r.slice(1) + ')';
        }
        return result;
    },
    /**
     * string function
     * @param v
     * @param decimals
     * @param noCommas
     * @returns {String}
     * @memberOf jFN
     */
    FIXED:function (v, decimals, noCommas) {
        decimals = (decimals === undefined ? 2 : decimals);
        var multiplier = Math.pow( 10, decimals),
            result,
            v = Math.round( v * multiplier ) / multiplier;



        result = new String(v.toFixed(decimals));
        result.html = Globalize.format(v, 'n' + decimals);

        if (noCommas) {
            result.html = result.html.replace(Globalize.culture().numberFormat[','], '');
        }

        return result;

    },
    /**
     * string function
     * @param v
     * @param numberOfChars
     * @returns {string}
     * @memberOf jFN
     */
    LEFT:function (v, numberOfChars) {
        numberOfChars = numberOfChars || 1;
        return v.substring(0, numberOfChars);
    },
    /**
     * string function
     * @param v
     * @returns {*}
     * @memberOf jFN
     */
    LEN:function (v) {
        if (!v) {
            return 0;
        }
        return v.length;
    },
    /**
     * string function
     * @param v
     * @returns {string}
     * @memberOf jFN
     */
    LOWER:function (v) {
        return v.toLowerCase();
    },

    /**
     * string function
     * @param v
     * @param start
     * @param end
     * @returns {*}
     * @memberOf jFN
     */
    MID:function (v, start, end) {
        if (!v || !start || !end) {
            return this.jS.s.error({error:'ERROR'});
        }
        return v.substring(start - 1, end + start - 1);
    },
    /**
     * string function
     * @param oldText
     * @param start
     * @param numberOfChars
     * @param newText
     * @returns {*}
     * @memberOf jFN
     */
    REPLACE:function (oldText, start, numberOfChars, newText) {
        if (!oldText || !start || !numberOfChars || !newText) {
            return this.jS.s.error({error:'ERROR'});
        }
        var result = oldText.split('');
        result.splice(start - 1, numberOfChars);
        result.splice(start - 1, 0, newText);
        return result.join('');
    },
    /**
     * string function
     * @param v
     * @param times
     * @returns {string}
     * @memberOf jFN
     */
    REPT:function (v, times) {
        var result = '';
        for (var i = 0; i < times; i++) {
            result += v;
        }
        return result;
    },
    /**
     * string function
     * @param v
     * @param numberOfChars
     * @returns {string}
     * @memberOf jFN
     */
    RIGHT:function (v, numberOfChars) {
        numberOfChars = numberOfChars || 1;
        return v.substring(v.length - numberOfChars, v.length);
    },
    /**
     * string function
     * @param find
     * @param body
     * @param start
     * @returns {*}
     * @memberOf jFN
     */
    SEARCH:function (find, body, start) {
        start = start || 0;
        if (start) {
            body = body.split('');
            body.splice(0, start - 1);
            body = body.join('');
        }
        var i = body.search(find);

        if (i < 0) {
            return this.jS.s.error({error:'#VALUE!'});
        }

        return start + (start ? 0 : 1) + i;
    },
    /**
     * string function
     * @param text
     * @param oldText
     * @param newText
     * @param nthAppearance
     * @returns {string}
     * @memberOf jFN
     */
    SUBSTITUTE:function (text, oldText, newText, nthAppearance) {
        nthAppearance = nthAppearance || 0;
        oldText = new RegExp(oldText, 'g');
        var i = 1;
        text = text.replace(oldText, function (match, contents, offset, s) {
            var result = match;
            if (nthAppearance) {
                if (i >= nthAppearance) {
                    result = newText;
                }
            } else {
                result = newText;
            }

            i++;
            return result;
        });
        return text;
    },
    /**
     * string function
     * @returns {*}
     * @memberOf jFN
     */
    TEXT:function () {
        return this.jS.s.error({error:'Not Yet Implemented'});
    },
    /**
     * string function
     * @param v
     * @returns {string}
     * @memberOf jFN
     */
    UPPER:function (v) {
        return v.toUpperCase();
    },
    /**
     * string function
     * @param v
     * @returns {*}
     * @memberOf jFN
     */
    VALUE:function (v) {
        if (jQuery.isNumeric(v)) {
            return v *= 1;
        } else {
            return this.jS.s.error({error:"#VALUE!"})
        }
    },

    /**
     * date/time function
     * @returns {Date}
     * @memberOf jFN
     */
    NOW:function () {
        var today = new Date();
        today.html = dates.toString(today);
        return today;
    },
    /**
     * date/time function
     * @returns {Number}
     * @memberOf jFN
     */
    TODAY:function () {
        var today = new Date(),
            result = new Number(dates.toCentury(today) - 1);
        result.html = dates.toString(today, 'd');
        return result;
    },
    /**
     * date/time function
     * @param weeksBack
     * @returns {Date}
     * @memberOf jFN
     */
    WEEKENDING:function (weeksBack) {
        var date = new Date();
        date = new Date(
            date.getFullYear(),
            date.getMonth(),
            date.getDate() + 5 - date.getDay() - ((weeksBack || 0) * 7)
        );

        date.html = dates.toString(date, 'd');
        return date;
    },
    /**
     * date/time function
     * @param date
     * @param returnValue
     * @returns {number}
     * @memberOf jFN
     */
    WEEKDAY:function (date, returnValue) {
        date = dates.get(date);
        var day = date.getDay();

        returnValue = (returnValue ? returnValue : 1);
        switch (returnValue) {
            case 3:
                switch (day) {
                    case 0:return 7;
                    case 1:return 1;
                    case 2:return 2;
                    case 3:return 3;
                    case 4:return 4;
                    case 5:return 5;
                    case 6:return 6;
                }
                break;
            case 2:
                switch (day) {
                    case 0:return 6;
                    case 1:return 0;
                    case 2:return 1;
                    case 3:return 2;
                    case 4:return 3;
                    case 5:return 4;
                    case 6:return 5;
                }
                break;
            case 1:
                day++;
                break;
        }

        return day;
    },
    /**
     * date/time function
     * @param date
     * @returns {number}
     * @memberOf jFN
     */
    WEEKNUM:function (date) {//TODO: implement week starting
        date = dates.get(date);
        return dates.week(date) + 1;
    },
    /**
     * date/time function
     * @param date
     * @returns {number}
     * @memberOf jFN
     */
    YEAR:function (date) {
        date = dates.get(date);
        return date.getFullYear();
    },
    /**
     * date/time function
     * @param year
     * @param month
     * @param day
     * @returns {number}
     * @memberOf jFN
     */
    DAYSFROM:function (year, month, day) {
        return Math.floor((new Date() - new Date(year, (month - 1), day)) / dates.dayDiv);
    },
    /**
     * date/time function
     * @param v1
     * @param v2
     * @returns {number}
     * @memberOf jFN
     */
    DAYS:function (v1, v2) {
        var date1 = dates.get(v1),
            date2 = dates.get(v2),
            ONE_DAY = 1000 * 60 * 60 * 24;
        return Math.round(Math.abs(date1.getTime() - date2.getTime()) / ONE_DAY);
    },
    /**
     * date/time function
     * @param date
     * @returns {number}
     * @memberOf jFN
     */
    DAY:function (date) {
        date = dates.get(date);
        return date.getDate();
    },
    /**
     * date/time function
     * @param date1
     * @param date2
     * @param method
     * @returns {number}
     * @memberOf jFN
     */
    DAYS360:function (date1, date2, method) {
        date1 = dates.get(date1);
        date2 = dates.get(date2);

        var startDate = date1.getDate(),
            endDate = date2.getDate(),
            startIsLastDay = dates.isLastDayOfMonth(date1),
            endIsLastDay = dates.isLastDayOfMonth(date2),
            monthCount = dates.diffMonths(date1, date2);

        if (method) {//Euro method
            startDate = Math.min(startDate, 30);
            endDate = Math.min(endDate, 30);
        } else { //Standard
            if (startIsLastDay) {
                startDate = 30;
            }
            if (endIsLastDay) {
                if (startDate < 30) {
                    monthCount++;
                    endDate = 1;
                } else {
                    endDate = 30;
                }
            }
        }

        return (monthCount * 30) + (endDate - startDate);
    },
    /**
     * date/time function
     * @param year
     * @param month
     * @param day
     * @returns {Number}
     * @memberOf jFN
     */
    DATE:function (year, month, day) {
        var date = new Date(year, month - 1, day),
            result = new Number(dates.toCentury(date));
        result.html = dates.toString(date, 'd');

        return result;
    },
    /**
     * date/time function
     * @param date
     * @returns {Number}
     * @memberOf jFN
     */
    DATEVALUE:function (date) {
        date = dates.get(date);
        var result = new Number(dates.toCentury(date));
        result.html = dates.toString(date, 'd');
        return result;
    },
    /**
     * date/time function
     * @param date
     * @param months
     * @returns {Number}
     * @memberOf jFN
     */
    EDATE:function (date, months) {
        date = dates.get(date);
        date.setMonth(date.getMonth() + months);
        var result = new Number(dates.toCentury(date));
        result.html = dates.toString(date, 'd');
        return result;
    },
    /**
     * date/time function
     * @param date
     * @param months
     * @returns {Number}
     * @memberOf jFN
     */
    EOMONTH:function (date, months) {
        date = dates.get(date);
        date.setMonth(date.getMonth() + months + 1);
        date = new Date(date.getFullYear(), date.getMonth(), 0);
        var result = new Number(dates.toCentury(date));
        result.html = dates.toString(date, 'd');
        return result;
    },
    /**
     * date/time function
     * @param time
     * @returns {*}
     * @memberOf jFN
     */
    HOUR:function (time) {
        time = times.fromMath(time);
        return time.hour;
    },
    /**
     * date/time function
     * @param time
     * @returns {*}
     * @memberOf jFN
     */
    MINUTE:function (time) {
        return times.fromMath(time).minute;
    },
    /**
     * date/time function
     * @param date
     * @returns {number}
     * @memberOf jFN
     */
    MONTH:function (date) {
        date = dates.get(date);
        return date.getMonth() + 1;
    },
    /**
     * date/time function
     * @param time
     * @returns {*}
     * @memberOf jFN
     */
    SECOND:function (time) {
        return times.fromMath(time).second;
    },
    /**
     * date/time function
     * @param hour
     * @param minute
     * @param second
     * @returns {number}
     * @memberOf jFN
     */
    TIME:function (hour, minute, second) {
        second = (second ? second : 0);
        minute = (minute ? minute : 0);
        hour = (hour ? hour : 0);

        if (second && second > 60) {
            var minuteFromSecond = (((second / 60) + '').split('.')[0]) * 1;
            second = second - (minuteFromSecond * 60);
            minute += minuteFromSecond;
        }

        if (minute && minute > 60) {
            var hourFromMinute = (((minute / 60) + '').split('.')[0]) * 1;
            minute = minute - (hourFromMinute * 60);
            hour += hourFromMinute;
        }

        var millisecond = (hour * 60 * 60 * 1000) + (minute * 60 * 1000) + (second * 1000);

        return millisecond / dates.dayDiv;
    },
    /**
     * date/time function
     * @param time
     * @returns {*}
     * @memberOf jFN
     */
    TIMEVALUE:function (time) {
        if (!isNaN(time)) {
            return time;
        }
        if (/([0]?[1-9]|1[0-2])[:][0-5][0-9]([:][0-5][0-9])?[ ]?(AM|am|aM|Am|PM|pm|pM|Pm)/.test(time)) {
            return times.fromString(time, true);
        } else if (/([0]?[0-9]|1[0-9]|2[0-3])[:][0-5][0-9]([:][0-5][0-9])?/.test(time)) {
            return times.fromString(time);
        }
        return 0;
    },
    /**
     * date/time function
     * @param startDate
     * @param days
     * @param holidays
     * @returns {Number}
     * @memberOf jFN
     */
    WORKDAY:function (startDate, days, holidays) {
        var workDays = {1:true, 2:true, 3:true, 4:true, 5:true},
            startDate = dates.get(startDate),
            days = (days && !isNaN(days) ? days : 0),
            dayCounter = 0,
            daysSoFar = 0,
            workingDate = startDate,
            result;

        if (holidays) {
            if (!jQuery.isArray(holidays)) {
                holidays = [holidays];
            }
            holidays = arrHelpers.flatten(holidays);
            var holidaysTemp = {};
            jQuery.each(holidays, function (i) {
                if (holidays[i]) {
                    holidaysTemp[dates.toString(dates.get(holidays[i]), 'd')] = true;
                }
            });
            holidays = holidaysTemp;
        } else {
            holidays = {};
        }

        while (daysSoFar < days) {
            workingDate = new Date(workingDate.setDate(workingDate.getDate() + 1));
            if (workDays[workingDate.getDay()]) {
                if (!holidays[dates.toString(workingDate, 'd')]) {
                    daysSoFar++;
                }
            }
            dayCounter++;
        }

        result = new Number(dates.toCentury(workingDate));
        result.html = dates.toString(workingDate, 'd');
        return result;
    },
    /**
     * date/time function
     * @param startDate
     * @param endDate
     * @param basis
     * @returns {*}
     * @memberOf jFN
     */
    YEARFRAC:function (startDate, endDate, basis) {
        startDate = dates.get(startDate);
        endDate = dates.get(endDate);

        if (!startDate || !endDate) {
            return this.jS.s.error({error:'#VALUE!'});
        }

        basis = (basis ? basis : 0);
        this.html = [];

        var numerator = dates.diff(startDate, endDate, basis),
            denom = dates.calcAnnualBasis(startDate, endDate, basis);
        return numerator / denom;
    },

    /**
     * logical function
     * @returns {*}
     * @memberOf jFN
     */
    AND:function () {
        var args = arguments,
            res,
            cell = this;
        $.each(args, function (i) {
            if (args[i].valueOf() !== true && res == undefined) {
                res = jFN.FALSE();
            }
        });
        if (!res) {
            res = jFN.TRUE();
        }
        return res;
    },
    /**
     * logical function
     * @returns {Boolean}
     * @memberOf jFN
     */
    FALSE:function () {
        var result = new Boolean(false);
        result.html = 'FALSE';
        return result;
    },
    /**
     * logical function
     * @param expression
     * @param resultTrue
     * @param resultFalse
     * @returns {*}
     * @memberOf jFN
     */
    IF:function (expression, resultTrue, resultFalse) {
        if (expression.valueOf()) {
            return resultTrue;
        } else {
            return resultFalse;
        }
    },
    /**
     * logical function
     * @param v
     * @returns {Boolean}
     * @memberOf jFN
     */
    NOT:function (v) {
        var result;
        if (!v.valueOf()) {
            result = new Boolean(true);
            result.html = 'TRUE';
        } else {
            result = new Boolean(false);
            result.html = 'FALSE';
        }

        return result;
    },
    /**
     * logical function
     * @returns {Boolean}
     * @memberOf jFN
     */
    OR:function () {
        var args = arguments,
            result,
            i = args.length - 1,
            v;

        if (i > -1) {
            do {
                v = args[i].valueOf();
                if (v) {
                    result = new Boolean(true);
                    result.html = 'TRUE';
                    return result;
                }
            } while (i--);
        }
        result = new Boolean(false);
        result.html = 'FALSE';
        return result;
    },
    /**
     * logical function
     * @returns {Boolean}
     * @memberOf jFN
     */
    TRUE:function () {
        var result = new Boolean(true);
        result.html = 'TRUE';
        return result;
    },
    /**
     * logical function
     * @param left
     * @param right
     * @returns {Boolean}
     * @memberOf jFN
     */
    GREATER:function(left, right) {
        var result;

        if (left > right) {
            result = new Boolean(true);
            result.html = 'TRUE';
        } else {
            result = new Boolean(false);
            result.html = 'FALSE';
        }

        return result;
    },
    /**
     * logical function
     * @param left
     * @param right
     * @returns {Boolean}
     * @memberOf jFN
     */
    LESS:function(left, right) {
        var result;

        if (left < right) {
            result = new Boolean(true);
            result.html = 'TRUE';
        } else {
            result = new Boolean(false);
            result.html = 'FALSE';
        }

        return result;
    },
    /**
     * logical function
     * @param left
     * @param right
     * @returns {Boolean}
     * @memberOf jFN
     */
    EQUAL: function(left, right) {
        var result;

        if (left.valueOf() == right.valueOf()) {
            result = new Boolean(true);
            result.html = 'TRUE';
        } else {
            result = new Boolean(false);
            result.html = 'FALSE';
        }

        return result;
    },
    /**
     * logical function
     * @param left
     * @param right
     * @returns {Boolean}
     * @memberOf jFN
     */
    GREATER_EQUAL:function(left, right) {
        var result;

        if (left >= right) {
            result = new Boolean(true);
            result.html = 'TRUE';
        } else {
            result = new Boolean(false);
            result.html = 'FALSE';
        }

        return result;
    },
    /**
     * logical function
     * @param left
     * @param right
     * @returns {Boolean}
     * @memberOf jFN
     */
    LESS_EQUAL:function(left, right) {
        var result;

        if (left <= right) {
            result = new Boolean(true);
            result.html = 'TRUE';
        } else {
            result = new Boolean(false);
            result.html = 'FALSE';
        }

        return result;
    },

    /**
     * html function
     * @param v
     * @returns {String}
     * @memberOf jFN
     */
    IMG:function (v) {
        var result = new String('');
        result.html = $(document.createElement('img'))
            .attr('src', v);
        return result;
    },
    /**
     * html function
     * @param v
     * @returns {*}
     * @memberOf jFN
     */
    TRIM:function (v) {
        if (typeof(v) == 'string') {
            v = $.trim(v);
        }
        return v;
    },
    /**
     * html function
     * @param link
     * @param [name]
     * @returns {String}
     * @memberOf jFN
     */
    HYPERLINK:function (link, name) {
        name = name || 'LINK';
        var result = new String(name);
        result.html = $(document.createElement('a'))
            .attr('href', link)
            .attr('target', '_new')
            .text(name);

        return result;
    },
    /**
     * html function
     * @returns {*}
     * @memberOf jFN
     */
    DROPDOWN:function () {
        var cell = this,
            jS = this.jS,
            v,
            html = this.td.children().detach(),
            loc,
            $td = $(cell.td),
            select,
            id,
            result;

        if (!html.length || cell.needsUpdated) {
            v = arrHelpers.flatten(arguments);
            v = arrHelpers.unique(v);
            loc = jS.getTdLocation(cell.td);
            id = "dropdown" + this.sheet + "_" + loc.row + "_" + loc.col + '_' + jS.I;

            select = document.createElement('select');
            select.setAttribute('name', id);
            select.setAttribute('id', id);
            select.className = 'jSDropdown';
            select.cell = this;

            select.onmouseup = function() {
                jS.cellEdit($td);
            };
            select.onchange = function () {
                cell.value = this.value;
                jS.calcDependencies.call(cell, cell.calcDependenciesLast);
            };

            jS.controls.inputs[jS.i] = jS.obj.inputs().add(select);

            for (var i = 0; i < (v.length <= 50 ? v.length : 50); i++) {
                if (v[i]) {
                    var opt = document.createElement('option');
                    opt.setAttribute('value', v[i]);
                    opt.text = opt.innerText = v[i];
                    select.appendChild(opt);
                }
            }

            if (!jS.s.editable) {
                select.setAttribute('disabled', true);
            } else {
                jS.s.parent.bind('sheetKill', function() {
                    $td.text(cell.value = select.value);
                });
            }

            select.value = cell.value || v[0];
            select.onchange();
        }
        if (typeof cell.value != 'object') {
            result = new String(cell.value);
        }
        result.html = select;
        return result;
    },
    /**
     * html function
     * @returns {*}
     * @memberOf jFN
     */
    RADIO:function () {
        var cell = this,
            jS = this.jS,
            v,
            html = this.td.children().detach(),
            loc,
            $td,
            inputs,
            $inputs,
            radio,
            id,
            result;

        if (!html.length || cell.needsUpdated) {
            v = arrHelpers.flatten(arguments);
            v = arrHelpers.unique(v);
            loc = jS.getTdLocation(cell.td);
            $td = $(cell.td);
            inputs = [];
            id = "radio" + this.sheet + "_" + loc.row + "_" + loc.col + '_' + jS.I;

            radio = document.createElement('span');
            radio.className = 'jSRadio';
            radio.onmousedown = function () {
                jS.cellEdit($td);
            };
            radio.jSCell = cell;
            jS.controls.inputs[jS.i] = jS.obj.inputs().add(radio);

            for (var i = 0; i < (v.length <= 25 ? v.length : 25); i++) {
                if (v[i]) {
                    var input = document.createElement('input'),
                        label = document.createElement('span');

                    input.setAttribute('type', 'radio');
                    input.setAttribute('name', id);
                    input.className = id;
                    input.value = v[i];
                    input.onchange = function() {
                        cell.value = jQuery(this).val();
                        jS.calcDependencies.call(cell, cell.calcDependenciesLast);
                    };

                    inputs.push(input);

                    if (v[i] == cell.value) {
                        input.setAttribute('checked', 'true');
                        input.onchange();
                    }
                    label.textContent = label.innerText = v[i];
                    radio.appendChild(input);
                    radio.input = input;
                    label.onclick = function () {
                        $(this).prev().click();
                    };
                    radio.appendChild(label);
                    radio.appendChild(document.createElement('br'));
                }
            }

            $inputs = $(inputs);

            if (!jS.s.editable) {
                cell.value = $inputs.filter(':checked').val();
                $inputs.attr('disabled', true);
            } else {
                jS.s.parent.bind('sheetKill', function() {
                    cell.value = $inputs.filter(':checked').val();
                    $td.text(cell.value);
                });
            }
        }

        if (typeof cell.value != 'object') {
            result = new String(cell.value);
        }

        result.html = radio;

        return result;
    },
    /**
     * html function
     * @param v
     * @returns {*}
     * @memberOf jFN
     */
    CHECKBOX:function (v) {
        if ($.isArray(v)) v = v[0];

        var cell = this,
            jS = this.jS,
            html = this.td.children().detach(),
            loc,
            checkbox,
            $td,
            label,
            id,
            result;

        if ((!html.length || cell.needsUpdated)) {
            loc = jS.getTdLocation(cell.td);
            checkbox = $([]);
            $td = $(cell.td);
            id = "checkbox" + this.sheet + "_" + loc.row + "_" + loc.col + '_' + jS.I;
            checkbox = document.createElement('input');
            checkbox.setAttribute('type', 'checkbox');
            checkbox.setAttribute('name', id);
            checkbox.className = id;
            checkbox.value = v;
            checkbox.onchange = function () {
                if ($(this).is(':checked')) {
                    cell.value = v;
                } else {
                    cell.value = '';
                }
                jS.calcDependencies.call(cell, cell.calcDependenciesLast);
            };

            if (!jS.s.editable) {
                checkbox.setAttribute('disabled', 'true');
            } else {
                jS.s.parent.bind('sheetKill', function() {
                    cell.value = (cell.value == 'true' || $(checkbox).is(':checked') ? v : '');
                    $td.text(cell.value);
                });
            }

            html = document.createElement('span');
            html.className='SCheckbox';
            html.appendChild(checkbox);
            label = document.createElement('span');
            label.textContent = label.innerText = v;
            html.appendChild(label);
            html.appendChild(document.createElement('br'));
            html.onmousedown = function () {
                jS.cellEdit($td);
            };
            html.cell = cell;

            jS.controls.inputs[jS.i] = jS.obj.inputs().add(html);

            if (v == cell.value) {
                checkbox.setAttribute('checked', true);
                checkbox.onchange();
            }
        }

        result = new String(cell.value == 'true' || $(checkbox).is(':checked') ? v : '');
        result.html = html;
        return result;
    },
    /**
     * html function
     * @param values
     * @param legend
     * @param title
     * @returns {String}
     * @memberOf jFN
     */
    BARCHART:function (values, legend, title) {
        var result = new String('');
        result.html = jSE.chart.call(this, {
            type:'bar',
            data:values,
            legend:legend,
            title:title
        });
        return result;
    },
    /**
     * html function
     * @param values
     * @param legend
     * @param title
     * @returns {String}
     * @memberOf jFN
     */
    HBARCHART:function (values, legend, title) {
        var result = new String('');
        result.html = jSE.chart.call(this, {
            type:'hbar',
            data:values,
            legend:legend,
            title:title
        });
        return result;
    },
    /**
     * html function
     * @param valuesX
     * @param valuesY
     * @returns {String}
     * @memberOf jFN
     */
    LINECHART:function (valuesX, valuesY) {
        var result = new String('');
        result.html = jSE.chart.call(this, {
            type:'line',
            x:{
                data:valuesX
            },
            y:{
                data:valuesY
            },
            title:""
        });
        return result;
    },
    /**
     * html function
     * @param values
     * @param legend
     * @param title
     * @returns {String}
     * @memberOf jFN
     */
    PIECHART:function (values, legend, title) {
        var result = new String('');
        result.html = jSE.chart.call(this, {
            type:'pie',
            data:values,
            legend:legend,
            title:title
        });
        return result;
    },
    /**
     * html function
     * @param valuesX
     * @param valuesY
     * @param values
     * @param legendX
     * @param legendY
     * @param title
     * @returns {String}
     * @memberOf jFN
     */
    DOTCHART:function (valuesX, valuesY, values, legendX, legendY, title) {
        var result = new String('');
        result.html = jSE.chart.call(this, {
            type:'dot',
            data:(values ? values : valuesX),
            x:{
                data:valuesX,
                legend:legendX
            },
            y:{
                data:(valuesY ? valuesY : valuesX),
                legend:(legendY ? legendY : legendX)
            },
            title:title
        });
        return result;
    },
    /**
     * html function
     * @param v
     * @returns {*}
     * @memberOf jFN
     */
    CELLREF:function (v) {
        return (this.jS.spreadsheets[v] ? this.jS.spreadsheets[v] : 'Cell Reference Not Found');
    },
    /**
     * html function
     * @param [pre] text before
     * @param [post] test after
     * @returns {string}
     * @memberOf jFN
     */
    CALCTIME:function (pre, post) {
        pre = pre || '';
        post = post || '';

        var cell = this,
            jS = this.jS;

        this.jS.s.parent.one('sheetCalculation', function () {
            jS.time.last = jS.calcLast;
            cell.td.text(pre + jS.time.diff() + post);
        });
        return "";
    },


    /**
     * cell function
     * @param value
     * @param tableArray
     * @param indexNumber
     * @param notExactMatch
     * @returns {*}
     * @memberOf jFN
     */
    HLOOKUP:function (value, tableArray, indexNumber, notExactMatch) {
        var jS = this.jS,
            lookupTable = this.jS.cellLookup.call(this),
            result = {html: '#N/A', value:''};

        indexNumber = indexNumber || 1;
        notExactMatch = notExactMatch !== undefined ? notExactMatch : true;

        if (isNaN(value)) {
            var i = tableArray[0].indexOf(value);
            if (i > -1) {
                result = jS.updateCellValue(lookupTable[i].sheet, indexNumber, jS.getTdLocation(lookupTable[i].td).col);
            }
        } else {
            arrHelpers.getClosestNum(value, tableArray[0], function(closest, i) {
                var num = jS.updateCellValue(lookupTable[i].sheet, indexNumber, jS.getTdLocation(lookupTable[i].td).col);

                if (notExactMatch) {
                    result = num;
                } else if (num == value) {
                    result = num;
                }
            });
        }

        return result;
    },
    /**
     * cell function
     * @param value
     * @param tableArray
     * @param indexNumber
     * @param notExactMatch
     * @returns {*}
     * @memberOf jFN
     */
    VLOOKUP:function (value, tableArray, indexNumber, notExactMatch) {
        var jS = this.jS,
            lookupTable = this.jS.cellLookup.call(this),
            result = {html: '#N/A', value:''};

        notExactMatch = notExactMatch !== undefined ? notExactMatch : true;

        if (isNaN(value)) {
            var i = tableArray[0].indexOf(value);
            if (i > -1) {
                result = jS.updateCellValue(lookupTable[i].sheet, indexNumber, jS.getTdLocation(lookupTable[i].td).col);
            }
        } else {
            arrHelpers.getClosestNum(value, tableArray[0], function(closest, i) {
                var num = jS.updateCellValue(lookupTable[i].sheet, jS.getTdLocation(lookupTable[i].td).row, indexNumber);

                if (notExactMatch) {
                    result = num;
                } else if (num == value) {
                    result = num;
                }
            });
        }

        return result;
    },
    /**
     * cell function
     * @param col
     * @returns {*}
     * @memberOf jFN
     */
    THISROWCELL:function (col) {
        var jS = this.jS, loc = jS.getTdLocation(this.td);
        if (isNaN(col)) {
            col = jSE.columnLabelIndex(col);
        }
        return jS.updateCellValue(this.sheet, loc.row, col);
    },
    /**
     * cell function
     * @param row
     * @returns {*}
     * @memberOf jFN
     */
    THISCOLCELL:function (row) {
        var jS = this.jS, loc = jS.getTdLocation(this.td);
        return jS.updateCellValue(this.sheet, row, loc.col);
    }
};var key = { /* key objects, makes it easier to develop */
    BACKSPACE: 			8,
    CAPS_LOCK: 			20,
    COMMA: 				188,
    CONTROL: 			17,
    ALT:				18,
    DELETE: 			46,
    DOWN: 				40,
    END: 				35,
    ENTER: 				13,
    ESCAPE: 			27,
    HOME: 				36,
    INSERT: 			45,
    LEFT: 				37,
    NUMPAD_ADD: 		107,
    NUMPAD_DECIMAL: 	110,
    NUMPAD_DIVIDE: 		111,
    NUMPAD_ENTER: 		108,
    NUMPAD_MULTIPLY: 	106,
    NUMPAD_SUBTRACT: 	109,
    PAGE_DOWN: 			34,
    PAGE_UP: 			33,
    PERIOD: 			190,
    RIGHT: 				39,
    SHIFT: 				16,
    SPACE: 				32,
    TAB: 				9,
    UP: 				38,
    C:                  67,
    F:					70,
    V:					86,
    X:                  88,
    Y:					89,
    Z:					90
};

var arrHelpers = window.arrHelpers = {
    math: Math,
    toNumbers:function (arr) {
        arr = this.flatten(arr);
        var i = arr.length - 1;

        if (i < 0) {
            return [];
        }

        do {
            if (arr[i]) {
                arr[i] = $.trim(arr[i]);
                if (isNaN(arr[i])) {
                    arr[i] = 0;
                } else {
                    arr[i] = arr[i] * 1;
                }
            } else {
                arr[i] = 0;
            }
        } while (i--);

        return arr;
    },
    unique:function (arr) {
        var a = [],
            l = arr.length;
        for (var i = 0; i < l; i++) {
            for (var j = i + 1; j < l; j++) {
                // If this[i] is found later in the array
                if (arr[i] === arr[j])
                    j = ++i;
            }
            a.push(arr[i]);
        }
        return a;
    },
    flatten:function (arr) {
        var flat = [];
        for (var i = 0, l = arr.length; i < l; i++) {
            var type = Object.prototype.toString.call(arr[i]).split(' ').pop().split(']').shift().toLowerCase();
            if (type) {
                flat = flat.concat(/^(array|collection|arguments|object)$/.test(type) ? this.flatten(arr[i]) : arr[i]);
            }
        }
        return flat;
    },
    insertAt:function (arr, val, index) {
        $(val).each(function () {
            if (index > -1 && index <= arr.length) {
                arr.splice(index, 0, this);
            }
        });
        return arr;
    },
    closest:function (array, num, min, max) {
        array = array || [];
        num = num || 0;
        min = min || 0;
        max = max || array.length - 1;

        var target;

        while (true) {
            target = ((min + max) >> 1);
            if (target === max || target === min) {
                return array[target];
            }
            if (array[target] > num) {
                max = target;
            } else if (array[target] < num) {
                min = target;
            } else {
                return array[target];
            }
        }
    },
    getClosestNum: function(num, ar, fn) {
        var i = 0, I, closest, closestDiff, currentDiff;
        if(ar.length) {
            closest = ar[0];
            I = i;
            for(i;i<ar.length;i++) {
                closestDiff = Math.abs(num - closest);
                currentDiff = Math.abs(num - ar[i]);
                if(currentDiff < closestDiff)
                {
                    I = i;
                    closest = ar[i];
                }
                closestDiff = null;
                currentDiff = null;
            }
            //returns first element that is closest to number
            if (fn) {
                return fn(closest, I);
            }
            return closest;
        }
        //no length
        return false;
    }
};

var dates = {
    dayDiv: 86400000,
    math: Math,
    toCentury:function (date, dayDiv) {
        dayDiv = dayDiv || 86400000;

        return this.math.round(this.math.abs((new Date(1900, 0, -1)) - date) / dayDiv);
    },
    get:function (date, dayDiv) {
        dayDiv = dayDiv || 86400000;

        if (date.getMonth) {
            return date;
        } else if (isNaN(date)) {
            return new Date(Globalize.parseDate(date));
        } else {
            date *= dayDiv;
            //date = new Date(date);
            var newDate = (new Date(1900, 0, -1)) * 1;
            date += newDate;
            date = new Date(date);
            return date;
        }
    },
    week:function (date, dayDiv) {
        dayDiv = dayDiv || 86400000;

        var onejan = new Date(date.getFullYear(), 0, 1);
        return this.math.ceil((((date - onejan) / dayDiv) + onejan.getDay() + 1) / 7);
    },
    toString:function (date, pattern) {
        if (!pattern) {
            return Globalize.format(date);
        }
        return Globalize.format(date, Globalize.culture().calendar.patterns[pattern]);
    },
    diff:function (start, end, basis, dayDiv) {
        dayDiv = dayDiv || 86400000;

        switch (basis) {
            case 0:
                return this.days360Nasd(start, end, 0, true);
            case 1:
            case 2:
            case 3:
                var result = this.math.abs(end - start) / dayDiv;
                return result;
            case 4:
                return this.days360Euro(start, end);
        }

        return 0;
    },
    diffMonths:function (start, end) {
        var months;
        months = (end.getFullYear() - start.getFullYear()) * 12;
        months -= start.getMonth() + 1;
        months += end.getMonth() + 1;
        return months;
    },
    days360:function (startYear, endYear, startMonth, endMonth, startDate, endDate) {
        return ((endYear - startYear) * 360) + ((endMonth - startMonth) * 30) + (endDate - startDate)
    },
    days360Nasd:function (start, end, method, useEom) {
        var startDate = start.getDate(),
            startMonth = start.getMonth(),
            startYear = start.getFullYear(),
            endDate = end.getDate(),
            endMonth = end.getMonth(),
            endYear = end.getFullYear();

        if (
            (endMonth == 2 && this.isEndOfMonth(endDate, endMonth, endYear)) &&
                (
                    (startMonth == 2 && this.isEndOfMonth(startDate, startMonth, startYear)) ||
                        method == 3
                    )
            ) {
            endDate = 30;
        }

        if (endDate == 31 && (startDate >= 30 || method == 3)) {
            endDate = 30;
        }

        if (startDate == 31) {
            startDate = 30;
        }

        if (useEom && startMonth == 2 && this.isEndOfMonth(startDate, startMonth, startYear)) {
            startDate = 30;
        }

        return this.days360(startYear, endYear, startMonth, endMonth, startDate, endDate);
    },
    days360Euro:function (start, end) {
        var startDate = start.getDate(),
            startMonth = start.getMonth(),
            startYear = start.getFullYear(),
            endDate = end.getDate(),
            endMonth = end.getMonth(),
            endYear = end.getFullYear();

        if (startDate == 31) startDate = 30;
        if (endDate == 31) endDate = 30;

        return this.days360(startYear, endYear, startMonth, endMonth, startDate, endDate);
    },
    isEndOfMonth:function (day, month, year) {
        return day == (new Date(year, month + 1, 0, 23, 59, 59)).getDate();
    },
    isLeapYear:function (year) {
        return new Date(year, 1, 29).getMonth() == 1;
    },
    calcAnnualBasis:function (start, end, basis) {
        switch (basis) {
            case 0:
            case 2:
            case 4: return 360;
            case 3: return 365;
            case 1:
                var startDate = start.getDate(),
                    startMonth = start.getMonth(),
                    startYear = start.getFullYear(),
                    endDate = end.getDate(),
                    endMonth = end.getMonth(),
                    endYear = end.getFullYear(),
                    result = 0;

                if (startYear == endYear) {
                    if (this.isLeapYear(startYear)) {
                        result = 366;
                    } else {
                        result = 365;
                    }
                } else if (((endYear - 1) == startYear) && ((startMonth > endMonth) || ((startMonth == endMonth) && startDate >= endDate))) {
                    if (this.isLeapYear(startYear)) {
                        if (startMonth < 2 || (startMonth == 2 && startDate <= 29)) {
                            result = 366;
                        } else {
                            result = 365;
                        }
                    } else if (this.isLeapYear(endYear)) {
                        if (endMonth > 2 || (endMonth == 2 && endDate == 29)) {
                            result = 366;
                        } else {
                            result = 365;
                        }
                    } else {
                        result = 365;
                    }
                } else {
                    for (var iYear = startYear; iYear <= endYear; iYear++) {
                        if (this.isLeapYear(iYear)) {
                            result += 366;
                        } else {
                            result += 365;
                        }
                    }
                    result = result / (endYear - startYear + 1);
                }
                return result;
        }
        return 0;
    },
    lastDayOfMonth:function (date) {
        date.setDate(0);
        return date.getDate();
    },
    isLastDayOfMonth:function (date) {
        return (date.getDate() == this.lastDayOfMonth(date));
    }
};

var times = window.times = {
    math: Math,
    fromMath:function (time) {
        var result = {}, me = this;

        result.hour = ((time * 24) + '').split('.')[0] * 1;

        result.minute = function (time) {
            time = me.math.round(time * 24 * 100) / 100;
            time = (time + '').split('.');
            var minute = 0;
            if (time[1]) {
                if (time[1].length < 2) {
                    time[1] += '0';
                }
                minute = time[1] * 0.6;
            }
            return me.math.round(minute);
        }(time);

        result.second = function (time) {
            time = me.math.round(time * 24 * 10000) / 10000;
            time = (time + '').split('.');
            var second = 0;
            if (time[1]) {
                for (var i = 0; i < 4; i++) {
                    if (!time[1].charAt(i)) {
                        time[1] += '0';
                    }
                }
                var secondDecimal = ((time[1] * 0.006) + '').split('.');
                if (secondDecimal[1]) {
                    if (secondDecimal[1] && secondDecimal[1].length > 2) {
                        secondDecimal[1] = secondDecimal[1].substr(0, 2);
                    }

                    return me.math.round(secondDecimal[1] * 0.6);
                }
            }
            return second;
        }(time);

        return result;
    },
    fromString:function (time, isAmPm) {
        var date = new Date(), timeParts = time, timeValue, hour, minute, second, meridiem;
        if (isAmPm) {
            meridiem = timeParts.substr(-2).toLowerCase(); //get ampm;
            timeParts = timeParts.replace(/(am|pm)/i, '');
        }

        timeParts = timeParts.split(':');
        hour = timeParts[0] * 1;
        minute = timeParts[1] * 1;
        second = (timeParts[2] ? timeParts[2] : 0) * 1;

        if (isAmPm && meridiem == 'pm') {
            hour += 12;
        }

        return jFN.TIME(hour, minute, second);
    }
};


$.extend(Math, {
    log10:function (arg) {
        // http://kevin.vanzonneveld.net
        // +   original by: Philip Peterson
        // +   improved by: Onno Marsman
        // +   improved by: Tod Gentille
        // +   improved by: Brett Zamir (http://brett-zamir.me)
        // *	 example 1: log10(10);
        // *	 returns 1: 1
        // *	 example 2: log10(1);
        // *	 returns 2: 0
        return Math.log(arg) / 2.302585092994046; // Math.LN10
    },
    signum:function (x) {
        return (x / Math.abs(x)) || x;
    },
    log1p: function (x) {
        // http://kevin.vanzonneveld.net
        // +   original by: Brett Zamir (http://brett-zamir.me)
        // %          note 1: Precision 'n' can be adjusted as desired
        // *     example 1: log1p(1e-15);
        // *     returns 1: 9.999999999999995e-16

        var ret = 0,
            n = 50; // degree of precision
        if (x <= -1) {
            return '-INF'; // JavaScript style would be to return Number.NEGATIVE_INFINITY
        }
        if (x < 0 || x > 1) {
            return Math.log(1 + x);
        }
        for (var i = 1; i < n; i++) {
            if ((i % 2) === 0) {
                ret -= Math.pow(x, i) / i;
            } else {
                ret += Math.pow(x, i) / i;
            }
        }
        return ret;
    }
});$.print = function (s) {
    var w = win.open();
    w.document.write("<html><body><xmp>" + s + "\n</xmp></body></html>");
    w.document.close();
};

//This is a fix for Jison
if (!Object.getPrototypeOf) {
    Object.getPrototypeOf = function(obj) {
        return obj || {};
    };
}

//IE8 fix
if (!Array.prototype.indexOf) {
    $.sheet.max = 60;
    Array.prototype.indexOf = function(obj, start) {
        for (var i = (start || 0), j = this.length; i < j; i++) {
            if (this[i] === obj) { return i; }
        }
        return -1;
    }
}

    return Sheet;
})(jQuery, document, window, Date, String, Number, Boolean, Math, RegExp, Error);


