bootstrap-editable-rails 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ /*! X-editable - v1.1.1
2
+ * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
3
+ * http://github.com/vitalets/x-editable
4
+ * Copyright (c) 2012 Vitaliy Potapov; Licensed MIT */
5
+ (function(e){var t=function(t,n){this.options=e.extend({},e.fn.editableform.defaults,n),this.$element=e(t),this.initInput()};t.prototype={constructor:t,initInput:function(){var t,n;if(typeof e.fn.editableform.types[this.options.type]!="function"){e.error("Unknown type: "+this.options.type);return}t=e.fn.editableform.types[this.options.type],n=e.fn.editableform.utils.sliceObj(this.options,e.fn.editableform.utils.objectKeys(t.defaults)),this.input=new t(n),this.value=this.input.str2value(this.options.value)},initTemplate:function(){this.$form=e(e.fn.editableform.template)},initButtons:function(){this.$form.find(".editable-buttons").append(e.fn.editableform.buttons)},render:function(){this.$loading=e(e.fn.editableform.loading),this.$element.empty().append(this.$loading),this.showLoading(),this.initTemplate(),this.options.showbuttons?this.initButtons():this.$form.find(".editable-buttons").remove(),this.$element.triggerHandler("rendering"),e.when(this.input.render()).then(e.proxy(function(){this.$form.find("div.editable-input").append(this.input.$input),this.options.showbuttons||this.input.autosubmit(),this.input.$clear&&this.$form.find("div.editable-input").append(e('<div class="editable-clear">').append(this.input.$clear)),this.$element.append(this.$form),this.$form.find(".editable-cancel").click(e.proxy(this.cancel,this)),this.input.error?(this.error(this.input.error),this.$form.find(".editable-submit").attr("disabled",!0),this.input.$input.attr("disabled",!0)):(this.error(!1),this.input.$input.removeAttr("disabled"),this.$form.find(".editable-submit").removeAttr("disabled"),this.input.value2input(this.value),this.$form.submit(e.proxy(this.submit,this))),this.$element.triggerHandler("rendered"),this.showForm()},this))},cancel:function(){this.$element.triggerHandler("cancel")},showLoading:function(){var e;this.$form?(this.$loading.width(this.$form.outerWidth()),this.$loading.height(this.$form.outerHeight()),this.$form.hide()):(e=this.$loading.parent().width(),e&&this.$loading.width(e)),this.$loading.show()},showForm:function(){this.$loading.hide(),this.$form.show(),this.input.activate(),this.$element.triggerHandler("show")},error:function(t){var n=this.$form.find(".control-group"),r=this.$form.find(".editable-error-block");t===!1?(n.removeClass(e.fn.editableform.errorGroupClass),r.removeClass(e.fn.editableform.errorBlockClass).empty().hide()):(n.addClass(e.fn.editableform.errorGroupClass),r.addClass(e.fn.editableform.errorBlockClass).text(t).show())},submit:function(t){t.stopPropagation(),t.preventDefault();var n,r=this.input.input2value(),i;if(n=this.validate(r)){this.error(n),this.showForm();return}i=this.input.value2str(r);if(i==this.input.value2str(this.value)){this.cancel();return}e.when(this.save(i)).done(e.proxy(function(e){var t=typeof this.options.success=="function"?this.options.success.call(this,e,r):null;if(t&&typeof t=="string"){this.error(t),this.showForm();return}t&&typeof t=="object"&&t.hasOwnProperty("newValue")&&(r=t.newValue),this.error(!1),this.value=r,this.$element.triggerHandler("save",{newValue:r,response:e})},this)).fail(e.proxy(function(e){this.error(typeof e=="string"?e:e.responseText||e.statusText||"Unknown error!"),this.showForm()},this))},save:function(t){var n=typeof this.options.pk=="function"?this.options.pk.call(this):this.options.pk,r=!!(typeof this.options.url=="function"||this.options.url&&(this.options.send==="always"||this.options.send==="auto"&&n)),i,s;if(r)return this.showLoading(),i={name:this.options.name||"",value:t,pk:n},typeof this.options.params=="function"?e.extend(i,this.options.params.call(this,i)):(this.options.params=e.fn.editableform.utils.tryParseJson(this.options.params,!0),e.extend(i,this.options.params)),typeof this.options.url=="function"?this.options.url.call(this,i):(s=e.extend({url:this.options.url,data:i,type:"post",dataType:"json"},this.options.ajaxOptions),e.ajax(s))},validate:function(e){e===undefined&&(e=this.value);if(typeof this.options.validate=="function")return this.options.validate.call(this,e)},option:function(e,t){this.options[e]=t,e==="value"&&this.setValue(t)},setValue:function(e,t){t?this.value=this.input.str2value(e):this.value=e}},e.fn.editableform=function(n){var r=arguments;return this.each(function(){var i=e(this),s=i.data("editableform"),o=typeof n=="object"&&n;s||i.data("editableform",s=new t(this,o)),typeof n=="string"&&s[n].apply(s,Array.prototype.slice.call(r,1))})},e.fn.editableform.Constructor=t,e.fn.editableform.defaults={type:"text",url:null,params:null,name:null,pk:null,value:null,send:"auto",validate:null,success:function(e,t){},ajaxOptions:null,showbuttons:!0},e.fn.editableform.template='<form class="form-inline editableform"><div class="control-group"><div><div class="editable-input"></div><div class="editable-buttons"></div></div><div class="editable-error-block"></div></div></form>',e.fn.editableform.loading='<div class="editableform-loading"></div>',e.fn.editableform.buttons='<button type="submit" class="editable-submit">ok</button><button type="button" class="editable-cancel">cancel</button>',e.fn.editableform.errorGroupClass=null,e.fn.editableform.errorBlockClass="editable-error",e.fn.editableform.types={},e.fn.editableform.utils={}})(window.jQuery),function(e){e.fn.editableform.utils={inherit:function(e,t){var n=function(){};n.prototype=t.prototype,e.prototype=new n,e.prototype.constructor=e,e.superclass=t.prototype},setCursorPosition:function(e,t){if(e.setSelectionRange)e.setSelectionRange(t,t);else if(e.createTextRange){var n=e.createTextRange();n.collapse(!0),n.moveEnd("character",t),n.moveStart("character",t),n.select()}},tryParseJson:function(e,t){if(typeof e=="string"&&e.length&&e.match(/^[\{\[].*[\}\]]$/))if(t)try{e=(new Function("return "+e))()}catch(n){}finally{return e}else e=(new Function("return "+e))();return e},sliceObj:function(t,n,r){var i,s,o={};if(!e.isArray(n)||!n.length)return o;for(var u=0;u<n.length;u++){i=n[u],t.hasOwnProperty(i)&&(o[i]=t[i]);if(r===!0)continue;s=i.toLowerCase(),t.hasOwnProperty(s)&&(o[i]=t[s])}return o},getConfigData:function(t){var n={};return e.each(t.data(),function(e,t){if(typeof t!="object"||t&&typeof t=="object"&&t.constructor===Object)n[e]=t}),n},objectKeys:function(e){if(Object.keys)return Object.keys(e);if(e!==Object(e))throw new TypeError("Object.keys called on a non-object");var t=[],n;for(n in e)Object.prototype.hasOwnProperty.call(e,n)&&t.push(n);return t}}}(window.jQuery),function(e){var t=function(e,t){this.init(e,t)};t.prototype={containerName:null,innerCss:null,init:function(n,r){this.$element=e(n),this.options=e.extend({},e.fn.editableContainer.defaults,e.fn.editableform.utils.getConfigData(this.$element),r),this.splitOptions(),this.initContainer(),this.$element.on("destroyed",e.proxy(function(){this.destroy()},this)),e(document).data("editable-handlers-attached")||(e(document).on("keyup.editable",function(t){t.which===27&&e(".editable-open").editableContainer("hide")}),e(document).on("click.editable",function(n){var r=e(n.target);if(r.is(".editable-container")||r.parents(".editable-container").length||r.parents(".ui-datepicker-header").length)return;t.prototype.closeOthers(n.target)}),e(document).data("editable-handlers-attached",!0))},splitOptions:function(){this.containerOptions={},this.formOptions={};var t=e.fn[this.containerName].defaults;for(var n in this.options)n in t?this.containerOptions[n]=this.options[n]:this.formOptions[n]=this.options[n]},initContainer:function(){this.call(this.containerOptions)},initForm:function(){return this.$form=e("<div>").editableform(this.formOptions).on({save:e.proxy(this.save,this),cancel:e.proxy(this.cancel,this),show:e.proxy(this.setPosition,this),rendering:e.proxy(this.setPosition,this),rendered:e.proxy(function(){this.$element.triggerHandler("shown")},this)}),this.$form},tip:function(){return this.container().$tip},container:function(){return this.$element.data(this.containerName)},call:function(){this.$element[this.containerName].apply(this.$element,arguments)},show:function(e){this.$element.addClass("editable-open"),e!==!1&&this.closeOthers(this.$element[0]),this.innerShow()},innerShow:function(){this.call("show"),this.tip().addClass("editable-container"),this.initForm(),this.tip().find(this.innerCss).empty().append(this.$form),this.$form.editableform("render")},hide:function(){if(!this.tip()||!this.tip().is(":visible")||!this.$element.hasClass("editable-open"))return;this.$element.removeClass("editable-open"),this.innerHide(),this.$element.triggerHandler("hidden")},innerHide:function(){this.call("hide")},toggle:function(e){this.tip&&this.tip().is(":visible")?this.hide():this.show(e)},setPosition:function(){},cancel:function(){this.options.autohide&&this.hide(),this.$element.triggerHandler("cancel")},save:function(e,t){this.options.autohide&&this.hide(),this.$element.triggerHandler("save",t)},option:function(e,t){this.options[e]=t,e in this.containerOptions?(this.containerOptions[e]=t,this.setContainerOption(e,t)):(this.formOptions[e]=t,this.$form&&this.$form.editableform("option",e,t))},setContainerOption:function(e,t){this.call("option",e,t)},destroy:function(){this.call("destroy")},closeOthers:function(t){e(".editable-open").each(function(n,r){if(r===t)return;var i=e(r),s=i.data("editableContainer");if(!s)return;s.options.onblur==="cancel"?i.data("editableContainer").hide():s.options.onblur==="submit"&&i.data("editableContainer").tip().find("form").submit()})},activate:function(){this.tip&&this.tip().is(":visible")&&this.$form&&this.$form.data("editableform").input.activate()}},e.fn.editableContainer=function(n){var r=arguments;return this.each(function(){var i=e(this),s="editableContainer",o=i.data(s),u=typeof n=="object"&&n;o||i.data(s,o=new t(this,u)),typeof n=="string"&&o[n].apply(o,Array.prototype.slice.call(r,1))})},e.fn.editableContainer.Constructor=t,e.fn.editableContainer.defaults={value:null,placement:"top",autohide:!0,onblur:"cancel"},jQuery.event.special.destroyed={remove:function(e){e.handler&&e.handler()}}}(window.jQuery),function(e){var t=function(t,n){this.$element=e(t),this.options=e.extend({},e.fn.editable.defaults,e.fn.editableform.utils.getConfigData(this.$element),n),this.init()};t.prototype={constructor:t,init:function(){var t,n=!1,r,i;this.isInit=!0;if(!e.fn.editableContainer){e.error("You must define $.fn.editableContainer via including corresponding file (e.g. editable-popover.js)");return}this.options.name=this.options.name||this.$element.attr("id");if(typeof e.fn.editableform.types[this.options.type]!="function"){e.error("Unknown type: "+this.options.type);return}t=e.fn.editableform.types[this.options.type],this.typeOptions=e.fn.editableform.utils.sliceObj(this.options,e.fn.editableform.utils.objectKeys(t.defaults)),this.input=new t(this.typeOptions),this.options.value===undefined||this.options.value===null?(this.value=this.input.html2value(e.trim(this.$element.html())),n=!0):(typeof this.options.value=="string"&&(this.options.value=e.trim(this.options.value)),this.value=this.input.str2value(this.options.value)),this.$element.addClass("editable"),this.options.toggle!=="manual"?(this.$element.addClass("editable-click"),this.$element.on(this.options.toggle+".editable",e.proxy(function(e){e.preventDefault();if(this.options.toggle==="mouseenter")this.show();else{var t=this.options.toggle!=="click";this.toggle(t)}},this))):this.$element.attr("tabindex",-1),r=!n&&this.value!==null&&this.value!==undefined,r&=this.options.autotext==="always"||this.options.autotext==="auto"&&!this.$element.text().length,e.when(r?this.input.value2html(this.value,this.$element):!0).then(e.proxy(function(){this.options.disabled?this.disable():this.enable(),this.$element.triggerHandler("render",this),this.isInit=!1},this))},enable:function(){this.options.disabled=!1,this.$element.removeClass("editable-disabled"),this.handleEmpty(),this.options.toggle!=="manual"&&this.$element.attr("tabindex")==="-1"&&this.$element.removeAttr("tabindex")},disable:function(){this.options.disabled=!0,this.hide(),this.$element.addClass("editable-disabled"),this.handleEmpty(),this.$element.attr("tabindex",-1)},toggleDisabled:function(){this.options.disabled?this.enable():this.disable()},option:function(t,n){if(t&&typeof t=="object"){e.each(t,e.proxy(function(t,n){this.option(e.trim(t),n)},this));return}this.options[t]=n;if(t==="disabled"){n?this.disable():this.enable();return}t==="value"&&this.setValue(n),this.container&&this.container.option(t,n)},handleEmpty:function(){var t="editable-empty";this.options.disabled?this.$element.hasClass(t)&&(this.$element.empty(),this.$element.removeClass(t)):e.trim(this.$element.text())===""?this.$element.addClass(t).text(this.options.emptytext):this.$element.removeClass(t)},show:function(t){if(this.options.disabled)return;if(!this.container){var n=e.extend({},this.options,{value:this.value,autohide:!1});this.$element.editableContainer(n),this.$element.on({save:e.proxy(this.save,this),cancel:e.proxy(this.hide,this)}),this.container=this.$element.data("editableContainer")}else if(this.container.tip().is(":visible"))return;this.container.show(t)},hide:function(){this.container&&this.container.hide(),this.options.enablefocus&&this.options.toggle==="click"&&this.$element.focus()},toggle:function(e){this.container&&this.container.tip().is(":visible")?this.hide():this.show(e)},save:function(e,t){typeof this.options.url!="function"&&t.response===undefined&&this.input.value2str(this.value)!==this.input.value2str(t.newValue)?this.$element.addClass("editable-unsaved"):this.$element.removeClass("editable-unsaved"),this.hide(),this.setValue(t.newValue)},validate:function(){if(typeof this.options.validate=="function")return this.options.validate.call(this,this.value)},setValue:function(t,n){n?this.value=this.input.str2value(t):this.value=t,this.container&&this.container.option("value",this.value),e.when(this.input.value2html(this.value,this.$element)).then(e.proxy(function(){this.handleEmpty(),this.$element.triggerHandler("render",this)},this))},activate:function(){this.container&&this.container.activate()}},e.fn.editable=function(n){var r={},i=arguments,s="editable";switch(n){case"validate":return this.each(function(){var t=e(this),n=t.data(s),i;n&&(i=n.validate())&&(r[n.options.name]=i)}),r;case"getValue":return this.each(function(){var t=e(this),n=t.data(s);n&&n.value!==undefined&&n.value!==null&&(r[n.options.name]=n.input.value2str(n.value))}),r;case"submit":var o=arguments[1]||{},u=this,a=this.editable("validate"),f;return typeof o.error!="function"&&(o.error=function(){}),e.isEmptyObject(a)?(f=this.editable("getValue"),o.data&&e.extend(f,o.data),e.ajax({type:"POST",url:o.url,data:f,dataType:"json"}).success(function(e){typeof e=="object"&&e.id?(u.editable("option","pk",e.id),u.removeClass("editable-unsaved"),typeof o.success=="function"&&o.success.apply(u,arguments)):o.error.apply(u,arguments)}).error(function(){o.error.apply(u,arguments)})):o.error.call(u,{errors:a}),this}return this.each(function(){var r=e(this),o=r.data(s),u=typeof n=="object"&&n;o||r.data(s,o=new t(this,u)),typeof n=="string"&&o[n].apply(o,Array.prototype.slice.call(i,1))})},e.fn.editable.defaults={type:"text",disabled:!1,toggle:"click",emptytext:"Empty",autotext:"auto",enablefocus:!1,value:null}}(window.jQuery),function(e){var t=function(){};t.prototype={init:function(t,n,r){this.type=t,this.options=e.extend({},r,n),this.$input=null,this.$clear=null,this.error=null},render:function(){this.$input=e(this.options.tpl),this.options.inputclass&&this.$input.addClass(this.options.inputclass),this.options.placeholder&&this.$input.attr("placeholder",this.options.placeholder)},value2html:function(t,n){var r=this.escape(t);e(n).html(r)},html2value:function(t){return e("<div>").html(t).text()},value2str:function(e){return e},str2value:function(e){return e},value2input:function(e){this.$input.val(e)},input2value:function(){return this.$input.val()},activate:function(){this.$input.is(":visible")&&this.$input.focus()},clear:function(){this.$input.val(null)},escape:function(t){return e("<div>").text(t).html()},autosubmit:function(){}},t.defaults={tpl:"",inputclass:"span2",name:null},e.extend(e.fn.editableform.types,{"abstract":t})}(window.jQuery),function(e){var t=function(e){};e.fn.editableform.utils.inherit(t,e.fn.editableform.types.abstract),e.extend(t.prototype,{render:function(){t.superclass.render.call(this);var n=e.Deferred();return this.error=null,this.sourceData=null,this.prependData=null,this.onSourceReady(function(){this.renderList(),n.resolve()},function(){this.error=this.options.sourceError,n.resolve()}),n.promise()},html2value:function(e){return null},value2html:function(n,r){var i=e.Deferred();return this.onSourceReady(function(){this.value2htmlFinal(n,r),i.resolve()},function(){t.superclass.value2html(this.options.sourceError,r),i.resolve()}),i.promise()},onSourceReady:function(t,n){if(e.isArray(this.sourceData)){t.call(this);return}try{this.options.source=e.fn.editableform.utils.tryParseJson(this.options.source,!1)}catch(r){n.call(this);return}if(typeof this.options.source=="string"){var i=this.options.source+(this.options.name?"-"+this.options.name:""),s;e(document).data(i)||e(document).data(i,{}),s=e(document).data(i);if(s.loading===!1&&s.sourceData){this.sourceData=s.sourceData,t.call(this);return}if(s.loading===!0){s.callbacks.push(e.proxy(function(){this.sourceData=s.sourceData,t.call(this)},this)),s.err_callbacks.push(e.proxy(n,this));return}s.loading=!0,s.callbacks=[],s.err_callbacks=[],e.ajax({url:this.options.source,type:"get",cache:!1,data:this.options.name?{name:this.options.name}:{},dataType:"json",success:e.proxy(function(r){s.loading=!1,this.sourceData=this.makeArray(r),e.isArray(this.sourceData)?(this.doPrepend(),s.sourceData=this.sourceData,t.call(this),e.each(s.callbacks,function(){this.call()})):(n.call(this),e.each(s.err_callbacks,function(){this.call()}))},this),error:e.proxy(function(){s.loading=!1,n.call(this),e.each(s.err_callbacks,function(){this.call()})},this)})}else this.sourceData=this.makeArray(this.options.source),e.isArray(this.sourceData)?(this.doPrepend(),t.call(this)):n.call(this)},doPrepend:function(){if(this.options.prepend===null||this.options.prepend===undefined)return;e.isArray(this.prependData)||(this.options.prepend=e.fn.editableform.utils.tryParseJson(this.options.prepend,!0),typeof this.options.prepend=="string"&&(this.options.prepend={"":this.options.prepend}),this.prependData=this.makeArray(this.options.prepend)),e.isArray(this.prependData)&&e.isArray(this.sourceData)&&(this.sourceData=this.prependData.concat(this.sourceData))},renderList:function(){},value2htmlFinal:function(e,t){},makeArray:function(t){var n,r,i=[],s;if(!t||typeof t=="string")return null;if(e.isArray(t)){s=function(e,t){r={value:e,text:t};if(n++>=2)return!1};for(var o=0;o<t.length;o++)typeof t[o]=="object"?(n=0,e.each(t[o],s),n===1?i.push(r):n>1&&t[o].hasOwnProperty("value")&&t[o].hasOwnProperty("text")&&i.push(t[o])):i.push({value:t[o],text:t[o]})}else e.each(t,function(e,t){i.push({value:e,text:t})});return i},itemByVal:function(t){if(e.isArray(this.sourceData))for(var n=0;n<this.sourceData.length;n++)if(this.sourceData[n].value==t)return this.sourceData[n]}}),t.defaults=e.extend({},e.fn.editableform.types.abstract.defaults,{source:null,prepend:!1,sourceError:"Error when loading list"}),e.fn.editableform.types.list=t}(window.jQuery),function(e){var t=function(e){this.init("text",e,t.defaults)};e.fn.editableform.utils.inherit(t,e.fn.editableform.types.abstract),e.extend(t.prototype,{activate:function(){this.$input.is(":visible")&&(this.$input.focus(),e.fn.editableform.utils.setCursorPosition(this.$input.get(0),this.$input.val().length))}}),t.defaults=e.extend({},e.fn.editableform.types.abstract.defaults,{tpl:'<input type="text">',placeholder:null}),e.fn.editableform.types.text=t}(window.jQuery),function(e){var t=function(e){this.init("textarea",e,t.defaults)};e.fn.editableform.utils.inherit(t,e.fn.editableform.types.abstract),e.extend(t.prototype,{render:function(){t.superclass.render.call(this),this.$input.keydown(function(t){t.ctrlKey&&t.which===13&&e(this).closest("form").submit()})},value2html:function(t,n){var r="",i;if(t){i=t.split("\n");for(var s=0;s<i.length;s++)i[s]=e("<div>").text(i[s]).html();r=i.join("<br>")}e(n).html(r)},html2value:function(t){if(!t)return"";var n=t.split(/<br\s*\/?>/i);for(var r=0;r<n.length;r++)n[r]=e("<div>").html(n[r]).text();return n.join("\n")},activate:function(){this.$input.is(":visible")&&(e.fn.editableform.utils.setCursorPosition(this.$input.get(0),this.$input.val().length),this.$input.focus())}}),t.defaults=e.extend({},e.fn.editableform.types.abstract.defaults,{tpl:"<textarea></textarea>",inputclass:"span3",placeholder:null}),e.fn.editableform.types.textarea=t}(window.jQuery),function(e){var t=function(e){this.init("select",e,t.defaults)};e.fn.editableform.utils.inherit(t,e.fn.editableform.types.list),e.extend(t.prototype,{renderList:function(){if(!e.isArray(this.sourceData))return;for(var t=0;t<this.sourceData.length;t++)this.$input.append(e("<option>",{value:this.sourceData[t].value}).text(this.sourceData[t].text))},value2htmlFinal:function(e,n){var r="",i=this.itemByVal(e);i&&(r=i.text),t.superclass.constructor.superclass.value2html(r,n)},autosubmit:function(){this.$input.on("change",function(){e(this).closest("form").submit()})}}),t.defaults=e.extend({},e.fn.editableform.types.list.defaults,{tpl:"<select></select>"}),e.fn.editableform.types.select=t}(window.jQuery),function(e){var t=function(e){this.init("checklist",e,t.defaults)};e.fn.editableform.utils.inherit(t,e.fn.editableform.types.list),e.extend(t.prototype,{renderList:function(){var t,n;if(!e.isArray(this.sourceData))return;for(var r=0;r<this.sourceData.length;r++)t=e("<label>").append(e("<input>",{type:"checkbox",value:this.sourceData[r].value,name:this.options.name})).append(e("<span>").text(" "+this.sourceData[r].text)),e("<div>").append(t).appendTo(this.$input)},value2str:function(t){return e.isArray(t)?t.join(e.trim(this.options.separator)):""},str2value:function(t){var n,r=null;return typeof t=="string"&&t.length?(n=new RegExp("\\s*"+e.trim(this.options.separator)+"\\s*"),r=t.split(n)):e.isArray(t)&&(r=t),r},value2input:function(t){var n=this.$input.find('input[type="checkbox"]');n.removeAttr("checked"),e.isArray(t)&&t.length&&n.each(function(n,r){var i=e(r);e.each(t,function(e,t){i.val()==t&&i.attr("checked","checked")})})},input2value:function(){var t=[];return this.$input.find("input:checked").each(function(n,r){t.push(e(r).val())}),t},value2htmlFinal:function(t,n){var r=[],i,s,o="";if(e.isArray(t)&&t.length<=this.options.limit){for(s=0;s<t.length;s++)i=this.itemByVal(t[s]),i&&r.push(e("<div>").text(i.text).html());o=r.join(this.options.viewseparator)}else o=this.options.limitText.replace("{checked}",e.isArray(t)?t.length:0).replace("{count}",this.sourceData.length);e(n).html(o)},activate:function(){this.$input.find('input[type="checkbox"]').first().focus()},autosubmit:function(){this.$input.find('input[type="checkbox"]').on("keydown",function(t){t.which===13&&e(this).closest("form").submit()})}}),t.defaults=e.extend({},e.fn.editableform.types.list.defaults,{tpl:"<div></div>",inputclass:"span2 editable-checklist",separator:",",viewseparator:"<br>",limit:4,limitText:"Selected {checked} of {count}"}),e.fn.editableform.types.checklist=t}(window.jQuery),function(e){e.extend(e.fn.editableform.Constructor.prototype,{initTemplate:function(){this.$form=e(e.fn.editableform.template),this.$form.find(".editable-error-block").addClass("help-block")}}),e.fn.editableform.buttons='<button type="submit" class="btn btn-primary editable-submit"><i class="icon-ok icon-white"></i></button><button type="button" class="btn editable-cancel"><i class="icon-remove"></i></button>',e.fn.editableform.errorGroupClass="error",e.fn.editableform.errorBlockClass=null}(window.jQuery),function(e){e.extend(e.fn.editableContainer.Constructor.prototype,{containerName:"editableform",innerCss:null,initContainer:function(){this.options.anim||(this.options.anim=0)},splitOptions:function(){this.containerOptions={},this.formOptions=this.options},tip:function(){return this.$form},innerShow:function(){this.$element.hide(),this.$form&&this.$form.remove(),this.initForm(),this.tip().addClass("editable-container").addClass("editable-inline"),this.$form.insertAfter(this.$element),this.$form.show(this.options.anim),this.$form.editableform("render")},innerHide:function(){this.$form.hide(this.options.anim,e.proxy(function(){this.$element.show(),this.options.enablefocus&&this.$element.focus()},this))},destroy:function(){this.tip().remove()}}),e.fn.editableContainer.defaults=e.extend({},e.fn.editableContainer.defaults,{anim:"fast",enablefocus:!1})}(window.jQuery),function(e){var t=function(n){this.init("date",n,t.defaults);var r=e.fn.editableform.utils.sliceObj(this.options,["format"]);this.options.datepicker=e.extend({},t.defaults.datepicker,r,n.datepicker),this.options.viewformat||(this.options.viewformat=this.options.datepicker.format),this.options.datepicker.language=this.options.datepicker.language||"en",this.dpg=e.fn.datepicker.DPGlobal,this.parsedFormat=this.dpg.parseFormat(this.options.datepicker.format),this.parsedViewFormat=this.dpg.parseFormat(this.options.viewformat)};e.fn.editableform.utils.inherit(t,e.fn.editableform.types.abstract),e.extend(t.prototype,{render:function(){t.superclass.render.call(this),this.$input.datepicker(this.options.datepicker),this.options.clear&&(this.$clear=e('<a href="#"></a>').html(this.options.clear).click(e.proxy(function(e){e.preventDefault(),e.stopPropagation(),this.clear()},this)))},value2html:function(e,n){var r=e?this.dpg.formatDate(e,this.parsedViewFormat,this.options.datepicker.language):"";t.superclass.value2html(r,n)},html2value:function(e){return e?this.dpg.parseDate(e,this.parsedViewFormat,this.options.datepicker.language):null},value2str:function(e){return e?this.dpg.formatDate(e,this.parsedFormat,this.options.datepicker.language):""},str2value:function(e){return e?this.dpg.parseDate(e,this.parsedFormat,this.options.datepicker.language):null},value2input:function(e){this.$input.datepicker("update",e)},input2value:function(){return this.$input.data("datepicker").date},activate:function(){},clear:function(){this.$input.data("datepicker").date=null,this.$input.find(".active").removeClass("active")},autosubmit:function(){this.$input.on("changeDate",function(t){var n=e(this).closest("form");setTimeout(function(){n.submit()},200)})}}),t.defaults=e.extend({},e.fn.editableform.types.abstract.defaults,{tpl:"<div></div>",inputclass:"editable-date well",format:"yyyy-mm-dd",viewformat:null,datepicker:{weekStart:0,startView:0,autoclose:!1},clear:"&times; clear"}),e.fn.editableform.types.date=t}(window.jQuery),!function(e){function t(){return new Date(Date.UTC.apply(Date,arguments))}function n(){var e=new Date;return t(e.getUTCFullYear(),e.getUTCMonth(),e.getUTCDate())}var r=function(t,n){var r=this;this.element=e(t),this.language=n.language||this.element.data("date-language")||"en",this.language=this.language in i?this.language:"en",this.format=s.parseFormat(n.format||this.element.data("date-format")||"mm/dd/yyyy"),this.isInline=!1,this.isInput=this.element.is("input"),this.component=this.element.is(".date")?this.element.find(".add-on"):!1,this.hasInput=this.component&&this.element.find("input").length,this.component&&this.component.length===0&&(this.component=!1),this.isInput?this.element.on({focus:e.proxy(this.show,this),keyup:e.proxy(this.update,this),keydown:e.proxy(this.keydown,this)}):this.component&&this.hasInput?(this.element.find("input").on({focus:e.proxy(this.show,this),keyup:e.proxy(this.update,this),keydown:e.proxy(this.keydown,this)}),this.component.on("click",e.proxy(this.show,this))):this.element.is("div")?this.isInline=!0:this.element.on("click",e.proxy(this.show,this)),this.picker=e(s.template).appendTo(this.isInline?this.element:"body").on({click:e.proxy(this.click,this),mousedown:e.proxy(this.mousedown,this)}),this.isInline?this.picker.addClass("datepicker-inline"):this.picker.addClass("dropdown-menu"),e(document).on("mousedown",function(t){e(t.target).closest(".datepicker").length==0&&r.hide()}),this.autoclose=!1,"autoclose"in n?this.autoclose=n.autoclose:"dateAutoclose"in this.element.data()&&(this.autoclose=this.element.data("date-autoclose")),this.keyboardNavigation=!0,"keyboardNavigation"in n?this.keyboardNavigation=n.keyboardNavigation:"dateKeyboardNavigation"in this.element.data()&&(this.keyboardNavigation=this.element.data("date-keyboard-navigation"));switch(n.startView||this.element.data("date-start-view")){case 2:case"decade":this.viewMode=this.startViewMode=2;break;case 1:case"year":this.viewMode=this.startViewMode=1;break;case 0:case"month":default:this.viewMode=this.startViewMode=0}this.todayBtn=n.todayBtn||this.element.data("date-today-btn")||!1,this.todayHighlight=n.todayHighlight||this.element.data("date-today-highlight")||!1,this.weekStart=(n.weekStart||this.element.data("date-weekstart")||i[this.language].weekStart||0)%7,this.weekEnd=(this.weekStart+6)%7,this.startDate=-Infinity,this.endDate=Infinity,this.setStartDate(n.startDate||this.element.data("date-startdate")),this.setEndDate(n.endDate||this.element.data("date-enddate")),this.fillDow(),this.fillMonths(),this.update(),this.showMode(),this.isInline&&this.show()};r.prototype={constructor:r,show:function(t){this.picker.show(),this.height=this.component?this.component.outerHeight():this.element.outerHeight(),this.update(),this.place(),e(window).on("resize",e.proxy(this.place,this)),t&&(t.stopPropagation(),t.preventDefault()),this.element.trigger({type:"show",date:this.date})},hide:function(t){if(this.isInline)return;this.picker.hide(),e(window).off("resize",this.place),this.viewMode=this.startViewMode,this.showMode(),this.isInput||e(document).off("mousedown",this.hide),t&&t.currentTarget.value&&this.setValue(),this.element.trigger({type:"hide",date:this.date})},getDate:function(){var e=this.getUTCDate();return new Date(e.getTime()+e.getTimezoneOffset()*6e4)},getUTCDate:function(){return this.date},setDate:function(e){this.setUTCDate(new Date(e.getTime()-e.getTimezoneOffset()*6e4))},setUTCDate:function(e){this.date=e,this.setValue()},setValue:function(){var e=this.getFormattedDate();this.isInput?this.element.prop("value",e):(this.component&&this.element.find("input").prop("value",e),this.element.data("date",e))},getFormattedDate:function(e){return e==undefined&&(e=this.format),s.formatDate(this.date,e,this.language)},setStartDate:function(e){this.startDate=e||-Infinity,this.startDate!==-Infinity&&(this.startDate=s.parseDate(this.startDate,this.format,this.language)),this.update(),this.updateNavArrows()},setEndDate:function(e){this.endDate=e||Infinity,this.endDate!==Infinity&&(this.endDate=s.parseDate(this.endDate,this.format,this.language)),this.update(),this.updateNavArrows()},place:function(){if(this.isInline)return;var t=parseInt(this.element.parents().filter(function(){return e(this).css("z-index")!="auto"}).first().css("z-index"))+10,n=this.component?this.component.offset():this.element.offset();this.picker.css({top:n.top+this.height,left:n.left,zIndex:t})},update:function(){var e,t=!1;arguments&&arguments.length&&(typeof arguments[0]=="string"||arguments[0]instanceof Date)?(e=arguments[0],t=!0):e=this.isInput?this.element.prop("value"):this.element.data("date")||this.element.find("input").prop("value"),this.date=s.parseDate(e,this.format,this.language),t&&this.setValue(),this.date<this.startDate?this.viewDate=new Date(this.startDate):this.date>this.endDate?this.viewDate=new Date(this.endDate):this.viewDate=new Date(this.date),this.fill()},fillDow:function(){var e=this.weekStart,t="<tr>";while(e<this.weekStart+7)t+='<th class="dow">'+i[this.language].daysMin[e++%7]+"</th>";t+="</tr>",this.picker.find(".datepicker-days thead").append(t)},fillMonths:function(){var e="",t=0;while(t<12)e+='<span class="month">'+i[this.language].monthsShort[t++]+"</span>";this.picker.find(".datepicker-months td").html(e)},fill:function(){var e=new Date(this.viewDate),n=e.getUTCFullYear(),r=e.getUTCMonth(),o=this.startDate!==-Infinity?this.startDate.getUTCFullYear():-Infinity,u=this.startDate!==-Infinity?this.startDate.getUTCMonth():-Infinity,a=this.endDate!==Infinity?this.endDate.getUTCFullYear():Infinity,f=this.endDate!==Infinity?this.endDate.getUTCMonth():Infinity,l=this.date.valueOf(),c=new Date;this.picker.find(".datepicker-days thead th:eq(1)").text(i[this.language].months[r]+" "+n),this.picker.find("tfoot th.today").text(i[this.language].today).toggle(this.todayBtn),this.updateNavArrows(),this.fillMonths();var h=t(n,r-1,28,0,0,0,0),p=s.getDaysInMonth(h.getUTCFullYear(),h.getUTCMonth());h.setUTCDate(p),h.setUTCDate(p-(h.getUTCDay()-this.weekStart+7)%7);var d=new Date(h);d.setUTCDate(d.getUTCDate()+42),d=d.valueOf();var v=[],m;while(h.valueOf()<d){h.getUTCDay()==this.weekStart&&v.push("<tr>"),m="";if(h.getUTCFullYear()<n||h.getUTCFullYear()==n&&h.getUTCMonth()<r)m+=" old";else if(h.getUTCFullYear()>n||h.getUTCFullYear()==n&&h.getUTCMonth()>r)m+=" new";this.todayHighlight&&h.getUTCFullYear()==c.getFullYear()&&h.getUTCMonth()==c.getMonth()&&h.getUTCDate()==c.getDate()&&(m+=" today"),h.valueOf()==l&&(m+=" active");if(h.valueOf()<this.startDate||h.valueOf()>this.endDate)m+=" disabled";v.push('<td class="day'+m+'">'+h.getUTCDate()+"</td>"),h.getUTCDay()==this.weekEnd&&v.push("</tr>"),h.setUTCDate(h.getUTCDate()+1)}this.picker.find(".datepicker-days tbody").empty().append(v.join(""));var g=this.date.getUTCFullYear(),y=this.picker.find(".datepicker-months").find("th:eq(1)").text(n).end().find("span").removeClass("active");g==n&&y.eq(this.date.getUTCMonth()).addClass("active"),(n<o||n>a)&&y.addClass("disabled"),n==o&&y.slice(0,u).addClass("disabled"),n==a&&y.slice(f+1).addClass("disabled"),v="",n=parseInt(n/10,10)*10;var b=this.picker.find(".datepicker-years").find("th:eq(1)").text(n+"-"+(n+9)).end().find("td");n-=1;for(var w=-1;w<11;w++)v+='<span class="year'+(w==-1||w==10?" old":"")+(g==n?" active":"")+(n<o||n>a?" disabled":"")+'">'+n+"</span>",n+=1;b.html(v)},updateNavArrows:function(){var e=new Date(this.viewDate),t=e.getUTCFullYear(),n=e.getUTCMonth();switch(this.viewMode){case 0:this.startDate!==-Infinity&&t<=this.startDate.getUTCFullYear()&&n<=this.startDate.getUTCMonth()?this.picker.find(".prev").css({visibility:"hidden"}):this.picker.find(".prev").css({visibility:"visible"}),this.endDate!==Infinity&&t>=this.endDate.getUTCFullYear()&&n>=this.endDate.getUTCMonth()?this.picker.find(".next").css({visibility:"hidden"}):this.picker.find(".next").css({visibility:"visible"});break;case 1:case 2:this.startDate!==-Infinity&&t<=this.startDate.getUTCFullYear()?this.picker.find(".prev").css({visibility:"hidden"}):this.picker.find(".prev").css({visibility:"visible"}),this.endDate!==Infinity&&t>=this.endDate.getUTCFullYear()?this.picker.find(".next").css({visibility:"hidden"}):this.picker.find(".next").css({visibility:"visible"})}},click:function(n){n.stopPropagation(),n.preventDefault();var r=e(n.target).closest("span, td, th");if(r.length==1)switch(r[0].nodeName.toLowerCase()){case"th":switch(r[0].className){case"switch":this.showMode(1);break;case"prev":case"next":var i=s.modes[this.viewMode].navStep*(r[0].className=="prev"?-1:1);switch(this.viewMode){case 0:this.viewDate=this.moveMonth(this.viewDate,i);break;case 1:case 2:this.viewDate=this.moveYear(this.viewDate,i)}this.fill();break;case"today":var o=new Date;o.setUTCHours(0),o.setUTCMinutes(0),o.setUTCSeconds(0),o.setUTCMilliseconds(0),this.showMode(-2);var u=this.todayBtn=="linked"?null:"view";this._setDate(o,u)}break;case"span":if(!r.is(".disabled")){this.viewDate.setUTCDate(1);if(r.is(".month")){var a=r.parent().find("span").index(r);this.viewDate.setUTCMonth(a),this.element.trigger({type:"changeMonth",date:this.viewDate})}else{var f=parseInt(r.text(),10)||0;this.viewDate.setUTCFullYear(f),this.element.trigger({type:"changeYear",date:this.viewDate})}this.showMode(-1),this.fill()}break;case"td":if(r.is(".day")&&!r.is(".disabled")){var l=parseInt(r.text(),10)||1,f=this.viewDate.getUTCFullYear(),a=this.viewDate.getUTCMonth();r.is(".old")?a==0?(a=11,f-=1):a-=1:r.is(".new")&&(a==11?(a=0,f+=1):a+=1),this._setDate(t(f,a,l,0,0,0,0))}}},_setDate:function(e,t){if(!t||t=="date")this.date=e;if(!t||t=="view")this.viewDate=e;this.fill(),this.setValue(),this.element.trigger({type:"changeDate",date:this.date});var n;this.isInput?n=this.element:this.component&&(n=this.element.find("input")),n&&(n.change(),this.autoclose&&this.hide())},moveMonth:function(e,t){if(!t)return e;var n=new Date(e.valueOf()),r=n.getUTCDate(),i=n.getUTCMonth(),s=Math.abs(t),o,u;t=t>0?1:-1;if(s==1){u=t==-1?function(){return n.getUTCMonth()==i}:function(){return n.getUTCMonth()!=o},o=i+t,n.setUTCMonth(o);if(o<0||o>11)o=(o+12)%12}else{for(var a=0;a<s;a++)n=this.moveMonth(n,t);o=n.getUTCMonth(),n.setUTCDate(r),u=function(){return o!=n.getUTCMonth()}}while(u())n.setUTCDate(--r),n.setUTCMonth(o);return n},moveYear:function(e,t){return this.moveMonth(e,t*12)},dateWithinRange:function(e){return e>=this.startDate&&e<=this.endDate},keydown:function(e){if(this.picker.is(":not(:visible)")){e.keyCode==27&&this.show();return}var t=!1,n,r,i,s,o;switch(e.keyCode){case 27:this.hide(),e.preventDefault();break;case 37:case 39:if(!this.keyboardNavigation)break;n=e.keyCode==37?-1:1,e.ctrlKey?(s=this.moveYear(this.date,n),o=this.moveYear(this.viewDate,n)):e.shiftKey?(s=this.moveMonth(this.date,n),o=this.moveMonth(this.viewDate,n)):(s=new Date(this.date),s.setUTCDate(this.date.getUTCDate()+n),o=new Date(this.viewDate),o.setUTCDate(this.viewDate.getUTCDate()+n)),this.dateWithinRange(s)&&(this.date=s,this.viewDate=o,this.setValue(),this.update(),e.preventDefault(),t=!0);break;case 38:case 40:if(!this.keyboardNavigation)break;n=e.keyCode==38?-1:1,e.ctrlKey?(s=this.moveYear(this.date,n),o=this.moveYear(this.viewDate,n)):e.shiftKey?(s=this.moveMonth(this.date,n),o=this.moveMonth(this.viewDate,n)):(s=new Date(this.date),s.setUTCDate(this.date.getUTCDate()+n*7),o=new Date(this.viewDate),o.setUTCDate(this.viewDate.getUTCDate()+n*7)),this.dateWithinRange(s)&&(this.date=s,this.viewDate=o,this.setValue(),this.update(),e.preventDefault(),t=!0);break;case 13:this.hide(),e.preventDefault();break;case 9:this.hide()}if(t){this.element.trigger({type:"changeDate",date:this.date});var u;this.isInput?u=this.element:this.component&&(u=this.element.find("input")),u&&u.change()}},showMode:function(e){e&&(this.viewMode=Math.max(0,Math.min(2,this.viewMode+e))),this.picker.find(">div").hide().filter(".datepicker-"+s.modes[this.viewMode].clsName).show(),this.updateNavArrows()}},e.fn.datepicker=function(t){var n=Array.apply(null,arguments);return n.shift(),this.each(function(){var i=e(this),s=i.data("datepicker"),o=typeof t=="object"&&t;s||i.data("datepicker",s=new r(this,e.extend({},e.fn.datepicker.defaults,o))),typeof t=="string"&&typeof s[t]=="function"&&s[t].apply(s,n)})},e.fn.datepicker.defaults={},e.fn.datepicker.Constructor=r;var i=e.fn.datepicker.dates={en:{days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],daysShort:["Sun","Mon","Tue","Wed","Thu","Fri","Sat","Sun"],daysMin:["Su","Mo","Tu","We","Th","Fr","Sa","Su"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],monthsShort:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],today:"Today"}},s={modes:[{clsName:"days",navFnc:"Month",navStep:1},{clsName:"months",navFnc:"FullYear",navStep:1},{clsName:"years",navFnc:"FullYear",navStep:10}],isLeapYear:function(e){return e%4===0&&e%100!==0||e%400===0},getDaysInMonth:function(e,t){return[31,s.isLeapYear(e)?29:28,31,30,31,30,31,31,30,31,30,31][t]},validParts:/dd?|mm?|MM?|yy(?:yy)?/g,nonpunctuation:/[^ -\/:-@\[-`{-~\t\n\r]+/g,parseFormat:function(e){var t=e.replace(this.validParts,"\0").split("\0"),n=e.match(this.validParts);if(!t||!t.length||!n||n.length==0)throw new Error("Invalid date format.");return{separators:t,parts:n}},parseDate:function(n,s,o){if(n instanceof Date)return n;if(/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(n)){var u=/([-+]\d+)([dmwy])/,a=n.match(/([-+]\d+)([dmwy])/g),f,l;n=new Date;for(var c=0;c<a.length;c++){f=u.exec(a[c]),l=parseInt(f[1]);switch(f[2]){case"d":n.setUTCDate(n.getUTCDate()+l);break;case"m":n=r.prototype.moveMonth.call(r.prototype,n,l);break;case"w":n.setUTCDate(n.getUTCDate()+l*7);break;case"y":n=r.prototype.moveYear.call(r.prototype,n,l)}}return t(n.getUTCFullYear(),n.getUTCMonth(),n.getUTCDate(),0,0,0)}var a=n&&n.match(this.nonpunctuation)||[],n=new Date,h={},p=["yyyy","yy","M","MM","m","mm","d","dd"],d={yyyy:function(e,t){return e.setUTCFullYear(t)},yy:function(e,t){return e.setUTCFullYear(2e3+t)},m:function(e,t){t-=1;while(t<0)t+=12;t%=12,e.setUTCMonth(t);while(e.getUTCMonth()!=t)e.setUTCDate(e.getUTCDate()-1);return e},d:function(e,t){return e.setUTCDate(t)}},v,m,f;d.M=d.MM=d.mm=d.m,d.dd=d.d,n=t(n.getUTCFullYear(),n.getUTCMonth(),n.getUTCDate(),0,0,0);if(a.length==s.parts.length){for(var c=0,g=s.parts.length;c<g;c++){v=parseInt(a[c],10),f=s.parts[c];if(isNaN(v))switch(f){case"MM":m=e(i[o].months).filter(function(){var e=this.slice(0,a[c].length),t=a[c].slice(0,e.length);return e==t}),v=e.inArray(m[0],i[o].months)+1;break;case"M":m=e(i[o].monthsShort).filter(function(){var e=this.slice(0,a[c].length),t=a[c].slice(0,e.length);return e==t}),v=e.inArray(m[0],i[o].monthsShort)+1}h[f]=v}for(var c=0,y;c<p.length;c++)y=p[c],y in h&&d[y](n,h[y])}return n},formatDate:function(t,n,r){var s={d:t.getUTCDate(),m:t.getUTCMonth()+1,M:i[r].monthsShort[t.getUTCMonth()],MM:i[r].months[t.getUTCMonth()],yy:t.getUTCFullYear().toString().substring(2),yyyy:t.getUTCFullYear()};s.dd=(s.d<10?"0":"")+s.d,s.mm=(s.m<10?"0":"")+s.m;var t=[],o=e.extend([],n.separators);for(var u=0,a=n.parts.length;u<a;u++)o.length&&t.push(o.shift()),t.push(s[n.parts[u]]);return t.join("")},headTemplate:'<thead><tr><th class="prev"><i class="icon-arrow-left"/></th><th colspan="5" class="switch"></th><th class="next"><i class="icon-arrow-right"/></th></tr></thead>',contTemplate:'<tbody><tr><td colspan="7"></td></tr></tbody>',footTemplate:'<tfoot><tr><th colspan="7" class="today"></th></tr></tfoot>'};s.template='<div class="datepicker"><div class="datepicker-days"><table class=" table-condensed">'+s.headTemplate+"<tbody></tbody>"+s.footTemplate+"</table>"+"</div>"+'<div class="datepicker-months">'+'<table class="table-condensed">'+s.headTemplate+s.contTemplate+s.footTemplate+"</table>"+"</div>"+'<div class="datepicker-years">'+'<table class="table-condensed">'+s.headTemplate+s.contTemplate+s.footTemplate+"</table>"+"</div>"+"</div>",e.fn.datepicker.DPGlobal=s}(window.jQuery);
@@ -0,0 +1,27 @@
1
+ # bootstrap-editable-rails.js.coffee
2
+ # Modify parameters of X-editable suitable for Rails.
3
+
4
+ jQuery ($) ->
5
+ EditableForm = $.fn.editableform.Constructor
6
+ EditableForm.prototype.saveWithUrlHook = (value) ->
7
+ originalUrl = @options.url
8
+ resource = @options.resource
9
+ @options.url = (params) ->
10
+ # TODO: should not send when create new object
11
+ if typeof originalUrl == 'function' # user's function
12
+ originalUrl.call(@, params)
13
+ else # send ajax to server and return deferred object
14
+ obj = {}
15
+ data = {}
16
+ obj[params.name] = params.value
17
+ data[resource] = obj
18
+ ajaxOptions = $.extend({
19
+ url : originalUrl
20
+ data : data
21
+ type : 'PUT' # TODO: should be 'POST' when create new object
22
+ dataType: 'json'
23
+ }, @options.ajaxOptions)
24
+ $.ajax(ajaxOptions)
25
+ @saveWithoutUrlHook(value)
26
+ EditableForm.prototype.saveWithoutUrlHook = EditableForm.prototype.save
27
+ EditableForm.prototype.save = EditableForm.prototype.saveWithUrlHook
@@ -0,0 +1,3518 @@
1
+ /*! X-editable - v1.1.1
2
+ * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
3
+ * http://github.com/vitalets/x-editable
4
+ * Copyright (c) 2012 Vitaliy Potapov; Licensed MIT */
5
+
6
+ /**
7
+ Form with single input element, two buttons and two states: normal/loading.
8
+ Applied as jQuery method to DIV tag (not to form tag!)
9
+ Editableform is linked with one of input types, e.g. 'text' or 'select'.
10
+
11
+ @class editableform
12
+ @uses text
13
+ @uses textarea
14
+ **/
15
+ (function ($) {
16
+
17
+ var EditableForm = function (element, options) {
18
+ this.options = $.extend({}, $.fn.editableform.defaults, options);
19
+ this.$element = $(element); //div, containing form. Not form tag! Not editable-element.
20
+ this.initInput();
21
+ };
22
+
23
+ EditableForm.prototype = {
24
+ constructor: EditableForm,
25
+ initInput: function() { //called once
26
+ var TypeConstructor, typeOptions;
27
+
28
+ //create input of specified type
29
+ if(typeof $.fn.editableform.types[this.options.type] === 'function') {
30
+ TypeConstructor = $.fn.editableform.types[this.options.type];
31
+ typeOptions = $.fn.editableform.utils.sliceObj(this.options, $.fn.editableform.utils.objectKeys(TypeConstructor.defaults));
32
+ this.input = new TypeConstructor(typeOptions);
33
+ } else {
34
+ $.error('Unknown type: '+ this.options.type);
35
+ return;
36
+ }
37
+
38
+ this.value = this.input.str2value(this.options.value);
39
+ },
40
+ initTemplate: function() {
41
+ this.$form = $($.fn.editableform.template);
42
+ },
43
+ initButtons: function() {
44
+ this.$form.find('.editable-buttons').append($.fn.editableform.buttons);
45
+ },
46
+ /**
47
+ Renders editableform
48
+
49
+ @method render
50
+ **/
51
+ render: function() {
52
+ this.$loading = $($.fn.editableform.loading);
53
+ this.$element.empty().append(this.$loading);
54
+ this.showLoading();
55
+
56
+ //init form template and buttons
57
+ this.initTemplate();
58
+ if(this.options.showbuttons) {
59
+ this.initButtons();
60
+ } else {
61
+ this.$form.find('.editable-buttons').remove();
62
+ }
63
+
64
+ /**
65
+ Fired when rendering starts
66
+ @event rendering
67
+ @param {Object} event event object
68
+ **/
69
+ this.$element.triggerHandler('rendering');
70
+
71
+ //render input
72
+ $.when(this.input.render())
73
+ .then($.proxy(function () {
74
+ //input
75
+ this.$form.find('div.editable-input').append(this.input.$input);
76
+
77
+ //automatically submit inputs when no buttons shown
78
+ if(!this.options.showbuttons) {
79
+ this.input.autosubmit();
80
+ }
81
+
82
+ //"clear" link
83
+ if(this.input.$clear) {
84
+ this.$form.find('div.editable-input').append($('<div class="editable-clear">').append(this.input.$clear));
85
+ }
86
+
87
+ //append form to container
88
+ this.$element.append(this.$form);
89
+
90
+ //attach 'cancel' handler
91
+ this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
92
+ // this.$form.find('.editable-buttons button').eq(1).click($.proxy(this.cancel, this));
93
+
94
+ if(this.input.error) {
95
+ this.error(this.input.error);
96
+ this.$form.find('.editable-submit').attr('disabled', true);
97
+ this.input.$input.attr('disabled', true);
98
+ } else {
99
+ this.error(false);
100
+ this.input.$input.removeAttr('disabled');
101
+ this.$form.find('.editable-submit').removeAttr('disabled');
102
+ this.input.value2input(this.value);
103
+ this.$form.submit($.proxy(this.submit, this));
104
+ }
105
+
106
+ /**
107
+ Fired when form is rendered
108
+ @event rendered
109
+ @param {Object} event event object
110
+ **/
111
+ this.$element.triggerHandler('rendered');
112
+
113
+ this.showForm();
114
+ }, this));
115
+ },
116
+ cancel: function() {
117
+ /**
118
+ Fired when form was cancelled by user
119
+ @event cancel
120
+ @param {Object} event event object
121
+ **/
122
+ this.$element.triggerHandler('cancel');
123
+ },
124
+ showLoading: function() {
125
+ var w;
126
+ if(this.$form) {
127
+ //set loading size equal to form
128
+ this.$loading.width(this.$form.outerWidth());
129
+ this.$loading.height(this.$form.outerHeight());
130
+ this.$form.hide();
131
+ } else {
132
+ //stretch loading to fill container width
133
+ w = this.$loading.parent().width();
134
+ if(w) {
135
+ this.$loading.width(w);
136
+ }
137
+ }
138
+ this.$loading.show();
139
+ },
140
+
141
+ showForm: function() {
142
+ this.$loading.hide();
143
+ this.$form.show();
144
+ this.input.activate();
145
+ /**
146
+ Fired when form is shown
147
+ @event show
148
+ @param {Object} event event object
149
+ **/
150
+ this.$element.triggerHandler('show');
151
+ },
152
+
153
+ error: function(msg) {
154
+ var $group = this.$form.find('.control-group'),
155
+ $block = this.$form.find('.editable-error-block');
156
+
157
+ if(msg === false) {
158
+ $group.removeClass($.fn.editableform.errorGroupClass);
159
+ $block.removeClass($.fn.editableform.errorBlockClass).empty().hide();
160
+ } else {
161
+ $group.addClass($.fn.editableform.errorGroupClass);
162
+ $block.addClass($.fn.editableform.errorBlockClass).text(msg).show();
163
+ }
164
+ },
165
+
166
+ submit: function(e) {
167
+ e.stopPropagation();
168
+ e.preventDefault();
169
+
170
+ var error,
171
+ newValue = this.input.input2value(), //get new value from input
172
+ newValueStr;
173
+
174
+ //validation
175
+ if (error = this.validate(newValue)) {
176
+ this.error(error);
177
+ this.showForm();
178
+ return;
179
+ }
180
+
181
+ //value as string
182
+ newValueStr = this.input.value2str(newValue);
183
+
184
+ //if value not changed --> cancel
185
+ /*jslint eqeq: true*/
186
+ if (newValueStr == this.input.value2str(this.value)) {
187
+ /*jslint eqeq: false*/
188
+ this.cancel();
189
+ return;
190
+ }
191
+
192
+ //sending data to server
193
+ $.when(this.save(newValueStr))
194
+ .done($.proxy(function(response) {
195
+ //run success callback
196
+ var res = typeof this.options.success === 'function' ? this.options.success.call(this, response, newValue) : null;
197
+
198
+ //if success callback returns string --> show error
199
+ if(res && typeof res === 'string') {
200
+ this.error(res);
201
+ this.showForm();
202
+ return;
203
+ }
204
+
205
+ //if success callback returns object like {newValue: <something>} --> use that value instead of submitted
206
+ if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
207
+ newValue = res.newValue;
208
+ }
209
+
210
+ //clear error message
211
+ this.error(false);
212
+ this.value = newValue;
213
+ /**
214
+ Fired when form is submitted
215
+ @event save
216
+ @param {Object} event event object
217
+ @param {Object} params additional params
218
+ @param {mixed} params.newValue submitted value
219
+ @param {Object} params.response ajax response
220
+
221
+ @example
222
+ $('#form-div').on('save'), function(e, params){
223
+ if(params.newValue === 'username') {...}
224
+ });
225
+ **/
226
+ this.$element.triggerHandler('save', {newValue: newValue, response: response});
227
+ }, this))
228
+ .fail($.proxy(function(xhr) {
229
+ this.error(typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!');
230
+ this.showForm();
231
+ }, this));
232
+ },
233
+
234
+ save: function(value) {
235
+ var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this) : this.options.pk,
236
+ send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk)))),
237
+ params, ajaxOptions;
238
+
239
+ if (send) { //send to server
240
+ this.showLoading();
241
+
242
+ //standard params
243
+ params = {
244
+ name: this.options.name || '',
245
+ value: value,
246
+ pk: pk
247
+ };
248
+
249
+ //additional params
250
+ if(typeof this.options.params === 'function') {
251
+ $.extend(params, this.options.params.call(this, params));
252
+ } else {
253
+ //try parse json in single quotes (from data-params attribute)
254
+ this.options.params = $.fn.editableform.utils.tryParseJson(this.options.params, true);
255
+ $.extend(params, this.options.params);
256
+ }
257
+
258
+ if(typeof this.options.url === 'function') { //user's function
259
+ return this.options.url.call(this, params);
260
+ } else { //send ajax to server and return deferred object
261
+ ajaxOptions = $.extend({
262
+ url : this.options.url,
263
+ data : params,
264
+ type : 'post',
265
+ dataType: 'json'
266
+ }, this.options.ajaxOptions);
267
+
268
+ return $.ajax(ajaxOptions);
269
+ }
270
+ }
271
+ },
272
+
273
+ validate: function (value) {
274
+ if (value === undefined) {
275
+ value = this.value;
276
+ }
277
+ if (typeof this.options.validate === 'function') {
278
+ return this.options.validate.call(this, value);
279
+ }
280
+ },
281
+
282
+ option: function(key, value) {
283
+ this.options[key] = value;
284
+ if(key === 'value') {
285
+ this.setValue(value);
286
+ }
287
+ },
288
+
289
+ setValue: function(value, convertStr) {
290
+ if(convertStr) {
291
+ this.value = this.input.str2value(value);
292
+ } else {
293
+ this.value = value;
294
+ }
295
+ }
296
+ };
297
+
298
+ /*
299
+ Initialize editableform. Applied to jQuery object.
300
+
301
+ @method $().editableform(options)
302
+ @params {Object} options
303
+ @example
304
+ var $form = $('&lt;div&gt;').editableform({
305
+ type: 'text',
306
+ name: 'username',
307
+ url: '/post',
308
+ value: 'vitaliy'
309
+ });
310
+
311
+ //to display form you should call 'render' method
312
+ $form.editableform('render');
313
+ */
314
+ $.fn.editableform = function (option) {
315
+ var args = arguments;
316
+ return this.each(function () {
317
+ var $this = $(this),
318
+ data = $this.data('editableform'),
319
+ options = typeof option === 'object' && option;
320
+ if (!data) {
321
+ $this.data('editableform', (data = new EditableForm(this, options)));
322
+ }
323
+
324
+ if (typeof option === 'string') { //call method
325
+ data[option].apply(data, Array.prototype.slice.call(args, 1));
326
+ }
327
+ });
328
+ };
329
+
330
+ //keep link to constructor to allow inheritance
331
+ $.fn.editableform.Constructor = EditableForm;
332
+
333
+ //defaults
334
+ $.fn.editableform.defaults = {
335
+ /* see also defaults for input */
336
+
337
+ /**
338
+ Type of input. Can be <code>text|textarea|select|date|checklist</code>
339
+
340
+ @property type
341
+ @type string
342
+ @default 'text'
343
+ **/
344
+ type: 'text',
345
+ /**
346
+ Url for submit, e.g. <code>'/post'</code>
347
+ If function - it will be called instead of ajax. Function can return deferred object to run fail/done callbacks.
348
+
349
+ @property url
350
+ @type string|function
351
+ @default null
352
+ @example
353
+ url: function(params) {
354
+ if(params.value === 'abc') {
355
+ var d = new $.Deferred;
356
+ return d.reject('field cannot be "abc"'); //returning error via deferred object
357
+ } else {
358
+ someModel.set(params.name, params.value); //save data in some js model
359
+ }
360
+ }
361
+ **/
362
+ url:null,
363
+ /**
364
+ Additional params for submit. Function can be used to calculate params dynamically
365
+ @example
366
+ params: function(params) {
367
+ return { a: 1 };
368
+ }
369
+
370
+ @property params
371
+ @type object|function
372
+ @default null
373
+ **/
374
+ params:null,
375
+ /**
376
+ Name of field. Will be submitted on server. Can be taken from <code>id</code> attribute
377
+
378
+ @property name
379
+ @type string
380
+ @default null
381
+ **/
382
+ name: null,
383
+ /**
384
+ Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>.
385
+ Can be calculated dinamically via function.
386
+
387
+ @property pk
388
+ @type string|object|function
389
+ @default null
390
+ **/
391
+ pk: null,
392
+ /**
393
+ Initial value. If not defined - will be taken from element's content.
394
+ For __select__ type should be defined (as it is ID of shown text).
395
+
396
+ @property value
397
+ @type string|object
398
+ @default null
399
+ **/
400
+ value: null,
401
+ /**
402
+ Strategy for sending data on server. Can be <code>auto|always|never</code>.
403
+ When 'auto' data will be sent on server only if pk defined, otherwise new value will be stored in element.
404
+
405
+ @property send
406
+ @type string
407
+ @default 'auto'
408
+ **/
409
+ send: 'auto',
410
+ /**
411
+ Function for client-side validation. If returns string - means validation not passed and string showed as error.
412
+
413
+ @property validate
414
+ @type function
415
+ @default null
416
+ @example
417
+ validate: function(value) {
418
+ if($.trim(value) == '') {
419
+ return 'This field is required';
420
+ }
421
+ }
422
+ **/
423
+ validate: null,
424
+ /**
425
+ Success callback. Called when value successfully sent on server and **response status = 200**.
426
+ Usefull to work with json response. For example, if your backend response can be <code>{success: true}</code>
427
+ or <code>{success: false, msg: "server error"}</code> you can check it inside this callback.
428
+ If it returns **string** - means error occured and string is shown as error message.
429
+ If it returns **object like** <code>{newValue: &lt;something&gt;}</code> - it overwrites value, submitted by user.
430
+ Otherwise newValue simply rendered into element.
431
+
432
+ @property success
433
+ @type function
434
+ @default null
435
+ @example
436
+ success: function(response, newValue) {
437
+ if(!response.success) return response.msg;
438
+ }
439
+ **/
440
+ success: function(response, newValue) {},
441
+ /**
442
+ Additional options for ajax request.
443
+ List of values: http://api.jquery.com/jQuery.ajax
444
+
445
+ @property ajaxOptions
446
+ @type object
447
+ @default null
448
+ **/
449
+ ajaxOptions: null,
450
+ /**
451
+ Wether to show buttons or not.
452
+ Form without buttons can be auto-submitted by input or by onblur = 'submit'.
453
+
454
+ @property showbuttons
455
+ @type boolean
456
+ @default true
457
+ **/
458
+ showbuttons: true
459
+
460
+ /*todo:
461
+ Submit strategy. Can be <code>normal|never</code>
462
+ <code>submitmode='never'</code> usefull for turning into classic form several inputs and submitting them together manually.
463
+ Works pretty with <code>showbuttons=false</code>
464
+
465
+ @property submitmode
466
+ @type string
467
+ @default normal
468
+ */
469
+ // submitmode: 'normal'
470
+ };
471
+
472
+ /*
473
+ Note: following params could redefined in engine: bootstrap or jqueryui:
474
+ Classes 'control-group' and 'editable-error-block' must always present!
475
+ */
476
+ $.fn.editableform.template = '<form class="form-inline editableform">'+
477
+ '<div class="control-group">' +
478
+ '<div><div class="editable-input"></div><div class="editable-buttons"></div></div>'+
479
+ '<div class="editable-error-block"></div>' +
480
+ '</div>' +
481
+ '</form>';
482
+
483
+ //loading div
484
+ $.fn.editableform.loading = '<div class="editableform-loading"></div>';
485
+
486
+ //buttons
487
+ $.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+
488
+ '<button type="button" class="editable-cancel">cancel</button>';
489
+
490
+ //error class attahced to control-group
491
+ $.fn.editableform.errorGroupClass = null;
492
+
493
+ //error class attahced to editable-error-block
494
+ $.fn.editableform.errorBlockClass = 'editable-error';
495
+
496
+ //input types
497
+ $.fn.editableform.types = {};
498
+ //utils
499
+ $.fn.editableform.utils = {};
500
+
501
+ }(window.jQuery));
502
+ /**
503
+ * EditableForm utilites
504
+ */
505
+ (function ($) {
506
+ $.fn.editableform.utils = {
507
+ /**
508
+ * classic JS inheritance function
509
+ */
510
+ inherit: function (Child, Parent) {
511
+ var F = function() { };
512
+ F.prototype = Parent.prototype;
513
+ Child.prototype = new F();
514
+ Child.prototype.constructor = Child;
515
+ Child.superclass = Parent.prototype;
516
+ },
517
+
518
+ /**
519
+ * set caret position in input
520
+ * see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
521
+ */
522
+ setCursorPosition: function(elem, pos) {
523
+ if (elem.setSelectionRange) {
524
+ elem.setSelectionRange(pos, pos);
525
+ } else if (elem.createTextRange) {
526
+ var range = elem.createTextRange();
527
+ range.collapse(true);
528
+ range.moveEnd('character', pos);
529
+ range.moveStart('character', pos);
530
+ range.select();
531
+ }
532
+ },
533
+
534
+ /**
535
+ * function to parse JSON in *single* quotes. (jquery automatically parse only double quotes)
536
+ * That allows such code as: <a data-source="{'a': 'b', 'c': 'd'}">
537
+ * safe = true --> means no exception will be thrown
538
+ * for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery
539
+ */
540
+ tryParseJson: function(s, safe) {
541
+ if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) {
542
+ if (safe) {
543
+ try {
544
+ /*jslint evil: true*/
545
+ s = (new Function('return ' + s))();
546
+ /*jslint evil: false*/
547
+ } catch (e) {} finally {
548
+ return s;
549
+ }
550
+ } else {
551
+ /*jslint evil: true*/
552
+ s = (new Function('return ' + s))();
553
+ /*jslint evil: false*/
554
+ }
555
+ }
556
+ return s;
557
+ },
558
+
559
+ /**
560
+ * slice object by specified keys
561
+ */
562
+ sliceObj: function(obj, keys, caseSensitive /* default: false */) {
563
+ var key, keyLower, newObj = {};
564
+
565
+ if (!$.isArray(keys) || !keys.length) {
566
+ return newObj;
567
+ }
568
+
569
+ for (var i = 0; i < keys.length; i++) {
570
+ key = keys[i];
571
+ if (obj.hasOwnProperty(key)) {
572
+ newObj[key] = obj[key];
573
+ }
574
+
575
+ if(caseSensitive === true) {
576
+ continue;
577
+ }
578
+
579
+ //when getting data-* attributes via $.data() it's converted to lowercase.
580
+ //details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery
581
+ //workaround is code below.
582
+ keyLower = key.toLowerCase();
583
+ if (obj.hasOwnProperty(keyLower)) {
584
+ newObj[key] = obj[keyLower];
585
+ }
586
+ }
587
+
588
+ return newObj;
589
+ },
590
+
591
+ /**
592
+ * exclude complex objects from $.data() before pass to config
593
+ */
594
+ getConfigData: function($element) {
595
+ var data = {};
596
+ $.each($element.data(), function(k, v) {
597
+ if(typeof v !== 'object' || (v && typeof v === 'object' && v.constructor === Object)) {
598
+ data[k] = v;
599
+ }
600
+ });
601
+ return data;
602
+ },
603
+
604
+ objectKeys: function(o) {
605
+ if (Object.keys) {
606
+ return Object.keys(o);
607
+ } else {
608
+ if (o !== Object(o)) {
609
+ throw new TypeError('Object.keys called on a non-object');
610
+ }
611
+ var k=[], p;
612
+ for (p in o) {
613
+ if (Object.prototype.hasOwnProperty.call(o,p)) {
614
+ k.push(p);
615
+ }
616
+ }
617
+ return k;
618
+ }
619
+
620
+ }
621
+ };
622
+ }(window.jQuery));
623
+ /**
624
+ Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br>
625
+ This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>
626
+ Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.<br>
627
+ Applied as jQuery method.
628
+
629
+ @class editableContainer
630
+ @uses editableform
631
+ **/
632
+ (function ($) {
633
+
634
+ var EditableContainer = function (element, options) {
635
+ this.init(element, options);
636
+ };
637
+
638
+ //methods
639
+ EditableContainer.prototype = {
640
+ containerName: null, //tbd in child class
641
+ innerCss: null, //tbd in child class
642
+ init: function(element, options) {
643
+ this.$element = $(element);
644
+ //todo: what is in priority: data or js?
645
+ this.options = $.extend({}, $.fn.editableContainer.defaults, $.fn.editableform.utils.getConfigData(this.$element), options);
646
+ this.splitOptions();
647
+ this.initContainer();
648
+
649
+ //bind 'destroyed' listener to destroy container when element is removed from dom
650
+ this.$element.on('destroyed', $.proxy(function(){
651
+ this.destroy();
652
+ }, this));
653
+
654
+ //attach document handlers (once)
655
+ if(!$(document).data('editable-handlers-attached')) {
656
+ //close all on escape
657
+ $(document).on('keyup.editable', function (e) {
658
+ if (e.which === 27) {
659
+ $('.editable-open').editableContainer('hide');
660
+ //todo: return focus on element
661
+ }
662
+ });
663
+
664
+ //close containers when click outside
665
+ $(document).on('click.editable', function(e) {
666
+ var $target = $(e.target);
667
+
668
+ //if click inside some editableContainer --> no nothing
669
+ if($target.is('.editable-container') || $target.parents('.editable-container').length || $target.parents('.ui-datepicker-header').length) {
670
+ return;
671
+ } else {
672
+ //close all open containers (except one)
673
+ EditableContainer.prototype.closeOthers(e.target);
674
+ }
675
+ });
676
+
677
+ $(document).data('editable-handlers-attached', true);
678
+ }
679
+ },
680
+
681
+ //split options on containerOptions and formOptions
682
+ splitOptions: function() {
683
+ this.containerOptions = {};
684
+ this.formOptions = {};
685
+ var cDef = $.fn[this.containerName].defaults;
686
+ for(var k in this.options) {
687
+ if(k in cDef) {
688
+ this.containerOptions[k] = this.options[k];
689
+ } else {
690
+ this.formOptions[k] = this.options[k];
691
+ }
692
+ }
693
+ },
694
+
695
+ initContainer: function(){
696
+ this.call(this.containerOptions);
697
+ },
698
+
699
+ initForm: function() {
700
+ this.$form = $('<div>')
701
+ .editableform(this.formOptions)
702
+ .on({
703
+ save: $.proxy(this.save, this),
704
+ cancel: $.proxy(this.cancel, this),
705
+ show: $.proxy(this.setPosition, this), //re-position container every time form is shown (after loading state)
706
+ rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
707
+ rendered: $.proxy(function(){
708
+ /**
709
+ Fired when container is shown and form is rendered (for select will wait for loading dropdown options)
710
+
711
+ @event shown
712
+ @param {Object} event event object
713
+ @example
714
+ $('#username').on('shown', function() {
715
+ var $tip = $(this).data('editableContainer').tip();
716
+ $tip.find('input').val('overwriting value of input..');
717
+ });
718
+ **/
719
+ this.$element.triggerHandler('shown');
720
+ }, this)
721
+ });
722
+ return this.$form;
723
+ },
724
+
725
+ /*
726
+ Returns jquery object of container
727
+ @method tip()
728
+ */
729
+ tip: function() {
730
+ return this.container().$tip;
731
+ },
732
+
733
+ container: function() {
734
+ return this.$element.data(this.containerName);
735
+ },
736
+
737
+ call: function() {
738
+ this.$element[this.containerName].apply(this.$element, arguments);
739
+ },
740
+
741
+ /**
742
+ Shows container with form
743
+ @method show()
744
+ @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true.
745
+ **/
746
+ show: function (closeAll) {
747
+ this.$element.addClass('editable-open');
748
+ if(closeAll !== false) {
749
+ //close all open containers (except this)
750
+ this.closeOthers(this.$element[0]);
751
+ }
752
+
753
+ this.innerShow();
754
+ },
755
+
756
+ /* internal show method. To be overwritten in child classes */
757
+ innerShow: function () {
758
+ this.call('show');
759
+ this.tip().addClass('editable-container');
760
+ this.initForm();
761
+ this.tip().find(this.innerCss).empty().append(this.$form);
762
+ this.$form.editableform('render');
763
+ },
764
+
765
+ /**
766
+ Hides container with form
767
+ @method hide()
768
+ **/
769
+ hide: function() {
770
+ if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
771
+ return;
772
+ }
773
+ this.$element.removeClass('editable-open');
774
+ this.innerHide();
775
+ /**
776
+ Fired when container was hidden. It occurs on both save or cancel.
777
+
778
+ @event hidden
779
+ @param {Object} event event object
780
+ **/
781
+ this.$element.triggerHandler('hidden');
782
+ },
783
+
784
+ /* internal hide method. To be overwritten in child classes */
785
+ innerHide: function () {
786
+ this.call('hide');
787
+ },
788
+
789
+ /**
790
+ Toggles container visibility (show / hide)
791
+ @method toggle()
792
+ @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true.
793
+ **/
794
+ toggle: function(closeAll) {
795
+ if(this.tip && this.tip().is(':visible')) {
796
+ this.hide();
797
+ } else {
798
+ this.show(closeAll);
799
+ }
800
+ },
801
+
802
+ /*
803
+ Updates the position of container when content changed.
804
+ @method setPosition()
805
+ */
806
+ setPosition: function() {
807
+ //tbd in child class
808
+ },
809
+
810
+ cancel: function() {
811
+ if(this.options.autohide) {
812
+ this.hide();
813
+ }
814
+ /**
815
+ Fired when form was cancelled by user
816
+
817
+ @event cancel
818
+ @param {Object} event event object
819
+ **/
820
+ this.$element.triggerHandler('cancel');
821
+ },
822
+
823
+ save: function(e, params) {
824
+ if(this.options.autohide) {
825
+ this.hide();
826
+ }
827
+ /**
828
+ Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance
829
+
830
+ @event save
831
+ @param {Object} event event object
832
+ @param {Object} params additional params
833
+ @param {mixed} params.newValue submitted value
834
+ @param {Object} params.response ajax response
835
+ @example
836
+ $('#username').on('save', function(e, params) {
837
+ //assuming server response: '{success: true}'
838
+ var pk = $(this).data('editableContainer').options.pk;
839
+ if(params.response && params.response.success) {
840
+ alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');
841
+ } else {
842
+ alert('error!');
843
+ }
844
+ });
845
+ **/
846
+ this.$element.triggerHandler('save', params);
847
+ },
848
+
849
+ /**
850
+ Sets new option
851
+
852
+ @method option(key, value)
853
+ @param {string} key
854
+ @param {mixed} value
855
+ **/
856
+ option: function(key, value) {
857
+ this.options[key] = value;
858
+ if(key in this.containerOptions) {
859
+ this.containerOptions[key] = value;
860
+ this.setContainerOption(key, value);
861
+ } else {
862
+ this.formOptions[key] = value;
863
+ if(this.$form) {
864
+ this.$form.editableform('option', key, value);
865
+ }
866
+ }
867
+ },
868
+
869
+ setContainerOption: function(key, value) {
870
+ this.call('option', key, value);
871
+ },
872
+
873
+ /**
874
+ Destroys the container instance
875
+ @method destroy()
876
+ **/
877
+ destroy: function() {
878
+ this.call('destroy');
879
+ },
880
+
881
+ /*
882
+ Closes other containers except one related to passed element.
883
+ Other containers can be cancelled or submitted (depends on onblur option)
884
+ */
885
+ closeOthers: function(element) {
886
+ $('.editable-open').each(function(i, el){
887
+ //do nothing with passed element
888
+ if(el === element) {
889
+ return;
890
+ }
891
+
892
+ //otherwise cancel or submit all open containers
893
+ var $el = $(el),
894
+ ec = $el.data('editableContainer');
895
+
896
+ if(!ec) {
897
+ return;
898
+ }
899
+
900
+ if(ec.options.onblur === 'cancel') {
901
+ $el.data('editableContainer').hide();
902
+ } else if(ec.options.onblur === 'submit') {
903
+ $el.data('editableContainer').tip().find('form').submit();
904
+ }
905
+ });
906
+
907
+ },
908
+
909
+ /**
910
+ Activates input of visible container (e.g. set focus)
911
+ @method activate()
912
+ **/
913
+ activate: function() {
914
+ if(this.tip && this.tip().is(':visible') && this.$form) {
915
+ this.$form.data('editableform').input.activate();
916
+ }
917
+ }
918
+
919
+ };
920
+
921
+ /**
922
+ jQuery method to initialize editableContainer.
923
+
924
+ @method $().editableContainer(options)
925
+ @params {Object} options
926
+ @example
927
+ $('#edit').editableContainer({
928
+ type: 'text',
929
+ url: '/post',
930
+ pk: 1,
931
+ value: 'hello'
932
+ });
933
+ **/
934
+ $.fn.editableContainer = function (option) {
935
+ var args = arguments;
936
+ return this.each(function () {
937
+ var $this = $(this),
938
+ dataKey = 'editableContainer',
939
+ data = $this.data(dataKey),
940
+ options = typeof option === 'object' && option;
941
+
942
+ if (!data) {
943
+ $this.data(dataKey, (data = new EditableContainer(this, options)));
944
+ }
945
+
946
+ if (typeof option === 'string') { //call method
947
+ data[option].apply(data, Array.prototype.slice.call(args, 1));
948
+ }
949
+ });
950
+ };
951
+
952
+ //store constructor
953
+ $.fn.editableContainer.Constructor = EditableContainer;
954
+
955
+ //defaults
956
+ $.fn.editableContainer.defaults = {
957
+ /**
958
+ Initial value of form input
959
+
960
+ @property value
961
+ @type mixed
962
+ @default null
963
+ @private
964
+ **/
965
+ value: null,
966
+ /**
967
+ Placement of container relative to element. Can be <code>top|right|bottom|left</code>. Not used for inline container.
968
+
969
+ @property placement
970
+ @type string
971
+ @default 'top'
972
+ **/
973
+ placement: 'top',
974
+ /**
975
+ Wether to hide container on save/cancel.
976
+
977
+ @property autohide
978
+ @type boolean
979
+ @default true
980
+ @private
981
+ **/
982
+ autohide: true,
983
+ /**
984
+ Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>.
985
+ Setting <code>ignore</code> allows to have several containers open.
986
+
987
+ @property onblur
988
+ @type string
989
+ @default 'cancel'
990
+ **/
991
+ onblur: 'cancel'
992
+ };
993
+
994
+ /*
995
+ * workaround to have 'destroyed' event to destroy popover when element is destroyed
996
+ * see http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom
997
+ */
998
+ jQuery.event.special.destroyed = {
999
+ remove: function(o) {
1000
+ if (o.handler) {
1001
+ o.handler();
1002
+ }
1003
+ }
1004
+ };
1005
+
1006
+ }(window.jQuery));
1007
+
1008
+ /**
1009
+ Makes editable any HTML element on the page. Applied as jQuery method.
1010
+
1011
+ @class editable
1012
+ @uses editableContainer
1013
+ **/
1014
+ (function ($) {
1015
+
1016
+ var Editable = function (element, options) {
1017
+ this.$element = $(element);
1018
+ this.options = $.extend({}, $.fn.editable.defaults, $.fn.editableform.utils.getConfigData(this.$element), options);
1019
+ this.init();
1020
+ };
1021
+
1022
+ Editable.prototype = {
1023
+ constructor: Editable,
1024
+ init: function () {
1025
+ var TypeConstructor,
1026
+ isValueByText = false,
1027
+ doAutotext,
1028
+ finalize;
1029
+
1030
+ //initialization flag
1031
+ this.isInit = true;
1032
+
1033
+ //editableContainer must be defined
1034
+ if(!$.fn.editableContainer) {
1035
+ $.error('You must define $.fn.editableContainer via including corresponding file (e.g. editable-popover.js)');
1036
+ return;
1037
+ }
1038
+
1039
+ //name
1040
+ this.options.name = this.options.name || this.$element.attr('id');
1041
+
1042
+ //create input of specified type. Input will be used for converting value, not in form
1043
+ if(typeof $.fn.editableform.types[this.options.type] === 'function') {
1044
+ TypeConstructor = $.fn.editableform.types[this.options.type];
1045
+ this.typeOptions = $.fn.editableform.utils.sliceObj(this.options, $.fn.editableform.utils.objectKeys(TypeConstructor.defaults));
1046
+ this.input = new TypeConstructor(this.typeOptions);
1047
+ } else {
1048
+ $.error('Unknown type: '+ this.options.type);
1049
+ return;
1050
+ }
1051
+
1052
+ //set value from settings or by element's text
1053
+ if (this.options.value === undefined || this.options.value === null) {
1054
+ this.value = this.input.html2value($.trim(this.$element.html()));
1055
+ isValueByText = true;
1056
+ } else {
1057
+ if(typeof this.options.value === 'string') {
1058
+ this.options.value = $.trim(this.options.value);
1059
+ }
1060
+ this.value = this.input.str2value(this.options.value);
1061
+ }
1062
+
1063
+ //add 'editable' class
1064
+ this.$element.addClass('editable');
1065
+
1066
+ //attach handler activating editable. In disabled mode it just prevent default action (useful for links)
1067
+ if(this.options.toggle !== 'manual') {
1068
+ this.$element.addClass('editable-click');
1069
+ this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
1070
+ e.preventDefault();
1071
+ //stop propagation not required anymore because in document click handler it checks event target
1072
+ //e.stopPropagation();
1073
+
1074
+ if(this.options.toggle === 'mouseenter') {
1075
+ //for hover only show container
1076
+ this.show();
1077
+ } else {
1078
+ //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
1079
+ var closeAll = (this.options.toggle !== 'click');
1080
+ this.toggle(closeAll);
1081
+ }
1082
+ }, this));
1083
+ } else {
1084
+ this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually
1085
+ }
1086
+
1087
+ //check conditions for autotext:
1088
+ //if value was generated by text or value is empty, no sense to run autotext
1089
+ doAutotext = !isValueByText && this.value !== null && this.value !== undefined;
1090
+ doAutotext &= (this.options.autotext === 'always') || (this.options.autotext === 'auto' && !this.$element.text().length);
1091
+ $.when(doAutotext ? this.input.value2html(this.value, this.$element) : true).then($.proxy(function() {
1092
+ if(this.options.disabled) {
1093
+ this.disable();
1094
+ } else {
1095
+ this.enable();
1096
+ }
1097
+ /**
1098
+ Fired each time when element's text is rendered. Occurs on initialization and on each update of value.
1099
+ Can be used for display customization.
1100
+
1101
+ @event render
1102
+ @param {Object} event event object
1103
+ @param {Object} editable editable instance
1104
+ @example
1105
+ $('#action').on('render', function(e, editable) {
1106
+ var colors = {0: "gray", 1: "green", 2: "blue", 3: "red"};
1107
+ $(this).css("color", colors[editable.value]);
1108
+ });
1109
+ **/
1110
+ this.$element.triggerHandler('render', this);
1111
+ this.isInit = false;
1112
+ }, this));
1113
+ },
1114
+
1115
+ /**
1116
+ Enables editable
1117
+ @method enable()
1118
+ **/
1119
+ enable: function() {
1120
+ this.options.disabled = false;
1121
+ this.$element.removeClass('editable-disabled');
1122
+ this.handleEmpty();
1123
+ if(this.options.toggle !== 'manual') {
1124
+ if(this.$element.attr('tabindex') === '-1') {
1125
+ this.$element.removeAttr('tabindex');
1126
+ }
1127
+ }
1128
+ },
1129
+
1130
+ /**
1131
+ Disables editable
1132
+ @method disable()
1133
+ **/
1134
+ disable: function() {
1135
+ this.options.disabled = true;
1136
+ this.hide();
1137
+ this.$element.addClass('editable-disabled');
1138
+ this.handleEmpty();
1139
+ //do not stop focus on this element
1140
+ this.$element.attr('tabindex', -1);
1141
+ },
1142
+
1143
+ /**
1144
+ Toggles enabled / disabled state of editable element
1145
+ @method toggleDisabled()
1146
+ **/
1147
+ toggleDisabled: function() {
1148
+ if(this.options.disabled) {
1149
+ this.enable();
1150
+ } else {
1151
+ this.disable();
1152
+ }
1153
+ },
1154
+
1155
+ /**
1156
+ Sets new option
1157
+
1158
+ @method option(key, value)
1159
+ @param {string|object} key option name or object with several options
1160
+ @param {mixed} value option new value
1161
+ @example
1162
+ $('.editable').editable('option', 'pk', 2);
1163
+ **/
1164
+ option: function(key, value) {
1165
+ //set option(s) by object
1166
+ if(key && typeof key === 'object') {
1167
+ $.each(key, $.proxy(function(k, v){
1168
+ this.option($.trim(k), v);
1169
+ }, this));
1170
+ return;
1171
+ }
1172
+
1173
+ //set option by string
1174
+ this.options[key] = value;
1175
+
1176
+ //disabled
1177
+ if(key === 'disabled') {
1178
+ if(value) {
1179
+ this.disable();
1180
+ } else {
1181
+ this.enable();
1182
+ }
1183
+ return;
1184
+ }
1185
+
1186
+ //value
1187
+ if(key === 'value') {
1188
+ this.setValue(value);
1189
+ }
1190
+
1191
+ //transfer new option to container!
1192
+ if(this.container) {
1193
+ this.container.option(key, value);
1194
+ }
1195
+ },
1196
+
1197
+ /*
1198
+ * set emptytext if element is empty (reverse: remove emptytext if needed)
1199
+ */
1200
+ handleEmpty: function () {
1201
+ var emptyClass = 'editable-empty';
1202
+ //emptytext shown only for enabled
1203
+ if(!this.options.disabled) {
1204
+ if ($.trim(this.$element.text()) === '') {
1205
+ this.$element.addClass(emptyClass).text(this.options.emptytext);
1206
+ } else {
1207
+ this.$element.removeClass(emptyClass);
1208
+ }
1209
+ } else {
1210
+ //below required if element disable property was changed
1211
+ if(this.$element.hasClass(emptyClass)) {
1212
+ this.$element.empty();
1213
+ this.$element.removeClass(emptyClass);
1214
+ }
1215
+ }
1216
+ },
1217
+
1218
+ /**
1219
+ Shows container with form
1220
+ @method show()
1221
+ @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true.
1222
+ **/
1223
+ show: function (closeAll) {
1224
+ if(this.options.disabled) {
1225
+ return;
1226
+ }
1227
+
1228
+ //init editableContainer: popover, tooltip, inline, etc..
1229
+ if(!this.container) {
1230
+ var containerOptions = $.extend({}, this.options, {
1231
+ value: this.value,
1232
+ autohide: false //element will take care to show/hide container
1233
+ });
1234
+ this.$element.editableContainer(containerOptions);
1235
+ this.$element.on({
1236
+ save: $.proxy(this.save, this),
1237
+ cancel: $.proxy(this.hide, this)
1238
+ });
1239
+ this.container = this.$element.data('editableContainer');
1240
+ } else if(this.container.tip().is(':visible')) {
1241
+ return;
1242
+ }
1243
+
1244
+ //show container
1245
+ this.container.show(closeAll);
1246
+ },
1247
+
1248
+ /**
1249
+ Hides container with form
1250
+ @method hide()
1251
+ **/
1252
+ hide: function () {
1253
+ if(this.container) {
1254
+ this.container.hide();
1255
+ }
1256
+
1257
+ //return focus on element
1258
+ if (this.options.enablefocus && this.options.toggle === 'click') {
1259
+ this.$element.focus();
1260
+ }
1261
+ },
1262
+
1263
+ /**
1264
+ Toggles container visibility (show / hide)
1265
+ @method toggle()
1266
+ @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true.
1267
+ **/
1268
+ toggle: function(closeAll) {
1269
+ if(this.container && this.container.tip().is(':visible')) {
1270
+ this.hide();
1271
+ } else {
1272
+ this.show(closeAll);
1273
+ }
1274
+ },
1275
+
1276
+ /*
1277
+ * called when form was submitted
1278
+ */
1279
+ save: function(e, params) {
1280
+ //if url is not user's function and value was not sent to server and value changed --> mark element with unsaved css.
1281
+ if(typeof this.options.url !== 'function' && params.response === undefined && this.input.value2str(this.value) !== this.input.value2str(params.newValue)) {
1282
+ this.$element.addClass('editable-unsaved');
1283
+ } else {
1284
+ this.$element.removeClass('editable-unsaved');
1285
+ }
1286
+
1287
+ this.hide();
1288
+ this.setValue(params.newValue);
1289
+
1290
+ /**
1291
+ Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance
1292
+
1293
+ @event save
1294
+ @param {Object} event event object
1295
+ @param {Object} params additional params
1296
+ @param {mixed} params.newValue submitted value
1297
+ @param {Object} params.response ajax response
1298
+ @example
1299
+ $('#username').on('save', function(e, params) {
1300
+ //assuming server response: '{success: true}'
1301
+ var pk = $(this).data('editable').options.pk;
1302
+ if(params.response && params.response.success) {
1303
+ alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');
1304
+ } else {
1305
+ alert('error!');
1306
+ }
1307
+ });
1308
+ **/
1309
+ //event itself is triggered by editableContainer. Description here is only for documentation
1310
+ },
1311
+
1312
+ validate: function () {
1313
+ if (typeof this.options.validate === 'function') {
1314
+ return this.options.validate.call(this, this.value);
1315
+ }
1316
+ },
1317
+
1318
+ /**
1319
+ Sets new value of editable
1320
+ @method setValue(value, convertStr)
1321
+ @param {mixed} value new value
1322
+ @param {boolean} convertStr wether to convert value from string to internal format
1323
+ **/
1324
+ setValue: function(value, convertStr) {
1325
+ if(convertStr) {
1326
+ this.value = this.input.str2value(value);
1327
+ } else {
1328
+ this.value = value;
1329
+ }
1330
+ if(this.container) {
1331
+ this.container.option('value', this.value);
1332
+ }
1333
+ $.when(this.input.value2html(this.value, this.$element))
1334
+ .then($.proxy(function() {
1335
+ this.handleEmpty();
1336
+ this.$element.triggerHandler('render', this);
1337
+ }, this));
1338
+ },
1339
+
1340
+ /**
1341
+ Activates input of visible container (e.g. set focus)
1342
+ @method activate()
1343
+ **/
1344
+ activate: function() {
1345
+ if(this.container) {
1346
+ this.container.activate();
1347
+ }
1348
+ }
1349
+ };
1350
+
1351
+ /* EDITABLE PLUGIN DEFINITION
1352
+ * ======================= */
1353
+
1354
+ /**
1355
+ jQuery method to initialize editable element.
1356
+
1357
+ @method $().editable(options)
1358
+ @params {Object} options
1359
+ @example
1360
+ $('#username').editable({
1361
+ type: 'text',
1362
+ url: '/post',
1363
+ pk: 1
1364
+ });
1365
+ **/
1366
+ $.fn.editable = function (option) {
1367
+ //special API methods returning non-jquery object
1368
+ var result = {}, args = arguments, datakey = 'editable';
1369
+ switch (option) {
1370
+ /**
1371
+ Runs client-side validation for all matched editables
1372
+
1373
+ @method validate()
1374
+ @returns {Object} validation errors map
1375
+ @example
1376
+ $('#username, #fullname').editable('validate');
1377
+ // possible result:
1378
+ {
1379
+ username: "username is requied",
1380
+ fullname: "fullname should be minimum 3 letters length"
1381
+ }
1382
+ **/
1383
+ case 'validate':
1384
+ this.each(function () {
1385
+ var $this = $(this), data = $this.data(datakey), error;
1386
+ if (data && (error = data.validate())) {
1387
+ result[data.options.name] = error;
1388
+ }
1389
+ });
1390
+ return result;
1391
+
1392
+ /**
1393
+ Returns current values of editable elements. If value is <code>null</code> or <code>undefined</code> it will not be returned
1394
+ @method getValue()
1395
+ @returns {Object} object of element names and values
1396
+ @example
1397
+ $('#username, #fullname').editable('validate');
1398
+ // possible result:
1399
+ {
1400
+ username: "superuser",
1401
+ fullname: "John"
1402
+ }
1403
+ **/
1404
+ case 'getValue':
1405
+ this.each(function () {
1406
+ var $this = $(this), data = $this.data(datakey);
1407
+ if (data && data.value !== undefined && data.value !== null) {
1408
+ result[data.options.name] = data.input.value2str(data.value);
1409
+ }
1410
+ });
1411
+ return result;
1412
+
1413
+ /**
1414
+ This method collects values from several editable elements and submit them all to server.
1415
+ It is designed mainly for <a href="#newrecord">creating new records</a>.
1416
+
1417
+ @method submit(options)
1418
+ @param {object} options
1419
+ @param {object} options.url url to submit data
1420
+ @param {object} options.data additional data to submit
1421
+ @param {function} options.error(obj) error handler (called on both client-side and server-side validation errors)
1422
+ @param {function} options.success(obj) success handler
1423
+ @returns {Object} jQuery object
1424
+ **/
1425
+ case 'submit': //collects value, validate and submit to server for creating new record
1426
+ var config = arguments[1] || {},
1427
+ $elems = this,
1428
+ errors = this.editable('validate'),
1429
+ values;
1430
+
1431
+ if(typeof config.error !== 'function') {
1432
+ config.error = function() {};
1433
+ }
1434
+
1435
+ if($.isEmptyObject(errors)) {
1436
+ values = this.editable('getValue');
1437
+ if(config.data) {
1438
+ $.extend(values, config.data);
1439
+ }
1440
+ $.ajax({
1441
+ type: 'POST',
1442
+ url: config.url,
1443
+ data: values,
1444
+ dataType: 'json'
1445
+ }).success(function(response) {
1446
+ if(typeof response === 'object' && response.id) {
1447
+ $elems.editable('option', 'pk', response.id);
1448
+ $elems.removeClass('editable-unsaved');
1449
+ if(typeof config.success === 'function') {
1450
+ config.success.apply($elems, arguments);
1451
+ }
1452
+ } else { //server-side validation error
1453
+ config.error.apply($elems, arguments);
1454
+ }
1455
+ }).error(function(){ //ajax error
1456
+ config.error.apply($elems, arguments);
1457
+ });
1458
+ } else { //client-side validation error
1459
+ config.error.call($elems, {errors: errors});
1460
+ }
1461
+ return this;
1462
+ }
1463
+
1464
+ //return jquery object
1465
+ return this.each(function () {
1466
+ var $this = $(this),
1467
+ data = $this.data(datakey),
1468
+ options = typeof option === 'object' && option;
1469
+
1470
+ if (!data) {
1471
+ $this.data(datakey, (data = new Editable(this, options)));
1472
+ }
1473
+
1474
+ if (typeof option === 'string') { //call method
1475
+ data[option].apply(data, Array.prototype.slice.call(args, 1));
1476
+ }
1477
+ });
1478
+ };
1479
+
1480
+
1481
+ $.fn.editable.defaults = {
1482
+ /**
1483
+ Type of input. Can be <code>text|textarea|select|date|checklist</code> and more
1484
+
1485
+ @property type
1486
+ @type string
1487
+ @default 'text'
1488
+ **/
1489
+ type: 'text',
1490
+ /**
1491
+ Sets disabled state of editable
1492
+
1493
+ @property disabled
1494
+ @type boolean
1495
+ @default false
1496
+ **/
1497
+ disabled: false,
1498
+ /**
1499
+ How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>.
1500
+ When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable.
1501
+ **Note**: if you call <code>show</code> or <code>toggle</code> inside **click** handler of some DOM element,
1502
+ you need to apply <code>e.stopPropagation()</code> because containers are being closed on any click on document.
1503
+
1504
+ @example
1505
+ $('#edit-button').click(function(e) {
1506
+ e.stopPropagation();
1507
+ $('#username').editable('toggle');
1508
+ });
1509
+
1510
+ @property toggle
1511
+ @type string
1512
+ @default 'click'
1513
+ **/
1514
+ toggle: 'click',
1515
+
1516
+ /**
1517
+ Text shown when element is empty.
1518
+
1519
+ @property emptytext
1520
+ @type string
1521
+ @default 'Empty'
1522
+ **/
1523
+ emptytext: 'Empty',
1524
+ /**
1525
+ Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Usefull for select and date.
1526
+ For example, if dropdown list is <code>{1: 'a', 2: 'b'}</code> and element's value set to <code>1</code>, it's html will be automatically set to <code>'a'</code>.
1527
+ <code>auto</code> - text will be automatically set only if element is empty.
1528
+ <code>always|never</code> - always(never) try to set element's text.
1529
+
1530
+ @property autotext
1531
+ @type string
1532
+ @default 'auto'
1533
+ **/
1534
+ autotext: 'auto',
1535
+ /**
1536
+ Wether to return focus on element after form is closed.
1537
+ This allows fully keyboard input.
1538
+
1539
+ @property enablefocus
1540
+ @type boolean
1541
+ @default false
1542
+ **/
1543
+ enablefocus: false,
1544
+ /**
1545
+ Initial value of input. Taken from <code>data-value</code> or element's text.
1546
+
1547
+ @property value
1548
+ @type mixed
1549
+ @default element's text
1550
+ **/
1551
+ value: null
1552
+ };
1553
+
1554
+ }(window.jQuery));
1555
+ /**
1556
+ Abstract editable input class.
1557
+ To create your own input you should inherit from this class.
1558
+
1559
+ @class abstract
1560
+ **/
1561
+ (function ($) {
1562
+
1563
+ var Abstract = function () { };
1564
+
1565
+ Abstract.prototype = {
1566
+ /**
1567
+ Iinitializes input
1568
+
1569
+ @method init()
1570
+ **/
1571
+ init: function(type, options, defaults) {
1572
+ this.type = type;
1573
+ this.options = $.extend({}, defaults, options);
1574
+ this.$input = null;
1575
+ this.$clear = null;
1576
+ this.error = null;
1577
+ },
1578
+
1579
+ /**
1580
+ Renders input. Can return jQuery deferred object.
1581
+
1582
+ @method render()
1583
+ **/
1584
+ render: function() {
1585
+ this.$input = $(this.options.tpl);
1586
+ if(this.options.inputclass) {
1587
+ this.$input.addClass(this.options.inputclass);
1588
+ }
1589
+
1590
+ if (this.options.placeholder) {
1591
+ this.$input.attr('placeholder', this.options.placeholder);
1592
+ }
1593
+ },
1594
+
1595
+ /**
1596
+ Sets element's html by value.
1597
+
1598
+ @method value2html(value, element)
1599
+ @param {mixed} value
1600
+ @param {DOMElement} element
1601
+ **/
1602
+ value2html: function(value, element) {
1603
+ var html = this.escape(value);
1604
+ $(element).html(html);
1605
+ },
1606
+
1607
+ /**
1608
+ Converts element's html to value
1609
+
1610
+ @method html2value(html)
1611
+ @param {string} html
1612
+ @returns {mixed}
1613
+ **/
1614
+ html2value: function(html) {
1615
+ return $('<div>').html(html).text();
1616
+ },
1617
+
1618
+ /**
1619
+ Converts value to string (for submiting to server)
1620
+
1621
+ @method value2str(value)
1622
+ @param {mixed} value
1623
+ @returns {string}
1624
+ **/
1625
+ value2str: function(value) {
1626
+ return value;
1627
+ },
1628
+
1629
+ /**
1630
+ Converts string received from server into value.
1631
+
1632
+ @method str2value(str)
1633
+ @param {string} str
1634
+ @returns {mixed}
1635
+ **/
1636
+ str2value: function(str) {
1637
+ return str;
1638
+ },
1639
+
1640
+ /**
1641
+ Sets value of input.
1642
+
1643
+ @method value2input(value)
1644
+ @param {mixed} value
1645
+ **/
1646
+ value2input: function(value) {
1647
+ this.$input.val(value);
1648
+ },
1649
+
1650
+ /**
1651
+ Returns value of input. Value can be object (e.g. datepicker)
1652
+
1653
+ @method input2value()
1654
+ **/
1655
+ input2value: function() {
1656
+ return this.$input.val();
1657
+ },
1658
+
1659
+ /**
1660
+ Activates input. For text it sets focus.
1661
+
1662
+ @method activate()
1663
+ **/
1664
+ activate: function() {
1665
+ if(this.$input.is(':visible')) {
1666
+ this.$input.focus();
1667
+ }
1668
+ },
1669
+
1670
+ /**
1671
+ Creares input.
1672
+
1673
+ @method clear()
1674
+ **/
1675
+ clear: function() {
1676
+ this.$input.val(null);
1677
+ },
1678
+
1679
+ /**
1680
+ method to escape html.
1681
+ **/
1682
+ escape: function(str) {
1683
+ return $('<div>').text(str).html();
1684
+ },
1685
+
1686
+ /**
1687
+ attach handler to automatically submit form when value changed (usefull when buttons not shown)
1688
+ **/
1689
+ autosubmit: function() {
1690
+
1691
+ }
1692
+ };
1693
+
1694
+ Abstract.defaults = {
1695
+ /**
1696
+ HTML template of input. Normally you should not change it.
1697
+
1698
+ @property tpl
1699
+ @type string
1700
+ @default ''
1701
+ **/
1702
+ tpl: '',
1703
+ /**
1704
+ CSS class automatically applied to input
1705
+
1706
+ @property inputclass
1707
+ @type string
1708
+ @default span2
1709
+ **/
1710
+ inputclass: 'span2',
1711
+ /**
1712
+ Name attribute of input
1713
+
1714
+ @property name
1715
+ @type string
1716
+ @default null
1717
+ **/
1718
+ name: null
1719
+ };
1720
+
1721
+ $.extend($.fn.editableform.types, {abstract: Abstract});
1722
+
1723
+ }(window.jQuery));
1724
+ /**
1725
+ List - abstract class for inputs that have source option loaded from js array or via ajax
1726
+
1727
+ @class list
1728
+ @extends abstract
1729
+ **/
1730
+ (function ($) {
1731
+
1732
+ var List = function (options) {
1733
+
1734
+ };
1735
+
1736
+ $.fn.editableform.utils.inherit(List, $.fn.editableform.types.abstract);
1737
+
1738
+ $.extend(List.prototype, {
1739
+ render: function () {
1740
+ List.superclass.render.call(this);
1741
+ var deferred = $.Deferred();
1742
+ this.error = null;
1743
+ this.sourceData = null;
1744
+ this.prependData = null;
1745
+ this.onSourceReady(function () {
1746
+ this.renderList();
1747
+ deferred.resolve();
1748
+ }, function () {
1749
+ this.error = this.options.sourceError;
1750
+ deferred.resolve();
1751
+ });
1752
+
1753
+ return deferred.promise();
1754
+ },
1755
+
1756
+ html2value: function (html) {
1757
+ return null; //can't set value by text
1758
+ },
1759
+
1760
+ value2html: function (value, element) {
1761
+ var deferred = $.Deferred();
1762
+ this.onSourceReady(function () {
1763
+ this.value2htmlFinal(value, element);
1764
+ deferred.resolve();
1765
+ }, function () {
1766
+ List.superclass.value2html(this.options.sourceError, element);
1767
+ deferred.resolve();
1768
+ });
1769
+
1770
+ return deferred.promise();
1771
+ },
1772
+
1773
+ // ------------- additional functions ------------
1774
+
1775
+ onSourceReady: function (success, error) {
1776
+ //if allready loaded just call success
1777
+ if($.isArray(this.sourceData)) {
1778
+ success.call(this);
1779
+ return;
1780
+ }
1781
+
1782
+ // try parse json in single quotes (for double quotes jquery does automatically)
1783
+ try {
1784
+ this.options.source = $.fn.editableform.utils.tryParseJson(this.options.source, false);
1785
+ } catch (e) {
1786
+ error.call(this);
1787
+ return;
1788
+ }
1789
+
1790
+ //loading from url
1791
+ if (typeof this.options.source === 'string') {
1792
+ var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''),
1793
+ cache;
1794
+
1795
+ if (!$(document).data(cacheID)) {
1796
+ $(document).data(cacheID, {});
1797
+ }
1798
+ cache = $(document).data(cacheID);
1799
+
1800
+ //check for cached data
1801
+ if (cache.loading === false && cache.sourceData) { //take source from cache
1802
+ this.sourceData = cache.sourceData;
1803
+ success.call(this);
1804
+ return;
1805
+ } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later
1806
+ cache.callbacks.push($.proxy(function () {
1807
+ this.sourceData = cache.sourceData;
1808
+ success.call(this);
1809
+ }, this));
1810
+
1811
+ //also collecting error callbacks
1812
+ cache.err_callbacks.push($.proxy(error, this));
1813
+ return;
1814
+ } else { //no cache yet, activate it
1815
+ cache.loading = true;
1816
+ cache.callbacks = [];
1817
+ cache.err_callbacks = [];
1818
+ }
1819
+
1820
+ //loading sourceData from server
1821
+ $.ajax({
1822
+ url: this.options.source,
1823
+ type: 'get',
1824
+ cache: false,
1825
+ data: this.options.name ? {name: this.options.name} : {},
1826
+ dataType: 'json',
1827
+ success: $.proxy(function (data) {
1828
+ cache.loading = false;
1829
+ this.sourceData = this.makeArray(data);
1830
+ if($.isArray(this.sourceData)) {
1831
+ this.doPrepend();
1832
+ //store result in cache
1833
+ cache.sourceData = this.sourceData;
1834
+ success.call(this);
1835
+ $.each(cache.callbacks, function () { this.call(); }); //run success callbacks for other fields
1836
+ } else {
1837
+ error.call(this);
1838
+ $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields
1839
+ }
1840
+ }, this),
1841
+ error: $.proxy(function () {
1842
+ cache.loading = false;
1843
+ error.call(this);
1844
+ $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields
1845
+ }, this)
1846
+ });
1847
+ } else { //options as json/array
1848
+ this.sourceData = this.makeArray(this.options.source);
1849
+ if($.isArray(this.sourceData)) {
1850
+ this.doPrepend();
1851
+ success.call(this);
1852
+ } else {
1853
+ error.call(this);
1854
+ }
1855
+ }
1856
+ },
1857
+
1858
+ doPrepend: function () {
1859
+ if(this.options.prepend === null || this.options.prepend === undefined) {
1860
+ return;
1861
+ }
1862
+
1863
+ if(!$.isArray(this.prependData)) {
1864
+ //try parse json in single quotes
1865
+ this.options.prepend = $.fn.editableform.utils.tryParseJson(this.options.prepend, true);
1866
+ if (typeof this.options.prepend === 'string') {
1867
+ this.options.prepend = {'': this.options.prepend};
1868
+ }
1869
+ this.prependData = this.makeArray(this.options.prepend);
1870
+ }
1871
+
1872
+ if($.isArray(this.prependData) && $.isArray(this.sourceData)) {
1873
+ this.sourceData = this.prependData.concat(this.sourceData);
1874
+ }
1875
+ },
1876
+
1877
+ /*
1878
+ renders input list
1879
+ */
1880
+ renderList: function() {
1881
+ // this method should be overwritten in child class
1882
+ },
1883
+
1884
+ /*
1885
+ set element's html by value
1886
+ */
1887
+ value2htmlFinal: function(value, element) {
1888
+ // this method should be overwritten in child class
1889
+ },
1890
+
1891
+ /**
1892
+ * convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}]
1893
+ */
1894
+ makeArray: function(data) {
1895
+ var count, obj, result = [], iterateEl;
1896
+ if(!data || typeof data === 'string') {
1897
+ return null;
1898
+ }
1899
+
1900
+ if($.isArray(data)) { //array
1901
+ iterateEl = function (k, v) {
1902
+ obj = {value: k, text: v};
1903
+ if(count++ >= 2) {
1904
+ return false;// exit each if object has more than one value
1905
+ }
1906
+ };
1907
+
1908
+ for(var i = 0; i < data.length; i++) {
1909
+ if(typeof data[i] === 'object') {
1910
+ count = 0;
1911
+ $.each(data[i], iterateEl);
1912
+ if(count === 1) {
1913
+ result.push(obj);
1914
+ } else if(count > 1 && data[i].hasOwnProperty('value') && data[i].hasOwnProperty('text')) {
1915
+ result.push(data[i]);
1916
+ } else {
1917
+ //data contains incorrect objects
1918
+ }
1919
+ } else {
1920
+ result.push({value: data[i], text: data[i]});
1921
+ }
1922
+ }
1923
+ } else { //object
1924
+ $.each(data, function (k, v) {
1925
+ result.push({value: k, text: v});
1926
+ });
1927
+ }
1928
+ return result;
1929
+ },
1930
+
1931
+ //search for item by particular value
1932
+ itemByVal: function(val) {
1933
+ if($.isArray(this.sourceData)) {
1934
+ for(var i=0; i<this.sourceData.length; i++){
1935
+ /*jshint eqeqeq: false*/
1936
+ if(this.sourceData[i].value == val) {
1937
+ /*jshint eqeqeq: true*/
1938
+ return this.sourceData[i];
1939
+ }
1940
+ }
1941
+ }
1942
+ }
1943
+
1944
+ });
1945
+
1946
+ List.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
1947
+ /**
1948
+ Source data for list. If string - considered ajax url to load items. Otherwise should be an array.
1949
+ Array format is: <code>[{value: 1, text: "text"}, {...}]</code><br>
1950
+ For compability it also supports format <code>{value1: "text1", value2: "text2" ...}</code> but it does not guarantee elements order.
1951
+
1952
+ @property source
1953
+ @type string|array|object
1954
+ @default null
1955
+ **/
1956
+ source:null,
1957
+ /**
1958
+ Data automatically prepended to the begining of dropdown list.
1959
+
1960
+ @property prepend
1961
+ @type string|array|object
1962
+ @default false
1963
+ **/
1964
+ prepend:false,
1965
+ /**
1966
+ Error message when list cannot be loaded (e.g. ajax error)
1967
+
1968
+ @property sourceError
1969
+ @type string
1970
+ @default Error when loading list
1971
+ **/
1972
+ sourceError: 'Error when loading list'
1973
+ });
1974
+
1975
+ $.fn.editableform.types.list = List;
1976
+
1977
+ }(window.jQuery));
1978
+ /**
1979
+ Text input
1980
+
1981
+ @class text
1982
+ @extends abstract
1983
+ @final
1984
+ @example
1985
+ <a href="#" id="username" data-type="text" data-pk="1">awesome</a>
1986
+ <script>
1987
+ $(function(){
1988
+ $('#username').editable({
1989
+ url: '/post',
1990
+ title: 'Enter username'
1991
+ });
1992
+ });
1993
+ </script>
1994
+ **/
1995
+ (function ($) {
1996
+ var Text = function (options) {
1997
+ this.init('text', options, Text.defaults);
1998
+ };
1999
+
2000
+ $.fn.editableform.utils.inherit(Text, $.fn.editableform.types.abstract);
2001
+
2002
+ $.extend(Text.prototype, {
2003
+ activate: function() {
2004
+ if(this.$input.is(':visible')) {
2005
+ this.$input.focus();
2006
+ $.fn.editableform.utils.setCursorPosition(this.$input.get(0), this.$input.val().length);
2007
+ }
2008
+ }
2009
+ });
2010
+
2011
+ Text.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
2012
+ /**
2013
+ @property tpl
2014
+ @default <input type="text">
2015
+ **/
2016
+ tpl: '<input type="text">',
2017
+ /**
2018
+ Placeholder attribute of input. Shown when input is empty.
2019
+
2020
+ @property placeholder
2021
+ @type string
2022
+ @default null
2023
+ **/
2024
+ placeholder: null
2025
+ });
2026
+
2027
+ $.fn.editableform.types.text = Text;
2028
+
2029
+ }(window.jQuery));
2030
+
2031
+ /**
2032
+ Textarea input
2033
+
2034
+ @class textarea
2035
+ @extends abstract
2036
+ @final
2037
+ @example
2038
+ <a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a>
2039
+ <script>
2040
+ $(function(){
2041
+ $('#comments').editable({
2042
+ url: '/post',
2043
+ title: 'Enter comments'
2044
+ });
2045
+ });
2046
+ </script>
2047
+ **/
2048
+ (function ($) {
2049
+
2050
+ var Textarea = function (options) {
2051
+ this.init('textarea', options, Textarea.defaults);
2052
+ };
2053
+
2054
+ $.fn.editableform.utils.inherit(Textarea, $.fn.editableform.types.abstract);
2055
+
2056
+ $.extend(Textarea.prototype, {
2057
+ render: function () {
2058
+ Textarea.superclass.render.call(this);
2059
+
2060
+ //ctrl + enter
2061
+ this.$input.keydown(function (e) {
2062
+ if (e.ctrlKey && e.which === 13) {
2063
+ $(this).closest('form').submit();
2064
+ }
2065
+ });
2066
+ },
2067
+
2068
+ value2html: function(value, element) {
2069
+ var html = '', lines;
2070
+ if(value) {
2071
+ lines = value.split("\n");
2072
+ for (var i = 0; i < lines.length; i++) {
2073
+ lines[i] = $('<div>').text(lines[i]).html();
2074
+ }
2075
+ html = lines.join('<br>');
2076
+ }
2077
+ $(element).html(html);
2078
+ },
2079
+
2080
+ html2value: function(html) {
2081
+ if(!html) {
2082
+ return '';
2083
+ }
2084
+ var lines = html.split(/<br\s*\/?>/i);
2085
+ for (var i = 0; i < lines.length; i++) {
2086
+ lines[i] = $('<div>').html(lines[i]).text();
2087
+ }
2088
+ return lines.join("\n");
2089
+ },
2090
+
2091
+ activate: function() {
2092
+ if(this.$input.is(':visible')) {
2093
+ $.fn.editableform.utils.setCursorPosition(this.$input.get(0), this.$input.val().length);
2094
+ this.$input.focus();
2095
+ }
2096
+ }
2097
+ });
2098
+
2099
+ Textarea.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
2100
+ /**
2101
+ @property tpl
2102
+ @default <textarea></textarea>
2103
+ **/
2104
+ tpl:'<textarea></textarea>',
2105
+ /**
2106
+ @property inputclass
2107
+ @default span3
2108
+ **/
2109
+ inputclass:'span3',
2110
+ /**
2111
+ Placeholder attribute of input. Shown when input is empty.
2112
+
2113
+ @property placeholder
2114
+ @type string
2115
+ @default null
2116
+ **/
2117
+ placeholder: null
2118
+ });
2119
+
2120
+ $.fn.editableform.types.textarea = Textarea;
2121
+
2122
+ }(window.jQuery));
2123
+ /**
2124
+ Select (dropdown)
2125
+
2126
+ @class select
2127
+ @extends list
2128
+ @final
2129
+ @example
2130
+ <a href="#" id="status" data-type="select" data-pk="1" data-url="/post" data-original-title="Select status"></a>
2131
+ <script>
2132
+ $(function(){
2133
+ $('#status').editable({
2134
+ value: 2,
2135
+ source: [
2136
+ {value: 1, text: 'Active'},
2137
+ {value: 2, text: 'Blocked'},
2138
+ {value: 3, text: 'Deleted'}
2139
+ ]
2140
+ }
2141
+ });
2142
+ });
2143
+ </script>
2144
+ **/
2145
+ (function ($) {
2146
+
2147
+ var Select = function (options) {
2148
+ this.init('select', options, Select.defaults);
2149
+ };
2150
+
2151
+ $.fn.editableform.utils.inherit(Select, $.fn.editableform.types.list);
2152
+
2153
+ $.extend(Select.prototype, {
2154
+ renderList: function() {
2155
+ if(!$.isArray(this.sourceData)) {
2156
+ return;
2157
+ }
2158
+
2159
+ for(var i=0; i<this.sourceData.length; i++) {
2160
+ this.$input.append($('<option>', {value: this.sourceData[i].value}).text(this.sourceData[i].text));
2161
+ }
2162
+ },
2163
+
2164
+ value2htmlFinal: function(value, element) {
2165
+ var text = '', item = this.itemByVal(value);
2166
+ if(item) {
2167
+ text = item.text;
2168
+ }
2169
+ Select.superclass.constructor.superclass.value2html(text, element);
2170
+ },
2171
+
2172
+ autosubmit: function() {
2173
+ this.$input.on('change', function(){
2174
+ $(this).closest('form').submit();
2175
+ });
2176
+ }
2177
+ });
2178
+
2179
+ Select.defaults = $.extend({}, $.fn.editableform.types.list.defaults, {
2180
+ /**
2181
+ @property tpl
2182
+ @default <select></select>
2183
+ **/
2184
+ tpl:'<select></select>'
2185
+ });
2186
+
2187
+ $.fn.editableform.types.select = Select;
2188
+
2189
+ }(window.jQuery));
2190
+ /**
2191
+ List of checkboxes.
2192
+ Internally value stored as javascript array of values.
2193
+
2194
+ @class checklist
2195
+ @extends list
2196
+ @final
2197
+ @example
2198
+ <a href="#" id="options" data-type="checklist" data-pk="1" data-url="/post" data-original-title="Select options"></a>
2199
+ <script>
2200
+ $(function(){
2201
+ $('#options').editable({
2202
+ value: [2, 3],
2203
+ source: [
2204
+ {value: 1, text: 'option1'},
2205
+ {value: 2, text: 'option2'},
2206
+ {value: 3, text: 'option3'}
2207
+ ]
2208
+ }
2209
+ });
2210
+ });
2211
+ </script>
2212
+ **/
2213
+ (function ($) {
2214
+
2215
+ var Checklist = function (options) {
2216
+ this.init('checklist', options, Checklist.defaults);
2217
+ };
2218
+
2219
+ $.fn.editableform.utils.inherit(Checklist, $.fn.editableform.types.list);
2220
+
2221
+ $.extend(Checklist.prototype, {
2222
+ renderList: function() {
2223
+ var $label, $div;
2224
+ if(!$.isArray(this.sourceData)) {
2225
+ return;
2226
+ }
2227
+
2228
+ for(var i=0; i<this.sourceData.length; i++) {
2229
+ $label = $('<label>').append($('<input>', {
2230
+ type: 'checkbox',
2231
+ value: this.sourceData[i].value,
2232
+ name: this.options.name
2233
+ }))
2234
+ .append($('<span>').text(' '+this.sourceData[i].text));
2235
+
2236
+ $('<div>').append($label).appendTo(this.$input);
2237
+ }
2238
+ },
2239
+
2240
+ value2str: function(value) {
2241
+ return $.isArray(value) ? value.join($.trim(this.options.separator)) : '';
2242
+ //it is also possible to sent as array
2243
+ //return value;
2244
+ },
2245
+
2246
+ //parse separated string
2247
+ str2value: function(str) {
2248
+ var reg, value = null;
2249
+ if(typeof str === 'string' && str.length) {
2250
+ reg = new RegExp('\\s*'+$.trim(this.options.separator)+'\\s*');
2251
+ value = str.split(reg);
2252
+ } else if($.isArray(str)) {
2253
+ value = str;
2254
+ }
2255
+ return value;
2256
+ },
2257
+
2258
+ //set checked on required checkboxes
2259
+ value2input: function(value) {
2260
+ var $checks = this.$input.find('input[type="checkbox"]');
2261
+ $checks.removeAttr('checked');
2262
+ if($.isArray(value) && value.length) {
2263
+ $checks.each(function(i, el) {
2264
+ var $el = $(el);
2265
+ // cannot use $.inArray as it performs strict comparison
2266
+ $.each(value, function(j, val){
2267
+ /*jslint eqeq: true*/
2268
+ if($el.val() == val) {
2269
+ /*jslint eqeq: false*/
2270
+ $el.attr('checked', 'checked');
2271
+ }
2272
+ });
2273
+ });
2274
+ }
2275
+ },
2276
+
2277
+ input2value: function() {
2278
+ var checked = [];
2279
+ this.$input.find('input:checked').each(function(i, el) {
2280
+ checked.push($(el).val());
2281
+ });
2282
+ return checked;
2283
+ },
2284
+
2285
+ //collect text of checked boxes
2286
+ value2htmlFinal: function(value, element) {
2287
+ var selected = [], item, i, html = '';
2288
+ if($.isArray(value) && value.length <= this.options.limit) {
2289
+ for(i=0; i<value.length; i++){
2290
+ item = this.itemByVal(value[i]);
2291
+ if(item) {
2292
+ selected.push($('<div>').text(item.text).html());
2293
+ }
2294
+ }
2295
+ html = selected.join(this.options.viewseparator);
2296
+ } else {
2297
+ html = this.options.limitText.replace('{checked}', $.isArray(value) ? value.length : 0).replace('{count}', this.sourceData.length);
2298
+ }
2299
+ $(element).html(html);
2300
+ },
2301
+
2302
+ activate: function() {
2303
+ this.$input.find('input[type="checkbox"]').first().focus();
2304
+ },
2305
+
2306
+ autosubmit: function() {
2307
+ this.$input.find('input[type="checkbox"]').on('keydown', function(e){
2308
+ if (e.which === 13) {
2309
+ $(this).closest('form').submit();
2310
+ }
2311
+ });
2312
+ }
2313
+ });
2314
+
2315
+ Checklist.defaults = $.extend({}, $.fn.editableform.types.list.defaults, {
2316
+ /**
2317
+ @property tpl
2318
+ @default <div></div>
2319
+ **/
2320
+ tpl:'<div></div>',
2321
+
2322
+ /**
2323
+ @property inputclass
2324
+ @type string
2325
+ @default span2 editable-checklist
2326
+ **/
2327
+ inputclass: 'span2 editable-checklist',
2328
+
2329
+ /**
2330
+ Separator of values in string when sending to server
2331
+
2332
+ @property separator
2333
+ @type string
2334
+ @default ', '
2335
+ **/
2336
+ separator: ',',
2337
+ /**
2338
+ Separator of text when display as element content.
2339
+
2340
+ @property viewseparator
2341
+ @type string
2342
+ @default '<br>'
2343
+ **/
2344
+ viewseparator: '<br>',
2345
+ /**
2346
+ Maximum number of items shown as element content.
2347
+ If checked more items - <code>limitText</code> will be shown.
2348
+
2349
+ @property limit
2350
+ @type integer
2351
+ @default 4
2352
+ **/
2353
+ limit: 4,
2354
+ /**
2355
+ Text shown when count of checked items is greater than <code>limit</code> parameter.
2356
+ You can use <code>{checked}</code> and <code>{count}</code> placeholders.
2357
+
2358
+ @property limitText
2359
+ @type string
2360
+ @default 'Selected {checked} of {count}'
2361
+ **/
2362
+ limitText: 'Selected {checked} of {count}'
2363
+ });
2364
+
2365
+ $.fn.editableform.types.checklist = Checklist;
2366
+
2367
+ }(window.jQuery));
2368
+
2369
+ /*
2370
+ Editableform based on Twitter Bootstrap
2371
+ */
2372
+ (function ($) {
2373
+
2374
+ $.extend($.fn.editableform.Constructor.prototype, {
2375
+ initTemplate: function() {
2376
+ this.$form = $($.fn.editableform.template);
2377
+ this.$form.find('.editable-error-block').addClass('help-block');
2378
+ }
2379
+ });
2380
+
2381
+ //buttons
2382
+ $.fn.editableform.buttons = '<button type="submit" class="btn btn-primary editable-submit"><i class="icon-ok icon-white"></i></button>'+
2383
+ '<button type="button" class="btn editable-cancel"><i class="icon-remove"></i></button>';
2384
+
2385
+ //error classes
2386
+ $.fn.editableform.errorGroupClass = 'error';
2387
+ $.fn.editableform.errorBlockClass = null;
2388
+
2389
+ }(window.jQuery));
2390
+ /**
2391
+ * Editable Popover
2392
+ * ---------------------
2393
+ * requires bootstrap-popover.js
2394
+ */
2395
+ (function ($) {
2396
+
2397
+ //extend methods
2398
+ $.extend($.fn.editableContainer.Constructor.prototype, {
2399
+ containerName: 'popover',
2400
+ innerCss: '.popover-content p',
2401
+
2402
+ initContainer: function(){
2403
+ $.extend(this.containerOptions, {
2404
+ trigger: 'manual',
2405
+ selector: false,
2406
+ content: 'dfgh'
2407
+ });
2408
+ this.call(this.containerOptions);
2409
+ },
2410
+
2411
+ setContainerOption: function(key, value) {
2412
+ this.container().options[key] = value;
2413
+ },
2414
+
2415
+ /**
2416
+ * move popover to new position. This function mainly copied from bootstrap-popover.
2417
+ */
2418
+ /*jshint laxcomma: true*/
2419
+ setPosition: function () {
2420
+
2421
+ (function() {
2422
+ var $tip = this.tip()
2423
+ , inside
2424
+ , pos
2425
+ , actualWidth
2426
+ , actualHeight
2427
+ , placement
2428
+ , tp;
2429
+
2430
+ placement = typeof this.options.placement === 'function' ?
2431
+ this.options.placement.call(this, $tip[0], this.$element[0]) :
2432
+ this.options.placement;
2433
+
2434
+ inside = /in/.test(placement);
2435
+
2436
+ $tip
2437
+ // .detach()
2438
+ //vitalets: remove any placement class because otherwise they dont influence on re-positioning of visible popover
2439
+ .removeClass('top right bottom left')
2440
+ .css({ top: 0, left: 0, display: 'block' });
2441
+ // .insertAfter(this.$element);
2442
+
2443
+ pos = this.getPosition(inside);
2444
+
2445
+ actualWidth = $tip[0].offsetWidth;
2446
+ actualHeight = $tip[0].offsetHeight;
2447
+
2448
+ switch (inside ? placement.split(' ')[1] : placement) {
2449
+ case 'bottom':
2450
+ tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2};
2451
+ break;
2452
+ case 'top':
2453
+ tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2};
2454
+ break;
2455
+ case 'left':
2456
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth};
2457
+ break;
2458
+ case 'right':
2459
+ tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width};
2460
+ break;
2461
+ }
2462
+
2463
+ $tip
2464
+ .offset(tp)
2465
+ .addClass(placement)
2466
+ .addClass('in');
2467
+
2468
+ }).call(this.container());
2469
+ /*jshint laxcomma: false*/
2470
+ }
2471
+ });
2472
+
2473
+ //defaults
2474
+ /*
2475
+ $.fn.editableContainer.defaults = $.extend({}, $.fn.popover.defaults, $.fn.editableContainer.defaults, {
2476
+
2477
+ });
2478
+ */
2479
+
2480
+ }(window.jQuery));
2481
+ /**
2482
+ Bootstrap-datepicker.
2483
+ Description and examples: http://vitalets.github.com/bootstrap-datepicker.
2484
+ For localization you can include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
2485
+
2486
+ @class date
2487
+ @extends abstract
2488
+ @final
2489
+ @example
2490
+ <a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-original-title="Select date">15/05/1984</a>
2491
+ <script>
2492
+ $(function(){
2493
+ $('#dob').editable({
2494
+ format: 'yyyy-mm-dd',
2495
+ viewformat: 'dd/mm/yyyy',
2496
+ datepicker: {
2497
+ weekStart: 1
2498
+ }
2499
+ }
2500
+ });
2501
+ });
2502
+ </script>
2503
+ **/
2504
+ (function ($) {
2505
+
2506
+ var Date = function (options) {
2507
+ this.init('date', options, Date.defaults);
2508
+
2509
+ //set popular options directly from settings or data-* attributes
2510
+ var directOptions = $.fn.editableform.utils.sliceObj(this.options, ['format']);
2511
+
2512
+ //overriding datepicker config (as by default jQuery extend() is not recursive)
2513
+ this.options.datepicker = $.extend({}, Date.defaults.datepicker, directOptions, options.datepicker);
2514
+
2515
+ //by default viewformat equals to format
2516
+ if(!this.options.viewformat) {
2517
+ this.options.viewformat = this.options.datepicker.format;
2518
+ }
2519
+
2520
+ //language
2521
+ this.options.datepicker.language = this.options.datepicker.language || 'en';
2522
+
2523
+ //store DPglobal
2524
+ this.dpg = $.fn.datepicker.DPGlobal;
2525
+
2526
+ //store parsed formats
2527
+ this.parsedFormat = this.dpg.parseFormat(this.options.datepicker.format);
2528
+ this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
2529
+ };
2530
+
2531
+ $.fn.editableform.utils.inherit(Date, $.fn.editableform.types.abstract);
2532
+
2533
+ $.extend(Date.prototype, {
2534
+ render: function () {
2535
+ Date.superclass.render.call(this);
2536
+ this.$input.datepicker(this.options.datepicker);
2537
+
2538
+ if(this.options.clear) {
2539
+ this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
2540
+ e.preventDefault();
2541
+ e.stopPropagation();
2542
+ this.clear();
2543
+ }, this));
2544
+ }
2545
+ },
2546
+
2547
+ value2html: function(value, element) {
2548
+ var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
2549
+ Date.superclass.value2html(text, element);
2550
+ },
2551
+
2552
+ html2value: function(html) {
2553
+ return html ? this.dpg.parseDate(html, this.parsedViewFormat, this.options.datepicker.language) : null;
2554
+ },
2555
+
2556
+ value2str: function(value) {
2557
+ return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';
2558
+ },
2559
+
2560
+ str2value: function(str) {
2561
+ return str ? this.dpg.parseDate(str, this.parsedFormat, this.options.datepicker.language) : null;
2562
+ },
2563
+
2564
+ value2input: function(value) {
2565
+ this.$input.datepicker('update', value);
2566
+ },
2567
+
2568
+ input2value: function() {
2569
+ return this.$input.data('datepicker').date;
2570
+ },
2571
+
2572
+ activate: function() {
2573
+ },
2574
+
2575
+ clear: function() {
2576
+ this.$input.data('datepicker').date = null;
2577
+ this.$input.find('.active').removeClass('active');
2578
+ },
2579
+
2580
+ autosubmit: function() {
2581
+ this.$input.on('changeDate', function(e){
2582
+ var $form = $(this).closest('form');
2583
+ setTimeout(function() {
2584
+ $form.submit();
2585
+ }, 200);
2586
+ });
2587
+ }
2588
+
2589
+ });
2590
+
2591
+ Date.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
2592
+ /**
2593
+ @property tpl
2594
+ @default <div></div>
2595
+ **/
2596
+ tpl:'<div></div>',
2597
+ /**
2598
+ @property inputclass
2599
+ @default editable-date well
2600
+ **/
2601
+ inputclass: 'editable-date well',
2602
+ /**
2603
+ Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
2604
+ Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code>
2605
+
2606
+ @property format
2607
+ @type string
2608
+ @default yyyy-mm-dd
2609
+ **/
2610
+ format:'yyyy-mm-dd',
2611
+ /**
2612
+ Format used for displaying date. Also applied when converting date from element's text on init.
2613
+ If not specified equals to <code>format</code>
2614
+
2615
+ @property viewformat
2616
+ @type string
2617
+ @default null
2618
+ **/
2619
+ viewformat: null,
2620
+ /**
2621
+ Configuration of datepicker.
2622
+ Full list of options: http://vitalets.github.com/bootstrap-datepicker
2623
+
2624
+ @property datepicker
2625
+ @type object
2626
+ @default {
2627
+ weekStart: 0,
2628
+ startView: 0,
2629
+ autoclose: false
2630
+ }
2631
+ **/
2632
+ datepicker:{
2633
+ weekStart: 0,
2634
+ startView: 0,
2635
+ autoclose: false
2636
+ },
2637
+ /**
2638
+ Text shown as clear date button.
2639
+ If <code>false</code> clear button will not be rendered.
2640
+
2641
+ @property clear
2642
+ @type boolean|string
2643
+ @default 'x clear'
2644
+ **/
2645
+ clear: '&times; clear'
2646
+ });
2647
+
2648
+ $.fn.editableform.types.date = Date;
2649
+
2650
+ }(window.jQuery));
2651
+
2652
+ /* =========================================================
2653
+ * bootstrap-datepicker.js
2654
+ * http://www.eyecon.ro/bootstrap-datepicker
2655
+ * =========================================================
2656
+ * Copyright 2012 Stefan Petre
2657
+ * Improvements by Andrew Rowls
2658
+ *
2659
+ * Licensed under the Apache License, Version 2.0 (the "License");
2660
+ * you may not use this file except in compliance with the License.
2661
+ * You may obtain a copy of the License at
2662
+ *
2663
+ * http://www.apache.org/licenses/LICENSE-2.0
2664
+ *
2665
+ * Unless required by applicable law or agreed to in writing, software
2666
+ * distributed under the License is distributed on an "AS IS" BASIS,
2667
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
2668
+ * See the License for the specific language governing permissions and
2669
+ * limitations under the License.
2670
+ * ========================================================= */
2671
+
2672
+ !function( $ ) {
2673
+
2674
+ function UTCDate(){
2675
+ return new Date(Date.UTC.apply(Date, arguments));
2676
+ }
2677
+ function UTCToday(){
2678
+ var today = new Date();
2679
+ return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());
2680
+ }
2681
+
2682
+ // Picker object
2683
+
2684
+ var Datepicker = function(element, options) {
2685
+ var that = this;
2686
+
2687
+ this.element = $(element);
2688
+ this.language = options.language||this.element.data('date-language')||"en";
2689
+ this.language = this.language in dates ? this.language : "en";
2690
+ this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy');
2691
+ this.isInline = false;
2692
+ this.isInput = this.element.is('input');
2693
+ this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
2694
+ this.hasInput = this.component && this.element.find('input').length;
2695
+ if(this.component && this.component.length === 0)
2696
+ this.component = false;
2697
+
2698
+ if (this.isInput) { //single input
2699
+ this.element.on({
2700
+ focus: $.proxy(this.show, this),
2701
+ keyup: $.proxy(this.update, this),
2702
+ keydown: $.proxy(this.keydown, this)
2703
+ });
2704
+ } else if(this.component && this.hasInput) { //component: input + button
2705
+ // For components that are not readonly, allow keyboard nav
2706
+ this.element.find('input').on({
2707
+ focus: $.proxy(this.show, this),
2708
+ keyup: $.proxy(this.update, this),
2709
+ keydown: $.proxy(this.keydown, this)
2710
+ });
2711
+
2712
+ this.component.on('click', $.proxy(this.show, this));
2713
+ } else if(this.element.is('div')) { //inline datepicker
2714
+ this.isInline = true;
2715
+ } else {
2716
+ this.element.on('click', $.proxy(this.show, this));
2717
+ }
2718
+
2719
+ this.picker = $(DPGlobal.template)
2720
+ .appendTo(this.isInline ? this.element : 'body')
2721
+ .on({
2722
+ click: $.proxy(this.click, this),
2723
+ mousedown: $.proxy(this.mousedown, this)
2724
+ });
2725
+
2726
+ if(this.isInline) {
2727
+ this.picker.addClass('datepicker-inline');
2728
+ } else {
2729
+ this.picker.addClass('dropdown-menu');
2730
+ }
2731
+
2732
+ $(document).on('mousedown', function (e) {
2733
+ // Clicked outside the datepicker, hide it
2734
+ if ($(e.target).closest('.datepicker').length == 0) {
2735
+ that.hide();
2736
+ }
2737
+ });
2738
+
2739
+ this.autoclose = false;
2740
+ if ('autoclose' in options) {
2741
+ this.autoclose = options.autoclose;
2742
+ } else if ('dateAutoclose' in this.element.data()) {
2743
+ this.autoclose = this.element.data('date-autoclose');
2744
+ }
2745
+
2746
+ this.keyboardNavigation = true;
2747
+ if ('keyboardNavigation' in options) {
2748
+ this.keyboardNavigation = options.keyboardNavigation;
2749
+ } else if ('dateKeyboardNavigation' in this.element.data()) {
2750
+ this.keyboardNavigation = this.element.data('date-keyboard-navigation');
2751
+ }
2752
+
2753
+ switch(options.startView || this.element.data('date-start-view')){
2754
+ case 2:
2755
+ case 'decade':
2756
+ this.viewMode = this.startViewMode = 2;
2757
+ break;
2758
+ case 1:
2759
+ case 'year':
2760
+ this.viewMode = this.startViewMode = 1;
2761
+ break;
2762
+ case 0:
2763
+ case 'month':
2764
+ default:
2765
+ this.viewMode = this.startViewMode = 0;
2766
+ break;
2767
+ }
2768
+
2769
+ this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false);
2770
+ this.todayHighlight = (options.todayHighlight||this.element.data('date-today-highlight')||false);
2771
+
2772
+ this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||dates[this.language].weekStart||0) % 7);
2773
+ this.weekEnd = ((this.weekStart + 6) % 7);
2774
+ this.startDate = -Infinity;
2775
+ this.endDate = Infinity;
2776
+ this.setStartDate(options.startDate||this.element.data('date-startdate'));
2777
+ this.setEndDate(options.endDate||this.element.data('date-enddate'));
2778
+ this.fillDow();
2779
+ this.fillMonths();
2780
+ this.update();
2781
+ this.showMode();
2782
+
2783
+ if(this.isInline) {
2784
+ this.show();
2785
+ }
2786
+ };
2787
+
2788
+ Datepicker.prototype = {
2789
+ constructor: Datepicker,
2790
+
2791
+ show: function(e) {
2792
+ this.picker.show();
2793
+ this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
2794
+ this.update();
2795
+ this.place();
2796
+ $(window).on('resize', $.proxy(this.place, this));
2797
+ if (e ) {
2798
+ e.stopPropagation();
2799
+ e.preventDefault();
2800
+ }
2801
+ this.element.trigger({
2802
+ type: 'show',
2803
+ date: this.date
2804
+ });
2805
+ },
2806
+
2807
+ hide: function(e){
2808
+ if(this.isInline) return;
2809
+ this.picker.hide();
2810
+ $(window).off('resize', this.place);
2811
+ this.viewMode = this.startViewMode;
2812
+ this.showMode();
2813
+ if (!this.isInput) {
2814
+ $(document).off('mousedown', this.hide);
2815
+ }
2816
+ if (e && e.currentTarget.value)
2817
+ this.setValue();
2818
+ this.element.trigger({
2819
+ type: 'hide',
2820
+ date: this.date
2821
+ });
2822
+ },
2823
+
2824
+ getDate: function() {
2825
+ var d = this.getUTCDate();
2826
+ return new Date(d.getTime() + (d.getTimezoneOffset()*60000))
2827
+ },
2828
+
2829
+ getUTCDate: function() {
2830
+ return this.date;
2831
+ },
2832
+
2833
+ setDate: function(d) {
2834
+ this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000)));
2835
+ },
2836
+
2837
+ setUTCDate: function(d) {
2838
+ this.date = d;
2839
+ this.setValue();
2840
+ },
2841
+
2842
+ setValue: function() {
2843
+ var formatted = this.getFormattedDate();
2844
+ if (!this.isInput) {
2845
+ if (this.component){
2846
+ this.element.find('input').prop('value', formatted);
2847
+ }
2848
+ this.element.data('date', formatted);
2849
+ } else {
2850
+ this.element.prop('value', formatted);
2851
+ }
2852
+ },
2853
+
2854
+ getFormattedDate: function(format) {
2855
+ if(format == undefined) format = this.format;
2856
+ return DPGlobal.formatDate(this.date, format, this.language);
2857
+ },
2858
+
2859
+ setStartDate: function(startDate){
2860
+ this.startDate = startDate||-Infinity;
2861
+ if (this.startDate !== -Infinity) {
2862
+ this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language);
2863
+ }
2864
+ this.update();
2865
+ this.updateNavArrows();
2866
+ },
2867
+
2868
+ setEndDate: function(endDate){
2869
+ this.endDate = endDate||Infinity;
2870
+ if (this.endDate !== Infinity) {
2871
+ this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language);
2872
+ }
2873
+ this.update();
2874
+ this.updateNavArrows();
2875
+ },
2876
+
2877
+ place: function(){
2878
+ if(this.isInline) return;
2879
+ var zIndex = parseInt(this.element.parents().filter(function() {
2880
+ return $(this).css('z-index') != 'auto';
2881
+ }).first().css('z-index'))+10;
2882
+ var offset = this.component ? this.component.offset() : this.element.offset();
2883
+ this.picker.css({
2884
+ top: offset.top + this.height,
2885
+ left: offset.left,
2886
+ zIndex: zIndex
2887
+ });
2888
+ },
2889
+
2890
+ update: function(){
2891
+ var date, fromArgs = false;
2892
+ if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
2893
+ date = arguments[0];
2894
+ fromArgs = true;
2895
+ } else {
2896
+ date = this.isInput ? this.element.prop('value') : this.element.data('date') || this.element.find('input').prop('value');
2897
+ }
2898
+
2899
+ this.date = DPGlobal.parseDate(date, this.format, this.language);
2900
+
2901
+ if(fromArgs) this.setValue();
2902
+
2903
+ if (this.date < this.startDate) {
2904
+ this.viewDate = new Date(this.startDate);
2905
+ } else if (this.date > this.endDate) {
2906
+ this.viewDate = new Date(this.endDate);
2907
+ } else {
2908
+ this.viewDate = new Date(this.date);
2909
+ }
2910
+ this.fill();
2911
+ },
2912
+
2913
+ fillDow: function(){
2914
+ var dowCnt = this.weekStart;
2915
+ var html = '<tr>';
2916
+ while (dowCnt < this.weekStart + 7) {
2917
+ html += '<th class="dow">'+dates[this.language].daysMin[(dowCnt++)%7]+'</th>';
2918
+ }
2919
+ html += '</tr>';
2920
+ this.picker.find('.datepicker-days thead').append(html);
2921
+ },
2922
+
2923
+ fillMonths: function(){
2924
+ var html = '';
2925
+ var i = 0
2926
+ while (i < 12) {
2927
+ html += '<span class="month">'+dates[this.language].monthsShort[i++]+'</span>';
2928
+ }
2929
+ this.picker.find('.datepicker-months td').html(html);
2930
+ },
2931
+
2932
+ fill: function() {
2933
+ var d = new Date(this.viewDate),
2934
+ year = d.getUTCFullYear(),
2935
+ month = d.getUTCMonth(),
2936
+ startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity,
2937
+ startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
2938
+ endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
2939
+ endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
2940
+ currentDate = this.date.valueOf(),
2941
+ today = new Date();
2942
+ this.picker.find('.datepicker-days thead th:eq(1)')
2943
+ .text(dates[this.language].months[month]+' '+year);
2944
+ this.picker.find('tfoot th.today')
2945
+ .text(dates[this.language].today)
2946
+ .toggle(this.todayBtn);
2947
+ this.updateNavArrows();
2948
+ this.fillMonths();
2949
+ var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
2950
+ day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
2951
+ prevMonth.setUTCDate(day);
2952
+ prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7);
2953
+ var nextMonth = new Date(prevMonth);
2954
+ nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
2955
+ nextMonth = nextMonth.valueOf();
2956
+ var html = [];
2957
+ var clsName;
2958
+ while(prevMonth.valueOf() < nextMonth) {
2959
+ if (prevMonth.getUTCDay() == this.weekStart) {
2960
+ html.push('<tr>');
2961
+ }
2962
+ clsName = '';
2963
+ if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
2964
+ clsName += ' old';
2965
+ } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) {
2966
+ clsName += ' new';
2967
+ }
2968
+ // Compare internal UTC date with local today, not UTC today
2969
+ if (this.todayHighlight &&
2970
+ prevMonth.getUTCFullYear() == today.getFullYear() &&
2971
+ prevMonth.getUTCMonth() == today.getMonth() &&
2972
+ prevMonth.getUTCDate() == today.getDate()) {
2973
+ clsName += ' today';
2974
+ }
2975
+ if (prevMonth.valueOf() == currentDate) {
2976
+ clsName += ' active';
2977
+ }
2978
+ if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate) {
2979
+ clsName += ' disabled';
2980
+ }
2981
+ html.push('<td class="day'+clsName+'">'+prevMonth.getUTCDate() + '</td>');
2982
+ if (prevMonth.getUTCDay() == this.weekEnd) {
2983
+ html.push('</tr>');
2984
+ }
2985
+ prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
2986
+ }
2987
+ this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
2988
+ var currentYear = this.date.getUTCFullYear();
2989
+
2990
+ var months = this.picker.find('.datepicker-months')
2991
+ .find('th:eq(1)')
2992
+ .text(year)
2993
+ .end()
2994
+ .find('span').removeClass('active');
2995
+ if (currentYear == year) {
2996
+ months.eq(this.date.getUTCMonth()).addClass('active');
2997
+ }
2998
+ if (year < startYear || year > endYear) {
2999
+ months.addClass('disabled');
3000
+ }
3001
+ if (year == startYear) {
3002
+ months.slice(0, startMonth).addClass('disabled');
3003
+ }
3004
+ if (year == endYear) {
3005
+ months.slice(endMonth+1).addClass('disabled');
3006
+ }
3007
+
3008
+ html = '';
3009
+ year = parseInt(year/10, 10) * 10;
3010
+ var yearCont = this.picker.find('.datepicker-years')
3011
+ .find('th:eq(1)')
3012
+ .text(year + '-' + (year + 9))
3013
+ .end()
3014
+ .find('td');
3015
+ year -= 1;
3016
+ for (var i = -1; i < 11; i++) {
3017
+ html += '<span class="year'+(i == -1 || i == 10 ? ' old' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
3018
+ year += 1;
3019
+ }
3020
+ yearCont.html(html);
3021
+ },
3022
+
3023
+ updateNavArrows: function() {
3024
+ var d = new Date(this.viewDate),
3025
+ year = d.getUTCFullYear(),
3026
+ month = d.getUTCMonth();
3027
+ switch (this.viewMode) {
3028
+ case 0:
3029
+ if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) {
3030
+ this.picker.find('.prev').css({visibility: 'hidden'});
3031
+ } else {
3032
+ this.picker.find('.prev').css({visibility: 'visible'});
3033
+ }
3034
+ if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) {
3035
+ this.picker.find('.next').css({visibility: 'hidden'});
3036
+ } else {
3037
+ this.picker.find('.next').css({visibility: 'visible'});
3038
+ }
3039
+ break;
3040
+ case 1:
3041
+ case 2:
3042
+ if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) {
3043
+ this.picker.find('.prev').css({visibility: 'hidden'});
3044
+ } else {
3045
+ this.picker.find('.prev').css({visibility: 'visible'});
3046
+ }
3047
+ if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) {
3048
+ this.picker.find('.next').css({visibility: 'hidden'});
3049
+ } else {
3050
+ this.picker.find('.next').css({visibility: 'visible'});
3051
+ }
3052
+ break;
3053
+ }
3054
+ },
3055
+
3056
+ click: function(e) {
3057
+ e.stopPropagation();
3058
+ e.preventDefault();
3059
+ var target = $(e.target).closest('span, td, th');
3060
+ if (target.length == 1) {
3061
+ switch(target[0].nodeName.toLowerCase()) {
3062
+ case 'th':
3063
+ switch(target[0].className) {
3064
+ case 'switch':
3065
+ this.showMode(1);
3066
+ break;
3067
+ case 'prev':
3068
+ case 'next':
3069
+ var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
3070
+ switch(this.viewMode){
3071
+ case 0:
3072
+ this.viewDate = this.moveMonth(this.viewDate, dir);
3073
+ break;
3074
+ case 1:
3075
+ case 2:
3076
+ this.viewDate = this.moveYear(this.viewDate, dir);
3077
+ break;
3078
+ }
3079
+ this.fill();
3080
+ break;
3081
+ case 'today':
3082
+ var date = new Date();
3083
+ date.setUTCHours(0);
3084
+ date.setUTCMinutes(0);
3085
+ date.setUTCSeconds(0);
3086
+ date.setUTCMilliseconds(0);
3087
+
3088
+ this.showMode(-2);
3089
+ var which = this.todayBtn == 'linked' ? null : 'view';
3090
+ this._setDate(date, which);
3091
+ break;
3092
+ }
3093
+ break;
3094
+ case 'span':
3095
+ if (!target.is('.disabled')) {
3096
+ this.viewDate.setUTCDate(1);
3097
+ if (target.is('.month')) {
3098
+ var month = target.parent().find('span').index(target);
3099
+ this.viewDate.setUTCMonth(month);
3100
+ this.element.trigger({
3101
+ type: 'changeMonth',
3102
+ date: this.viewDate
3103
+ });
3104
+ } else {
3105
+ var year = parseInt(target.text(), 10)||0;
3106
+ this.viewDate.setUTCFullYear(year);
3107
+ this.element.trigger({
3108
+ type: 'changeYear',
3109
+ date: this.viewDate
3110
+ });
3111
+ }
3112
+ this.showMode(-1);
3113
+ this.fill();
3114
+ }
3115
+ break;
3116
+ case 'td':
3117
+ if (target.is('.day') && !target.is('.disabled')){
3118
+ var day = parseInt(target.text(), 10)||1;
3119
+ var year = this.viewDate.getUTCFullYear(),
3120
+ month = this.viewDate.getUTCMonth();
3121
+ if (target.is('.old')) {
3122
+ if (month == 0) {
3123
+ month = 11;
3124
+ year -= 1;
3125
+ } else {
3126
+ month -= 1;
3127
+ }
3128
+ } else if (target.is('.new')) {
3129
+ if (month == 11) {
3130
+ month = 0;
3131
+ year += 1;
3132
+ } else {
3133
+ month += 1;
3134
+ }
3135
+ }
3136
+ this._setDate(UTCDate(year, month, day,0,0,0,0));
3137
+ }
3138
+ break;
3139
+ }
3140
+ }
3141
+ },
3142
+
3143
+ _setDate: function(date, which){
3144
+ if (!which || which == 'date')
3145
+ this.date = date;
3146
+ if (!which || which == 'view')
3147
+ this.viewDate = date;
3148
+ this.fill();
3149
+ this.setValue();
3150
+ this.element.trigger({
3151
+ type: 'changeDate',
3152
+ date: this.date
3153
+ });
3154
+ var element;
3155
+ if (this.isInput) {
3156
+ element = this.element;
3157
+ } else if (this.component){
3158
+ element = this.element.find('input');
3159
+ }
3160
+ if (element) {
3161
+ element.change();
3162
+ if (this.autoclose) {
3163
+ this.hide();
3164
+ }
3165
+ }
3166
+ },
3167
+
3168
+ moveMonth: function(date, dir){
3169
+ if (!dir) return date;
3170
+ var new_date = new Date(date.valueOf()),
3171
+ day = new_date.getUTCDate(),
3172
+ month = new_date.getUTCMonth(),
3173
+ mag = Math.abs(dir),
3174
+ new_month, test;
3175
+ dir = dir > 0 ? 1 : -1;
3176
+ if (mag == 1){
3177
+ test = dir == -1
3178
+ // If going back one month, make sure month is not current month
3179
+ // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)
3180
+ ? function(){ return new_date.getUTCMonth() == month; }
3181
+ // If going forward one month, make sure month is as expected
3182
+ // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)
3183
+ : function(){ return new_date.getUTCMonth() != new_month; };
3184
+ new_month = month + dir;
3185
+ new_date.setUTCMonth(new_month);
3186
+ // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11
3187
+ if (new_month < 0 || new_month > 11)
3188
+ new_month = (new_month + 12) % 12;
3189
+ } else {
3190
+ // For magnitudes >1, move one month at a time...
3191
+ for (var i=0; i<mag; i++)
3192
+ // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...
3193
+ new_date = this.moveMonth(new_date, dir);
3194
+ // ...then reset the day, keeping it in the new month
3195
+ new_month = new_date.getUTCMonth();
3196
+ new_date.setUTCDate(day);
3197
+ test = function(){ return new_month != new_date.getUTCMonth(); };
3198
+ }
3199
+ // Common date-resetting loop -- if date is beyond end of month, make it
3200
+ // end of month
3201
+ while (test()){
3202
+ new_date.setUTCDate(--day);
3203
+ new_date.setUTCMonth(new_month);
3204
+ }
3205
+ return new_date;
3206
+ },
3207
+
3208
+ moveYear: function(date, dir){
3209
+ return this.moveMonth(date, dir*12);
3210
+ },
3211
+
3212
+ dateWithinRange: function(date){
3213
+ return date >= this.startDate && date <= this.endDate;
3214
+ },
3215
+
3216
+ keydown: function(e){
3217
+ if (this.picker.is(':not(:visible)')){
3218
+ if (e.keyCode == 27) // allow escape to hide and re-show picker
3219
+ this.show();
3220
+ return;
3221
+ }
3222
+ var dateChanged = false,
3223
+ dir, day, month,
3224
+ newDate, newViewDate;
3225
+ switch(e.keyCode){
3226
+ case 27: // escape
3227
+ this.hide();
3228
+ e.preventDefault();
3229
+ break;
3230
+ case 37: // left
3231
+ case 39: // right
3232
+ if (!this.keyboardNavigation) break;
3233
+ dir = e.keyCode == 37 ? -1 : 1;
3234
+ if (e.ctrlKey){
3235
+ newDate = this.moveYear(this.date, dir);
3236
+ newViewDate = this.moveYear(this.viewDate, dir);
3237
+ } else if (e.shiftKey){
3238
+ newDate = this.moveMonth(this.date, dir);
3239
+ newViewDate = this.moveMonth(this.viewDate, dir);
3240
+ } else {
3241
+ newDate = new Date(this.date);
3242
+ newDate.setUTCDate(this.date.getUTCDate() + dir);
3243
+ newViewDate = new Date(this.viewDate);
3244
+ newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);
3245
+ }
3246
+ if (this.dateWithinRange(newDate)){
3247
+ this.date = newDate;
3248
+ this.viewDate = newViewDate;
3249
+ this.setValue();
3250
+ this.update();
3251
+ e.preventDefault();
3252
+ dateChanged = true;
3253
+ }
3254
+ break;
3255
+ case 38: // up
3256
+ case 40: // down
3257
+ if (!this.keyboardNavigation) break;
3258
+ dir = e.keyCode == 38 ? -1 : 1;
3259
+ if (e.ctrlKey){
3260
+ newDate = this.moveYear(this.date, dir);
3261
+ newViewDate = this.moveYear(this.viewDate, dir);
3262
+ } else if (e.shiftKey){
3263
+ newDate = this.moveMonth(this.date, dir);
3264
+ newViewDate = this.moveMonth(this.viewDate, dir);
3265
+ } else {
3266
+ newDate = new Date(this.date);
3267
+ newDate.setUTCDate(this.date.getUTCDate() + dir * 7);
3268
+ newViewDate = new Date(this.viewDate);
3269
+ newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);
3270
+ }
3271
+ if (this.dateWithinRange(newDate)){
3272
+ this.date = newDate;
3273
+ this.viewDate = newViewDate;
3274
+ this.setValue();
3275
+ this.update();
3276
+ e.preventDefault();
3277
+ dateChanged = true;
3278
+ }
3279
+ break;
3280
+ case 13: // enter
3281
+ this.hide();
3282
+ e.preventDefault();
3283
+ break;
3284
+ case 9: // tab
3285
+ this.hide();
3286
+ break;
3287
+ }
3288
+ if (dateChanged){
3289
+ this.element.trigger({
3290
+ type: 'changeDate',
3291
+ date: this.date
3292
+ });
3293
+ var element;
3294
+ if (this.isInput) {
3295
+ element = this.element;
3296
+ } else if (this.component){
3297
+ element = this.element.find('input');
3298
+ }
3299
+ if (element) {
3300
+ element.change();
3301
+ }
3302
+ }
3303
+ },
3304
+
3305
+ showMode: function(dir) {
3306
+ if (dir) {
3307
+ this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir));
3308
+ }
3309
+ this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
3310
+ this.updateNavArrows();
3311
+ }
3312
+ };
3313
+
3314
+ $.fn.datepicker = function ( option ) {
3315
+ var args = Array.apply(null, arguments);
3316
+ args.shift();
3317
+ return this.each(function () {
3318
+ var $this = $(this),
3319
+ data = $this.data('datepicker'),
3320
+ options = typeof option == 'object' && option;
3321
+ if (!data) {
3322
+ $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
3323
+ }
3324
+ if (typeof option == 'string' && typeof data[option] == 'function') {
3325
+ data[option].apply(data, args);
3326
+ }
3327
+ });
3328
+ };
3329
+
3330
+ $.fn.datepicker.defaults = {
3331
+ };
3332
+ $.fn.datepicker.Constructor = Datepicker;
3333
+ var dates = $.fn.datepicker.dates = {
3334
+ en: {
3335
+ days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
3336
+ daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
3337
+ daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
3338
+ months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
3339
+ monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
3340
+ today: "Today"
3341
+ }
3342
+ }
3343
+
3344
+ var DPGlobal = {
3345
+ modes: [
3346
+ {
3347
+ clsName: 'days',
3348
+ navFnc: 'Month',
3349
+ navStep: 1
3350
+ },
3351
+ {
3352
+ clsName: 'months',
3353
+ navFnc: 'FullYear',
3354
+ navStep: 1
3355
+ },
3356
+ {
3357
+ clsName: 'years',
3358
+ navFnc: 'FullYear',
3359
+ navStep: 10
3360
+ }],
3361
+ isLeapYear: function (year) {
3362
+ return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0))
3363
+ },
3364
+ getDaysInMonth: function (year, month) {
3365
+ return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]
3366
+ },
3367
+ validParts: /dd?|mm?|MM?|yy(?:yy)?/g,
3368
+ nonpunctuation: /[^ -\/:-@\[-`{-~\t\n\r]+/g,
3369
+ parseFormat: function(format){
3370
+ // IE treats \0 as a string end in inputs (truncating the value),
3371
+ // so it's a bad format delimiter, anyway
3372
+ var separators = format.replace(this.validParts, '\0').split('\0'),
3373
+ parts = format.match(this.validParts);
3374
+ if (!separators || !separators.length || !parts || parts.length == 0){
3375
+ throw new Error("Invalid date format.");
3376
+ }
3377
+ return {separators: separators, parts: parts};
3378
+ },
3379
+ parseDate: function(date, format, language) {
3380
+ if (date instanceof Date) return date;
3381
+ if (/^[-+]\d+[dmwy]([\s,]+[-+]\d+[dmwy])*$/.test(date)) {
3382
+ var part_re = /([-+]\d+)([dmwy])/,
3383
+ parts = date.match(/([-+]\d+)([dmwy])/g),
3384
+ part, dir;
3385
+ date = new Date();
3386
+ for (var i=0; i<parts.length; i++) {
3387
+ part = part_re.exec(parts[i]);
3388
+ dir = parseInt(part[1]);
3389
+ switch(part[2]){
3390
+ case 'd':
3391
+ date.setUTCDate(date.getUTCDate() + dir);
3392
+ break;
3393
+ case 'm':
3394
+ date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);
3395
+ break;
3396
+ case 'w':
3397
+ date.setUTCDate(date.getUTCDate() + dir * 7);
3398
+ break;
3399
+ case 'y':
3400
+ date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);
3401
+ break;
3402
+ }
3403
+ }
3404
+ return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
3405
+ }
3406
+ var parts = date && date.match(this.nonpunctuation) || [],
3407
+ date = new Date(),
3408
+ parsed = {},
3409
+ setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],
3410
+ setters_map = {
3411
+ yyyy: function(d,v){ return d.setUTCFullYear(v); },
3412
+ yy: function(d,v){ return d.setUTCFullYear(2000+v); },
3413
+ m: function(d,v){
3414
+ v -= 1;
3415
+ while (v<0) v += 12;
3416
+ v %= 12;
3417
+ d.setUTCMonth(v);
3418
+ while (d.getUTCMonth() != v)
3419
+ d.setUTCDate(d.getUTCDate()-1);
3420
+ return d;
3421
+ },
3422
+ d: function(d,v){ return d.setUTCDate(v); }
3423
+ },
3424
+ val, filtered, part;
3425
+ setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];
3426
+ setters_map['dd'] = setters_map['d'];
3427
+ date = UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
3428
+ if (parts.length == format.parts.length) {
3429
+ for (var i=0, cnt = format.parts.length; i < cnt; i++) {
3430
+ val = parseInt(parts[i], 10);
3431
+ part = format.parts[i];
3432
+ if (isNaN(val)) {
3433
+ switch(part) {
3434
+ case 'MM':
3435
+ filtered = $(dates[language].months).filter(function(){
3436
+ var m = this.slice(0, parts[i].length),
3437
+ p = parts[i].slice(0, m.length);
3438
+ return m == p;
3439
+ });
3440
+ val = $.inArray(filtered[0], dates[language].months) + 1;
3441
+ break;
3442
+ case 'M':
3443
+ filtered = $(dates[language].monthsShort).filter(function(){
3444
+ var m = this.slice(0, parts[i].length),
3445
+ p = parts[i].slice(0, m.length);
3446
+ return m == p;
3447
+ });
3448
+ val = $.inArray(filtered[0], dates[language].monthsShort) + 1;
3449
+ break;
3450
+ }
3451
+ }
3452
+ parsed[part] = val;
3453
+ }
3454
+ for (var i=0, s; i<setters_order.length; i++){
3455
+ s = setters_order[i];
3456
+ if (s in parsed)
3457
+ setters_map[s](date, parsed[s])
3458
+ }
3459
+ }
3460
+ return date;
3461
+ },
3462
+ formatDate: function(date, format, language){
3463
+ var val = {
3464
+ d: date.getUTCDate(),
3465
+ m: date.getUTCMonth() + 1,
3466
+ M: dates[language].monthsShort[date.getUTCMonth()],
3467
+ MM: dates[language].months[date.getUTCMonth()],
3468
+ yy: date.getUTCFullYear().toString().substring(2),
3469
+ yyyy: date.getUTCFullYear()
3470
+ };
3471
+ val.dd = (val.d < 10 ? '0' : '') + val.d;
3472
+ val.mm = (val.m < 10 ? '0' : '') + val.m;
3473
+ var date = [],
3474
+ seps = $.extend([], format.separators);
3475
+ for (var i=0, cnt = format.parts.length; i < cnt; i++) {
3476
+ if (seps.length)
3477
+ date.push(seps.shift())
3478
+ date.push(val[format.parts[i]]);
3479
+ }
3480
+ return date.join('');
3481
+ },
3482
+ headTemplate: '<thead>'+
3483
+ '<tr>'+
3484
+ '<th class="prev"><i class="icon-arrow-left"/></th>'+
3485
+ '<th colspan="5" class="switch"></th>'+
3486
+ '<th class="next"><i class="icon-arrow-right"/></th>'+
3487
+ '</tr>'+
3488
+ '</thead>',
3489
+ contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
3490
+ footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr></tfoot>'
3491
+ };
3492
+ DPGlobal.template = '<div class="datepicker">'+
3493
+ '<div class="datepicker-days">'+
3494
+ '<table class=" table-condensed">'+
3495
+ DPGlobal.headTemplate+
3496
+ '<tbody></tbody>'+
3497
+ DPGlobal.footTemplate+
3498
+ '</table>'+
3499
+ '</div>'+
3500
+ '<div class="datepicker-months">'+
3501
+ '<table class="table-condensed">'+
3502
+ DPGlobal.headTemplate+
3503
+ DPGlobal.contTemplate+
3504
+ DPGlobal.footTemplate+
3505
+ '</table>'+
3506
+ '</div>'+
3507
+ '<div class="datepicker-years">'+
3508
+ '<table class="table-condensed">'+
3509
+ DPGlobal.headTemplate+
3510
+ DPGlobal.contTemplate+
3511
+ DPGlobal.footTemplate+
3512
+ '</table>'+
3513
+ '</div>'+
3514
+ '</div>';
3515
+
3516
+ $.fn.datepicker.DPGlobal = DPGlobal;
3517
+
3518
+ }( window.jQuery );