Path: blob/main/website/GAUSS/js/bootstrap-editable.js
2941 views
/*! X-editable - v1.5.11* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery2* http://github.com/vitalets/x-editable3* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */4/**5Form with single input element, two buttons and two states: normal/loading.6Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown.7Editableform is linked with one of input types, e.g. 'text', 'select' etc.89@class editableform10@uses text11@uses textarea12**/13(function ($) {14"use strict";1516var EditableForm = function (div, options) {17this.options = $.extend({}, $.fn.editableform.defaults, options);18this.$div = $(div); //div, containing form. Not form tag. Not editable-element.19if(!this.options.scope) {20this.options.scope = this;21}22//nothing shown after init23};2425EditableForm.prototype = {26constructor: EditableForm,27initInput: function() { //called once28//take input from options (as it is created in editable-element)29this.input = this.options.input;3031//set initial value32//todo: may be add check: typeof str === 'string' ?33this.value = this.input.str2value(this.options.value);3435//prerender: get input.$input36this.input.prerender();37},38initTemplate: function() {39this.$form = $($.fn.editableform.template);40},41initButtons: function() {42var $btn = this.$form.find('.editable-buttons');43$btn.append($.fn.editableform.buttons);44if(this.options.showbuttons === 'bottom') {45$btn.addClass('editable-buttons-bottom');46}47},48/**49Renders editableform5051@method render52**/53render: function() {54//init loader55this.$loading = $($.fn.editableform.loading);56this.$div.empty().append(this.$loading);5758//init form template and buttons59this.initTemplate();60if(this.options.showbuttons) {61this.initButtons();62} else {63this.$form.find('.editable-buttons').remove();64}6566//show loading state67this.showLoading();6869//flag showing is form now saving value to server.70//It is needed to wait when closing form.71this.isSaving = false;7273/**74Fired when rendering starts75@event rendering76@param {Object} event event object77**/78this.$div.triggerHandler('rendering');7980//init input81this.initInput();8283//append input to form84this.$form.find('div.editable-input').append(this.input.$tpl);8586//append form to container87this.$div.append(this.$form);8889//render input90$.when(this.input.render())91.then($.proxy(function () {92//setup input to submit automatically when no buttons shown93if(!this.options.showbuttons) {94this.input.autosubmit();95}9697//attach 'cancel' handler98this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));99100if(this.input.error) {101this.error(this.input.error);102this.$form.find('.editable-submit').attr('disabled', true);103this.input.$input.attr('disabled', true);104//prevent form from submitting105this.$form.submit(function(e){ e.preventDefault(); });106} else {107this.error(false);108this.input.$input.removeAttr('disabled');109this.$form.find('.editable-submit').removeAttr('disabled');110var value = (this.value === null || this.value === undefined || this.value === '') ? this.options.defaultValue : this.value;111this.input.value2input(value);112//attach submit handler113this.$form.submit($.proxy(this.submit, this));114}115116/**117Fired when form is rendered118@event rendered119@param {Object} event event object120**/121this.$div.triggerHandler('rendered');122123this.showForm();124125//call postrender method to perform actions required visibility of form126if(this.input.postrender) {127this.input.postrender();128}129}, this));130},131cancel: function() {132/**133Fired when form was cancelled by user134@event cancel135@param {Object} event event object136**/137this.$div.triggerHandler('cancel');138},139showLoading: function() {140var w, h;141if(this.$form) {142//set loading size equal to form143w = this.$form.outerWidth();144h = this.$form.outerHeight();145if(w) {146this.$loading.width(w);147}148if(h) {149this.$loading.height(h);150}151this.$form.hide();152} else {153//stretch loading to fill container width154w = this.$loading.parent().width();155if(w) {156this.$loading.width(w);157}158}159this.$loading.show();160},161162showForm: function(activate) {163this.$loading.hide();164this.$form.show();165if(activate !== false) {166this.input.activate();167}168/**169Fired when form is shown170@event show171@param {Object} event event object172**/173this.$div.triggerHandler('show');174},175176error: function(msg) {177var $group = this.$form.find('.control-group'),178$block = this.$form.find('.editable-error-block'),179lines;180181if(msg === false) {182$group.removeClass($.fn.editableform.errorGroupClass);183$block.removeClass($.fn.editableform.errorBlockClass).empty().hide();184} else {185//convert newline to <br> for more pretty error display186if(msg) {187lines = (''+msg).split('\n');188for (var i = 0; i < lines.length; i++) {189lines[i] = $('<div>').text(lines[i]).html();190}191msg = lines.join('<br>');192}193$group.addClass($.fn.editableform.errorGroupClass);194$block.addClass($.fn.editableform.errorBlockClass).html(msg).show();195}196},197198submit: function(e) {199e.stopPropagation();200e.preventDefault();201202//get new value from input203var newValue = this.input.input2value();204205//validation: if validate returns string or truthy value - means error206//if returns object like {newValue: '...'} => submitted value is reassigned to it207var error = this.validate(newValue);208if ($.type(error) === 'object' && error.newValue !== undefined) {209newValue = error.newValue;210this.input.value2input(newValue);211if(typeof error.msg === 'string') {212this.error(error.msg);213this.showForm();214return;215}216} else if (error) {217this.error(error);218this.showForm();219return;220}221222//if value not changed --> trigger 'nochange' event and return223/*jslint eqeq: true*/224if (!this.options.savenochange && this.input.value2str(newValue) == this.input.value2str(this.value)) {225/*jslint eqeq: false*/226/**227Fired when value not changed but form is submitted. Requires savenochange = false.228@event nochange229@param {Object} event event object230**/231this.$div.triggerHandler('nochange');232return;233}234235//convert value for submitting to server236var submitValue = this.input.value2submit(newValue);237238this.isSaving = true;239240//sending data to server241$.when(this.save(submitValue))242.done($.proxy(function(response) {243this.isSaving = false;244245//run success callback246var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;247248//if success callback returns false --> keep form open and do not activate input249if(res === false) {250this.error(false);251this.showForm(false);252return;253}254255//if success callback returns string --> keep form open, show error and activate input256if(typeof res === 'string') {257this.error(res);258this.showForm();259return;260}261262//if success callback returns object like {newValue: <something>} --> use that value instead of submitted263//it is usefull if you want to chnage value in url-function264if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {265newValue = res.newValue;266}267268//clear error message269this.error(false);270this.value = newValue;271/**272Fired when form is submitted273@event save274@param {Object} event event object275@param {Object} params additional params276@param {mixed} params.newValue raw new value277@param {mixed} params.submitValue submitted value as string278@param {Object} params.response ajax response279280@example281$('#form-div').on('save'), function(e, params){282if(params.newValue === 'username') {...}283});284**/285this.$div.triggerHandler('save', {newValue: newValue, submitValue: submitValue, response: response});286}, this))287.fail($.proxy(function(xhr) {288this.isSaving = false;289290var msg;291if(typeof this.options.error === 'function') {292msg = this.options.error.call(this.options.scope, xhr, newValue);293} else {294msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!';295}296297this.error(msg);298this.showForm();299}, this));300},301302save: function(submitValue) {303//try parse composite pk defined as json string in data-pk304this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true);305306var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,307/*308send on server in following cases:3091. url is function3102. url is string AND (pk defined OR send option = always)311*/312send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))),313params;314315if (send) { //send to server316this.showLoading();317318//standard params319params = {320name: this.options.name || '',321value: submitValue,322pk: pk323};324325//additional params326if(typeof this.options.params === 'function') {327params = this.options.params.call(this.options.scope, params);328} else {329//try parse json in single quotes (from data-params attribute)330this.options.params = $.fn.editableutils.tryParseJson(this.options.params, true);331$.extend(params, this.options.params);332}333334if(typeof this.options.url === 'function') { //user's function335return this.options.url.call(this.options.scope, params);336} else {337//send ajax to server and return deferred object338return $.ajax($.extend({339url : this.options.url,340data : params,341type : 'POST'342}, this.options.ajaxOptions));343}344}345},346347validate: function (value) {348if (value === undefined) {349value = this.value;350}351if (typeof this.options.validate === 'function') {352return this.options.validate.call(this.options.scope, value);353}354},355356option: function(key, value) {357if(key in this.options) {358this.options[key] = value;359}360361if(key === 'value') {362this.setValue(value);363}364365//do not pass option to input as it is passed in editable-element366},367368setValue: function(value, convertStr) {369if(convertStr) {370this.value = this.input.str2value(value);371} else {372this.value = value;373}374375//if form is visible, update input376if(this.$form && this.$form.is(':visible')) {377this.input.value2input(this.value);378}379}380};381382/*383Initialize editableform. Applied to jQuery object.384385@method $().editableform(options)386@params {Object} options387@example388var $form = $('<div>').editableform({389type: 'text',390name: 'username',391url: '/post',392value: 'vitaliy'393});394395//to display form you should call 'render' method396$form.editableform('render');397*/398$.fn.editableform = function (option) {399var args = arguments;400return this.each(function () {401var $this = $(this),402data = $this.data('editableform'),403options = typeof option === 'object' && option;404if (!data) {405$this.data('editableform', (data = new EditableForm(this, options)));406}407408if (typeof option === 'string') { //call method409data[option].apply(data, Array.prototype.slice.call(args, 1));410}411});412};413414//keep link to constructor to allow inheritance415$.fn.editableform.Constructor = EditableForm;416417//defaults418$.fn.editableform.defaults = {419/* see also defaults for input */420421/**422Type of input. Can be <code>text|textarea|select|date|checklist</code>423424@property type425@type string426@default 'text'427**/428type: 'text',429/**430Url for submit, e.g. <code>'/post'</code>431If function - it will be called instead of ajax. Function should return deferred object to run fail/done callbacks.432433@property url434@type string|function435@default null436@example437url: function(params) {438var d = new $.Deferred;439if(params.value === 'abc') {440return d.reject('error message'); //returning error via deferred object441} else {442//async saving data in js model443someModel.asyncSaveMethod({444...,445success: function(){446d.resolve();447}448});449return d.promise();450}451}452**/453url:null,454/**455Additional params for submit. If defined as <code>object</code> - it is **appended** to original ajax data (pk, name and value).456If defined as <code>function</code> - returned object **overwrites** original ajax data.457@example458params: function(params) {459//originally params contain pk, name and value460params.a = 1;461return params;462}463464@property params465@type object|function466@default null467**/468params:null,469/**470Name of field. Will be submitted on server. Can be taken from <code>id</code> attribute471472@property name473@type string474@default null475**/476name: null,477/**478Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>.479Can be calculated dynamically via function.480481@property pk482@type string|object|function483@default null484**/485pk: null,486/**487Initial value. If not defined - will be taken from element's content.488For __select__ type should be defined (as it is ID of shown text).489490@property value491@type string|object492@default null493**/494value: null,495/**496Value that will be displayed in input if original field value is empty (`null|undefined|''`).497498@property defaultValue499@type string|object500@default null501@since 1.4.6502**/503defaultValue: null,504/**505Strategy for sending data on server. Can be `auto|always|never`.506When 'auto' data will be sent on server **only if pk and url defined**, otherwise new value will be stored locally.507508@property send509@type string510@default 'auto'511**/512send: 'auto',513/**514Function for client-side validation. If returns string - means validation not passed and string showed as error.515Since 1.5.1 you can modify submitted value by returning object from `validate`:516`{newValue: '...'}` or `{newValue: '...', msg: '...'}`517518@property validate519@type function520@default null521@example522validate: function(value) {523if($.trim(value) == '') {524return 'This field is required';525}526}527**/528validate: null,529/**530Success callback. Called when value successfully sent on server and **response status = 200**.531Usefull to work with json response. For example, if your backend response can be <code>{success: true}</code>532or <code>{success: false, msg: "server error"}</code> you can check it inside this callback.533If it returns **string** - means error occured and string is shown as error message.534If it returns **object like** <code>{newValue: <something>}</code> - it overwrites value, submitted by user.535Otherwise newValue simply rendered into element.536537@property success538@type function539@default null540@example541success: function(response, newValue) {542if(!response.success) return response.msg;543}544**/545success: null,546/**547Error callback. Called when request failed (response status != 200).548Usefull when you want to parse error response and display a custom message.549Must return **string** - the message to be displayed in the error block.550551@property error552@type function553@default null554@since 1.4.4555@example556error: function(response, newValue) {557if(response.status === 500) {558return 'Service unavailable. Please try later.';559} else {560return response.responseText;561}562}563**/564error: null,565/**566Additional options for submit ajax request.567List of values: http://api.jquery.com/jQuery.ajax568569@property ajaxOptions570@type object571@default null572@since 1.1.1573@example574ajaxOptions: {575type: 'put',576dataType: 'json'577}578**/579ajaxOptions: null,580/**581Where to show buttons: left(true)|bottom|false582Form without buttons is auto-submitted.583584@property showbuttons585@type boolean|string586@default true587@since 1.1.1588**/589showbuttons: true,590/**591Scope for callback methods (success, validate).592If <code>null</code> means editableform instance itself.593594@property scope595@type DOMElement|object596@default null597@since 1.2.0598@private599**/600scope: null,601/**602Whether to save or cancel value when it was not changed but form was submitted603604@property savenochange605@type boolean606@default false607@since 1.2.0608**/609savenochange: false610};611612/*613Note: following params could redefined in engine: bootstrap or jqueryui:614Classes 'control-group' and 'editable-error-block' must always present!615*/616$.fn.editableform.template = '<form class="form-inline editableform">'+617'<div class="control-group">' +618'<div><div class="editable-input"></div><div class="editable-buttons"></div></div>'+619'<div class="editable-error-block"></div>' +620'</div>' +621'</form>';622623//loading div624$.fn.editableform.loading = '<div class="editableform-loading"></div>';625626//buttons627$.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+628'<button type="button" class="editable-cancel">cancel</button>';629630//error class attached to control-group631$.fn.editableform.errorGroupClass = null;632633//error class attached to editable-error-block634$.fn.editableform.errorBlockClass = 'editable-error';635636//engine637$.fn.editableform.engine = 'jquery';638}(window.jQuery));639640/**641* EditableForm utilites642*/643(function ($) {644"use strict";645646//utils647$.fn.editableutils = {648/**649* classic JS inheritance function650*/651inherit: function (Child, Parent) {652var F = function() { };653F.prototype = Parent.prototype;654Child.prototype = new F();655Child.prototype.constructor = Child;656Child.superclass = Parent.prototype;657},658659/**660* set caret position in input661* see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area662*/663setCursorPosition: function(elem, pos) {664if (elem.setSelectionRange) {665elem.setSelectionRange(pos, pos);666} else if (elem.createTextRange) {667var range = elem.createTextRange();668range.collapse(true);669range.moveEnd('character', pos);670range.moveStart('character', pos);671range.select();672}673},674675/**676* function to parse JSON in *single* quotes. (jquery automatically parse only double quotes)677* That allows such code as: <a data-source="{'a': 'b', 'c': 'd'}">678* safe = true --> means no exception will be thrown679* for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery680*/681tryParseJson: function(s, safe) {682if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) {683if (safe) {684try {685/*jslint evil: true*/686s = (new Function('return ' + s))();687/*jslint evil: false*/688} catch (e) {} finally {689return s;690}691} else {692/*jslint evil: true*/693s = (new Function('return ' + s))();694/*jslint evil: false*/695}696}697return s;698},699700/**701* slice object by specified keys702*/703sliceObj: function(obj, keys, caseSensitive /* default: false */) {704var key, keyLower, newObj = {};705706if (!$.isArray(keys) || !keys.length) {707return newObj;708}709710for (var i = 0; i < keys.length; i++) {711key = keys[i];712if (obj.hasOwnProperty(key)) {713newObj[key] = obj[key];714}715716if(caseSensitive === true) {717continue;718}719720//when getting data-* attributes via $.data() it's converted to lowercase.721//details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery722//workaround is code below.723keyLower = key.toLowerCase();724if (obj.hasOwnProperty(keyLower)) {725newObj[key] = obj[keyLower];726}727}728729return newObj;730},731732/*733exclude complex objects from $.data() before pass to config734*/735getConfigData: function($element) {736var data = {};737$.each($element.data(), function(k, v) {738if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) {739data[k] = v;740}741});742return data;743},744745/*746returns keys of object747*/748objectKeys: function(o) {749if (Object.keys) {750return Object.keys(o);751} else {752if (o !== Object(o)) {753throw new TypeError('Object.keys called on a non-object');754}755var k=[], p;756for (p in o) {757if (Object.prototype.hasOwnProperty.call(o,p)) {758k.push(p);759}760}761return k;762}763764},765766/**767method to escape html.768**/769escape: function(str) {770return $('<div>').text(str).html();771},772773/*774returns array items from sourceData having value property equal or inArray of 'value'775*/776itemsByValue: function(value, sourceData, valueProp) {777if(!sourceData || value === null) {778return [];779}780781if (typeof(valueProp) !== "function") {782var idKey = valueProp || 'value';783valueProp = function (e) { return e[idKey]; };784}785786var isValArray = $.isArray(value),787result = [],788that = this;789790$.each(sourceData, function(i, o) {791if(o.children) {792result = result.concat(that.itemsByValue(value, o.children, valueProp));793} else {794/*jslint eqeq: true*/795if(isValArray) {796if($.grep(value, function(v){ return v == (o && typeof o === 'object' ? valueProp(o) : o); }).length) {797result.push(o);798}799} else {800var itemValue = (o && (typeof o === 'object')) ? valueProp(o) : o;801if(value == itemValue) {802result.push(o);803}804}805/*jslint eqeq: false*/806}807});808809return result;810},811812/*813Returns input by options: type, mode.814*/815createInput: function(options) {816var TypeConstructor, typeOptions, input,817type = options.type;818819//`date` is some kind of virtual type that is transformed to one of exact types820//depending on mode and core lib821if(type === 'date') {822//inline823if(options.mode === 'inline') {824if($.fn.editabletypes.datefield) {825type = 'datefield';826} else if($.fn.editabletypes.dateuifield) {827type = 'dateuifield';828}829//popup830} else {831if($.fn.editabletypes.date) {832type = 'date';833} else if($.fn.editabletypes.dateui) {834type = 'dateui';835}836}837838//if type still `date` and not exist in types, replace with `combodate` that is base input839if(type === 'date' && !$.fn.editabletypes.date) {840type = 'combodate';841}842}843844//`datetime` should be datetimefield in 'inline' mode845if(type === 'datetime' && options.mode === 'inline') {846type = 'datetimefield';847}848849//change wysihtml5 to textarea for jquery UI and plain versions850if(type === 'wysihtml5' && !$.fn.editabletypes[type]) {851type = 'textarea';852}853854//create input of specified type. Input will be used for converting value, not in form855if(typeof $.fn.editabletypes[type] === 'function') {856TypeConstructor = $.fn.editabletypes[type];857typeOptions = this.sliceObj(options, this.objectKeys(TypeConstructor.defaults));858input = new TypeConstructor(typeOptions);859return input;860} else {861$.error('Unknown type: '+ type);862return false;863}864},865866//see http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr867supportsTransitions: function () {868var b = document.body || document.documentElement,869s = b.style,870p = 'transition',871v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];872873if(typeof s[p] === 'string') {874return true;875}876877// Tests for vendor specific prop878p = p.charAt(0).toUpperCase() + p.substr(1);879for(var i=0; i<v.length; i++) {880if(typeof s[v[i] + p] === 'string') {881return true;882}883}884return false;885}886887};888}(window.jQuery));889890/**891Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br>892This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>893Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.<br>894Applied as jQuery method.895896@class editableContainer897@uses editableform898**/899(function ($) {900"use strict";901902var Popup = function (element, options) {903this.init(element, options);904};905906var Inline = function (element, options) {907this.init(element, options);908};909910//methods911Popup.prototype = {912containerName: null, //method to call container on element913containerDataName: null, //object name in element's .data()914innerCss: null, //tbd in child class915containerClass: 'editable-container editable-popup', //css class applied to container element916defaults: {}, //container itself defaults917918init: function(element, options) {919this.$element = $(element);920//since 1.4.1 container do not use data-* directly as they already merged into options.921this.options = $.extend({}, $.fn.editableContainer.defaults, options);922this.splitOptions();923924//set scope of form callbacks to element925this.formOptions.scope = this.$element[0];926927this.initContainer();928929//flag to hide container, when saving value will finish930this.delayedHide = false;931932//bind 'destroyed' listener to destroy container when element is removed from dom933this.$element.on('destroyed', $.proxy(function(){934this.destroy();935}, this));936937//attach document handler to close containers on click / escape938if(!$(document).data('editable-handlers-attached')) {939//close all on escape940$(document).on('keyup.editable', function (e) {941if (e.which === 27) {942$('.editable-open').editableContainer('hide');943//todo: return focus on element944}945});946947//close containers when click outside948//(mousedown could be better than click, it closes everything also on drag drop)949$(document).on('click.editable', function(e) {950var $target = $(e.target), i,951exclude_classes = ['.editable-container',952'.ui-datepicker-header',953'.datepicker', //in inline mode datepicker is rendered into body954'.modal-backdrop',955'.bootstrap-wysihtml5-insert-image-modal',956'.bootstrap-wysihtml5-insert-link-modal'957];958959//check if element is detached. It occurs when clicking in bootstrap datepicker960if (!$.contains(document.documentElement, e.target)) {961return;962}963964//for some reason FF 20 generates extra event (click) in select2 widget with e.target = document965//we need to filter it via construction below. See https://github.com/vitalets/x-editable/issues/199966//Possibly related to http://stackoverflow.com/questions/10119793/why-does-firefox-react-differently-from-webkit-and-ie-to-click-event-on-selec967if($target.is(document)) {968return;969}970971//if click inside one of exclude classes --> no nothing972for(i=0; i<exclude_classes.length; i++) {973if($target.is(exclude_classes[i]) || $target.parents(exclude_classes[i]).length) {974return;975}976}977978//close all open containers (except one - target)979Popup.prototype.closeOthers(e.target);980});981982$(document).data('editable-handlers-attached', true);983}984},985986//split options on containerOptions and formOptions987splitOptions: function() {988this.containerOptions = {};989this.formOptions = {};990991if(!$.fn[this.containerName]) {992throw new Error(this.containerName + ' not found. Have you included corresponding js file?');993}994995//keys defined in container defaults go to container, others go to form996for(var k in this.options) {997if(k in this.defaults) {998this.containerOptions[k] = this.options[k];999} else {1000this.formOptions[k] = this.options[k];1001}1002}1003},10041005/*1006Returns jquery object of container1007@method tip()1008*/1009tip: function() {1010return this.container() ? this.container().$tip : null;1011},10121013/* returns container object */1014container: function() {1015var container;1016//first, try get it by `containerDataName`1017if(this.containerDataName) {1018if(container = this.$element.data(this.containerDataName)) {1019return container;1020}1021}1022//second, try `containerName`1023container = this.$element.data(this.containerName);1024return container;1025},10261027/* call native method of underlying container, e.g. this.$element.popover('method') */1028call: function() {1029this.$element[this.containerName].apply(this.$element, arguments);1030},10311032initContainer: function(){1033this.call(this.containerOptions);1034},10351036renderForm: function() {1037this.$form1038.editableform(this.formOptions)1039.on({1040save: $.proxy(this.save, this), //click on submit button (value changed)1041nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)1042cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on calcel button1043show: $.proxy(function() {1044if(this.delayedHide) {1045this.hide(this.delayedHide.reason);1046this.delayedHide = false;1047} else {1048this.setPosition();1049}1050}, this), //re-position container every time form is shown (occurs each time after loading state)1051rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown1052resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed1053rendered: $.proxy(function(){1054/**1055Fired when container is shown and form is rendered (for select will wait for loading dropdown options).1056**Note:** Bootstrap popover has own `shown` event that now cannot be separated from x-editable's one.1057The workaround is to check `arguments.length` that is always `2` for x-editable.10581059@event shown1060@param {Object} event event object1061@example1062$('#username').on('shown', function(e, editable) {1063editable.input.$input.val('overwriting value of input..');1064});1065**/1066/*1067TODO: added second param mainly to distinguish from bootstrap's shown event. It's a hotfix that will be solved in future versions via namespaced events.1068*/1069this.$element.triggerHandler('shown', $(this.options.scope).data('editable'));1070}, this)1071})1072.editableform('render');1073},10741075/**1076Shows container with form1077@method show()1078@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.1079**/1080/* Note: poshytip owerwrites this method totally! */1081show: function (closeAll) {1082this.$element.addClass('editable-open');1083if(closeAll !== false) {1084//close all open containers (except this)1085this.closeOthers(this.$element[0]);1086}10871088//show container itself1089this.innerShow();1090this.tip().addClass(this.containerClass);10911092/*1093Currently, form is re-rendered on every show.1094The main reason is that we dont know, what will container do with content when closed:1095remove(), detach() or just hide() - it depends on container.10961097Detaching form itself before hide and re-insert before show is good solution,1098but visually it looks ugly --> container changes size before hide.1099*/11001101//if form already exist - delete previous data1102if(this.$form) {1103//todo: destroy prev data!1104//this.$form.destroy();1105}11061107this.$form = $('<div>');11081109//insert form into container body1110if(this.tip().is(this.innerCss)) {1111//for inline container1112this.tip().append(this.$form);1113} else {1114this.tip().find(this.innerCss).append(this.$form);1115}11161117//render form1118this.renderForm();1119},11201121/**1122Hides container with form1123@method hide()1124@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>1125**/1126hide: function(reason) {1127if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {1128return;1129}11301131//if form is saving value, schedule hide1132if(this.$form.data('editableform').isSaving) {1133this.delayedHide = {reason: reason};1134return;1135} else {1136this.delayedHide = false;1137}11381139this.$element.removeClass('editable-open');1140this.innerHide();11411142/**1143Fired when container was hidden. It occurs on both save or cancel.1144**Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one.1145The workaround is to check `arguments.length` that is always `2` for x-editable.11461147@event hidden1148@param {object} event event object1149@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|manual</code>1150@example1151$('#username').on('hidden', function(e, reason) {1152if(reason === 'save' || reason === 'cancel') {1153//auto-open next editable1154$(this).closest('tr').next().find('.editable').editable('show');1155}1156});1157**/1158this.$element.triggerHandler('hidden', reason || 'manual');1159},11601161/* internal show method. To be overwritten in child classes */1162innerShow: function () {11631164},11651166/* internal hide method. To be overwritten in child classes */1167innerHide: function () {11681169},11701171/**1172Toggles container visibility (show / hide)1173@method toggle()1174@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.1175**/1176toggle: function(closeAll) {1177if(this.container() && this.tip() && this.tip().is(':visible')) {1178this.hide();1179} else {1180this.show(closeAll);1181}1182},11831184/*1185Updates the position of container when content changed.1186@method setPosition()1187*/1188setPosition: function() {1189//tbd in child class1190},11911192save: function(e, params) {1193/**1194Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance11951196@event save1197@param {Object} event event object1198@param {Object} params additional params1199@param {mixed} params.newValue submitted value1200@param {Object} params.response ajax response1201@example1202$('#username').on('save', function(e, params) {1203//assuming server response: '{success: true}'1204var pk = $(this).data('editableContainer').options.pk;1205if(params.response && params.response.success) {1206alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');1207} else {1208alert('error!');1209}1210});1211**/1212this.$element.triggerHandler('save', params);12131214//hide must be after trigger, as saving value may require methods of plugin, applied to input1215this.hide('save');1216},12171218/**1219Sets new option12201221@method option(key, value)1222@param {string} key1223@param {mixed} value1224**/1225option: function(key, value) {1226this.options[key] = value;1227if(key in this.containerOptions) {1228this.containerOptions[key] = value;1229this.setContainerOption(key, value);1230} else {1231this.formOptions[key] = value;1232if(this.$form) {1233this.$form.editableform('option', key, value);1234}1235}1236},12371238setContainerOption: function(key, value) {1239this.call('option', key, value);1240},12411242/**1243Destroys the container instance1244@method destroy()1245**/1246destroy: function() {1247this.hide();1248this.innerDestroy();1249this.$element.off('destroyed');1250this.$element.removeData('editableContainer');1251},12521253/* to be overwritten in child classes */1254innerDestroy: function() {12551256},12571258/*1259Closes other containers except one related to passed element.1260Other containers can be cancelled or submitted (depends on onblur option)1261*/1262closeOthers: function(element) {1263$('.editable-open').each(function(i, el){1264//do nothing with passed element and it's children1265if(el === element || $(el).find(element).length) {1266return;1267}12681269//otherwise cancel or submit all open containers1270var $el = $(el),1271ec = $el.data('editableContainer');12721273if(!ec) {1274return;1275}12761277if(ec.options.onblur === 'cancel') {1278$el.data('editableContainer').hide('onblur');1279} else if(ec.options.onblur === 'submit') {1280$el.data('editableContainer').tip().find('form').submit();1281}1282});12831284},12851286/**1287Activates input of visible container (e.g. set focus)1288@method activate()1289**/1290activate: function() {1291if(this.tip && this.tip().is(':visible') && this.$form) {1292this.$form.data('editableform').input.activate();1293}1294}12951296};12971298/**1299jQuery method to initialize editableContainer.13001301@method $().editableContainer(options)1302@params {Object} options1303@example1304$('#edit').editableContainer({1305type: 'text',1306url: '/post',1307pk: 1,1308value: 'hello'1309});1310**/1311$.fn.editableContainer = function (option) {1312var args = arguments;1313return this.each(function () {1314var $this = $(this),1315dataKey = 'editableContainer',1316data = $this.data(dataKey),1317options = typeof option === 'object' && option,1318Constructor = (options.mode === 'inline') ? Inline : Popup;13191320if (!data) {1321$this.data(dataKey, (data = new Constructor(this, options)));1322}13231324if (typeof option === 'string') { //call method1325data[option].apply(data, Array.prototype.slice.call(args, 1));1326}1327});1328};13291330//store constructors1331$.fn.editableContainer.Popup = Popup;1332$.fn.editableContainer.Inline = Inline;13331334//defaults1335$.fn.editableContainer.defaults = {1336/**1337Initial value of form input13381339@property value1340@type mixed1341@default null1342@private1343**/1344value: null,1345/**1346Placement of container relative to element. Can be <code>top|right|bottom|left</code>. Not used for inline container.13471348@property placement1349@type string1350@default 'top'1351**/1352placement: 'top',1353/**1354Whether to hide container on save/cancel.13551356@property autohide1357@type boolean1358@default true1359@private1360**/1361autohide: true,1362/**1363Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>.1364Setting <code>ignore</code> allows to have several containers open.13651366@property onblur1367@type string1368@default 'cancel'1369@since 1.1.11370**/1371onblur: 'cancel',13721373/**1374Animation speed (inline mode only)1375@property anim1376@type string1377@default false1378**/1379anim: false,13801381/**1382Mode of editable, can be `popup` or `inline`13831384@property mode1385@type string1386@default 'popup'1387@since 1.4.01388**/1389mode: 'popup'1390};13911392/*1393* workaround to have 'destroyed' event to destroy popover when element is destroyed1394* see http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom1395*/1396jQuery.event.special.destroyed = {1397remove: function(o) {1398if (o.handler) {1399o.handler();1400}1401}1402};14031404}(window.jQuery));14051406/**1407* Editable Inline1408* ---------------------1409*/1410(function ($) {1411"use strict";14121413//copy prototype from EditableContainer1414//extend methods1415$.extend($.fn.editableContainer.Inline.prototype, $.fn.editableContainer.Popup.prototype, {1416containerName: 'editableform',1417innerCss: '.editable-inline',1418containerClass: 'editable-container editable-inline', //css class applied to container element14191420initContainer: function(){1421//container is <span> element1422this.$tip = $('<span></span>');14231424//convert anim to miliseconds (int)1425if(!this.options.anim) {1426this.options.anim = 0;1427}1428},14291430splitOptions: function() {1431//all options are passed to form1432this.containerOptions = {};1433this.formOptions = this.options;1434},14351436tip: function() {1437return this.$tip;1438},14391440innerShow: function () {1441this.$element.hide();1442this.tip().insertAfter(this.$element).show();1443},14441445innerHide: function () {1446this.$tip.hide(this.options.anim, $.proxy(function() {1447this.$element.show();1448this.innerDestroy();1449}, this));1450},14511452innerDestroy: function() {1453if(this.tip()) {1454this.tip().empty().remove();1455}1456}1457});14581459}(window.jQuery));1460/**1461Makes editable any HTML element on the page. Applied as jQuery method.14621463@class editable1464@uses editableContainer1465**/1466(function ($) {1467"use strict";14681469var Editable = function (element, options) {1470this.$element = $(element);1471//data-* has more priority over js options: because dynamically created elements may change data-*1472this.options = $.extend({}, $.fn.editable.defaults, options, $.fn.editableutils.getConfigData(this.$element));1473if(this.options.selector) {1474this.initLive();1475} else {1476this.init();1477}14781479//check for transition support1480if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) {1481this.options.highlight = false;1482}1483};14841485Editable.prototype = {1486constructor: Editable,1487init: function () {1488var isValueByText = false,1489doAutotext, finalize;14901491//name1492this.options.name = this.options.name || this.$element.attr('id');14931494//create input of specified type. Input needed already here to convert value for initial display (e.g. show text by id for select)1495//also we set scope option to have access to element inside input specific callbacks (e. g. source as function)1496this.options.scope = this.$element[0];1497this.input = $.fn.editableutils.createInput(this.options);1498if(!this.input) {1499return;1500}15011502//set value from settings or by element's text1503if (this.options.value === undefined || this.options.value === null) {1504this.value = this.input.html2value($.trim(this.$element.html()));1505isValueByText = true;1506} else {1507/*1508value can be string when received from 'data-value' attribute1509for complext objects value can be set as json string in data-value attribute,1510e.g. data-value="{city: 'Moscow', street: 'Lenina'}"1511*/1512this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true);1513if(typeof this.options.value === 'string') {1514this.value = this.input.str2value(this.options.value);1515} else {1516this.value = this.options.value;1517}1518}15191520//add 'editable' class to every editable element1521this.$element.addClass('editable');15221523//specifically for "textarea" add class .editable-pre-wrapped to keep linebreaks1524if(this.input.type === 'textarea') {1525this.$element.addClass('editable-pre-wrapped');1526}15271528//attach handler activating editable. In disabled mode it just prevent default action (useful for links)1529if(this.options.toggle !== 'manual') {1530this.$element.addClass('editable-click');1531this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){1532//prevent following link if editable enabled1533if(!this.options.disabled) {1534e.preventDefault();1535}15361537//stop propagation not required because in document click handler it checks event target1538//e.stopPropagation();15391540if(this.options.toggle === 'mouseenter') {1541//for hover only show container1542this.show();1543} else {1544//when toggle='click' we should not close all other containers as they will be closed automatically in document click listener1545var closeAll = (this.options.toggle !== 'click');1546this.toggle(closeAll);1547}1548}, this));1549} else {1550this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually1551}15521553//if display is function it's far more convinient to have autotext = always to render correctly on init1554//see https://github.com/vitalets/x-editable-yii/issues/341555if(typeof this.options.display === 'function') {1556this.options.autotext = 'always';1557}15581559//check conditions for autotext:1560switch(this.options.autotext) {1561case 'always':1562doAutotext = true;1563break;1564case 'auto':1565//if element text is empty and value is defined and value not generated by text --> run autotext1566doAutotext = !$.trim(this.$element.text()).length && this.value !== null && this.value !== undefined && !isValueByText;1567break;1568default:1569doAutotext = false;1570}15711572//depending on autotext run render() or just finilize init1573$.when(doAutotext ? this.render() : true).then($.proxy(function() {1574if(this.options.disabled) {1575this.disable();1576} else {1577this.enable();1578}1579/**1580Fired when element was initialized by `$().editable()` method.1581Please note that you should setup `init` handler **before** applying `editable`.15821583@event init1584@param {Object} event event object1585@param {Object} editable editable instance (as here it cannot accessed via data('editable'))1586@since 1.2.01587@example1588$('#username').on('init', function(e, editable) {1589alert('initialized ' + editable.options.name);1590});1591$('#username').editable();1592**/1593this.$element.triggerHandler('init', this);1594}, this));1595},15961597/*1598Initializes parent element for live editables1599*/1600initLive: function() {1601//store selector1602var selector = this.options.selector;1603//modify options for child elements1604this.options.selector = false;1605this.options.autotext = 'never';1606//listen toggle events1607this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){1608var $target = $(e.target);1609if(!$target.data('editable')) {1610//if delegated element initially empty, we need to clear it's text (that was manually set to `empty` by user)1611//see https://github.com/vitalets/x-editable/issues/1371612if($target.hasClass(this.options.emptyclass)) {1613$target.empty();1614}1615$target.editable(this.options).trigger(e);1616}1617}, this));1618},16191620/*1621Renders value into element's text.1622Can call custom display method from options.1623Can return deferred object.1624@method render()1625@param {mixed} response server response (if exist) to pass into display function1626*/1627render: function(response) {1628//do not display anything1629if(this.options.display === false) {1630return;1631}16321633//if input has `value2htmlFinal` method, we pass callback in third param to be called when source is loaded1634if(this.input.value2htmlFinal) {1635return this.input.value2html(this.value, this.$element[0], this.options.display, response);1636//if display method defined --> use it1637} else if(typeof this.options.display === 'function') {1638return this.options.display.call(this.$element[0], this.value, response);1639//else use input's original value2html() method1640} else {1641return this.input.value2html(this.value, this.$element[0]);1642}1643},16441645/**1646Enables editable1647@method enable()1648**/1649enable: function() {1650this.options.disabled = false;1651this.$element.removeClass('editable-disabled');1652this.handleEmpty(this.isEmpty);1653if(this.options.toggle !== 'manual') {1654if(this.$element.attr('tabindex') === '-1') {1655this.$element.removeAttr('tabindex');1656}1657}1658},16591660/**1661Disables editable1662@method disable()1663**/1664disable: function() {1665this.options.disabled = true;1666this.hide();1667this.$element.addClass('editable-disabled');1668this.handleEmpty(this.isEmpty);1669//do not stop focus on this element1670this.$element.attr('tabindex', -1);1671},16721673/**1674Toggles enabled / disabled state of editable element1675@method toggleDisabled()1676**/1677toggleDisabled: function() {1678if(this.options.disabled) {1679this.enable();1680} else {1681this.disable();1682}1683},16841685/**1686Sets new option16871688@method option(key, value)1689@param {string|object} key option name or object with several options1690@param {mixed} value option new value1691@example1692$('.editable').editable('option', 'pk', 2);1693**/1694option: function(key, value) {1695//set option(s) by object1696if(key && typeof key === 'object') {1697$.each(key, $.proxy(function(k, v){1698this.option($.trim(k), v);1699}, this));1700return;1701}17021703//set option by string1704this.options[key] = value;17051706//disabled1707if(key === 'disabled') {1708return value ? this.disable() : this.enable();1709}17101711//value1712if(key === 'value') {1713this.setValue(value);1714}17151716//transfer new option to container!1717if(this.container) {1718this.container.option(key, value);1719}17201721//pass option to input directly (as it points to the same in form)1722if(this.input.option) {1723this.input.option(key, value);1724}17251726},17271728/*1729* set emptytext if element is empty1730*/1731handleEmpty: function (isEmpty) {1732//do not handle empty if we do not display anything1733if(this.options.display === false) {1734return;1735}17361737/*1738isEmpty may be set directly as param of method.1739It is required when we enable/disable field and can't rely on content1740as node content is text: "Empty" that is not empty %)1741*/1742if(isEmpty !== undefined) {1743this.isEmpty = isEmpty;1744} else {1745//detect empty1746//for some inputs we need more smart check1747//e.g. wysihtml5 may have <br>, <p></p>, <img>1748if(typeof(this.input.isEmpty) === 'function') {1749this.isEmpty = this.input.isEmpty(this.$element);1750} else {1751this.isEmpty = $.trim(this.$element.html()) === '';1752}1753}17541755//emptytext shown only for enabled1756if(!this.options.disabled) {1757if (this.isEmpty) {1758this.$element.html(this.options.emptytext);1759if(this.options.emptyclass) {1760this.$element.addClass(this.options.emptyclass);1761}1762} else if(this.options.emptyclass) {1763this.$element.removeClass(this.options.emptyclass);1764}1765} else {1766//below required if element disable property was changed1767if(this.isEmpty) {1768this.$element.empty();1769if(this.options.emptyclass) {1770this.$element.removeClass(this.options.emptyclass);1771}1772}1773}1774},17751776/**1777Shows container with form1778@method show()1779@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.1780**/1781show: function (closeAll) {1782if(this.options.disabled) {1783return;1784}17851786//init editableContainer: popover, tooltip, inline, etc..1787if(!this.container) {1788var containerOptions = $.extend({}, this.options, {1789value: this.value,1790input: this.input //pass input to form (as it is already created)1791});1792this.$element.editableContainer(containerOptions);1793//listen `save` event1794this.$element.on("save.internal", $.proxy(this.save, this));1795this.container = this.$element.data('editableContainer');1796} else if(this.container.tip().is(':visible')) {1797return;1798}17991800//show container1801this.container.show(closeAll);1802},18031804/**1805Hides container with form1806@method hide()1807**/1808hide: function () {1809if(this.container) {1810this.container.hide();1811}1812},18131814/**1815Toggles container visibility (show / hide)1816@method toggle()1817@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.1818**/1819toggle: function(closeAll) {1820if(this.container && this.container.tip().is(':visible')) {1821this.hide();1822} else {1823this.show(closeAll);1824}1825},18261827/*1828* called when form was submitted1829*/1830save: function(e, params) {1831//mark element with unsaved class if needed1832if(this.options.unsavedclass) {1833/*1834Add unsaved css to element if:1835- url is not user's function1836- value was not sent to server1837- params.response === undefined, that means data was not sent1838- value changed1839*/1840var sent = false;1841sent = sent || typeof this.options.url === 'function';1842sent = sent || this.options.display === false;1843sent = sent || params.response !== undefined;1844sent = sent || (this.options.savenochange && this.input.value2str(this.value) !== this.input.value2str(params.newValue));18451846if(sent) {1847this.$element.removeClass(this.options.unsavedclass);1848} else {1849this.$element.addClass(this.options.unsavedclass);1850}1851}18521853//highlight when saving1854if(this.options.highlight) {1855var $e = this.$element,1856bgColor = $e.css('background-color');18571858$e.css('background-color', this.options.highlight);1859setTimeout(function(){1860if(bgColor === 'transparent') {1861bgColor = '';1862}1863$e.css('background-color', bgColor);1864$e.addClass('editable-bg-transition');1865setTimeout(function(){1866$e.removeClass('editable-bg-transition');1867}, 1700);1868}, 10);1869}18701871//set new value1872this.setValue(params.newValue, false, params.response);18731874/**1875Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance18761877@event save1878@param {Object} event event object1879@param {Object} params additional params1880@param {mixed} params.newValue submitted value1881@param {Object} params.response ajax response1882@example1883$('#username').on('save', function(e, params) {1884alert('Saved value: ' + params.newValue);1885});1886**/1887//event itself is triggered by editableContainer. Description here is only for documentation1888},18891890validate: function () {1891if (typeof this.options.validate === 'function') {1892return this.options.validate.call(this, this.value);1893}1894},18951896/**1897Sets new value of editable1898@method setValue(value, convertStr)1899@param {mixed} value new value1900@param {boolean} convertStr whether to convert value from string to internal format1901**/1902setValue: function(value, convertStr, response) {1903if(convertStr) {1904this.value = this.input.str2value(value);1905} else {1906this.value = value;1907}1908if(this.container) {1909this.container.option('value', this.value);1910}1911$.when(this.render(response))1912.then($.proxy(function() {1913this.handleEmpty();1914}, this));1915},19161917/**1918Activates input of visible container (e.g. set focus)1919@method activate()1920**/1921activate: function() {1922if(this.container) {1923this.container.activate();1924}1925},19261927/**1928Removes editable feature from element1929@method destroy()1930**/1931destroy: function() {1932this.disable();19331934if(this.container) {1935this.container.destroy();1936}19371938this.input.destroy();19391940if(this.options.toggle !== 'manual') {1941this.$element.removeClass('editable-click');1942this.$element.off(this.options.toggle + '.editable');1943}19441945this.$element.off("save.internal");19461947this.$element.removeClass('editable editable-open editable-disabled');1948this.$element.removeData('editable');1949}1950};19511952/* EDITABLE PLUGIN DEFINITION1953* ======================= */19541955/**1956jQuery method to initialize editable element.19571958@method $().editable(options)1959@params {Object} options1960@example1961$('#username').editable({1962type: 'text',1963url: '/post',1964pk: 11965});1966**/1967$.fn.editable = function (option) {1968//special API methods returning non-jquery object1969var result = {}, args = arguments, datakey = 'editable';1970switch (option) {1971/**1972Runs client-side validation for all matched editables19731974@method validate()1975@returns {Object} validation errors map1976@example1977$('#username, #fullname').editable('validate');1978// possible result:1979{1980username: "username is required",1981fullname: "fullname should be minimum 3 letters length"1982}1983**/1984case 'validate':1985this.each(function () {1986var $this = $(this), data = $this.data(datakey), error;1987if (data && (error = data.validate())) {1988result[data.options.name] = error;1989}1990});1991return result;19921993/**1994Returns current values of editable elements.1995Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements.1996If value of some editable is `null` or `undefined` it is excluded from result object.1997When param `isSingle` is set to **true** - it is supposed you have single element and will return value of editable instead of object.19981999@method getValue()2000@param {bool} isSingle whether to return just value of single element2001@returns {Object} object of element names and values2002@example2003$('#username, #fullname').editable('getValue');2004//result:2005{2006username: "superuser",2007fullname: "John"2008}2009//isSingle = true2010$('#username').editable('getValue', true);2011//result "superuser"2012**/2013case 'getValue':2014if(arguments.length === 2 && arguments[1] === true) { //isSingle = true2015result = this.eq(0).data(datakey).value;2016} else {2017this.each(function () {2018var $this = $(this), data = $this.data(datakey);2019if (data && data.value !== undefined && data.value !== null) {2020result[data.options.name] = data.input.value2submit(data.value);2021}2022});2023}2024return result;20252026/**2027This method collects values from several editable elements and submit them all to server.2028Internally it runs client-side validation for all fields and submits only in case of success.2029See <a href="#newrecord">creating new records</a> for details.2030Since 1.5.1 `submit` can be applied to single element to send data programmatically. In that case2031`url`, `success` and `error` is taken from initial options and you can just call `$('#username').editable('submit')`.20322033@method submit(options)2034@param {object} options2035@param {object} options.url url to submit data2036@param {object} options.data additional data to submit2037@param {object} options.ajaxOptions additional ajax options2038@param {function} options.error(obj) error handler2039@param {function} options.success(obj,config) success handler2040@returns {Object} jQuery object2041**/2042case 'submit': //collects value, validate and submit to server for creating new record2043var config = arguments[1] || {},2044$elems = this,2045errors = this.editable('validate');20462047// validation ok2048if($.isEmptyObject(errors)) {2049var ajaxOptions = {};20502051// for single element use url, success etc from options2052if($elems.length === 1) {2053var editable = $elems.data('editable');2054//standard params2055var params = {2056name: editable.options.name || '',2057value: editable.input.value2submit(editable.value),2058pk: (typeof editable.options.pk === 'function') ?2059editable.options.pk.call(editable.options.scope) :2060editable.options.pk2061};20622063//additional params2064if(typeof editable.options.params === 'function') {2065params = editable.options.params.call(editable.options.scope, params);2066} else {2067//try parse json in single quotes (from data-params attribute)2068editable.options.params = $.fn.editableutils.tryParseJson(editable.options.params, true);2069$.extend(params, editable.options.params);2070}20712072ajaxOptions = {2073url: editable.options.url,2074data: params,2075type: 'POST'2076};20772078// use success / error from options2079config.success = config.success || editable.options.success;2080config.error = config.error || editable.options.error;20812082// multiple elements2083} else {2084var values = this.editable('getValue');20852086ajaxOptions = {2087url: config.url,2088data: values,2089type: 'POST'2090};2091}20922093// ajax success callabck (response 200 OK)2094ajaxOptions.success = typeof config.success === 'function' ? function(response) {2095config.success.call($elems, response, config);2096} : $.noop;20972098// ajax error callabck2099ajaxOptions.error = typeof config.error === 'function' ? function() {2100config.error.apply($elems, arguments);2101} : $.noop;21022103// extend ajaxOptions2104if(config.ajaxOptions) {2105$.extend(ajaxOptions, config.ajaxOptions);2106}21072108// extra data2109if(config.data) {2110$.extend(ajaxOptions.data, config.data);2111}21122113// perform ajax request2114$.ajax(ajaxOptions);2115} else { //client-side validation error2116if(typeof config.error === 'function') {2117config.error.call($elems, errors);2118}2119}2120return this;2121}21222123//return jquery object2124return this.each(function () {2125var $this = $(this),2126data = $this.data(datakey),2127options = typeof option === 'object' && option;21282129//for delegated targets do not store `editable` object for element2130//it's allows several different selectors.2131//see: https://github.com/vitalets/x-editable/issues/3122132if(options && options.selector) {2133data = new Editable(this, options);2134return;2135}21362137if (!data) {2138$this.data(datakey, (data = new Editable(this, options)));2139}21402141if (typeof option === 'string') { //call method2142data[option].apply(data, Array.prototype.slice.call(args, 1));2143}2144});2145};214621472148$.fn.editable.defaults = {2149/**2150Type of input. Can be <code>text|textarea|select|date|checklist</code> and more21512152@property type2153@type string2154@default 'text'2155**/2156type: 'text',2157/**2158Sets disabled state of editable21592160@property disabled2161@type boolean2162@default false2163**/2164disabled: false,2165/**2166How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>.2167When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable.2168**Note**: if you call <code>show</code> or <code>toggle</code> inside **click** handler of some DOM element,2169you need to apply <code>e.stopPropagation()</code> because containers are being closed on any click on document.21702171@example2172$('#edit-button').click(function(e) {2173e.stopPropagation();2174$('#username').editable('toggle');2175});21762177@property toggle2178@type string2179@default 'click'2180**/2181toggle: 'click',2182/**2183Text shown when element is empty.21842185@property emptytext2186@type string2187@default 'Empty'2188**/2189emptytext: 'Empty',2190/**2191Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Useful for select and date.2192For 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>.2193<code>auto</code> - text will be automatically set only if element is empty.2194<code>always|never</code> - always(never) try to set element's text.21952196@property autotext2197@type string2198@default 'auto'2199**/2200autotext: 'auto',2201/**2202Initial value of input. If not set, taken from element's text.2203Note, that if element's text is empty - text is automatically generated from value and can be customized (see `autotext` option).2204For example, to display currency sign:2205@example2206<a id="price" data-type="text" data-value="100"></a>2207<script>2208$('#price').editable({2209...2210display: function(value) {2211$(this).text(value + '$');2212}2213})2214</script>22152216@property value2217@type mixed2218@default element's text2219**/2220value: null,2221/**2222Callback to perform custom displaying of value in element's text.2223If `null`, default input's display used.2224If `false`, no displaying methods will be called, element's text will never change.2225Runs under element's scope.2226_**Parameters:**_22272228* `value` current value to be displayed2229* `response` server response (if display called after ajax submit), since 1.4.022302231For _inputs with source_ (select, checklist) parameters are different:22322233* `value` current value to be displayed2234* `sourceData` array of items for current input (e.g. dropdown items)2235* `response` server response (if display called after ajax submit), since 1.4.022362237To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`.22382239@property display2240@type function|boolean2241@default null2242@since 1.2.02243@example2244display: function(value, sourceData) {2245//display checklist as comma-separated values2246var html = [],2247checked = $.fn.editableutils.itemsByValue(value, sourceData);22482249if(checked.length) {2250$.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });2251$(this).html(html.join(', '));2252} else {2253$(this).empty();2254}2255}2256**/2257display: null,2258/**2259Css class applied when editable text is empty.22602261@property emptyclass2262@type string2263@since 1.4.12264@default editable-empty2265**/2266emptyclass: 'editable-empty',2267/**2268Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`).2269You may set it to `null` if you work with editables locally and submit them together.22702271@property unsavedclass2272@type string2273@since 1.4.12274@default editable-unsaved2275**/2276unsavedclass: 'editable-unsaved',2277/**2278If selector is provided, editable will be delegated to the specified targets.2279Usefull for dynamically generated DOM elements.2280**Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options,2281as they actually become editable only after first click.2282You should manually set class `editable-click` to these elements.2283Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element:22842285@property selector2286@type string2287@since 1.4.12288@default null2289@example2290<div id="user">2291<!-- empty -->2292<a href="#" data-name="username" data-type="text" class="editable-click editable-empty" data-value="" title="Username">Empty</a>2293<!-- non-empty -->2294<a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" class="editable-click" title="Group">Operator</a>2295</div>22962297<script>2298$('#user').editable({2299selector: 'a',2300url: '/post',2301pk: 12302});2303</script>2304**/2305selector: null,2306/**2307Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers.23082309@property highlight2310@type string|boolean2311@since 1.4.52312@default #FFFF802313**/2314highlight: '#FFFF80'2315};23162317}(window.jQuery));23182319/**2320AbstractInput - base class for all editable inputs.2321It defines interface to be implemented by any input type.2322To create your own input you can inherit from this class.23232324@class abstractinput2325**/2326(function ($) {2327"use strict";23282329//types2330$.fn.editabletypes = {};23312332var AbstractInput = function () { };23332334AbstractInput.prototype = {2335/**2336Initializes input23372338@method init()2339**/2340init: function(type, options, defaults) {2341this.type = type;2342this.options = $.extend({}, defaults, options);2343},23442345/*2346this method called before render to init $tpl that is inserted in DOM2347*/2348prerender: function() {2349this.$tpl = $(this.options.tpl); //whole tpl as jquery object2350this.$input = this.$tpl; //control itself, can be changed in render method2351this.$clear = null; //clear button2352this.error = null; //error message, if input cannot be rendered2353},23542355/**2356Renders input from tpl. Can return jQuery deferred object.2357Can be overwritten in child objects23582359@method render()2360**/2361render: function() {23622363},23642365/**2366Sets element's html by value.23672368@method value2html(value, element)2369@param {mixed} value2370@param {DOMElement} element2371**/2372value2html: function(value, element) {2373$(element)[this.options.escape ? 'text' : 'html']($.trim(value));2374},23752376/**2377Converts element's html to value23782379@method html2value(html)2380@param {string} html2381@returns {mixed}2382**/2383html2value: function(html) {2384return $('<div>').html(html).text();2385},23862387/**2388Converts value to string (for internal compare). For submitting to server used value2submit().23892390@method value2str(value)2391@param {mixed} value2392@returns {string}2393**/2394value2str: function(value) {2395return value;2396},23972398/**2399Converts string received from server into value. Usually from `data-value` attribute.24002401@method str2value(str)2402@param {string} str2403@returns {mixed}2404**/2405str2value: function(str) {2406return str;2407},24082409/**2410Converts value for submitting to server. Result can be string or object.24112412@method value2submit(value)2413@param {mixed} value2414@returns {mixed}2415**/2416value2submit: function(value) {2417return value;2418},24192420/**2421Sets value of input.24222423@method value2input(value)2424@param {mixed} value2425**/2426value2input: function(value) {2427this.$input.val(value);2428},24292430/**2431Returns value of input. Value can be object (e.g. datepicker)24322433@method input2value()2434**/2435input2value: function() {2436return this.$input.val();2437},24382439/**2440Activates input. For text it sets focus.24412442@method activate()2443**/2444activate: function() {2445if(this.$input.is(':visible')) {2446this.$input.focus();2447}2448},24492450/**2451Creates input.24522453@method clear()2454**/2455clear: function() {2456this.$input.val(null);2457},24582459/**2460method to escape html.2461**/2462escape: function(str) {2463return $('<div>').text(str).html();2464},24652466/**2467attach handler to automatically submit form when value changed (useful when buttons not shown)2468**/2469autosubmit: function() {24702471},24722473/**2474Additional actions when destroying element2475**/2476destroy: function() {2477},24782479// -------- helper functions --------2480setClass: function() {2481if(this.options.inputclass) {2482this.$input.addClass(this.options.inputclass);2483}2484},24852486setAttr: function(attr) {2487if (this.options[attr] !== undefined && this.options[attr] !== null) {2488this.$input.attr(attr, this.options[attr]);2489}2490},24912492option: function(key, value) {2493this.options[key] = value;2494}24952496};24972498AbstractInput.defaults = {2499/**2500HTML template of input. Normally you should not change it.25012502@property tpl2503@type string2504@default ''2505**/2506tpl: '',2507/**2508CSS class automatically applied to input25092510@property inputclass2511@type string2512@default null2513**/2514inputclass: null,25152516/**2517If `true` - html will be escaped in content of element via $.text() method.2518If `false` - html will not be escaped, $.html() used.2519When you use own `display` function, this option obviosly has no effect.25202521@property escape2522@type boolean2523@since 1.5.02524@default true2525**/2526escape: true,25272528//scope for external methods (e.g. source defined as function)2529//for internal use only2530scope: null,25312532//need to re-declare showbuttons here to get it's value from common config (passed only options existing in defaults)2533showbuttons: true2534};25352536$.extend($.fn.editabletypes, {abstractinput: AbstractInput});25372538}(window.jQuery));25392540/**2541List - abstract class for inputs that have source option loaded from js array or via ajax25422543@class list2544@extends abstractinput2545**/2546(function ($) {2547"use strict";25482549var List = function (options) {25502551};25522553$.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput);25542555$.extend(List.prototype, {2556render: function () {2557var deferred = $.Deferred();25582559this.error = null;2560this.onSourceReady(function () {2561this.renderList();2562deferred.resolve();2563}, function () {2564this.error = this.options.sourceError;2565deferred.resolve();2566});25672568return deferred.promise();2569},25702571html2value: function (html) {2572return null; //can't set value by text2573},25742575value2html: function (value, element, display, response) {2576var deferred = $.Deferred(),2577success = function () {2578if(typeof display === 'function') {2579//custom display method2580display.call(element, value, this.sourceData, response);2581} else {2582this.value2htmlFinal(value, element);2583}2584deferred.resolve();2585};25862587//for null value just call success without loading source2588if(value === null) {2589success.call(this);2590} else {2591this.onSourceReady(success, function () { deferred.resolve(); });2592}25932594return deferred.promise();2595},25962597// ------------- additional functions ------------25982599onSourceReady: function (success, error) {2600//run source if it function2601var source;2602if ($.isFunction(this.options.source)) {2603source = this.options.source.call(this.options.scope);2604this.sourceData = null;2605//note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed2606} else {2607source = this.options.source;2608}26092610//if allready loaded just call success2611if(this.options.sourceCache && $.isArray(this.sourceData)) {2612success.call(this);2613return;2614}26152616//try parse json in single quotes (for double quotes jquery does automatically)2617try {2618source = $.fn.editableutils.tryParseJson(source, false);2619} catch (e) {2620error.call(this);2621return;2622}26232624//loading from url2625if (typeof source === 'string') {2626//try to get sourceData from cache2627if(this.options.sourceCache) {2628var cacheID = source,2629cache;26302631if (!$(document).data(cacheID)) {2632$(document).data(cacheID, {});2633}2634cache = $(document).data(cacheID);26352636//check for cached data2637if (cache.loading === false && cache.sourceData) { //take source from cache2638this.sourceData = cache.sourceData;2639this.doPrepend();2640success.call(this);2641return;2642} else if (cache.loading === true) { //cache is loading, put callback in stack to be called later2643cache.callbacks.push($.proxy(function () {2644this.sourceData = cache.sourceData;2645this.doPrepend();2646success.call(this);2647}, this));26482649//also collecting error callbacks2650cache.err_callbacks.push($.proxy(error, this));2651return;2652} else { //no cache yet, activate it2653cache.loading = true;2654cache.callbacks = [];2655cache.err_callbacks = [];2656}2657}26582659//ajaxOptions for source. Can be overwritten bt options.sourceOptions2660var ajaxOptions = $.extend({2661url: source,2662type: 'get',2663cache: false,2664dataType: 'json',2665success: $.proxy(function (data) {2666if(cache) {2667cache.loading = false;2668}2669this.sourceData = this.makeArray(data);2670if($.isArray(this.sourceData)) {2671if(cache) {2672//store result in cache2673cache.sourceData = this.sourceData;2674//run success callbacks for other fields waiting for this source2675$.each(cache.callbacks, function () { this.call(); });2676}2677this.doPrepend();2678success.call(this);2679} else {2680error.call(this);2681if(cache) {2682//run error callbacks for other fields waiting for this source2683$.each(cache.err_callbacks, function () { this.call(); });2684}2685}2686}, this),2687error: $.proxy(function () {2688error.call(this);2689if(cache) {2690cache.loading = false;2691//run error callbacks for other fields2692$.each(cache.err_callbacks, function () { this.call(); });2693}2694}, this)2695}, this.options.sourceOptions);26962697//loading sourceData from server2698$.ajax(ajaxOptions);26992700} else { //options as json/array2701this.sourceData = this.makeArray(source);27022703if($.isArray(this.sourceData)) {2704this.doPrepend();2705success.call(this);2706} else {2707error.call(this);2708}2709}2710},27112712doPrepend: function () {2713if(this.options.prepend === null || this.options.prepend === undefined) {2714return;2715}27162717if(!$.isArray(this.prependData)) {2718//run prepend if it is function (once)2719if ($.isFunction(this.options.prepend)) {2720this.options.prepend = this.options.prepend.call(this.options.scope);2721}27222723//try parse json in single quotes2724this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true);27252726//convert prepend from string to object2727if (typeof this.options.prepend === 'string') {2728this.options.prepend = {'': this.options.prepend};2729}27302731this.prependData = this.makeArray(this.options.prepend);2732}27332734if($.isArray(this.prependData) && $.isArray(this.sourceData)) {2735this.sourceData = this.prependData.concat(this.sourceData);2736}2737},27382739/*2740renders input list2741*/2742renderList: function() {2743// this method should be overwritten in child class2744},27452746/*2747set element's html by value2748*/2749value2htmlFinal: function(value, element) {2750// this method should be overwritten in child class2751},27522753/**2754* convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}]2755*/2756makeArray: function(data) {2757var count, obj, result = [], item, iterateItem;2758if(!data || typeof data === 'string') {2759return null;2760}27612762if($.isArray(data)) { //array2763/*2764function to iterate inside item of array if item is object.2765Caclulates count of keys in item and store in obj.2766*/2767iterateItem = function (k, v) {2768obj = {value: k, text: v};2769if(count++ >= 2) {2770return false;// exit from `each` if item has more than one key.2771}2772};27732774for(var i = 0; i < data.length; i++) {2775item = data[i];2776if(typeof item === 'object') {2777count = 0; //count of keys inside item2778$.each(item, iterateItem);2779//case: [{val1: 'text1'}, {val2: 'text2} ...]2780if(count === 1) {2781result.push(obj);2782//case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...]2783} else if(count > 1) {2784//removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text')2785if(item.children) {2786item.children = this.makeArray(item.children);2787}2788result.push(item);2789}2790} else {2791//case: ['text1', 'text2' ...]2792result.push({value: item, text: item});2793}2794}2795} else { //case: {val1: 'text1', val2: 'text2, ...}2796$.each(data, function (k, v) {2797result.push({value: k, text: v});2798});2799}2800return result;2801},28022803option: function(key, value) {2804this.options[key] = value;2805if(key === 'source') {2806this.sourceData = null;2807}2808if(key === 'prepend') {2809this.prependData = null;2810}2811}28122813});28142815List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {2816/**2817Source data for list.2818If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]`2819For compability, object format is also supported: `{"1": "text1", "2": "text2" ...}` but it does not guarantee elements order.28202821If **string** - considered ajax url to load items. In that case results will be cached for fields with the same source and name. See also `sourceCache` option.28222823If **function**, it should return data in format above (since 1.4.0).28242825Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only).2826`[{text: "group1", children: [{value: 1, text: "text1"}, {value: 2, text: "text2"}]}, ...]`282728282829@property source2830@type string | array | object | function2831@default null2832**/2833source: null,2834/**2835Data automatically prepended to the beginning of dropdown list.28362837@property prepend2838@type string | array | object | function2839@default false2840**/2841prepend: false,2842/**2843Error message when list cannot be loaded (e.g. ajax error)28442845@property sourceError2846@type string2847@default Error when loading list2848**/2849sourceError: 'Error when loading list',2850/**2851if <code>true</code> and source is **string url** - results will be cached for fields with the same source.2852Usefull for editable column in grid to prevent extra requests.28532854@property sourceCache2855@type boolean2856@default true2857@since 1.2.02858**/2859sourceCache: true,2860/**2861Additional ajax options to be used in $.ajax() when loading list from server.2862Useful to send extra parameters (`data` key) or change request method (`type` key).28632864@property sourceOptions2865@type object|function2866@default null2867@since 1.5.02868**/2869sourceOptions: null2870});28712872$.fn.editabletypes.list = List;28732874}(window.jQuery));28752876/**2877Text input28782879@class text2880@extends abstractinput2881@final2882@example2883<a href="#" id="username" data-type="text" data-pk="1">awesome</a>2884<script>2885$(function(){2886$('#username').editable({2887url: '/post',2888title: 'Enter username'2889});2890});2891</script>2892**/2893(function ($) {2894"use strict";28952896var Text = function (options) {2897this.init('text', options, Text.defaults);2898};28992900$.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);29012902$.extend(Text.prototype, {2903render: function() {2904this.renderClear();2905this.setClass();2906this.setAttr('placeholder');2907},29082909activate: function() {2910if(this.$input.is(':visible')) {2911this.$input.focus();2912$.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);2913if(this.toggleClear) {2914this.toggleClear();2915}2916}2917},29182919//render clear button2920renderClear: function() {2921if (this.options.clear) {2922this.$clear = $('<span class="editable-clear-x"></span>');2923this.$input.after(this.$clear)2924.css('padding-right', 24)2925.keyup($.proxy(function(e) {2926//arrows, enter, tab, etc2927if(~$.inArray(e.keyCode, [40,38,9,13,27])) {2928return;2929}29302931clearTimeout(this.t);2932var that = this;2933this.t = setTimeout(function() {2934that.toggleClear(e);2935}, 100);29362937}, this))2938.parent().css('position', 'relative');29392940this.$clear.click($.proxy(this.clear, this));2941}2942},29432944postrender: function() {2945/*2946//now `clear` is positioned via css2947if(this.$clear) {2948//can position clear button only here, when form is shown and height can be calculated2949// var h = this.$input.outerHeight(true) || 20,2950var h = this.$clear.parent().height(),2951delta = (h - this.$clear.height()) / 2;29522953//this.$clear.css({bottom: delta, right: delta});2954}2955*/2956},29572958//show / hide clear button2959toggleClear: function(e) {2960if(!this.$clear) {2961return;2962}29632964var len = this.$input.val().length,2965visible = this.$clear.is(':visible');29662967if(len && !visible) {2968this.$clear.show();2969}29702971if(!len && visible) {2972this.$clear.hide();2973}2974},29752976clear: function() {2977this.$clear.hide();2978this.$input.val('').focus();2979}2980});29812982Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {2983/**2984@property tpl2985@default <input type="text">2986**/2987tpl: '<input type="text">',2988/**2989Placeholder attribute of input. Shown when input is empty.29902991@property placeholder2992@type string2993@default null2994**/2995placeholder: null,29962997/**2998Whether to show `clear` button29993000@property clear3001@type boolean3002@default true3003**/3004clear: true3005});30063007$.fn.editabletypes.text = Text;30083009}(window.jQuery));30103011/**3012Textarea input30133014@class textarea3015@extends abstractinput3016@final3017@example3018<a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a>3019<script>3020$(function(){3021$('#comments').editable({3022url: '/post',3023title: 'Enter comments',3024rows: 103025});3026});3027</script>3028**/3029(function ($) {3030"use strict";30313032var Textarea = function (options) {3033this.init('textarea', options, Textarea.defaults);3034};30353036$.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput);30373038$.extend(Textarea.prototype, {3039render: function () {3040this.setClass();3041this.setAttr('placeholder');3042this.setAttr('rows');30433044//ctrl + enter3045this.$input.keydown(function (e) {3046if (e.ctrlKey && e.which === 13) {3047$(this).closest('form').submit();3048}3049});3050},30513052//using `white-space: pre-wrap` solves \n <--> BR conversion very elegant!3053/*3054value2html: function(value, element) {3055var html = '', lines;3056if(value) {3057lines = value.split("\n");3058for (var i = 0; i < lines.length; i++) {3059lines[i] = $('<div>').text(lines[i]).html();3060}3061html = lines.join('<br>');3062}3063$(element).html(html);3064},30653066html2value: function(html) {3067if(!html) {3068return '';3069}30703071var regex = new RegExp(String.fromCharCode(10), 'g');3072var lines = html.split(/<br\s*\/?>/i);3073for (var i = 0; i < lines.length; i++) {3074var text = $('<div>').html(lines[i]).text();30753076// Remove newline characters (\n) to avoid them being converted by value2html() method3077// thus adding extra <br> tags3078text = text.replace(regex, '');30793080lines[i] = text;3081}3082return lines.join("\n");3083},3084*/3085activate: function() {3086$.fn.editabletypes.text.prototype.activate.call(this);3087}3088});30893090Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {3091/**3092@property tpl3093@default <textarea></textarea>3094**/3095tpl:'<textarea></textarea>',3096/**3097@property inputclass3098@default input-large3099**/3100inputclass: 'input-large',3101/**3102Placeholder attribute of input. Shown when input is empty.31033104@property placeholder3105@type string3106@default null3107**/3108placeholder: null,3109/**3110Number of rows in textarea31113112@property rows3113@type integer3114@default 73115**/3116rows: 73117});31183119$.fn.editabletypes.textarea = Textarea;31203121}(window.jQuery));31223123/**3124Select (dropdown)31253126@class select3127@extends list3128@final3129@example3130<a href="#" id="status" data-type="select" data-pk="1" data-url="/post" data-title="Select status"></a>3131<script>3132$(function(){3133$('#status').editable({3134value: 2,3135source: [3136{value: 1, text: 'Active'},3137{value: 2, text: 'Blocked'},3138{value: 3, text: 'Deleted'}3139]3140});3141});3142</script>3143**/3144(function ($) {3145"use strict";31463147var Select = function (options) {3148this.init('select', options, Select.defaults);3149};31503151$.fn.editableutils.inherit(Select, $.fn.editabletypes.list);31523153$.extend(Select.prototype, {3154renderList: function() {3155this.$input.empty();31563157var fillItems = function($el, data) {3158var attr;3159if($.isArray(data)) {3160for(var i=0; i<data.length; i++) {3161attr = {};3162if(data[i].children) {3163attr.label = data[i].text;3164$el.append(fillItems($('<optgroup>', attr), data[i].children));3165} else {3166attr.value = data[i].value;3167if(data[i].disabled) {3168attr.disabled = true;3169}3170$el.append($('<option>', attr).text(data[i].text));3171}3172}3173}3174return $el;3175};31763177fillItems(this.$input, this.sourceData);31783179this.setClass();31803181//enter submit3182this.$input.on('keydown.editable', function (e) {3183if (e.which === 13) {3184$(this).closest('form').submit();3185}3186});3187},31883189value2htmlFinal: function(value, element) {3190var text = '',3191items = $.fn.editableutils.itemsByValue(value, this.sourceData);31923193if(items.length) {3194text = items[0].text;3195}31963197//$(element).text(text);3198$.fn.editabletypes.abstractinput.prototype.value2html.call(this, text, element);3199},32003201autosubmit: function() {3202this.$input.off('keydown.editable').on('change.editable', function(){3203$(this).closest('form').submit();3204});3205}3206});32073208Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {3209/**3210@property tpl3211@default <select></select>3212**/3213tpl:'<select></select>'3214});32153216$.fn.editabletypes.select = Select;32173218}(window.jQuery));32193220/**3221List of checkboxes.3222Internally value stored as javascript array of values.32233224@class checklist3225@extends list3226@final3227@example3228<a href="#" id="options" data-type="checklist" data-pk="1" data-url="/post" data-title="Select options"></a>3229<script>3230$(function(){3231$('#options').editable({3232value: [2, 3],3233source: [3234{value: 1, text: 'option1'},3235{value: 2, text: 'option2'},3236{value: 3, text: 'option3'}3237]3238});3239});3240</script>3241**/3242(function ($) {3243"use strict";32443245var Checklist = function (options) {3246this.init('checklist', options, Checklist.defaults);3247};32483249$.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list);32503251$.extend(Checklist.prototype, {3252renderList: function() {3253var $label, $div;32543255this.$tpl.empty();32563257if(!$.isArray(this.sourceData)) {3258return;3259}32603261for(var i=0; i<this.sourceData.length; i++) {3262$label = $('<label>').append($('<input>', {3263type: 'checkbox',3264value: this.sourceData[i].value3265}))3266.append($('<span>').text(' '+this.sourceData[i].text));32673268$('<div>').append($label).appendTo(this.$tpl);3269}32703271this.$input = this.$tpl.find('input[type="checkbox"]');3272this.setClass();3273},32743275value2str: function(value) {3276return $.isArray(value) ? value.sort().join($.trim(this.options.separator)) : '';3277},32783279//parse separated string3280str2value: function(str) {3281var reg, value = null;3282if(typeof str === 'string' && str.length) {3283reg = new RegExp('\\s*'+$.trim(this.options.separator)+'\\s*');3284value = str.split(reg);3285} else if($.isArray(str)) {3286value = str;3287} else {3288value = [str];3289}3290return value;3291},32923293//set checked on required checkboxes3294value2input: function(value) {3295this.$input.prop('checked', false);3296if($.isArray(value) && value.length) {3297this.$input.each(function(i, el) {3298var $el = $(el);3299// cannot use $.inArray as it performs strict comparison3300$.each(value, function(j, val){3301/*jslint eqeq: true*/3302if($el.val() == val) {3303/*jslint eqeq: false*/3304$el.prop('checked', true);3305}3306});3307});3308}3309},33103311input2value: function() {3312var checked = [];3313this.$input.filter(':checked').each(function(i, el) {3314checked.push($(el).val());3315});3316return checked;3317},33183319//collect text of checked boxes3320value2htmlFinal: function(value, element) {3321var html = [],3322checked = $.fn.editableutils.itemsByValue(value, this.sourceData),3323escape = this.options.escape;33243325if(checked.length) {3326$.each(checked, function(i, v) {3327var text = escape ? $.fn.editableutils.escape(v.text) : v.text;3328html.push(text);3329});3330$(element).html(html.join('<br>'));3331} else {3332$(element).empty();3333}3334},33353336activate: function() {3337this.$input.first().focus();3338},33393340autosubmit: function() {3341this.$input.on('keydown', function(e){3342if (e.which === 13) {3343$(this).closest('form').submit();3344}3345});3346}3347});33483349Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {3350/**3351@property tpl3352@default <div></div>3353**/3354tpl:'<div class="editable-checklist"></div>',33553356/**3357@property inputclass3358@type string3359@default null3360**/3361inputclass: null,33623363/**3364Separator of values when reading from `data-value` attribute33653366@property separator3367@type string3368@default ','3369**/3370separator: ','3371});33723373$.fn.editabletypes.checklist = Checklist;33743375}(window.jQuery));33763377/**3378HTML5 input types.3379Following types are supported:33803381* password33823383* url3384* tel3385* number3386* range3387* time33883389Learn more about html5 inputs:3390http://www.w3.org/wiki/HTML5_form_additions3391To check browser compatibility please see:3392https://developer.mozilla.org/en-US/docs/HTML/Element/Input33933394@class html5types3395@extends text3396@final3397@since 1.3.03398@example3399<a href="#" id="email" data-type="email" data-pk="1">[email protected]</a>3400<script>3401$(function(){3402$('#email').editable({3403url: '/post',3404title: 'Enter email'3405});3406});3407</script>3408**/34093410/**3411@property tpl3412@default depends on type3413**/34143415/*3416Password3417*/3418(function ($) {3419"use strict";34203421var Password = function (options) {3422this.init('password', options, Password.defaults);3423};3424$.fn.editableutils.inherit(Password, $.fn.editabletypes.text);3425$.extend(Password.prototype, {3426//do not display password, show '[hidden]' instead3427value2html: function(value, element) {3428if(value) {3429$(element).text('[hidden]');3430} else {3431$(element).empty();3432}3433},3434//as password not displayed, should not set value by html3435html2value: function(html) {3436return null;3437}3438});3439Password.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {3440tpl: '<input type="password">'3441});3442$.fn.editabletypes.password = Password;3443}(window.jQuery));344434453446/*34473448*/3449(function ($) {3450"use strict";34513452var Email = function (options) {3453this.init('email', options, Email.defaults);3454};3455$.fn.editableutils.inherit(Email, $.fn.editabletypes.text);3456Email.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {3457tpl: '<input type="email">'3458});3459$.fn.editabletypes.email = Email;3460}(window.jQuery));346134623463/*3464Url3465*/3466(function ($) {3467"use strict";34683469var Url = function (options) {3470this.init('url', options, Url.defaults);3471};3472$.fn.editableutils.inherit(Url, $.fn.editabletypes.text);3473Url.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {3474tpl: '<input type="url">'3475});3476$.fn.editabletypes.url = Url;3477}(window.jQuery));347834793480/*3481Tel3482*/3483(function ($) {3484"use strict";34853486var Tel = function (options) {3487this.init('tel', options, Tel.defaults);3488};3489$.fn.editableutils.inherit(Tel, $.fn.editabletypes.text);3490Tel.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {3491tpl: '<input type="tel">'3492});3493$.fn.editabletypes.tel = Tel;3494}(window.jQuery));349534963497/*3498Number3499*/3500(function ($) {3501"use strict";35023503var NumberInput = function (options) {3504this.init('number', options, NumberInput.defaults);3505};3506$.fn.editableutils.inherit(NumberInput, $.fn.editabletypes.text);3507$.extend(NumberInput.prototype, {3508render: function () {3509NumberInput.superclass.render.call(this);3510this.setAttr('min');3511this.setAttr('max');3512this.setAttr('step');3513},3514postrender: function() {3515if(this.$clear) {3516//increase right ffset for up/down arrows3517this.$clear.css({right: 24});3518/*3519//can position clear button only here, when form is shown and height can be calculated3520var h = this.$input.outerHeight(true) || 20,3521delta = (h - this.$clear.height()) / 2;35223523//add 12px to offset right for up/down arrows3524this.$clear.css({top: delta, right: delta + 16});3525*/3526}3527}3528});3529NumberInput.defaults = $.extend({}, $.fn.editabletypes.text.defaults, {3530tpl: '<input type="number">',3531inputclass: 'input-mini',3532min: null,3533max: null,3534step: null3535});3536$.fn.editabletypes.number = NumberInput;3537}(window.jQuery));353835393540/*3541Range (inherit from number)3542*/3543(function ($) {3544"use strict";35453546var Range = function (options) {3547this.init('range', options, Range.defaults);3548};3549$.fn.editableutils.inherit(Range, $.fn.editabletypes.number);3550$.extend(Range.prototype, {3551render: function () {3552this.$input = this.$tpl.filter('input');35533554this.setClass();3555this.setAttr('min');3556this.setAttr('max');3557this.setAttr('step');35583559this.$input.on('input', function(){3560$(this).siblings('output').text($(this).val());3561});3562},3563activate: function() {3564this.$input.focus();3565}3566});3567Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, {3568tpl: '<input type="range"><output style="width: 30px; display: inline-block"></output>',3569inputclass: 'input-medium'3570});3571$.fn.editabletypes.range = Range;3572}(window.jQuery));35733574/*3575Time3576*/3577(function ($) {3578"use strict";35793580var Time = function (options) {3581this.init('time', options, Time.defaults);3582};3583//inherit from abstract, as inheritance from text gives selection error.3584$.fn.editableutils.inherit(Time, $.fn.editabletypes.abstractinput);3585$.extend(Time.prototype, {3586render: function() {3587this.setClass();3588}3589});3590Time.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {3591tpl: '<input type="time">'3592});3593$.fn.editabletypes.time = Time;3594}(window.jQuery));35953596/**3597Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.3598Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.35993600You should manually download and include select2 distributive:36013602<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>3603<script src="select2/select2.js"></script>36043605To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):36063607<link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>36083609**Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.3610You need initially put both `data-value` and element's text youself:36113612<a href="#" data-type="select2" data-value="1">Text1</a>361336143615@class select23616@extends abstractinput3617@since 1.4.13618@final3619@example3620<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a>3621<script>3622$(function(){3623//local source3624$('#country').editable({3625source: [3626{id: 'gb', text: 'Great Britain'},3627{id: 'us', text: 'United States'},3628{id: 'ru', text: 'Russia'}3629],3630select2: {3631multiple: true3632}3633});3634//remote source (simple)3635$('#country').editable({3636source: '/getCountries',3637select2: {3638placeholder: 'Select Country',3639minimumInputLength: 13640}3641});3642//remote source (advanced)3643$('#country').editable({3644select2: {3645placeholder: 'Select Country',3646allowClear: true,3647minimumInputLength: 3,3648id: function (item) {3649return item.CountryId;3650},3651ajax: {3652url: '/getCountries',3653dataType: 'json',3654data: function (term, page) {3655return { query: term };3656},3657results: function (data, page) {3658return { results: data };3659}3660},3661formatResult: function (item) {3662return item.CountryName;3663},3664formatSelection: function (item) {3665return item.CountryName;3666},3667initSelection: function (element, callback) {3668return $.get('/getCountryById', { query: element.val() }, function (data) {3669callback(data);3670});3671}3672}3673});3674});3675</script>3676**/3677(function ($) {3678"use strict";36793680var Constructor = function (options) {3681this.init('select2', options, Constructor.defaults);36823683options.select2 = options.select2 || {};36843685this.sourceData = null;36863687//placeholder3688if(options.placeholder) {3689options.select2.placeholder = options.placeholder;3690}36913692//if not `tags` mode, use source3693if(!options.select2.tags && options.source) {3694var source = options.source;3695//if source is function, call it (once!)3696if ($.isFunction(options.source)) {3697source = options.source.call(options.scope);3698}36993700if (typeof source === 'string') {3701options.select2.ajax = options.select2.ajax || {};3702//some default ajax params3703if(!options.select2.ajax.data) {3704options.select2.ajax.data = function(term) {return { query:term };};3705}3706if(!options.select2.ajax.results) {3707options.select2.ajax.results = function(data) { return {results:data };};3708}3709options.select2.ajax.url = source;3710} else {3711//check format and convert x-editable format to select2 format (if needed)3712this.sourceData = this.convertSource(source);3713options.select2.data = this.sourceData;3714}3715}37163717//overriding objects in config (as by default jQuery extend() is not recursive)3718this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2);37193720//detect whether it is multi-valued3721this.isMultiple = this.options.select2.tags || this.options.select2.multiple;3722this.isRemote = ('ajax' in this.options.select2);37233724//store function returning ID of item3725//should be here as used inautotext for local source3726this.idFunc = this.options.select2.id;3727if (typeof(this.idFunc) !== "function") {3728var idKey = this.idFunc || 'id';3729this.idFunc = function (e) { return e[idKey]; };3730}37313732//store function that renders text in select23733this.formatSelection = this.options.select2.formatSelection;3734if (typeof(this.formatSelection) !== "function") {3735this.formatSelection = function (e) { return e.text; };3736}3737};37383739$.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);37403741$.extend(Constructor.prototype, {3742render: function() {3743this.setClass();37443745//can not apply select2 here as it calls initSelection3746//over input that does not have correct value yet.3747//apply select2 only in value2input3748//this.$input.select2(this.options.select2);37493750//when data is loaded via ajax, we need to know when it's done to populate listData3751if(this.isRemote) {3752//listen to loaded event to populate data3753this.$input.on('select2-loaded', $.proxy(function(e) {3754this.sourceData = e.items.results;3755}, this));3756}37573758//trigger resize of editableform to re-position container in multi-valued mode3759if(this.isMultiple) {3760this.$input.on('change', function() {3761$(this).closest('form').parent().triggerHandler('resize');3762});3763}3764},37653766value2html: function(value, element) {3767var text = '', data,3768that = this;37693770if(this.options.select2.tags) { //in tags mode just assign value3771data = value;3772//data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc);3773} else if(this.sourceData) {3774data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc);3775} else {3776//can not get list of possible values3777//(e.g. autotext for select2 with ajax source)3778}37793780//data may be array (when multiple values allowed)3781if($.isArray(data)) {3782//collect selected data and show with separator3783text = [];3784$.each(data, function(k, v){3785text.push(v && typeof v === 'object' ? that.formatSelection(v) : v);3786});3787} else if(data) {3788text = that.formatSelection(data);3789}37903791text = $.isArray(text) ? text.join(this.options.viewseparator) : text;37923793//$(element).text(text);3794Constructor.superclass.value2html.call(this, text, element);3795},37963797html2value: function(html) {3798return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;3799},38003801value2input: function(value) {3802// if value array => join it anyway3803if($.isArray(value)) {3804value = value.join(this.getSeparator());3805}38063807//for remote source just set value, text is updated by initSelection3808if(!this.$input.data('select2')) {3809this.$input.val(value);3810this.$input.select2(this.options.select2);3811} else {3812//second argument needed to separate initial change from user's click (for autosubmit)3813this.$input.val(value).trigger('change', true);38143815//Uncaught Error: cannot call val() if initSelection() is not defined3816//this.$input.select2('val', value);3817}38183819// if defined remote source AND no multiple mode AND no user's initSelection provided -->3820// we should somehow get text for provided id.3821// The solution is to use element's text as text for that id (exclude empty)3822if(this.isRemote && !this.isMultiple && !this.options.select2.initSelection) {3823// customId and customText are methods to extract `id` and `text` from data object3824// we can use this workaround only if user did not define these methods3825// otherwise we cant construct data object3826var customId = this.options.select2.id,3827customText = this.options.select2.formatSelection;38283829if(!customId && !customText) {3830var $el = $(this.options.scope);3831if (!$el.data('editable').isEmpty) {3832var data = {id: value, text: $el.text()};3833this.$input.select2('data', data);3834}3835}3836}3837},38383839input2value: function() {3840return this.$input.select2('val');3841},38423843str2value: function(str, separator) {3844if(typeof str !== 'string' || !this.isMultiple) {3845return str;3846}38473848separator = separator || this.getSeparator();38493850var val, i, l;38513852if (str === null || str.length < 1) {3853return null;3854}3855val = str.split(separator);3856for (i = 0, l = val.length; i < l; i = i + 1) {3857val[i] = $.trim(val[i]);3858}38593860return val;3861},38623863autosubmit: function() {3864this.$input.on('change', function(e, isInitial){3865if(!isInitial) {3866$(this).closest('form').submit();3867}3868});3869},38703871getSeparator: function() {3872return this.options.select2.separator || $.fn.select2.defaults.separator;3873},38743875/*3876Converts source from x-editable format: {value: 1, text: "1"} to3877select2 format: {id: 1, text: "1"}3878*/3879convertSource: function(source) {3880if($.isArray(source) && source.length && source[0].value !== undefined) {3881for(var i = 0; i<source.length; i++) {3882if(source[i].value !== undefined) {3883source[i].id = source[i].value;3884delete source[i].value;3885}3886}3887}3888return source;3889},38903891destroy: function() {3892if(this.$input.data('select2')) {3893this.$input.select2('destroy');3894}3895}38963897});38983899Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {3900/**3901@property tpl3902@default <input type="hidden">3903**/3904tpl:'<input type="hidden">',3905/**3906Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2).39073908@property select23909@type object3910@default null3911**/3912select2: null,3913/**3914Placeholder attribute of select39153916@property placeholder3917@type string3918@default null3919**/3920placeholder: null,3921/**3922Source data for select. It will be assigned to select2 `data` property and kept here just for convenience.3923Please note, that format is different from simple `select` input: use 'id' instead of 'value'.3924E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`.39253926@property source3927@type array|string|function3928@default null3929**/3930source: null,3931/**3932Separator used to display tags.39333934@property viewseparator3935@type string3936@default ', '3937**/3938viewseparator: ', '3939});39403941$.fn.editabletypes.select2 = Constructor;39423943}(window.jQuery));39443945/**3946* Combodate - 1.0.53947* Dropdown date and time picker.3948* Converts text input into dropdowns to pick day, month, year, hour, minute and second.3949* Uses momentjs as datetime library http://momentjs.com.3950* For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang3951*3952* Confusion at noon and midnight - see http://en.wikipedia.org/wiki/12-hour_clock#Confusion_at_noon_and_midnight3953* In combodate:3954* 12:00 pm --> 12:00 (24-h format, midday)3955* 12:00 am --> 00:00 (24-h format, midnight, start of day)3956*3957* Differs from momentjs parse rules:3958* 00:00 pm, 12:00 pm --> 12:00 (24-h format, day not change)3959* 00:00 am, 12:00 am --> 00:00 (24-h format, day not change)3960*3961*3962* Author: Vitaliy Potapov3963* Project page: http://github.com/vitalets/combodate3964* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.3965**/3966(function ($) {39673968var Combodate = function (element, options) {3969this.$element = $(element);3970if(!this.$element.is('input')) {3971$.error('Combodate should be applied to INPUT element');3972return;3973}3974this.options = $.extend({}, $.fn.combodate.defaults, options, this.$element.data());3975this.init();3976};39773978Combodate.prototype = {3979constructor: Combodate,3980init: function () {3981this.map = {3982//key regexp moment.method3983day: ['D', 'date'],3984month: ['M', 'month'],3985year: ['Y', 'year'],3986hour: ['[Hh]', 'hours'],3987minute: ['m', 'minutes'],3988second: ['s', 'seconds'],3989ampm: ['[Aa]', '']3990};39913992this.$widget = $('<span class="combodate"></span>').html(this.getTemplate());39933994this.initCombos();39953996//update original input on change3997this.$widget.on('change', 'select', $.proxy(function(e) {3998this.$element.val(this.getValue()).change();3999// update days count if month or year changes4000if (this.options.smartDays) {4001if ($(e.target).is('.month') || $(e.target).is('.year')) {4002this.fillCombo('day');4003}4004}4005}, this));40064007this.$widget.find('select').css('width', 'auto');40084009// hide original input and insert widget4010this.$element.hide().after(this.$widget);40114012// set initial value4013this.setValue(this.$element.val() || this.options.value);4014},40154016/*4017Replace tokens in template with <select> elements4018*/4019getTemplate: function() {4020var tpl = this.options.template;40214022//first pass4023$.each(this.map, function(k, v) {4024v = v[0];4025var r = new RegExp(v+'+'),4026token = v.length > 1 ? v.substring(1, 2) : v;40274028tpl = tpl.replace(r, '{'+token+'}');4029});40304031//replace spaces with 4032tpl = tpl.replace(/ /g, ' ');40334034//second pass4035$.each(this.map, function(k, v) {4036v = v[0];4037var token = v.length > 1 ? v.substring(1, 2) : v;40384039tpl = tpl.replace('{'+token+'}', '<select class="'+k+'"></select>');4040});40414042return tpl;4043},40444045/*4046Initialize combos that presents in template4047*/4048initCombos: function() {4049for (var k in this.map) {4050var $c = this.$widget.find('.'+k);4051// set properties like this.$day, this.$month etc.4052this['$'+k] = $c.length ? $c : null;4053// fill with items4054this.fillCombo(k);4055}4056},40574058/*4059Fill combo with items4060*/4061fillCombo: function(k) {4062var $combo = this['$'+k];4063if (!$combo) {4064return;4065}40664067// define method name to fill items, e.g `fillDays`4068var f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1);4069var items = this[f]();4070var value = $combo.val();40714072$combo.empty();4073for(var i=0; i<items.length; i++) {4074$combo.append('<option value="'+items[i][0]+'">'+items[i][1]+'</option>');4075}40764077$combo.val(value);4078},40794080/*4081Initialize items of combos. Handles `firstItem` option4082*/4083fillCommon: function(key) {4084var values = [],4085relTime;40864087if(this.options.firstItem === 'name') {4088//need both to support moment ver < 2 and >= 24089relTime = moment.relativeTime || moment.langData()._relativeTime;4090var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key];4091//take last entry (see momentjs lang files structure)4092header = header.split(' ').reverse()[0];4093values.push(['', header]);4094} else if(this.options.firstItem === 'empty') {4095values.push(['', '']);4096}4097return values;4098},409941004101/*4102fill day4103*/4104fillDay: function() {4105var items = this.fillCommon('d'), name, i,4106twoDigit = this.options.template.indexOf('DD') !== -1,4107daysCount = 31;41084109// detect days count (depends on month and year)4110// originally https://github.com/vitalets/combodate/pull/74111if (this.options.smartDays && this.$month && this.$year) {4112var month = parseInt(this.$month.val(), 10);4113var year = parseInt(this.$year.val(), 10);41144115if (!isNaN(month) && !isNaN(year)) {4116daysCount = moment([year, month]).daysInMonth();4117}4118}41194120for (i = 1; i <= daysCount; i++) {4121name = twoDigit ? this.leadZero(i) : i;4122items.push([i, name]);4123}4124return items;4125},41264127/*4128fill month4129*/4130fillMonth: function() {4131var items = this.fillCommon('M'), name, i,4132longNames = this.options.template.indexOf('MMMM') !== -1,4133shortNames = this.options.template.indexOf('MMM') !== -1,4134twoDigit = this.options.template.indexOf('MM') !== -1;41354136for(i=0; i<=11; i++) {4137if(longNames) {4138//see https://github.com/timrwood/momentjs.com/pull/364139name = moment().date(1).month(i).format('MMMM');4140} else if(shortNames) {4141name = moment().date(1).month(i).format('MMM');4142} else if(twoDigit) {4143name = this.leadZero(i+1);4144} else {4145name = i+1;4146}4147items.push([i, name]);4148}4149return items;4150},41514152/*4153fill year4154*/4155fillYear: function() {4156var items = [], name, i,4157longNames = this.options.template.indexOf('YYYY') !== -1;41584159for(i=this.options.maxYear; i>=this.options.minYear; i--) {4160name = longNames ? i : (i+'').substring(2);4161items[this.options.yearDescending ? 'push' : 'unshift']([i, name]);4162}41634164items = this.fillCommon('y').concat(items);41654166return items;4167},41684169/*4170fill hour4171*/4172fillHour: function() {4173var items = this.fillCommon('h'), name, i,4174h12 = this.options.template.indexOf('h') !== -1,4175h24 = this.options.template.indexOf('H') !== -1,4176twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,4177min = h12 ? 1 : 0,4178max = h12 ? 12 : 23;41794180for(i=min; i<=max; i++) {4181name = twoDigit ? this.leadZero(i) : i;4182items.push([i, name]);4183}4184return items;4185},41864187/*4188fill minute4189*/4190fillMinute: function() {4191var items = this.fillCommon('m'), name, i,4192twoDigit = this.options.template.indexOf('mm') !== -1;41934194for(i=0; i<=59; i+= this.options.minuteStep) {4195name = twoDigit ? this.leadZero(i) : i;4196items.push([i, name]);4197}4198return items;4199},42004201/*4202fill second4203*/4204fillSecond: function() {4205var items = this.fillCommon('s'), name, i,4206twoDigit = this.options.template.indexOf('ss') !== -1;42074208for(i=0; i<=59; i+= this.options.secondStep) {4209name = twoDigit ? this.leadZero(i) : i;4210items.push([i, name]);4211}4212return items;4213},42144215/*4216fill ampm4217*/4218fillAmpm: function() {4219var ampmL = this.options.template.indexOf('a') !== -1,4220ampmU = this.options.template.indexOf('A') !== -1,4221items = [4222['am', ampmL ? 'am' : 'AM'],4223['pm', ampmL ? 'pm' : 'PM']4224];4225return items;4226},42274228/*4229Returns current date value from combos.4230If format not specified - `options.format` used.4231If format = `null` - Moment object returned.4232*/4233getValue: function(format) {4234var dt, values = {},4235that = this,4236notSelected = false;42374238//getting selected values4239$.each(this.map, function(k, v) {4240if(k === 'ampm') {4241return;4242}4243var def = k === 'day' ? 1 : 0;42444245values[k] = that['$'+k] ? parseInt(that['$'+k].val(), 10) : def;42464247if(isNaN(values[k])) {4248notSelected = true;4249return false;4250}4251});42524253//if at least one visible combo not selected - return empty string4254if(notSelected) {4255return '';4256}42574258//convert hours 12h --> 24h4259if(this.$ampm) {4260//12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)4261if(values.hour === 12) {4262values.hour = this.$ampm.val() === 'am' ? 0 : 12;4263} else {4264values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;4265}4266}42674268dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);42694270//highlight invalid date4271this.highlight(dt);42724273format = format === undefined ? this.options.format : format;4274if(format === null) {4275return dt.isValid() ? dt : null;4276} else {4277return dt.isValid() ? dt.format(format) : '';4278}4279},42804281setValue: function(value) {4282if(!value) {4283return;4284}42854286var dt = typeof value === 'string' ? moment(value, this.options.format) : moment(value),4287that = this,4288values = {};42894290//function to find nearest value in select options4291function getNearest($select, value) {4292var delta = {};4293$select.children('option').each(function(i, opt){4294var optValue = $(opt).attr('value'),4295distance;42964297if(optValue === '') return;4298distance = Math.abs(optValue - value);4299if(typeof delta.distance === 'undefined' || distance < delta.distance) {4300delta = {value: optValue, distance: distance};4301}4302});4303return delta.value;4304}43054306if(dt.isValid()) {4307//read values from date object4308$.each(this.map, function(k, v) {4309if(k === 'ampm') {4310return;4311}4312values[k] = dt[v[1]]();4313});43144315if(this.$ampm) {4316//12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)4317if(values.hour >= 12) {4318values.ampm = 'pm';4319if(values.hour > 12) {4320values.hour -= 12;4321}4322} else {4323values.ampm = 'am';4324if(values.hour === 0) {4325values.hour = 12;4326}4327}4328}43294330$.each(values, function(k, v) {4331//call val() for each existing combo, e.g. this.$hour.val()4332if(that['$'+k]) {43334334if(k === 'minute' && that.options.minuteStep > 1 && that.options.roundTime) {4335v = getNearest(that['$'+k], v);4336}43374338if(k === 'second' && that.options.secondStep > 1 && that.options.roundTime) {4339v = getNearest(that['$'+k], v);4340}43414342that['$'+k].val(v);4343}4344});43454346// update days count4347if (this.options.smartDays) {4348this.fillCombo('day');4349}43504351this.$element.val(dt.format(this.options.format)).change();4352}4353},43544355/*4356highlight combos if date is invalid4357*/4358highlight: function(dt) {4359if(!dt.isValid()) {4360if(this.options.errorClass) {4361this.$widget.addClass(this.options.errorClass);4362} else {4363//store original border color4364if(!this.borderColor) {4365this.borderColor = this.$widget.find('select').css('border-color');4366}4367this.$widget.find('select').css('border-color', 'red');4368}4369} else {4370if(this.options.errorClass) {4371this.$widget.removeClass(this.options.errorClass);4372} else {4373this.$widget.find('select').css('border-color', this.borderColor);4374}4375}4376},43774378leadZero: function(v) {4379return v <= 9 ? '0' + v : v;4380},43814382destroy: function() {4383this.$widget.remove();4384this.$element.removeData('combodate').show();4385}43864387//todo: clear method4388};43894390$.fn.combodate = function ( option ) {4391var d, args = Array.apply(null, arguments);4392args.shift();43934394//getValue returns date as string / object (not jQuery object)4395if(option === 'getValue' && this.length && (d = this.eq(0).data('combodate'))) {4396return d.getValue.apply(d, args);4397}43984399return this.each(function () {4400var $this = $(this),4401data = $this.data('combodate'),4402options = typeof option == 'object' && option;4403if (!data) {4404$this.data('combodate', (data = new Combodate(this, options)));4405}4406if (typeof option == 'string' && typeof data[option] == 'function') {4407data[option].apply(data, args);4408}4409});4410};44114412$.fn.combodate.defaults = {4413//in this format value stored in original input4414format: 'DD-MM-YYYY HH:mm',4415//in this format items in dropdowns are displayed4416template: 'D / MMM / YYYY H : mm',4417//initial value, can be `new Date()`4418value: null,4419minYear: 1970,4420maxYear: 2015,4421yearDescending: true,4422minuteStep: 5,4423secondStep: 1,4424firstItem: 'empty', //'name', 'empty', 'none'4425errorClass: null,4426roundTime: true, // whether to round minutes and seconds if step > 14427smartDays: false // whether days in combo depend on selected month: 31, 30, 284428};44294430}(window.jQuery));4431/**4432Combodate input - dropdown date and time picker.4433Based on [combodate](http://vitalets.github.com/combodate) plugin (included). To use it you should manually include [momentjs](http://momentjs.com).44344435<script src="js/moment.min.js"></script>44364437Allows to input:44384439* only date4440* only time4441* both date and time44424443Please note, that format is taken from momentjs and **not compatible** with bootstrap-datepicker / jquery UI datepicker.4444Internally value stored as `momentjs` object.44454446@class combodate4447@extends abstractinput4448@final4449@since 1.4.04450@example4451<a href="#" id="dob" data-type="combodate" data-pk="1" data-url="/post" data-value="1984-05-15" data-title="Select date"></a>4452<script>4453$(function(){4454$('#dob').editable({4455format: 'YYYY-MM-DD',4456viewformat: 'DD.MM.YYYY',4457template: 'D / MMMM / YYYY',4458combodate: {4459minYear: 2000,4460maxYear: 2015,4461minuteStep: 14462}4463}4464});4465});4466</script>4467**/44684469/*global moment*/44704471(function ($) {4472"use strict";44734474var Constructor = function (options) {4475this.init('combodate', options, Constructor.defaults);44764477//by default viewformat equals to format4478if(!this.options.viewformat) {4479this.options.viewformat = this.options.format;4480}44814482//try parse combodate config defined as json string in data-combodate4483options.combodate = $.fn.editableutils.tryParseJson(options.combodate, true);44844485//overriding combodate config (as by default jQuery extend() is not recursive)4486this.options.combodate = $.extend({}, Constructor.defaults.combodate, options.combodate, {4487format: this.options.format,4488template: this.options.template4489});4490};44914492$.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);44934494$.extend(Constructor.prototype, {4495render: function () {4496this.$input.combodate(this.options.combodate);44974498if($.fn.editableform.engine === 'bs3') {4499this.$input.siblings().find('select').addClass('form-control');4500}45014502if(this.options.inputclass) {4503this.$input.siblings().find('select').addClass(this.options.inputclass);4504}4505//"clear" link4506/*4507if(this.options.clear) {4508this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){4509e.preventDefault();4510e.stopPropagation();4511this.clear();4512}, this));45134514this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));4515}4516*/4517},45184519value2html: function(value, element) {4520var text = value ? value.format(this.options.viewformat) : '';4521//$(element).text(text);4522Constructor.superclass.value2html.call(this, text, element);4523},45244525html2value: function(html) {4526return html ? moment(html, this.options.viewformat) : null;4527},45284529value2str: function(value) {4530return value ? value.format(this.options.format) : '';4531},45324533str2value: function(str) {4534return str ? moment(str, this.options.format) : null;4535},45364537value2submit: function(value) {4538return this.value2str(value);4539},45404541value2input: function(value) {4542this.$input.combodate('setValue', value);4543},45444545input2value: function() {4546return this.$input.combodate('getValue', null);4547},45484549activate: function() {4550this.$input.siblings('.combodate').find('select').eq(0).focus();4551},45524553/*4554clear: function() {4555this.$input.data('datepicker').date = null;4556this.$input.find('.active').removeClass('active');4557},4558*/45594560autosubmit: function() {45614562}45634564});45654566Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {4567/**4568@property tpl4569@default <input type="text">4570**/4571tpl:'<input type="text">',4572/**4573@property inputclass4574@default null4575**/4576inputclass: null,4577/**4578Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>4579See list of tokens in [momentjs docs](http://momentjs.com/docs/#/parsing/string-format)45804581@property format4582@type string4583@default YYYY-MM-DD4584**/4585format:'YYYY-MM-DD',4586/**4587Format used for displaying date. Also applied when converting date from element's text on init.4588If not specified equals to `format`.45894590@property viewformat4591@type string4592@default null4593**/4594viewformat: null,4595/**4596Template used for displaying dropdowns.45974598@property template4599@type string4600@default D / MMM / YYYY4601**/4602template: 'D / MMM / YYYY',4603/**4604Configuration of combodate.4605Full list of options: http://vitalets.github.com/combodate/#docs46064607@property combodate4608@type object4609@default null4610**/4611combodate: null46124613/*4614(not implemented yet)4615Text shown as clear date button.4616If <code>false</code> clear button will not be rendered.46174618@property clear4619@type boolean|string4620@default 'x clear'4621*/4622//clear: '× clear'4623});46244625$.fn.editabletypes.combodate = Constructor;46264627}(window.jQuery));46284629/*4630Editableform based on Twitter Bootstrap 34631*/4632(function ($) {4633"use strict";46344635//store parent methods4636var pInitInput = $.fn.editableform.Constructor.prototype.initInput;46374638$.extend($.fn.editableform.Constructor.prototype, {4639initTemplate: function() {4640this.$form = $($.fn.editableform.template);4641this.$form.find('.control-group').addClass('form-group');4642this.$form.find('.editable-error-block').addClass('help-block');4643},4644initInput: function() {4645pInitInput.apply(this);46464647//for bs3 set default class `input-sm` to standard inputs4648var emptyInputClass = this.input.options.inputclass === null || this.input.options.inputclass === false;4649var defaultClass = 'input-sm';46504651//bs3 add `form-control` class to standard inputs4652var stdtypes = 'text,select,textarea,password,email,url,tel,number,range,time,typeaheadjs'.split(',');4653if(~$.inArray(this.input.type, stdtypes)) {4654this.input.$input.addClass('form-control');4655if(emptyInputClass) {4656this.input.options.inputclass = defaultClass;4657this.input.$input.addClass(defaultClass);4658}4659}46604661//apply bs3 size class also to buttons (to fit size of control)4662var $btn = this.$form.find('.editable-buttons');4663var classes = emptyInputClass ? [defaultClass] : this.input.options.inputclass.split(' ');4664for(var i=0; i<classes.length; i++) {4665// `btn-sm` is default now4666/*4667if(classes[i].toLowerCase() === 'input-sm') {4668$btn.find('button').addClass('btn-sm');4669}4670*/4671if(classes[i].toLowerCase() === 'input-lg') {4672$btn.find('button').removeClass('btn-sm').addClass('btn-lg');4673}4674}4675}4676});46774678//buttons4679$.fn.editableform.buttons =4680'<button type="submit" class="btn btn-primary btn-sm editable-submit">'+4681'<i class="glyphicon glyphicon-ok"></i>'+4682'</button>'+4683'<button type="button" class="btn btn-default btn-sm editable-cancel">'+4684'<i class="glyphicon glyphicon-remove"></i>'+4685'</button>';46864687//error classes4688$.fn.editableform.errorGroupClass = 'has-error';4689$.fn.editableform.errorBlockClass = null;4690//engine4691$.fn.editableform.engine = 'bs3';4692}(window.jQuery));4693/**4694* Editable Popover3 (for Bootstrap 3)4695* ---------------------4696* requires bootstrap-popover.js4697*/4698(function ($) {4699"use strict";47004701//extend methods4702$.extend($.fn.editableContainer.Popup.prototype, {4703containerName: 'popover',4704containerDataName: 'bs.popover',4705innerCss: '.popover-content',4706defaults: $.fn.popover.Constructor.DEFAULTS,47074708initContainer: function(){4709$.extend(this.containerOptions, {4710trigger: 'manual',4711selector: false,4712content: ' ',4713template: this.defaults.template4714});47154716//as template property is used in inputs, hide it from popover4717var t;4718if(this.$element.data('template')) {4719t = this.$element.data('template');4720this.$element.removeData('template');4721}47224723this.call(this.containerOptions);47244725if(t) {4726//restore data('template')4727this.$element.data('template', t);4728}4729},47304731/* show */4732innerShow: function () {4733this.call('show');4734},47354736/* hide */4737innerHide: function () {4738this.call('hide');4739},47404741/* destroy */4742innerDestroy: function() {4743this.call('destroy');4744},47454746setContainerOption: function(key, value) {4747this.container().options[key] = value;4748},47494750/**4751* move popover to new position. This function mainly copied from bootstrap-popover.4752*/4753/*jshint laxcomma: true, eqeqeq: false*/4754setPosition: function () {47554756(function() {4757/*4758var $tip = this.tip()4759, inside4760, pos4761, actualWidth4762, actualHeight4763, placement4764, tp4765, tpt4766, tpb4767, tpl4768, tpr;47694770placement = typeof this.options.placement === 'function' ?4771this.options.placement.call(this, $tip[0], this.$element[0]) :4772this.options.placement;47734774inside = /in/.test(placement);47754776$tip4777// .detach()4778//vitalets: remove any placement class because otherwise they dont influence on re-positioning of visible popover4779.removeClass('top right bottom left')4780.css({ top: 0, left: 0, display: 'block' });4781// .insertAfter(this.$element);47824783pos = this.getPosition(inside);47844785actualWidth = $tip[0].offsetWidth;4786actualHeight = $tip[0].offsetHeight;47874788placement = inside ? placement.split(' ')[1] : placement;47894790tpb = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2};4791tpt = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2};4792tpl = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth};4793tpr = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width};47944795switch (placement) {4796case 'bottom':4797if ((tpb.top + actualHeight) > ($(window).scrollTop() + $(window).height())) {4798if (tpt.top > $(window).scrollTop()) {4799placement = 'top';4800} else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {4801placement = 'right';4802} else if (tpl.left > $(window).scrollLeft()) {4803placement = 'left';4804} else {4805placement = 'right';4806}4807}4808break;4809case 'top':4810if (tpt.top < $(window).scrollTop()) {4811if ((tpb.top + actualHeight) < ($(window).scrollTop() + $(window).height())) {4812placement = 'bottom';4813} else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {4814placement = 'right';4815} else if (tpl.left > $(window).scrollLeft()) {4816placement = 'left';4817} else {4818placement = 'right';4819}4820}4821break;4822case 'left':4823if (tpl.left < $(window).scrollLeft()) {4824if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {4825placement = 'right';4826} else if (tpt.top > $(window).scrollTop()) {4827placement = 'top';4828} else if (tpt.top > $(window).scrollTop()) {4829placement = 'bottom';4830} else {4831placement = 'right';4832}4833}4834break;4835case 'right':4836if ((tpr.left + actualWidth) > ($(window).scrollLeft() + $(window).width())) {4837if (tpl.left > $(window).scrollLeft()) {4838placement = 'left';4839} else if (tpt.top > $(window).scrollTop()) {4840placement = 'top';4841} else if (tpt.top > $(window).scrollTop()) {4842placement = 'bottom';4843}4844}4845break;4846}48474848switch (placement) {4849case 'bottom':4850tp = tpb;4851break;4852case 'top':4853tp = tpt;4854break;4855case 'left':4856tp = tpl;4857break;4858case 'right':4859tp = tpr;4860break;4861}48624863$tip4864.offset(tp)4865.addClass(placement)4866.addClass('in');4867*/486848694870var $tip = this.tip();48714872var placement = typeof this.options.placement == 'function' ?4873this.options.placement.call(this, $tip[0], this.$element[0]) :4874this.options.placement;48754876var autoToken = /\s?auto?\s?/i;4877var autoPlace = autoToken.test(placement);4878if (autoPlace) {4879placement = placement.replace(autoToken, '') || 'top';4880}488148824883var pos = this.getPosition();4884var actualWidth = $tip[0].offsetWidth;4885var actualHeight = $tip[0].offsetHeight;48864887if (autoPlace) {4888var $parent = this.$element.parent();48894890var orgPlacement = placement;4891var docScroll = document.documentElement.scrollTop || document.body.scrollTop;4892var parentWidth = this.options.container == 'body' ? window.innerWidth : $parent.outerWidth();4893var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight();4894var parentLeft = this.options.container == 'body' ? 0 : $parent.offset().left;48954896placement = placement == 'bottom' && pos.top + pos.height + actualHeight - docScroll > parentHeight ? 'top' :4897placement == 'top' && pos.top - docScroll - actualHeight < 0 ? 'bottom' :4898placement == 'right' && pos.right + actualWidth > parentWidth ? 'left' :4899placement == 'left' && pos.left - actualWidth < parentLeft ? 'right' :4900placement;49014902$tip4903.removeClass(orgPlacement)4904.addClass(placement);4905}490649074908var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight);49094910this.applyPlacement(calculatedOffset, placement);491149124913}).call(this.container());4914/*jshint laxcomma: false, eqeqeq: true*/4915}4916});49174918}(window.jQuery));49194920/* =========================================================4921* bootstrap-datepicker.js4922* http://www.eyecon.ro/bootstrap-datepicker4923* =========================================================4924* Copyright 2012 Stefan Petre4925* Improvements by Andrew Rowls4926*4927* Licensed under the Apache License, Version 2.0 (the "License");4928* you may not use this file except in compliance with the License.4929* You may obtain a copy of the License at4930*4931* http://www.apache.org/licenses/LICENSE-2.04932*4933* Unless required by applicable law or agreed to in writing, software4934* distributed under the License is distributed on an "AS IS" BASIS,4935* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.4936* See the License for the specific language governing permissions and4937* limitations under the License.4938* ========================================================= */49394940(function( $ ) {49414942function UTCDate(){4943return new Date(Date.UTC.apply(Date, arguments));4944}4945function UTCToday(){4946var today = new Date();4947return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate());4948}49494950// Picker object49514952var Datepicker = function(element, options) {4953var that = this;49544955this._process_options(options);49564957this.element = $(element);4958this.isInline = false;4959this.isInput = this.element.is('input');4960this.component = this.element.is('.date') ? this.element.find('.add-on, .btn') : false;4961this.hasInput = this.component && this.element.find('input').length;4962if(this.component && this.component.length === 0)4963this.component = false;49644965this.picker = $(DPGlobal.template);4966this._buildEvents();4967this._attachEvents();49684969if(this.isInline) {4970this.picker.addClass('datepicker-inline').appendTo(this.element);4971} else {4972this.picker.addClass('datepicker-dropdown dropdown-menu');4973}49744975if (this.o.rtl){4976this.picker.addClass('datepicker-rtl');4977this.picker.find('.prev i, .next i')4978.toggleClass('icon-arrow-left icon-arrow-right');4979}498049814982this.viewMode = this.o.startView;49834984if (this.o.calendarWeeks)4985this.picker.find('tfoot th.today')4986.attr('colspan', function(i, val){4987return parseInt(val) + 1;4988});49894990this._allow_update = false;49914992this.setStartDate(this.o.startDate);4993this.setEndDate(this.o.endDate);4994this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled);49954996this.fillDow();4997this.fillMonths();49984999this._allow_update = true;50005001this.update();5002this.showMode();50035004if(this.isInline) {5005this.show();5006}5007};50085009Datepicker.prototype = {5010constructor: Datepicker,50115012_process_options: function(opts){5013// Store raw options for reference5014this._o = $.extend({}, this._o, opts);5015// Processed options5016var o = this.o = $.extend({}, this._o);50175018// Check if "de-DE" style date is available, if not language should5019// fallback to 2 letter code eg "de"5020var lang = o.language;5021if (!dates[lang]) {5022lang = lang.split('-')[0];5023if (!dates[lang])5024lang = defaults.language;5025}5026o.language = lang;50275028switch(o.startView){5029case 2:5030case 'decade':5031o.startView = 2;5032break;5033case 1:5034case 'year':5035o.startView = 1;5036break;5037default:5038o.startView = 0;5039}50405041switch (o.minViewMode) {5042case 1:5043case 'months':5044o.minViewMode = 1;5045break;5046case 2:5047case 'years':5048o.minViewMode = 2;5049break;5050default:5051o.minViewMode = 0;5052}50535054o.startView = Math.max(o.startView, o.minViewMode);50555056o.weekStart %= 7;5057o.weekEnd = ((o.weekStart + 6) % 7);50585059var format = DPGlobal.parseFormat(o.format)5060if (o.startDate !== -Infinity) {5061o.startDate = DPGlobal.parseDate(o.startDate, format, o.language);5062}5063if (o.endDate !== Infinity) {5064o.endDate = DPGlobal.parseDate(o.endDate, format, o.language);5065}50665067o.daysOfWeekDisabled = o.daysOfWeekDisabled||[];5068if (!$.isArray(o.daysOfWeekDisabled))5069o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/);5070o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function (d) {5071return parseInt(d, 10);5072});5073},5074_events: [],5075_secondaryEvents: [],5076_applyEvents: function(evs){5077for (var i=0, el, ev; i<evs.length; i++){5078el = evs[i][0];5079ev = evs[i][1];5080el.on(ev);5081}5082},5083_unapplyEvents: function(evs){5084for (var i=0, el, ev; i<evs.length; i++){5085el = evs[i][0];5086ev = evs[i][1];5087el.off(ev);5088}5089},5090_buildEvents: function(){5091if (this.isInput) { // single input5092this._events = [5093[this.element, {5094focus: $.proxy(this.show, this),5095keyup: $.proxy(this.update, this),5096keydown: $.proxy(this.keydown, this)5097}]5098];5099}5100else if (this.component && this.hasInput){ // component: input + button5101this._events = [5102// For components that are not readonly, allow keyboard nav5103[this.element.find('input'), {5104focus: $.proxy(this.show, this),5105keyup: $.proxy(this.update, this),5106keydown: $.proxy(this.keydown, this)5107}],5108[this.component, {5109click: $.proxy(this.show, this)5110}]5111];5112}5113else if (this.element.is('div')) { // inline datepicker5114this.isInline = true;5115}5116else {5117this._events = [5118[this.element, {5119click: $.proxy(this.show, this)5120}]5121];5122}51235124this._secondaryEvents = [5125[this.picker, {5126click: $.proxy(this.click, this)5127}],5128[$(window), {5129resize: $.proxy(this.place, this)5130}],5131[$(document), {5132mousedown: $.proxy(function (e) {5133// Clicked outside the datepicker, hide it5134if (!(5135this.element.is(e.target) ||5136this.element.find(e.target).size() ||5137this.picker.is(e.target) ||5138this.picker.find(e.target).size()5139)) {5140this.hide();5141}5142}, this)5143}]5144];5145},5146_attachEvents: function(){5147this._detachEvents();5148this._applyEvents(this._events);5149},5150_detachEvents: function(){5151this._unapplyEvents(this._events);5152},5153_attachSecondaryEvents: function(){5154this._detachSecondaryEvents();5155this._applyEvents(this._secondaryEvents);5156},5157_detachSecondaryEvents: function(){5158this._unapplyEvents(this._secondaryEvents);5159},5160_trigger: function(event, altdate){5161var date = altdate || this.date,5162local_date = new Date(date.getTime() + (date.getTimezoneOffset()*60000));51635164this.element.trigger({5165type: event,5166date: local_date,5167format: $.proxy(function(altformat){5168var format = altformat || this.o.format;5169return DPGlobal.formatDate(date, format, this.o.language);5170}, this)5171});5172},51735174show: function(e) {5175if (!this.isInline)5176this.picker.appendTo('body');5177this.picker.show();5178this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();5179this.place();5180this._attachSecondaryEvents();5181if (e) {5182e.preventDefault();5183}5184this._trigger('show');5185},51865187hide: function(e){5188if(this.isInline) return;5189if (!this.picker.is(':visible')) return;5190this.picker.hide().detach();5191this._detachSecondaryEvents();5192this.viewMode = this.o.startView;5193this.showMode();51945195if (5196this.o.forceParse &&5197(5198this.isInput && this.element.val() ||5199this.hasInput && this.element.find('input').val()5200)5201)5202this.setValue();5203this._trigger('hide');5204},52055206remove: function() {5207this.hide();5208this._detachEvents();5209this._detachSecondaryEvents();5210this.picker.remove();5211delete this.element.data().datepicker;5212if (!this.isInput) {5213delete this.element.data().date;5214}5215},52165217getDate: function() {5218var d = this.getUTCDate();5219return new Date(d.getTime() + (d.getTimezoneOffset()*60000));5220},52215222getUTCDate: function() {5223return this.date;5224},52255226setDate: function(d) {5227this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000)));5228},52295230setUTCDate: function(d) {5231this.date = d;5232this.setValue();5233},52345235setValue: function() {5236var formatted = this.getFormattedDate();5237if (!this.isInput) {5238if (this.component){5239this.element.find('input').val(formatted);5240}5241} else {5242this.element.val(formatted);5243}5244},52455246getFormattedDate: function(format) {5247if (format === undefined)5248format = this.o.format;5249return DPGlobal.formatDate(this.date, format, this.o.language);5250},52515252setStartDate: function(startDate){5253this._process_options({startDate: startDate});5254this.update();5255this.updateNavArrows();5256},52575258setEndDate: function(endDate){5259this._process_options({endDate: endDate});5260this.update();5261this.updateNavArrows();5262},52635264setDaysOfWeekDisabled: function(daysOfWeekDisabled){5265this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});5266this.update();5267this.updateNavArrows();5268},52695270place: function(){5271if(this.isInline) return;5272var zIndex = parseInt(this.element.parents().filter(function() {5273return $(this).css('z-index') != 'auto';5274}).first().css('z-index'))+10;5275var offset = this.component ? this.component.parent().offset() : this.element.offset();5276var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(true);5277this.picker.css({5278top: offset.top + height,5279left: offset.left,5280zIndex: zIndex5281});5282},52835284_allow_update: true,5285update: function(){5286if (!this._allow_update) return;52875288var date, fromArgs = false;5289if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {5290date = arguments[0];5291fromArgs = true;5292} else {5293date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val();5294delete this.element.data().date;5295}52965297this.date = DPGlobal.parseDate(date, this.o.format, this.o.language);52985299if(fromArgs) this.setValue();53005301if (this.date < this.o.startDate) {5302this.viewDate = new Date(this.o.startDate);5303} else if (this.date > this.o.endDate) {5304this.viewDate = new Date(this.o.endDate);5305} else {5306this.viewDate = new Date(this.date);5307}5308this.fill();5309},53105311fillDow: function(){5312var dowCnt = this.o.weekStart,5313html = '<tr>';5314if(this.o.calendarWeeks){5315var cell = '<th class="cw"> </th>';5316html += cell;5317this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);5318}5319while (dowCnt < this.o.weekStart + 7) {5320html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>';5321}5322html += '</tr>';5323this.picker.find('.datepicker-days thead').append(html);5324},53255326fillMonths: function(){5327var html = '',5328i = 0;5329while (i < 12) {5330html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>';5331}5332this.picker.find('.datepicker-months td').html(html);5333},53345335setRange: function(range){5336if (!range || !range.length)5337delete this.range;5338else5339this.range = $.map(range, function(d){ return d.valueOf(); });5340this.fill();5341},53425343getClassNames: function(date){5344var cls = [],5345year = this.viewDate.getUTCFullYear(),5346month = this.viewDate.getUTCMonth(),5347currentDate = this.date.valueOf(),5348today = new Date();5349if (date.getUTCFullYear() < year || (date.getUTCFullYear() == year && date.getUTCMonth() < month)) {5350cls.push('old');5351} else if (date.getUTCFullYear() > year || (date.getUTCFullYear() == year && date.getUTCMonth() > month)) {5352cls.push('new');5353}5354// Compare internal UTC date with local today, not UTC today5355if (this.o.todayHighlight &&5356date.getUTCFullYear() == today.getFullYear() &&5357date.getUTCMonth() == today.getMonth() &&5358date.getUTCDate() == today.getDate()) {5359cls.push('today');5360}5361if (currentDate && date.valueOf() == currentDate) {5362cls.push('active');5363}5364if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate ||5365$.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1) {5366cls.push('disabled');5367}5368if (this.range){5369if (date > this.range[0] && date < this.range[this.range.length-1]){5370cls.push('range');5371}5372if ($.inArray(date.valueOf(), this.range) != -1){5373cls.push('selected');5374}5375}5376return cls;5377},53785379fill: function() {5380var d = new Date(this.viewDate),5381year = d.getUTCFullYear(),5382month = d.getUTCMonth(),5383startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,5384startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,5385endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,5386endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,5387currentDate = this.date && this.date.valueOf(),5388tooltip;5389this.picker.find('.datepicker-days thead th.datepicker-switch')5390.text(dates[this.o.language].months[month]+' '+year);5391this.picker.find('tfoot th.today')5392.text(dates[this.o.language].today)5393.toggle(this.o.todayBtn !== false);5394this.picker.find('tfoot th.clear')5395.text(dates[this.o.language].clear)5396.toggle(this.o.clearBtn !== false);5397this.updateNavArrows();5398this.fillMonths();5399var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),5400day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());5401prevMonth.setUTCDate(day);5402prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7);5403var nextMonth = new Date(prevMonth);5404nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);5405nextMonth = nextMonth.valueOf();5406var html = [];5407var clsName;5408while(prevMonth.valueOf() < nextMonth) {5409if (prevMonth.getUTCDay() == this.o.weekStart) {5410html.push('<tr>');5411if(this.o.calendarWeeks){5412// ISO 8601: First week contains first thursday.5413// ISO also states week starts on Monday, but we can be more abstract here.5414var5415// Start of current week: based on weekstart/current date5416ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),5417// Thursday of this week5418th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),5419// First Thursday of year, year from thursday5420yth = new Date(+(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5),5421// Calendar week: ms between thursdays, div ms per day, div 7 days5422calWeek = (th - yth) / 864e5 / 7 + 1;5423html.push('<td class="cw">'+ calWeek +'</td>');54245425}5426}5427clsName = this.getClassNames(prevMonth);5428clsName.push('day');54295430var before = this.o.beforeShowDay(prevMonth);5431if (before === undefined)5432before = {};5433else if (typeof(before) === 'boolean')5434before = {enabled: before};5435else if (typeof(before) === 'string')5436before = {classes: before};5437if (before.enabled === false)5438clsName.push('disabled');5439if (before.classes)5440clsName = clsName.concat(before.classes.split(/\s+/));5441if (before.tooltip)5442tooltip = before.tooltip;54435444clsName = $.unique(clsName);5445html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>');5446if (prevMonth.getUTCDay() == this.o.weekEnd) {5447html.push('</tr>');5448}5449prevMonth.setUTCDate(prevMonth.getUTCDate()+1);5450}5451this.picker.find('.datepicker-days tbody').empty().append(html.join(''));5452var currentYear = this.date && this.date.getUTCFullYear();54535454var months = this.picker.find('.datepicker-months')5455.find('th:eq(1)')5456.text(year)5457.end()5458.find('span').removeClass('active');5459if (currentYear && currentYear == year) {5460months.eq(this.date.getUTCMonth()).addClass('active');5461}5462if (year < startYear || year > endYear) {5463months.addClass('disabled');5464}5465if (year == startYear) {5466months.slice(0, startMonth).addClass('disabled');5467}5468if (year == endYear) {5469months.slice(endMonth+1).addClass('disabled');5470}54715472html = '';5473year = parseInt(year/10, 10) * 10;5474var yearCont = this.picker.find('.datepicker-years')5475.find('th:eq(1)')5476.text(year + '-' + (year + 9))5477.end()5478.find('td');5479year -= 1;5480for (var i = -1; i < 11; i++) {5481html += '<span class="year'+(i == -1 ? ' old' : i == 10 ? ' new' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';5482year += 1;5483}5484yearCont.html(html);5485},54865487updateNavArrows: function() {5488if (!this._allow_update) return;54895490var d = new Date(this.viewDate),5491year = d.getUTCFullYear(),5492month = d.getUTCMonth();5493switch (this.viewMode) {5494case 0:5495if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()) {5496this.picker.find('.prev').css({visibility: 'hidden'});5497} else {5498this.picker.find('.prev').css({visibility: 'visible'});5499}5500if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()) {5501this.picker.find('.next').css({visibility: 'hidden'});5502} else {5503this.picker.find('.next').css({visibility: 'visible'});5504}5505break;5506case 1:5507case 2:5508if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()) {5509this.picker.find('.prev').css({visibility: 'hidden'});5510} else {5511this.picker.find('.prev').css({visibility: 'visible'});5512}5513if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()) {5514this.picker.find('.next').css({visibility: 'hidden'});5515} else {5516this.picker.find('.next').css({visibility: 'visible'});5517}5518break;5519}5520},55215522click: function(e) {5523e.preventDefault();5524var target = $(e.target).closest('span, td, th');5525if (target.length == 1) {5526switch(target[0].nodeName.toLowerCase()) {5527case 'th':5528switch(target[0].className) {5529case 'datepicker-switch':5530this.showMode(1);5531break;5532case 'prev':5533case 'next':5534var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);5535switch(this.viewMode){5536case 0:5537this.viewDate = this.moveMonth(this.viewDate, dir);5538break;5539case 1:5540case 2:5541this.viewDate = this.moveYear(this.viewDate, dir);5542break;5543}5544this.fill();5545break;5546case 'today':5547var date = new Date();5548date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);55495550this.showMode(-2);5551var which = this.o.todayBtn == 'linked' ? null : 'view';5552this._setDate(date, which);5553break;5554case 'clear':5555var element;5556if (this.isInput)5557element = this.element;5558else if (this.component)5559element = this.element.find('input');5560if (element)5561element.val("").change();5562this._trigger('changeDate');5563this.update();5564if (this.o.autoclose)5565this.hide();5566break;5567}5568break;5569case 'span':5570if (!target.is('.disabled')) {5571this.viewDate.setUTCDate(1);5572if (target.is('.month')) {5573var day = 1;5574var month = target.parent().find('span').index(target);5575var year = this.viewDate.getUTCFullYear();5576this.viewDate.setUTCMonth(month);5577this._trigger('changeMonth', this.viewDate);5578if (this.o.minViewMode === 1) {5579this._setDate(UTCDate(year, month, day,0,0,0,0));5580}5581} else {5582var year = parseInt(target.text(), 10)||0;5583var day = 1;5584var month = 0;5585this.viewDate.setUTCFullYear(year);5586this._trigger('changeYear', this.viewDate);5587if (this.o.minViewMode === 2) {5588this._setDate(UTCDate(year, month, day,0,0,0,0));5589}5590}5591this.showMode(-1);5592this.fill();5593}5594break;5595case 'td':5596if (target.is('.day') && !target.is('.disabled')){5597var day = parseInt(target.text(), 10)||1;5598var year = this.viewDate.getUTCFullYear(),5599month = this.viewDate.getUTCMonth();5600if (target.is('.old')) {5601if (month === 0) {5602month = 11;5603year -= 1;5604} else {5605month -= 1;5606}5607} else if (target.is('.new')) {5608if (month == 11) {5609month = 0;5610year += 1;5611} else {5612month += 1;5613}5614}5615this._setDate(UTCDate(year, month, day,0,0,0,0));5616}5617break;5618}5619}5620},56215622_setDate: function(date, which){5623if (!which || which == 'date')5624this.date = new Date(date);5625if (!which || which == 'view')5626this.viewDate = new Date(date);5627this.fill();5628this.setValue();5629this._trigger('changeDate');5630var element;5631if (this.isInput) {5632element = this.element;5633} else if (this.component){5634element = this.element.find('input');5635}5636if (element) {5637element.change();5638if (this.o.autoclose && (!which || which == 'date')) {5639this.hide();5640}5641}5642},56435644moveMonth: function(date, dir){5645if (!dir) return date;5646var new_date = new Date(date.valueOf()),5647day = new_date.getUTCDate(),5648month = new_date.getUTCMonth(),5649mag = Math.abs(dir),5650new_month, test;5651dir = dir > 0 ? 1 : -1;5652if (mag == 1){5653test = dir == -15654// If going back one month, make sure month is not current month5655// (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02)5656? function(){ return new_date.getUTCMonth() == month; }5657// If going forward one month, make sure month is as expected5658// (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02)5659: function(){ return new_date.getUTCMonth() != new_month; };5660new_month = month + dir;5661new_date.setUTCMonth(new_month);5662// Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-115663if (new_month < 0 || new_month > 11)5664new_month = (new_month + 12) % 12;5665} else {5666// For magnitudes >1, move one month at a time...5667for (var i=0; i<mag; i++)5668// ...which might decrease the day (eg, Jan 31 to Feb 28, etc)...5669new_date = this.moveMonth(new_date, dir);5670// ...then reset the day, keeping it in the new month5671new_month = new_date.getUTCMonth();5672new_date.setUTCDate(day);5673test = function(){ return new_month != new_date.getUTCMonth(); };5674}5675// Common date-resetting loop -- if date is beyond end of month, make it5676// end of month5677while (test()){5678new_date.setUTCDate(--day);5679new_date.setUTCMonth(new_month);5680}5681return new_date;5682},56835684moveYear: function(date, dir){5685return this.moveMonth(date, dir*12);5686},56875688dateWithinRange: function(date){5689return date >= this.o.startDate && date <= this.o.endDate;5690},56915692keydown: function(e){5693if (this.picker.is(':not(:visible)')){5694if (e.keyCode == 27) // allow escape to hide and re-show picker5695this.show();5696return;5697}5698var dateChanged = false,5699dir, day, month,5700newDate, newViewDate;5701switch(e.keyCode){5702case 27: // escape5703this.hide();5704e.preventDefault();5705break;5706case 37: // left5707case 39: // right5708if (!this.o.keyboardNavigation) break;5709dir = e.keyCode == 37 ? -1 : 1;5710if (e.ctrlKey){5711newDate = this.moveYear(this.date, dir);5712newViewDate = this.moveYear(this.viewDate, dir);5713} else if (e.shiftKey){5714newDate = this.moveMonth(this.date, dir);5715newViewDate = this.moveMonth(this.viewDate, dir);5716} else {5717newDate = new Date(this.date);5718newDate.setUTCDate(this.date.getUTCDate() + dir);5719newViewDate = new Date(this.viewDate);5720newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir);5721}5722if (this.dateWithinRange(newDate)){5723this.date = newDate;5724this.viewDate = newViewDate;5725this.setValue();5726this.update();5727e.preventDefault();5728dateChanged = true;5729}5730break;5731case 38: // up5732case 40: // down5733if (!this.o.keyboardNavigation) break;5734dir = e.keyCode == 38 ? -1 : 1;5735if (e.ctrlKey){5736newDate = this.moveYear(this.date, dir);5737newViewDate = this.moveYear(this.viewDate, dir);5738} else if (e.shiftKey){5739newDate = this.moveMonth(this.date, dir);5740newViewDate = this.moveMonth(this.viewDate, dir);5741} else {5742newDate = new Date(this.date);5743newDate.setUTCDate(this.date.getUTCDate() + dir * 7);5744newViewDate = new Date(this.viewDate);5745newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7);5746}5747if (this.dateWithinRange(newDate)){5748this.date = newDate;5749this.viewDate = newViewDate;5750this.setValue();5751this.update();5752e.preventDefault();5753dateChanged = true;5754}5755break;5756case 13: // enter5757this.hide();5758e.preventDefault();5759break;5760case 9: // tab5761this.hide();5762break;5763}5764if (dateChanged){5765this._trigger('changeDate');5766var element;5767if (this.isInput) {5768element = this.element;5769} else if (this.component){5770element = this.element.find('input');5771}5772if (element) {5773element.change();5774}5775}5776},57775778showMode: function(dir) {5779if (dir) {5780this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir));5781}5782/*5783vitalets: fixing bug of very special conditions:5784jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.5785Method show() does not set display css correctly and datepicker is not shown.5786Changed to .css('display', 'block') solve the problem.5787See https://github.com/vitalets/x-editable/issues/3757885789In jquery 1.7.2+ everything works fine.5790*/5791//this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();5792this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');5793this.updateNavArrows();5794}5795};57965797var DateRangePicker = function(element, options){5798this.element = $(element);5799this.inputs = $.map(options.inputs, function(i){ return i.jquery ? i[0] : i; });5800delete options.inputs;58015802$(this.inputs)5803.datepicker(options)5804.bind('changeDate', $.proxy(this.dateUpdated, this));58055806this.pickers = $.map(this.inputs, function(i){ return $(i).data('datepicker'); });5807this.updateDates();5808};5809DateRangePicker.prototype = {5810updateDates: function(){5811this.dates = $.map(this.pickers, function(i){ return i.date; });5812this.updateRanges();5813},5814updateRanges: function(){5815var range = $.map(this.dates, function(d){ return d.valueOf(); });5816$.each(this.pickers, function(i, p){5817p.setRange(range);5818});5819},5820dateUpdated: function(e){5821var dp = $(e.target).data('datepicker'),5822new_date = dp.getUTCDate(),5823i = $.inArray(e.target, this.inputs),5824l = this.inputs.length;5825if (i == -1) return;58265827if (new_date < this.dates[i]){5828// Date being moved earlier/left5829while (i>=0 && new_date < this.dates[i]){5830this.pickers[i--].setUTCDate(new_date);5831}5832}5833else if (new_date > this.dates[i]){5834// Date being moved later/right5835while (i<l && new_date > this.dates[i]){5836this.pickers[i++].setUTCDate(new_date);5837}5838}5839this.updateDates();5840},5841remove: function(){5842$.map(this.pickers, function(p){ p.remove(); });5843delete this.element.data().datepicker;5844}5845};58465847function opts_from_el(el, prefix){5848// Derive options from element data-attrs5849var data = $(el).data(),5850out = {}, inkey,5851replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'),5852prefix = new RegExp('^' + prefix.toLowerCase());5853for (var key in data)5854if (prefix.test(key)){5855inkey = key.replace(replace, function(_,a){ return a.toLowerCase(); });5856out[inkey] = data[key];5857}5858return out;5859}58605861function opts_from_locale(lang){5862// Derive options from locale plugins5863var out = {};5864// Check if "de-DE" style date is available, if not language should5865// fallback to 2 letter code eg "de"5866if (!dates[lang]) {5867lang = lang.split('-')[0]5868if (!dates[lang])5869return;5870}5871var d = dates[lang];5872$.each(locale_opts, function(i,k){5873if (k in d)5874out[k] = d[k];5875});5876return out;5877}58785879var old = $.fn.datepicker;5880var datepicker = $.fn.datepicker = function ( option ) {5881var args = Array.apply(null, arguments);5882args.shift();5883var internal_return,5884this_return;5885this.each(function () {5886var $this = $(this),5887data = $this.data('datepicker'),5888options = typeof option == 'object' && option;5889if (!data) {5890var elopts = opts_from_el(this, 'date'),5891// Preliminary otions5892xopts = $.extend({}, defaults, elopts, options),5893locopts = opts_from_locale(xopts.language),5894// Options priority: js args, data-attrs, locales, defaults5895opts = $.extend({}, defaults, locopts, elopts, options);5896if ($this.is('.input-daterange') || opts.inputs){5897var ropts = {5898inputs: opts.inputs || $this.find('input').toArray()5899};5900$this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts))));5901}5902else{5903$this.data('datepicker', (data = new Datepicker(this, opts)));5904}5905}5906if (typeof option == 'string' && typeof data[option] == 'function') {5907internal_return = data[option].apply(data, args);5908if (internal_return !== undefined)5909return false;5910}5911});5912if (internal_return !== undefined)5913return internal_return;5914else5915return this;5916};59175918var defaults = $.fn.datepicker.defaults = {5919autoclose: false,5920beforeShowDay: $.noop,5921calendarWeeks: false,5922clearBtn: false,5923daysOfWeekDisabled: [],5924endDate: Infinity,5925forceParse: true,5926format: 'mm/dd/yyyy',5927keyboardNavigation: true,5928language: 'en',5929minViewMode: 0,5930rtl: false,5931startDate: -Infinity,5932startView: 0,5933todayBtn: false,5934todayHighlight: false,5935weekStart: 05936};5937var locale_opts = $.fn.datepicker.locale_opts = [5938'format',5939'rtl',5940'weekStart'5941];5942$.fn.datepicker.Constructor = Datepicker;5943var dates = $.fn.datepicker.dates = {5944en: {5945days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],5946daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],5947daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],5948months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],5949monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],5950today: "Today",5951clear: "Clear"5952}5953};59545955var DPGlobal = {5956modes: [5957{5958clsName: 'days',5959navFnc: 'Month',5960navStep: 15961},5962{5963clsName: 'months',5964navFnc: 'FullYear',5965navStep: 15966},5967{5968clsName: 'years',5969navFnc: 'FullYear',5970navStep: 105971}],5972isLeapYear: function (year) {5973return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0));5974},5975getDaysInMonth: function (year, month) {5976return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month];5977},5978validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g,5979nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g,5980parseFormat: function(format){5981// IE treats \0 as a string end in inputs (truncating the value),5982// so it's a bad format delimiter, anyway5983var separators = format.replace(this.validParts, '\0').split('\0'),5984parts = format.match(this.validParts);5985if (!separators || !separators.length || !parts || parts.length === 0){5986throw new Error("Invalid date format.");5987}5988return {separators: separators, parts: parts};5989},5990parseDate: function(date, format, language) {5991if (date instanceof Date) return date;5992if (typeof format === 'string')5993format = DPGlobal.parseFormat(format);5994if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) {5995var part_re = /([\-+]\d+)([dmwy])/,5996parts = date.match(/([\-+]\d+)([dmwy])/g),5997part, dir;5998date = new Date();5999for (var i=0; i<parts.length; i++) {6000part = part_re.exec(parts[i]);6001dir = parseInt(part[1]);6002switch(part[2]){6003case 'd':6004date.setUTCDate(date.getUTCDate() + dir);6005break;6006case 'm':6007date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir);6008break;6009case 'w':6010date.setUTCDate(date.getUTCDate() + dir * 7);6011break;6012case 'y':6013date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir);6014break;6015}6016}6017return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);6018}6019var parts = date && date.match(this.nonpunctuation) || [],6020date = new Date(),6021parsed = {},6022setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'],6023setters_map = {6024yyyy: function(d,v){ return d.setUTCFullYear(v); },6025yy: function(d,v){ return d.setUTCFullYear(2000+v); },6026m: function(d,v){6027v -= 1;6028while (v<0) v += 12;6029v %= 12;6030d.setUTCMonth(v);6031while (d.getUTCMonth() != v)6032d.setUTCDate(d.getUTCDate()-1);6033return d;6034},6035d: function(d,v){ return d.setUTCDate(v); }6036},6037val, filtered, part;6038setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m'];6039setters_map['dd'] = setters_map['d'];6040date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);6041var fparts = format.parts.slice();6042// Remove noop parts6043if (parts.length != fparts.length) {6044fparts = $(fparts).filter(function(i,p){6045return $.inArray(p, setters_order) !== -1;6046}).toArray();6047}6048// Process remainder6049if (parts.length == fparts.length) {6050for (var i=0, cnt = fparts.length; i < cnt; i++) {6051val = parseInt(parts[i], 10);6052part = fparts[i];6053if (isNaN(val)) {6054switch(part) {6055case 'MM':6056filtered = $(dates[language].months).filter(function(){6057var m = this.slice(0, parts[i].length),6058p = parts[i].slice(0, m.length);6059return m == p;6060});6061val = $.inArray(filtered[0], dates[language].months) + 1;6062break;6063case 'M':6064filtered = $(dates[language].monthsShort).filter(function(){6065var m = this.slice(0, parts[i].length),6066p = parts[i].slice(0, m.length);6067return m == p;6068});6069val = $.inArray(filtered[0], dates[language].monthsShort) + 1;6070break;6071}6072}6073parsed[part] = val;6074}6075for (var i=0, s; i<setters_order.length; i++){6076s = setters_order[i];6077if (s in parsed && !isNaN(parsed[s]))6078setters_map[s](date, parsed[s]);6079}6080}6081return date;6082},6083formatDate: function(date, format, language){6084if (typeof format === 'string')6085format = DPGlobal.parseFormat(format);6086var val = {6087d: date.getUTCDate(),6088D: dates[language].daysShort[date.getUTCDay()],6089DD: dates[language].days[date.getUTCDay()],6090m: date.getUTCMonth() + 1,6091M: dates[language].monthsShort[date.getUTCMonth()],6092MM: dates[language].months[date.getUTCMonth()],6093yy: date.getUTCFullYear().toString().substring(2),6094yyyy: date.getUTCFullYear()6095};6096val.dd = (val.d < 10 ? '0' : '') + val.d;6097val.mm = (val.m < 10 ? '0' : '') + val.m;6098var date = [],6099seps = $.extend([], format.separators);6100for (var i=0, cnt = format.parts.length; i <= cnt; i++) {6101if (seps.length)6102date.push(seps.shift());6103date.push(val[format.parts[i]]);6104}6105return date.join('');6106},6107headTemplate: '<thead>'+6108'<tr>'+6109'<th class="prev"><i class="icon-arrow-left"/></th>'+6110'<th colspan="5" class="datepicker-switch"></th>'+6111'<th class="next"><i class="icon-arrow-right"/></th>'+6112'</tr>'+6113'</thead>',6114contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',6115footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr><tr><th colspan="7" class="clear"></th></tr></tfoot>'6116};6117DPGlobal.template = '<div class="datepicker">'+6118'<div class="datepicker-days">'+6119'<table class=" table-condensed">'+6120DPGlobal.headTemplate+6121'<tbody></tbody>'+6122DPGlobal.footTemplate+6123'</table>'+6124'</div>'+6125'<div class="datepicker-months">'+6126'<table class="table-condensed">'+6127DPGlobal.headTemplate+6128DPGlobal.contTemplate+6129DPGlobal.footTemplate+6130'</table>'+6131'</div>'+6132'<div class="datepicker-years">'+6133'<table class="table-condensed">'+6134DPGlobal.headTemplate+6135DPGlobal.contTemplate+6136DPGlobal.footTemplate+6137'</table>'+6138'</div>'+6139'</div>';61406141$.fn.datepicker.DPGlobal = DPGlobal;614261436144/* DATEPICKER NO CONFLICT6145* =================== */61466147$.fn.datepicker.noConflict = function(){6148$.fn.datepicker = old;6149return this;6150};615161526153/* DATEPICKER DATA-API6154* ================== */61556156$(document).on(6157'focus.datepicker.data-api click.datepicker.data-api',6158'[data-provide="datepicker"]',6159function(e){6160var $this = $(this);6161if ($this.data('datepicker')) return;6162e.preventDefault();6163// component click requires us to explicitly show it6164datepicker.call($this, 'show');6165}6166);6167$(function(){6168//$('[data-provide="datepicker-inline"]').datepicker();6169//vit: changed to support noConflict()6170datepicker.call($('[data-provide="datepicker-inline"]'));6171});61726173}( window.jQuery ));61746175/**6176Bootstrap-datepicker.6177Description and examples: https://github.com/eternicode/bootstrap-datepicker.6178For **i18n** you should include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales6179and set `language` option.6180Since 1.4.0 date has different appearance in **popup** and **inline** modes.61816182@class date6183@extends abstractinput6184@final6185@example6186<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-title="Select date">15/05/1984</a>6187<script>6188$(function(){6189$('#dob').editable({6190format: 'yyyy-mm-dd',6191viewformat: 'dd/mm/yyyy',6192datepicker: {6193weekStart: 16194}6195}6196});6197});6198</script>6199**/6200(function ($) {6201"use strict";62026203//store bootstrap-datepicker as bdateicker to exclude conflict with jQuery UI one6204$.fn.bdatepicker = $.fn.datepicker.noConflict();6205if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name6206$.fn.datepicker = $.fn.bdatepicker;6207}62086209var Date = function (options) {6210this.init('date', options, Date.defaults);6211this.initPicker(options, Date.defaults);6212};62136214$.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);62156216$.extend(Date.prototype, {6217initPicker: function(options, defaults) {6218//'format' is set directly from settings or data-* attributes62196220//by default viewformat equals to format6221if(!this.options.viewformat) {6222this.options.viewformat = this.options.format;6223}62246225//try parse datepicker config defined as json string in data-datepicker6226options.datepicker = $.fn.editableutils.tryParseJson(options.datepicker, true);62276228//overriding datepicker config (as by default jQuery extend() is not recursive)6229//since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only6230this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {6231format: this.options.viewformat6232});62336234//language6235this.options.datepicker.language = this.options.datepicker.language || 'en';62366237//store DPglobal6238this.dpg = $.fn.bdatepicker.DPGlobal;62396240//store parsed formats6241this.parsedFormat = this.dpg.parseFormat(this.options.format);6242this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);6243},62446245render: function () {6246this.$input.bdatepicker(this.options.datepicker);62476248//"clear" link6249if(this.options.clear) {6250this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){6251e.preventDefault();6252e.stopPropagation();6253this.clear();6254}, this));62556256this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));6257}6258},62596260value2html: function(value, element) {6261var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';6262Date.superclass.value2html.call(this, text, element);6263},62646265html2value: function(html) {6266return this.parseDate(html, this.parsedViewFormat);6267},62686269value2str: function(value) {6270return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';6271},62726273str2value: function(str) {6274return this.parseDate(str, this.parsedFormat);6275},62766277value2submit: function(value) {6278return this.value2str(value);6279},62806281value2input: function(value) {6282this.$input.bdatepicker('update', value);6283},62846285input2value: function() {6286return this.$input.data('datepicker').date;6287},62886289activate: function() {6290},62916292clear: function() {6293this.$input.data('datepicker').date = null;6294this.$input.find('.active').removeClass('active');6295if(!this.options.showbuttons) {6296this.$input.closest('form').submit();6297}6298},62996300autosubmit: function() {6301this.$input.on('mouseup', '.day', function(e){6302if($(e.currentTarget).is('.old') || $(e.currentTarget).is('.new')) {6303return;6304}6305var $form = $(this).closest('form');6306setTimeout(function() {6307$form.submit();6308}, 200);6309});6310//changedate is not suitable as it triggered when showing datepicker. see #1496311/*6312this.$input.on('changeDate', function(e){6313var $form = $(this).closest('form');6314setTimeout(function() {6315$form.submit();6316}, 200);6317});6318*/6319},63206321/*6322For incorrect date bootstrap-datepicker returns current date that is not suitable6323for datefield.6324This function returns null for incorrect date.6325*/6326parseDate: function(str, format) {6327var date = null, formattedBack;6328if(str) {6329date = this.dpg.parseDate(str, format, this.options.datepicker.language);6330if(typeof str === 'string') {6331formattedBack = this.dpg.formatDate(date, format, this.options.datepicker.language);6332if(str !== formattedBack) {6333date = null;6334}6335}6336}6337return date;6338}63396340});63416342Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {6343/**6344@property tpl6345@default <div></div>6346**/6347tpl:'<div class="editable-date well"></div>',6348/**6349@property inputclass6350@default null6351**/6352inputclass: null,6353/**6354Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>6355Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code>63566357@property format6358@type string6359@default yyyy-mm-dd6360**/6361format:'yyyy-mm-dd',6362/**6363Format used for displaying date. Also applied when converting date from element's text on init.6364If not specified equals to <code>format</code>63656366@property viewformat6367@type string6368@default null6369**/6370viewformat: null,6371/**6372Configuration of datepicker.6373Full list of options: http://bootstrap-datepicker.readthedocs.org/en/latest/options.html63746375@property datepicker6376@type object6377@default {6378weekStart: 0,6379startView: 0,6380minViewMode: 0,6381autoclose: false6382}6383**/6384datepicker:{6385weekStart: 0,6386startView: 0,6387minViewMode: 0,6388autoclose: false6389},6390/**6391Text shown as clear date button.6392If <code>false</code> clear button will not be rendered.63936394@property clear6395@type boolean|string6396@default 'x clear'6397**/6398clear: '× clear'6399});64006401$.fn.editabletypes.date = Date;64026403}(window.jQuery));64046405/**6406Bootstrap datefield input - modification for inline mode.6407Shows normal <input type="text"> and binds popup datepicker.6408Automatically shown in inline mode.64096410@class datefield6411@extends date64126413@since 1.4.06414**/6415(function ($) {6416"use strict";64176418var DateField = function (options) {6419this.init('datefield', options, DateField.defaults);6420this.initPicker(options, DateField.defaults);6421};64226423$.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);64246425$.extend(DateField.prototype, {6426render: function () {6427this.$input = this.$tpl.find('input');6428this.setClass();6429this.setAttr('placeholder');64306431//bootstrap-datepicker is set `bdateicker` to exclude conflict with jQuery UI one. (in date.js)6432this.$tpl.bdatepicker(this.options.datepicker);64336434//need to disable original event handlers6435this.$input.off('focus keydown');64366437//update value of datepicker6438this.$input.keyup($.proxy(function(){6439this.$tpl.removeData('date');6440this.$tpl.bdatepicker('update');6441}, this));64426443},64446445value2input: function(value) {6446this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '');6447this.$tpl.bdatepicker('update');6448},64496450input2value: function() {6451return this.html2value(this.$input.val());6452},64536454activate: function() {6455$.fn.editabletypes.text.prototype.activate.call(this);6456},64576458autosubmit: function() {6459//reset autosubmit to empty6460}6461});64626463DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, {6464/**6465@property tpl6466**/6467tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',6468/**6469@property inputclass6470@default 'input-small'6471**/6472inputclass: 'input-small',64736474/* datepicker config */6475datepicker: {6476weekStart: 0,6477startView: 0,6478minViewMode: 0,6479autoclose: true6480}6481});64826483$.fn.editabletypes.datefield = DateField;64846485}(window.jQuery));6486/**6487Bootstrap-datetimepicker.6488Based on [smalot bootstrap-datetimepicker plugin](https://github.com/smalot/bootstrap-datetimepicker).6489Before usage you should manually include dependent js and css:64906491<link href="css/datetimepicker.css" rel="stylesheet" type="text/css"></link>6492<script src="js/bootstrap-datetimepicker.js"></script>64936494For **i18n** you should include js file from here: https://github.com/smalot/bootstrap-datetimepicker/tree/master/js/locales6495and set `language` option.64966497@class datetime6498@extends abstractinput6499@final6500@since 1.4.46501@example6502<a href="#" id="last_seen" data-type="datetime" data-pk="1" data-url="/post" title="Select date & time">15/03/2013 12:45</a>6503<script>6504$(function(){6505$('#last_seen').editable({6506format: 'yyyy-mm-dd hh:ii',6507viewformat: 'dd/mm/yyyy hh:ii',6508datetimepicker: {6509weekStart: 16510}6511}6512});6513});6514</script>6515**/6516(function ($) {6517"use strict";65186519var DateTime = function (options) {6520this.init('datetime', options, DateTime.defaults);6521this.initPicker(options, DateTime.defaults);6522};65236524$.fn.editableutils.inherit(DateTime, $.fn.editabletypes.abstractinput);65256526$.extend(DateTime.prototype, {6527initPicker: function(options, defaults) {6528//'format' is set directly from settings or data-* attributes65296530//by default viewformat equals to format6531if(!this.options.viewformat) {6532this.options.viewformat = this.options.format;6533}65346535//try parse datetimepicker config defined as json string in data-datetimepicker6536options.datetimepicker = $.fn.editableutils.tryParseJson(options.datetimepicker, true);65376538//overriding datetimepicker config (as by default jQuery extend() is not recursive)6539//since 1.4 datetimepicker internally uses viewformat instead of format. Format is for submit only6540this.options.datetimepicker = $.extend({}, defaults.datetimepicker, options.datetimepicker, {6541format: this.options.viewformat6542});65436544//language6545this.options.datetimepicker.language = this.options.datetimepicker.language || 'en';65466547//store DPglobal6548this.dpg = $.fn.datetimepicker.DPGlobal;65496550//store parsed formats6551this.parsedFormat = this.dpg.parseFormat(this.options.format, this.options.formatType);6552this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat, this.options.formatType);6553},65546555render: function () {6556this.$input.datetimepicker(this.options.datetimepicker);65576558//adjust container position when viewMode changes6559//see https://github.com/smalot/bootstrap-datetimepicker/pull/806560this.$input.on('changeMode', function(e) {6561var f = $(this).closest('form').parent();6562//timeout here, otherwise container changes position before form has new size6563setTimeout(function(){6564f.triggerHandler('resize');6565}, 0);6566});65676568//"clear" link6569if(this.options.clear) {6570this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){6571e.preventDefault();6572e.stopPropagation();6573this.clear();6574}, this));65756576this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));6577}6578},65796580value2html: function(value, element) {6581//formatDate works with UTCDate!6582var text = value ? this.dpg.formatDate(this.toUTC(value), this.parsedViewFormat, this.options.datetimepicker.language, this.options.formatType) : '';6583if(element) {6584DateTime.superclass.value2html.call(this, text, element);6585} else {6586return text;6587}6588},65896590html2value: function(html) {6591//parseDate return utc date!6592var value = this.parseDate(html, this.parsedViewFormat);6593return value ? this.fromUTC(value) : null;6594},65956596value2str: function(value) {6597//formatDate works with UTCDate!6598return value ? this.dpg.formatDate(this.toUTC(value), this.parsedFormat, this.options.datetimepicker.language, this.options.formatType) : '';6599},66006601str2value: function(str) {6602//parseDate return utc date!6603var value = this.parseDate(str, this.parsedFormat);6604return value ? this.fromUTC(value) : null;6605},66066607value2submit: function(value) {6608return this.value2str(value);6609},66106611value2input: function(value) {6612if(value) {6613this.$input.data('datetimepicker').setDate(value);6614}6615},66166617input2value: function() {6618//date may be cleared, in that case getDate() triggers error6619var dt = this.$input.data('datetimepicker');6620return dt.date ? dt.getDate() : null;6621},66226623activate: function() {6624},66256626clear: function() {6627this.$input.data('datetimepicker').date = null;6628this.$input.find('.active').removeClass('active');6629if(!this.options.showbuttons) {6630this.$input.closest('form').submit();6631}6632},66336634autosubmit: function() {6635this.$input.on('mouseup', '.minute', function(e){6636var $form = $(this).closest('form');6637setTimeout(function() {6638$form.submit();6639}, 200);6640});6641},66426643//convert date from local to utc6644toUTC: function(value) {6645return value ? new Date(value.valueOf() - value.getTimezoneOffset() * 60000) : value;6646},66476648//convert date from utc to local6649fromUTC: function(value) {6650return value ? new Date(value.valueOf() + value.getTimezoneOffset() * 60000) : value;6651},66526653/*6654For incorrect date bootstrap-datetimepicker returns current date that is not suitable6655for datetimefield.6656This function returns null for incorrect date.6657*/6658parseDate: function(str, format) {6659var date = null, formattedBack;6660if(str) {6661date = this.dpg.parseDate(str, format, this.options.datetimepicker.language, this.options.formatType);6662if(typeof str === 'string') {6663formattedBack = this.dpg.formatDate(date, format, this.options.datetimepicker.language, this.options.formatType);6664if(str !== formattedBack) {6665date = null;6666}6667}6668}6669return date;6670}66716672});66736674DateTime.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {6675/**6676@property tpl6677@default <div></div>6678**/6679tpl:'<div class="editable-date well"></div>',6680/**6681@property inputclass6682@default null6683**/6684inputclass: null,6685/**6686Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>6687Possible tokens are: <code>d, dd, m, mm, yy, yyyy, h, i</code>66886689@property format6690@type string6691@default yyyy-mm-dd hh:ii6692**/6693format:'yyyy-mm-dd hh:ii',6694formatType:'standard',6695/**6696Format used for displaying date. Also applied when converting date from element's text on init.6697If not specified equals to <code>format</code>66986699@property viewformat6700@type string6701@default null6702**/6703viewformat: null,6704/**6705Configuration of datetimepicker.6706Full list of options: https://github.com/smalot/bootstrap-datetimepicker67076708@property datetimepicker6709@type object6710@default { }6711**/6712datetimepicker:{6713todayHighlight: false,6714autoclose: false6715},6716/**6717Text shown as clear date button.6718If <code>false</code> clear button will not be rendered.67196720@property clear6721@type boolean|string6722@default 'x clear'6723**/6724clear: '× clear'6725});67266727$.fn.editabletypes.datetime = DateTime;67286729}(window.jQuery));6730/**6731Bootstrap datetimefield input - datetime input for inline mode.6732Shows normal <input type="text"> and binds popup datetimepicker.6733Automatically shown in inline mode.67346735@class datetimefield6736@extends datetime67376738**/6739(function ($) {6740"use strict";67416742var DateTimeField = function (options) {6743this.init('datetimefield', options, DateTimeField.defaults);6744this.initPicker(options, DateTimeField.defaults);6745};67466747$.fn.editableutils.inherit(DateTimeField, $.fn.editabletypes.datetime);67486749$.extend(DateTimeField.prototype, {6750render: function () {6751this.$input = this.$tpl.find('input');6752this.setClass();6753this.setAttr('placeholder');67546755this.$tpl.datetimepicker(this.options.datetimepicker);67566757//need to disable original event handlers6758this.$input.off('focus keydown');67596760//update value of datepicker6761this.$input.keyup($.proxy(function(){6762this.$tpl.removeData('date');6763this.$tpl.datetimepicker('update');6764}, this));67656766},67676768value2input: function(value) {6769this.$input.val(this.value2html(value));6770this.$tpl.datetimepicker('update');6771},67726773input2value: function() {6774return this.html2value(this.$input.val());6775},67766777activate: function() {6778$.fn.editabletypes.text.prototype.activate.call(this);6779},67806781autosubmit: function() {6782//reset autosubmit to empty6783}6784});67856786DateTimeField.defaults = $.extend({}, $.fn.editabletypes.datetime.defaults, {6787/**6788@property tpl6789**/6790tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',6791/**6792@property inputclass6793@default 'input-medium'6794**/6795inputclass: 'input-medium',67966797/* datetimepicker config */6798datetimepicker:{6799todayHighlight: false,6800autoclose: true6801}6802});68036804$.fn.editabletypes.datetimefield = DateTimeField;68056806}(window.jQuery));68076808