From 39bb5a3e716f5e23fdd7d4f74f04a72baff64bd5 Mon Sep 17 00:00:00 2001 From: Micha <espey@smart-q.de> Date: Sun, 23 Feb 2025 15:59:23 +0100 Subject: [PATCH] updted README.md --- README.md | 3 + .../js/bootstrap-editable.esm.js | 1 + .../js/bootstrap-editable.js | 6812 +---------------- package.json | 35 +- 4 files changed, 31 insertions(+), 6820 deletions(-) create mode 100644 dist/bootstrap5-editable/js/bootstrap-editable.esm.js diff --git a/README.md b/README.md index 42354a2..6e3c6f4 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,9 @@ The original **x-editable** library was designed for Bootstrap 3 and has not bee - **Bootstrap Icons** (replacing Glyphicons) - General improvements for usability and maintainability +NOTE: This repo has not yet been completely tested and is not available via NPM right now! + +Later: ### Using npm: ```sh npm install x-editable-bootstrap5 diff --git a/dist/bootstrap5-editable/js/bootstrap-editable.esm.js b/dist/bootstrap5-editable/js/bootstrap-editable.esm.js new file mode 100644 index 0000000..478a546 --- /dev/null +++ b/dist/bootstrap5-editable/js/bootstrap-editable.esm.js @@ -0,0 +1 @@ +!function(t){const n=t.fn.editableform.Constructor.prototype.initInput;t.extend(t.fn.editableform.Constructor.prototype,{initInput:function(){n.apply(this);const i=null===this.input.options.inputclass||!1===this.input.options.inputclass,s="input-sm",e="text,select,textarea,password,email,url,tel,number,range,time,typeaheadjs".split(",");~t.inArray(this.input.type,e)&&(this.input.$input.addClass("form-control editable"),i&&(this.input.options.inputclass=s,this.input.$input.addClass(s))),"select"===this.input.type&&setTimeout((()=>{this.input.$input.focus().click()}),50);const o=this.$form.find(".editable-buttons"),l=i?[s]:this.input.options.inputclass.split(" ");for(let t=0;t<l.length;t++)"input-lg"===l[t].toLowerCase()&&o.find("button").removeClass("btn-sm").addClass("btn-lg")}}),t.fn.editableform.buttons='<button type="submit" class="btn btn-primary btn-sm editable-submit"><i class="bi bi-check"></i></button><button type="button" class="btn btn-secondary btn-sm editable-cancel"><i class="bi bi-x"></i></button>',t.fn.editableform.errorGroupClass="has-error",t.fn.editableform.errorBlockClass=null,t.fn.editableform.engine="bs3"}(window.jQuery); \ No newline at end of file diff --git a/dist/bootstrap5-editable/js/bootstrap-editable.js b/dist/bootstrap5-editable/js/bootstrap-editable.js index 890199c..068d164 100644 --- a/dist/bootstrap5-editable/js/bootstrap-editable.js +++ b/dist/bootstrap5-editable/js/bootstrap-editable.js @@ -1,6811 +1 @@ -/*! X-editable - v1.5.1 -* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery -* http://github.com/vitalets/x-editable -* Copyright (c) 2025 Vitaliy Potapov; Licensed MIT */ -/** -Form with single input element, two buttons and two states: normal/loading. -Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown. -Editableform is linked with one of input types, e.g. 'text', 'select' etc. - -@class editableform -@uses text -@uses textarea -**/ -(function ($) { - "use strict"; - - var EditableForm = function (div, options) { - this.options = $.extend({}, $.fn.editableform.defaults, options); - this.$div = $(div); //div, containing form. Not form tag. Not editable-element. - if(!this.options.scope) { - this.options.scope = this; - } - //nothing shown after init - }; - - EditableForm.prototype = { - constructor: EditableForm, - initInput: function() { //called once - //take input from options (as it is created in editable-element) - this.input = this.options.input; - - //set initial value - //todo: may be add check: typeof str === 'string' ? - this.value = this.input.str2value(this.options.value); - - //prerender: get input.$input - this.input.prerender(); - }, - initTemplate: function() { - this.$form = $($.fn.editableform.template); - }, - initButtons: function() { - var $btn = this.$form.find('.editable-buttons'); - $btn.append($.fn.editableform.buttons); - if(this.options.showbuttons === 'bottom') { - $btn.addClass('editable-buttons-bottom'); - } - }, - /** - Renders editableform - - @method render - **/ - render: function() { - //init loader - this.$loading = $($.fn.editableform.loading); - this.$div.empty().append(this.$loading); - - //init form template and buttons - this.initTemplate(); - if(this.options.showbuttons) { - this.initButtons(); - } else { - this.$form.find('.editable-buttons').remove(); - } - - //show loading state - this.showLoading(); - - //flag showing is form now saving value to server. - //It is needed to wait when closing form. - this.isSaving = false; - - /** - Fired when rendering starts - @event rendering - @param {Object} event event object - **/ - this.$div.triggerHandler('rendering'); - - //init input - this.initInput(); - - //append input to form - this.$form.find('div.editable-input').append(this.input.$tpl); - - //append form to container - this.$div.append(this.$form); - - //render input - $.when(this.input.render()) - .then($.proxy(function () { - //setup input to submit automatically when no buttons shown - if(!this.options.showbuttons) { - this.input.autosubmit(); - } - - //attach 'cancel' handler - this.$form.find('.editable-cancel').click($.proxy(this.cancel, this)); - - if(this.input.error) { - this.error(this.input.error); - this.$form.find('.editable-submit').attr('disabled', true); - this.input.$input.attr('disabled', true); - //prevent form from submitting - this.$form.submit(function(e){ e.preventDefault(); }); - } else { - this.error(false); - this.input.$input.removeAttr('disabled'); - this.$form.find('.editable-submit').removeAttr('disabled'); - var value = (this.value === null || this.value === undefined || this.value === '') ? this.options.defaultValue : this.value; - this.input.value2input(value); - //attach submit handler - this.$form.submit($.proxy(this.submit, this)); - } - - /** - Fired when form is rendered - @event rendered - @param {Object} event event object - **/ - this.$div.triggerHandler('rendered'); - - this.showForm(); - - //call postrender method to perform actions required visibility of form - if(this.input.postrender) { - this.input.postrender(); - } - }, this)); - }, - cancel: function() { - /** - Fired when form was cancelled by user - @event cancel - @param {Object} event event object - **/ - this.$div.triggerHandler('cancel'); - }, - showLoading: function() { - var w, h; - if(this.$form) { - //set loading size equal to form - w = this.$form.outerWidth(); - h = this.$form.outerHeight(); - if(w) { - this.$loading.width(w); - } - if(h) { - this.$loading.height(h); - } - this.$form.hide(); - } else { - //stretch loading to fill container width - w = this.$loading.parent().width(); - if(w) { - this.$loading.width(w); - } - } - this.$loading.show(); - }, - - showForm: function(activate) { - this.$loading.hide(); - this.$form.show(); - if(activate !== false) { - this.input.activate(); - } - /** - Fired when form is shown - @event show - @param {Object} event event object - **/ - this.$div.triggerHandler('show'); - }, - - error: function(msg) { - var $group = this.$form.find('.control-group'), - $block = this.$form.find('.editable-error-block'), - lines; - - if(msg === false) { - $group.removeClass($.fn.editableform.errorGroupClass); - $block.removeClass($.fn.editableform.errorBlockClass).empty().hide(); - } else { - //convert newline to <br> for more pretty error display - if(msg) { - lines = (''+msg).split('\n'); - for (var i = 0; i < lines.length; i++) { - lines[i] = $('<div>').text(lines[i]).html(); - } - msg = lines.join('<br>'); - } - $group.addClass($.fn.editableform.errorGroupClass); - $block.addClass($.fn.editableform.errorBlockClass).html(msg).show(); - } - }, - - submit: function(e) { - e.stopPropagation(); - e.preventDefault(); - - //get new value from input - var newValue = this.input.input2value(); - - //validation: if validate returns string or truthy value - means error - //if returns object like {newValue: '...'} => submitted value is reassigned to it - var error = this.validate(newValue); - if ($.type(error) === 'object' && error.newValue !== undefined) { - newValue = error.newValue; - this.input.value2input(newValue); - if(typeof error.msg === 'string') { - this.error(error.msg); - this.showForm(); - return; - } - } else if (error) { - this.error(error); - this.showForm(); - return; - } - - //if value not changed --> trigger 'nochange' event and return - /*jslint eqeq: true*/ - if (!this.options.savenochange && this.input.value2str(newValue) == this.input.value2str(this.value)) { - /*jslint eqeq: false*/ - /** - Fired when value not changed but form is submitted. Requires savenochange = false. - @event nochange - @param {Object} event event object - **/ - this.$div.triggerHandler('nochange'); - return; - } - - //convert value for submitting to server - var submitValue = this.input.value2submit(newValue); - - this.isSaving = true; - - //sending data to server - $.when(this.save(submitValue)) - .done($.proxy(function(response) { - this.isSaving = false; - - //run success callback - var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null; - - //if success callback returns false --> keep form open and do not activate input - if(res === false) { - this.error(false); - this.showForm(false); - return; - } - - //if success callback returns string --> keep form open, show error and activate input - if(typeof res === 'string') { - this.error(res); - this.showForm(); - return; - } - - //if success callback returns object like {newValue: <something>} --> use that value instead of submitted - //it is usefull if you want to chnage value in url-function - if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) { - newValue = res.newValue; - } - - //clear error message - this.error(false); - this.value = newValue; - /** - Fired when form is submitted - @event save - @param {Object} event event object - @param {Object} params additional params - @param {mixed} params.newValue raw new value - @param {mixed} params.submitValue submitted value as string - @param {Object} params.response ajax response - - @example - $('#form-div').on('save'), function(e, params){ - if(params.newValue === 'username') {...} - }); - **/ - this.$div.triggerHandler('save', {newValue: newValue, submitValue: submitValue, response: response}); - }, this)) - .fail($.proxy(function(xhr) { - this.isSaving = false; - - var msg; - if(typeof this.options.error === 'function') { - msg = this.options.error.call(this.options.scope, xhr, newValue); - } else { - msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!'; - } - - this.error(msg); - this.showForm(); - }, this)); - }, - - save: function(submitValue) { - //try parse composite pk defined as json string in data-pk - this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true); - - var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk, - /* - send on server in following cases: - 1. url is function - 2. url is string AND (pk defined OR send option = always) - */ - send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))), - params; - - if (send) { //send to server - this.showLoading(); - - //standard params - params = { - name: this.options.name || '', - value: submitValue, - pk: pk - }; - - //additional params - if(typeof this.options.params === 'function') { - params = this.options.params.call(this.options.scope, params); - } else { - //try parse json in single quotes (from data-params attribute) - this.options.params = $.fn.editableutils.tryParseJson(this.options.params, true); - $.extend(params, this.options.params); - } - - if(typeof this.options.url === 'function') { //user's function - return this.options.url.call(this.options.scope, params); - } else { - //send ajax to server and return deferred object - return $.ajax($.extend({ - url : this.options.url, - data : params, - type : 'POST' - }, this.options.ajaxOptions)); - } - } - }, - - validate: function (value) { - if (value === undefined) { - value = this.value; - } - if (typeof this.options.validate === 'function') { - return this.options.validate.call(this.options.scope, value); - } - }, - - option: function(key, value) { - if(key in this.options) { - this.options[key] = value; - } - - if(key === 'value') { - this.setValue(value); - } - - //do not pass option to input as it is passed in editable-element - }, - - setValue: function(value, convertStr) { - if(convertStr) { - this.value = this.input.str2value(value); - } else { - this.value = value; - } - - //if form is visible, update input - if(this.$form && this.$form.is(':visible')) { - this.input.value2input(this.value); - } - } - }; - - /* - Initialize editableform. Applied to jQuery object. - - @method $().editableform(options) - @params {Object} options - @example - var $form = $('<div>').editableform({ - type: 'text', - name: 'username', - url: '/post', - value: 'vitaliy' - }); - - //to display form you should call 'render' method - $form.editableform('render'); - */ - $.fn.editableform = function (option) { - var args = arguments; - return this.each(function () { - var $this = $(this), - data = $this.data('editableform'), - options = typeof option === 'object' && option; - if (!data) { - $this.data('editableform', (data = new EditableForm(this, options))); - } - - if (typeof option === 'string') { //call method - data[option].apply(data, Array.prototype.slice.call(args, 1)); - } - }); - }; - - //keep link to constructor to allow inheritance - $.fn.editableform.Constructor = EditableForm; - - //defaults - $.fn.editableform.defaults = { - /* see also defaults for input */ - - /** - Type of input. Can be <code>text|textarea|select|date|checklist</code> - - @property type - @type string - @default 'text' - **/ - type: 'text', - /** - Url for submit, e.g. <code>'/post'</code> - If function - it will be called instead of ajax. Function should return deferred object to run fail/done callbacks. - - @property url - @type string|function - @default null - @example - url: function(params) { - var d = new $.Deferred; - if(params.value === 'abc') { - return d.reject('error message'); //returning error via deferred object - } else { - //async saving data in js model - someModel.asyncSaveMethod({ - ..., - success: function(){ - d.resolve(); - } - }); - return d.promise(); - } - } - **/ - url:null, - /** - Additional params for submit. If defined as <code>object</code> - it is **appended** to original ajax data (pk, name and value). - If defined as <code>function</code> - returned object **overwrites** original ajax data. - @example - params: function(params) { - //originally params contain pk, name and value - params.a = 1; - return params; - } - - @property params - @type object|function - @default null - **/ - params:null, - /** - Name of field. Will be submitted on server. Can be taken from <code>id</code> attribute - - @property name - @type string - @default null - **/ - name: null, - /** - Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. <code>{id: 1, lang: 'en'}</code>. - Can be calculated dynamically via function. - - @property pk - @type string|object|function - @default null - **/ - pk: null, - /** - Initial value. If not defined - will be taken from element's content. - For __select__ type should be defined (as it is ID of shown text). - - @property value - @type string|object - @default null - **/ - value: null, - /** - Value that will be displayed in input if original field value is empty (`null|undefined|''`). - - @property defaultValue - @type string|object - @default null - @since 1.4.6 - **/ - defaultValue: null, - /** - Strategy for sending data on server. Can be `auto|always|never`. - When 'auto' data will be sent on server **only if pk and url defined**, otherwise new value will be stored locally. - - @property send - @type string - @default 'auto' - **/ - send: 'auto', - /** - Function for client-side validation. If returns string - means validation not passed and string showed as error. - Since 1.5.1 you can modify submitted value by returning object from `validate`: - `{newValue: '...'}` or `{newValue: '...', msg: '...'}` - - @property validate - @type function - @default null - @example - validate: function(value) { - if($.trim(value) == '') { - return 'This field is required'; - } - } - **/ - validate: null, - /** - Success callback. Called when value successfully sent on server and **response status = 200**. - Usefull to work with json response. For example, if your backend response can be <code>{success: true}</code> - or <code>{success: false, msg: "server error"}</code> you can check it inside this callback. - If it returns **string** - means error occured and string is shown as error message. - If it returns **object like** <code>{newValue: <something>}</code> - it overwrites value, submitted by user. - Otherwise newValue simply rendered into element. - - @property success - @type function - @default null - @example - success: function(response, newValue) { - if(!response.success) return response.msg; - } - **/ - success: null, - /** - Error callback. Called when request failed (response status != 200). - Usefull when you want to parse error response and display a custom message. - Must return **string** - the message to be displayed in the error block. - - @property error - @type function - @default null - @since 1.4.4 - @example - error: function(response, newValue) { - if(response.status === 500) { - return 'Service unavailable. Please try later.'; - } else { - return response.responseText; - } - } - **/ - error: null, - /** - Additional options for submit ajax request. - List of values: http://api.jquery.com/jQuery.ajax - - @property ajaxOptions - @type object - @default null - @since 1.1.1 - @example - ajaxOptions: { - type: 'put', - dataType: 'json' - } - **/ - ajaxOptions: null, - /** - Where to show buttons: left(true)|bottom|false - Form without buttons is auto-submitted. - - @property showbuttons - @type boolean|string - @default true - @since 1.1.1 - **/ - showbuttons: true, - /** - Scope for callback methods (success, validate). - If <code>null</code> means editableform instance itself. - - @property scope - @type DOMElement|object - @default null - @since 1.2.0 - @private - **/ - scope: null, - /** - Whether to save or cancel value when it was not changed but form was submitted - - @property savenochange - @type boolean - @default false - @since 1.2.0 - **/ - savenochange: false - }; - - /* - Note: following params could redefined in engine: bootstrap or jqueryui: - Classes 'control-group' and 'editable-error-block' must always present! - */ - $.fn.editableform.template = '<form class="form-inline editableform">'+ - '<div class="control-group">' + - '<div><div class="editable-input"></div><div class="editable-buttons"></div></div>'+ - '<div class="editable-error-block"></div>' + - '</div>' + - '</form>'; - - //loading div - $.fn.editableform.loading = '<div class="editableform-loading"></div>'; - - //buttons - $.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+ - '<button type="button" class="editable-cancel">cancel</button>'; - - //error class attached to control-group - $.fn.editableform.errorGroupClass = null; - - //error class attached to editable-error-block - $.fn.editableform.errorBlockClass = 'editable-error'; - - //engine - $.fn.editableform.engine = 'jquery'; -}(window.jQuery)); - -/** -* EditableForm utilites -*/ -(function ($) { - "use strict"; - - //utils - $.fn.editableutils = { - /** - * classic JS inheritance function - */ - inherit: function (Child, Parent) { - var F = function() { }; - F.prototype = Parent.prototype; - Child.prototype = new F(); - Child.prototype.constructor = Child; - Child.superclass = Parent.prototype; - }, - - /** - * set caret position in input - * see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area - */ - setCursorPosition: function(elem, pos) { - if (elem.setSelectionRange) { - elem.setSelectionRange(pos, pos); - } else if (elem.createTextRange) { - var range = elem.createTextRange(); - range.collapse(true); - range.moveEnd('character', pos); - range.moveStart('character', pos); - range.select(); - } - }, - - /** - * function to parse JSON in *single* quotes. (jquery automatically parse only double quotes) - * That allows such code as: <a data-source="{'a': 'b', 'c': 'd'}"> - * safe = true --> means no exception will be thrown - * for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery - */ - tryParseJson: function(s, safe) { - if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) { - if (safe) { - try { - /*jslint evil: true*/ - s = (new Function('return ' + s))(); - /*jslint evil: false*/ - } catch (e) {} finally { - return s; - } - } else { - /*jslint evil: true*/ - s = (new Function('return ' + s))(); - /*jslint evil: false*/ - } - } - return s; - }, - - /** - * slice object by specified keys - */ - sliceObj: function(obj, keys, caseSensitive /* default: false */) { - var key, keyLower, newObj = {}; - - if (!$.isArray(keys) || !keys.length) { - return newObj; - } - - for (var i = 0; i < keys.length; i++) { - key = keys[i]; - if (obj.hasOwnProperty(key)) { - newObj[key] = obj[key]; - } - - if(caseSensitive === true) { - continue; - } - - //when getting data-* attributes via $.data() it's converted to lowercase. - //details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery - //workaround is code below. - keyLower = key.toLowerCase(); - if (obj.hasOwnProperty(keyLower)) { - newObj[key] = obj[keyLower]; - } - } - - return newObj; - }, - - /* - exclude complex objects from $.data() before pass to config - */ - getConfigData: function($element) { - var data = {}; - $.each($element.data(), function(k, v) { - if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) { - data[k] = v; - } - }); - return data; - }, - - /* - returns keys of object - */ - objectKeys: function(o) { - if (Object.keys) { - return Object.keys(o); - } else { - if (o !== Object(o)) { - throw new TypeError('Object.keys called on a non-object'); - } - var k=[], p; - for (p in o) { - if (Object.prototype.hasOwnProperty.call(o,p)) { - k.push(p); - } - } - return k; - } - - }, - - /** - method to escape html. - **/ - escape: function(str) { - return $('<div>').text(str).html(); - }, - - /* - returns array items from sourceData having value property equal or inArray of 'value' - */ - itemsByValue: function(value, sourceData, valueProp) { - if(!sourceData || value === null) { - return []; - } - - if (typeof(valueProp) !== "function") { - var idKey = valueProp || 'value'; - valueProp = function (e) { return e[idKey]; }; - } - - var isValArray = $.isArray(value), - result = [], - that = this; - - $.each(sourceData, function(i, o) { - if(o.children) { - result = result.concat(that.itemsByValue(value, o.children, valueProp)); - } else { - /*jslint eqeq: true*/ - if(isValArray) { - if($.grep(value, function(v){ return v == (o && typeof o === 'object' ? valueProp(o) : o); }).length) { - result.push(o); - } - } else { - var itemValue = (o && (typeof o === 'object')) ? valueProp(o) : o; - if(value == itemValue) { - result.push(o); - } - } - /*jslint eqeq: false*/ - } - }); - - return result; - }, - - /* - Returns input by options: type, mode. - */ - createInput: function(options) { - var TypeConstructor, typeOptions, input, - type = options.type; - - //`date` is some kind of virtual type that is transformed to one of exact types - //depending on mode and core lib - if(type === 'date') { - //inline - if(options.mode === 'inline') { - if($.fn.editabletypes.datefield) { - type = 'datefield'; - } else if($.fn.editabletypes.dateuifield) { - type = 'dateuifield'; - } - //popup - } else { - if($.fn.editabletypes.date) { - type = 'date'; - } else if($.fn.editabletypes.dateui) { - type = 'dateui'; - } - } - - //if type still `date` and not exist in types, replace with `combodate` that is base input - if(type === 'date' && !$.fn.editabletypes.date) { - type = 'combodate'; - } - } - - //`datetime` should be datetimefield in 'inline' mode - if(type === 'datetime' && options.mode === 'inline') { - type = 'datetimefield'; - } - - //change wysihtml5 to textarea for jquery UI and plain versions - if(type === 'wysihtml5' && !$.fn.editabletypes[type]) { - type = 'textarea'; - } - - //create input of specified type. Input will be used for converting value, not in form - if(typeof $.fn.editabletypes[type] === 'function') { - TypeConstructor = $.fn.editabletypes[type]; - typeOptions = this.sliceObj(options, this.objectKeys(TypeConstructor.defaults)); - input = new TypeConstructor(typeOptions); - return input; - } else { - $.error('Unknown type: '+ type); - return false; - } - }, - - //see http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr - supportsTransitions: function () { - var b = document.body || document.documentElement, - s = b.style, - p = 'transition', - v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms']; - - if(typeof s[p] === 'string') { - return true; - } - - // Tests for vendor specific prop - p = p.charAt(0).toUpperCase() + p.substr(1); - for(var i=0; i<v.length; i++) { - if(typeof s[v[i] + p] === 'string') { - return true; - } - } - return false; - } - - }; -}(window.jQuery)); - -/** -Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br> -This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br> -Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.<br> -Applied as jQuery method. - -@class editableContainer -@uses editableform -**/ -(function ($) { - "use strict"; - - var Popup = function (element, options) { - this.init(element, options); - }; - - var Inline = function (element, options) { - this.init(element, options); - }; - - //methods - Popup.prototype = { - containerName: null, //method to call container on element - containerDataName: null, //object name in element's .data() - innerCss: null, //tbd in child class - containerClass: 'editable-container editable-popup', //css class applied to container element - defaults: {}, //container itself defaults - - init: function(element, options) { - this.$element = $(element); - //since 1.4.1 container do not use data-* directly as they already merged into options. - this.options = $.extend({}, $.fn.editableContainer.defaults, options); - this.splitOptions(); - - //set scope of form callbacks to element - this.formOptions.scope = this.$element[0]; - - this.initContainer(); - - //flag to hide container, when saving value will finish - this.delayedHide = false; - - //bind 'destroyed' listener to destroy container when element is removed from dom - this.$element.on('destroyed', $.proxy(function(){ - this.destroy(); - }, this)); - - //attach document handler to close containers on click / escape - if(!$(document).data('editable-handlers-attached')) { - //close all on escape - $(document).on('keyup.editable', function (e) { - if (e.which === 27) { - $('.editable-open').editableContainer('hide'); - //todo: return focus on element - } - }); - - //close containers when click outside - //(mousedown could be better than click, it closes everything also on drag drop) - $(document).on('click.editable', function(e) { - var $target = $(e.target), i, - exclude_classes = ['.editable-container', - '.ui-datepicker-header', - '.datepicker', //in inline mode datepicker is rendered into body - '.modal-backdrop', - '.bootstrap-wysihtml5-insert-image-modal', - '.bootstrap-wysihtml5-insert-link-modal' - ]; - - //check if element is detached. It occurs when clicking in bootstrap datepicker - if (!$.contains(document.documentElement, e.target)) { - return; - } - - //for some reason FF 20 generates extra event (click) in select2 widget with e.target = document - //we need to filter it via construction below. See https://github.com/vitalets/x-editable/issues/199 - //Possibly related to http://stackoverflow.com/questions/10119793/why-does-firefox-react-differently-from-webkit-and-ie-to-click-event-on-selec - if($target.is(document)) { - return; - } - - //if click inside one of exclude classes --> no nothing - for(i=0; i<exclude_classes.length; i++) { - if($target.is(exclude_classes[i]) || $target.parents(exclude_classes[i]).length) { - return; - } - } - - //close all open containers (except one - target) - Popup.prototype.closeOthers(e.target); - }); - - $(document).data('editable-handlers-attached', true); - } - }, - - //split options on containerOptions and formOptions - splitOptions: function() { - this.containerOptions = {}; - this.formOptions = {}; - - if(!$.fn[this.containerName]) { - throw new Error(this.containerName + ' not found. Have you included corresponding js file?'); - } - - //keys defined in container defaults go to container, others go to form - for(var k in this.options) { - if(k in this.defaults) { - this.containerOptions[k] = this.options[k]; - } else { - this.formOptions[k] = this.options[k]; - } - } - }, - - /* - Returns jquery object of container - @method tip() - */ - tip: function() { - return this.container() ? this.container().$tip : null; - }, - - /* returns container object */ - container: function() { - var container; - //first, try get it by `containerDataName` - if(this.containerDataName) { - if(container = this.$element.data(this.containerDataName)) { - return container; - } - } - //second, try `containerName` - container = this.$element.data(this.containerName); - return container; - }, - - /* call native method of underlying container, e.g. this.$element.popover('method') */ - call: function() { - this.$element[this.containerName].apply(this.$element, arguments); - }, - - initContainer: function(){ - this.call(this.containerOptions); - }, - - renderForm: function() { - this.$form - .editableform(this.formOptions) - .on({ - save: $.proxy(this.save, this), //click on submit button (value changed) - nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed) - cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on cancel button - show: $.proxy(function() { - if(this.delayedHide) { - this.hide(this.delayedHide.reason); - this.delayedHide = false; - } else { - this.setPosition(); - } - }, this), //re-position container every time form is shown (occurs each time after loading state) - rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown - resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed - rendered: $.proxy(function(){ - /** - Fired when container is shown and form is rendered (for select will wait for loading dropdown options). - **Note:** Bootstrap popover has own `shown` event that now cannot be separated from x-editable's one. - The workaround is to check `arguments.length` that is always `2` for x-editable. - - @event shown - @param {Object} event event object - @example - $('#username').on('shown', function(e, editable) { - editable.input.$input.val('overwriting value of input..'); - }); - **/ - /* - TODO: 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. - */ - this.$element.triggerHandler('shown', $(this.options.scope).data('editable')); - }, this) - }) - .editableform('render'); - }, - - /** - Shows container with form - @method show() - @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true. - **/ - /* Note: poshytip owerwrites this method totally! */ - show: function (closeAll) { - this.$element.addClass('editable-open'); - if(closeAll !== false) { - //close all open containers (except this) - this.closeOthers(this.$element[0]); - } - - //show container itself - this.innerShow(); - this.tip().addClass(this.containerClass); - - /* - Currently, form is re-rendered on every show. - The main reason is that we dont know, what will container do with content when closed: - remove(), detach() or just hide() - it depends on container. - - Detaching form itself before hide and re-insert before show is good solution, - but visually it looks ugly --> container changes size before hide. - */ - - //if form already exist - delete previous data - if(this.$form) { - //todo: destroy prev data! - //this.$form.destroy(); - } - - this.$form = $('<div>'); - - //insert form into container body - if(this.tip().is(this.innerCss)) { - //for inline container - this.tip().append(this.$form); - } else { - this.tip().find(this.innerCss).append(this.$form); - } - - //render form - this.renderForm(); - }, - - /** - Hides container with form - @method hide() - @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code> - **/ - hide: function(reason) { - if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) { - return; - } - - //if form is saving value, schedule hide - if(this.$form.data('editableform').isSaving) { - this.delayedHide = {reason: reason}; - return; - } else { - this.delayedHide = false; - } - - this.$element.removeClass('editable-open'); - this.innerHide(); - - /** - Fired when container was hidden. It occurs on both save or cancel. - **Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one. - The workaround is to check `arguments.length` that is always `2` for x-editable. - - @event hidden - @param {object} event event object - @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|manual</code> - @example - $('#username').on('hidden', function(e, reason) { - if(reason === 'save' || reason === 'cancel') { - //auto-open next editable - $(this).closest('tr').next().find('.editable').editable('show'); - } - }); - **/ - this.$element.triggerHandler('hidden', reason || 'manual'); - }, - - /* internal show method. To be overwritten in child classes */ - innerShow: function () { - - }, - - /* internal hide method. To be overwritten in child classes */ - innerHide: function () { - - }, - - /** - Toggles container visibility (show / hide) - @method toggle() - @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true. - **/ - toggle: function(closeAll) { - if(this.container() && this.tip() && this.tip().is(':visible')) { - this.hide(); - } else { - this.show(closeAll); - } - }, - - /* - Updates the position of container when content changed. - @method setPosition() - */ - setPosition: function() { - //tbd in child class - }, - - save: function(e, params) { - /** - Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> inside handler to access to editableContainer instance - - @event save - @param {Object} event event object - @param {Object} params additional params - @param {mixed} params.newValue submitted value - @param {Object} params.response ajax response - @example - $('#username').on('save', function(e, params) { - //assuming server response: '{success: true}' - var pk = $(this).data('editableContainer').options.pk; - if(params.response && params.response.success) { - alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!'); - } else { - alert('error!'); - } - }); - **/ - this.$element.triggerHandler('save', params); - - //hide must be after trigger, as saving value may require methods of plugin, applied to input - this.hide('save'); - }, - - /** - Sets new option - - @method option(key, value) - @param {string} key - @param {mixed} value - **/ - option: function(key, value) { - this.options[key] = value; - if(key in this.containerOptions) { - this.containerOptions[key] = value; - this.setContainerOption(key, value); - } else { - this.formOptions[key] = value; - if(this.$form) { - this.$form.editableform('option', key, value); - } - } - }, - - setContainerOption: function(key, value) { - this.call('option', key, value); - }, - - /** - Destroys the container instance - @method destroy() - **/ - destroy: function() { - this.hide(); - this.innerDestroy(); - this.$element.off('destroyed'); - this.$element.removeData('editableContainer'); - }, - - /* to be overwritten in child classes */ - innerDestroy: function() { - - }, - - /* - Closes other containers except one related to passed element. - Other containers can be cancelled or submitted (depends on onblur option) - */ - closeOthers: function(element) { - $('.editable-open').each(function(i, el){ - //do nothing with passed element and it's children - if(el === element || $(el).find(element).length) { - return; - } - - //otherwise cancel or submit all open containers - var $el = $(el), - ec = $el.data('editableContainer'); - - if(!ec) { - return; - } - - if(ec.options.onblur === 'cancel') { - $el.data('editableContainer').hide('onblur'); - } else if(ec.options.onblur === 'submit') { - $el.data('editableContainer').tip().find('form').submit(); - } - }); - - }, - - /** - Activates input of visible container (e.g. set focus) - @method activate() - **/ - activate: function() { - if(this.tip && this.tip().is(':visible') && this.$form) { - this.$form.data('editableform').input.activate(); - } - } - - }; - - /** - jQuery method to initialize editableContainer. - - @method $().editableContainer(options) - @params {Object} options - @example - $('#edit').editableContainer({ - type: 'text', - url: '/post', - pk: 1, - value: 'hello' - }); - **/ - $.fn.editableContainer = function (option) { - var args = arguments; - return this.each(function () { - var $this = $(this), - dataKey = 'editableContainer', - data = $this.data(dataKey), - options = typeof option === 'object' && option, - Constructor = (options.mode === 'inline') ? Inline : Popup; - - if (!data) { - $this.data(dataKey, (data = new Constructor(this, options))); - } - - if (typeof option === 'string') { //call method - data[option].apply(data, Array.prototype.slice.call(args, 1)); - } - }); - }; - - //store constructors - $.fn.editableContainer.Popup = Popup; - $.fn.editableContainer.Inline = Inline; - - //defaults - $.fn.editableContainer.defaults = { - /** - Initial value of form input - - @property value - @type mixed - @default null - @private - **/ - value: null, - /** - Placement of container relative to element. Can be <code>top|right|bottom|left</code>. Not used for inline container. - - @property placement - @type string - @default 'top' - **/ - placement: 'top', - /** - Whether to hide container on save/cancel. - - @property autohide - @type boolean - @default true - @private - **/ - autohide: true, - /** - Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>. - Setting <code>ignore</code> allows to have several containers open. - - @property onblur - @type string - @default 'cancel' - @since 1.1.1 - **/ - onblur: 'cancel', - - /** - Animation speed (inline mode only) - @property anim - @type string - @default false - **/ - anim: false, - - /** - Mode of editable, can be `popup` or `inline` - - @property mode - @type string - @default 'popup' - @since 1.4.0 - **/ - mode: 'popup' - }; - - /* - * workaround to have 'destroyed' event to destroy popover when element is destroyed - * see http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom - */ - jQuery.event.special.destroyed = { - remove: function(o) { - if (o.handler) { - o.handler(); - } - } - }; - -}(window.jQuery)); - -/** -* Editable Inline -* --------------------- -*/ -(function ($) { - "use strict"; - - //copy prototype from EditableContainer - //extend methods - $.extend($.fn.editableContainer.Inline.prototype, $.fn.editableContainer.Popup.prototype, { - containerName: 'editableform', - innerCss: '.editable-inline', - containerClass: 'editable-container editable-inline', //css class applied to container element - - initContainer: function(){ - //container is <span> element - this.$tip = $('<span></span>'); - - //convert anim to miliseconds (int) - if(!this.options.anim) { - this.options.anim = 0; - } - }, - - splitOptions: function() { - //all options are passed to form - this.containerOptions = {}; - this.formOptions = this.options; - }, - - tip: function() { - return this.$tip; - }, - - innerShow: function () { - this.$element.hide(); - this.tip().insertAfter(this.$element).show(); - }, - - innerHide: function () { - this.$tip.hide(this.options.anim, $.proxy(function() { - this.$element.show(); - this.innerDestroy(); - }, this)); - }, - - innerDestroy: function() { - if(this.tip()) { - this.tip().empty().remove(); - } - } - }); - -}(window.jQuery)); -/** -Makes editable any HTML element on the page. Applied as jQuery method. - -@class editable -@uses editableContainer -**/ -(function ($) { - "use strict"; - - var Editable = function (element, options) { - this.$element = $(element); - //data-* has more priority over js options: because dynamically created elements may change data-* - this.options = $.extend({}, $.fn.editable.defaults, options, $.fn.editableutils.getConfigData(this.$element)); - if(this.options.selector) { - this.initLive(); - } else { - this.init(); - } - - //check for transition support - if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) { - this.options.highlight = false; - } - }; - - Editable.prototype = { - constructor: Editable, - init: function () { - var isValueByText = false, - doAutotext, finalize; - - //name - this.options.name = this.options.name || this.$element.attr('id'); - - //create input of specified type. Input needed already here to convert value for initial display (e.g. show text by id for select) - //also we set scope option to have access to element inside input specific callbacks (e. g. source as function) - this.options.scope = this.$element[0]; - this.input = $.fn.editableutils.createInput(this.options); - if(!this.input) { - return; - } - - //set value from settings or by element's text - if (this.options.value === undefined || this.options.value === null) { - this.value = this.input.html2value($.trim(this.$element.html())); - isValueByText = true; - } else { - /* - value can be string when received from 'data-value' attribute - for complext objects value can be set as json string in data-value attribute, - e.g. data-value="{city: 'Moscow', street: 'Lenina'}" - */ - this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true); - if(typeof this.options.value === 'string') { - this.value = this.input.str2value(this.options.value); - } else { - this.value = this.options.value; - } - } - - //add 'editable' class to every editable element - this.$element.addClass('editable'); - - //specifically for "textarea" add class .editable-pre-wrapped to keep linebreaks - if(this.input.type === 'textarea') { - this.$element.addClass('editable-pre-wrapped'); - } - - //attach handler activating editable. In disabled mode it just prevent default action (useful for links) - if(this.options.toggle !== 'manual') { - this.$element.addClass('editable-click'); - this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){ - //prevent following link if editable enabled - if(!this.options.disabled) { - e.preventDefault(); - } - - //stop propagation not required because in document click handler it checks event target - //e.stopPropagation(); - - if(this.options.toggle === 'mouseenter') { - //for hover only show container - this.show(); - } else { - //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener - var closeAll = (this.options.toggle !== 'click'); - this.toggle(closeAll); - } - }, this)); - } else { - this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually - } - - //if display is function it's far more convinient to have autotext = always to render correctly on init - //see https://github.com/vitalets/x-editable-yii/issues/34 - if(typeof this.options.display === 'function') { - this.options.autotext = 'always'; - } - - //check conditions for autotext: - switch(this.options.autotext) { - case 'always': - doAutotext = true; - break; - case 'auto': - //if element text is empty and value is defined and value not generated by text --> run autotext - doAutotext = !$.trim(this.$element.text()).length && this.value !== null && this.value !== undefined && !isValueByText; - break; - default: - doAutotext = false; - } - - //depending on autotext run render() or just finilize init - $.when(doAutotext ? this.render() : true).then($.proxy(function() { - if(this.options.disabled) { - this.disable(); - } else { - this.enable(); - } - /** - Fired when element was initialized by `$().editable()` method. - Please note that you should setup `init` handler **before** applying `editable`. - - @event init - @param {Object} event event object - @param {Object} editable editable instance (as here it cannot accessed via data('editable')) - @since 1.2.0 - @example - $('#username').on('init', function(e, editable) { - alert('initialized ' + editable.options.name); - }); - $('#username').editable(); - **/ - this.$element.triggerHandler('init', this); - }, this)); - }, - - /* - Initializes parent element for live editables - */ - initLive: function() { - //store selector - var selector = this.options.selector; - //modify options for child elements - this.options.selector = false; - this.options.autotext = 'never'; - //listen toggle events - this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){ - var $target = $(e.target); - if(!$target.data('editable')) { - //if delegated element initially empty, we need to clear it's text (that was manually set to `empty` by user) - //see https://github.com/vitalets/x-editable/issues/137 - if($target.hasClass(this.options.emptyclass)) { - $target.empty(); - } - $target.editable(this.options).trigger(e); - } - }, this)); - }, - - /* - Renders value into element's text. - Can call custom display method from options. - Can return deferred object. - @method render() - @param {mixed} response server response (if exist) to pass into display function - */ - render: function(response) { - //do not display anything - if(this.options.display === false) { - return; - } - - //if input has `value2htmlFinal` method, we pass callback in third param to be called when source is loaded - if(this.input.value2htmlFinal) { - return this.input.value2html(this.value, this.$element[0], this.options.display, response); - //if display method defined --> use it - } else if(typeof this.options.display === 'function') { - return this.options.display.call(this.$element[0], this.value, response); - //else use input's original value2html() method - } else { - return this.input.value2html(this.value, this.$element[0]); - } - }, - - /** - Enables editable - @method enable() - **/ - enable: function() { - this.options.disabled = false; - this.$element.removeClass('editable-disabled'); - this.handleEmpty(this.isEmpty); - if(this.options.toggle !== 'manual') { - if(this.$element.attr('tabindex') === '-1') { - this.$element.removeAttr('tabindex'); - } - } - }, - - /** - Disables editable - @method disable() - **/ - disable: function() { - this.options.disabled = true; - this.hide(); - this.$element.addClass('editable-disabled'); - this.handleEmpty(this.isEmpty); - //do not stop focus on this element - this.$element.attr('tabindex', -1); - }, - - /** - Toggles enabled / disabled state of editable element - @method toggleDisabled() - **/ - toggleDisabled: function() { - if(this.options.disabled) { - this.enable(); - } else { - this.disable(); - } - }, - - /** - Sets new option - - @method option(key, value) - @param {string|object} key option name or object with several options - @param {mixed} value option new value - @example - $('.editable').editable('option', 'pk', 2); - **/ - option: function(key, value) { - //set option(s) by object - if(key && typeof key === 'object') { - $.each(key, $.proxy(function(k, v){ - this.option($.trim(k), v); - }, this)); - return; - } - - //set option by string - this.options[key] = value; - - //disabled - if(key === 'disabled') { - return value ? this.disable() : this.enable(); - } - - //value - if(key === 'value') { - this.setValue(value); - } - - //transfer new option to container! - if(this.container) { - this.container.option(key, value); - } - - //pass option to input directly (as it points to the same in form) - if(this.input.option) { - this.input.option(key, value); - } - - }, - - /* - * set emptytext if element is empty - */ - handleEmpty: function (isEmpty) { - //do not handle empty if we do not display anything - if(this.options.display === false) { - return; - } - - /* - isEmpty may be set directly as param of method. - It is required when we enable/disable field and can't rely on content - as node content is text: "Empty" that is not empty %) - */ - if(isEmpty !== undefined) { - this.isEmpty = isEmpty; - } else { - //detect empty - //for some inputs we need more smart check - //e.g. wysihtml5 may have <br>, <p></p>, <img> - if(typeof(this.input.isEmpty) === 'function') { - this.isEmpty = this.input.isEmpty(this.$element); - } else { - this.isEmpty = $.trim(this.$element.html()) === ''; - } - } - - //emptytext shown only for enabled - if(!this.options.disabled) { - if (this.isEmpty) { - this.$element.html(this.options.emptytext); - if(this.options.emptyclass) { - this.$element.addClass(this.options.emptyclass); - } - } else if(this.options.emptyclass) { - this.$element.removeClass(this.options.emptyclass); - } - } else { - //below required if element disable property was changed - if(this.isEmpty) { - this.$element.empty(); - if(this.options.emptyclass) { - this.$element.removeClass(this.options.emptyclass); - } - } - } - }, - - /** - Shows container with form - @method show() - @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true. - **/ - show: function (closeAll) { - if(this.options.disabled) { - return; - } - - //init editableContainer: popover, tooltip, inline, etc.. - if(!this.container) { - var containerOptions = $.extend({}, this.options, { - value: this.value, - input: this.input //pass input to form (as it is already created) - }); - this.$element.editableContainer(containerOptions); - //listen `save` event - this.$element.on("save.internal", $.proxy(this.save, this)); - this.container = this.$element.data('editableContainer'); - } else if(this.container.tip().is(':visible')) { - return; - } - - //show container - this.container.show(closeAll); - }, - - /** - Hides container with form - @method hide() - **/ - hide: function () { - if(this.container) { - this.container.hide(); - } - }, - - /** - Toggles container visibility (show / hide) - @method toggle() - @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true. - **/ - toggle: function(closeAll) { - if(this.container && this.container.tip().is(':visible')) { - this.hide(); - } else { - this.show(closeAll); - } - }, - - /* - * called when form was submitted - */ - save: function(e, params) { - //mark element with unsaved class if needed - if(this.options.unsavedclass) { - /* - Add unsaved css to element if: - - url is not user's function - - value was not sent to server - - params.response === undefined, that means data was not sent - - value changed - */ - var sent = false; - sent = sent || typeof this.options.url === 'function'; - sent = sent || this.options.display === false; - sent = sent || params.response !== undefined; - sent = sent || (this.options.savenochange && this.input.value2str(this.value) !== this.input.value2str(params.newValue)); - - if(sent) { - this.$element.removeClass(this.options.unsavedclass); - } else { - this.$element.addClass(this.options.unsavedclass); - } - } - - //highlight when saving - if(this.options.highlight) { - var $e = this.$element, - bgColor = $e.css('background-color'); - - $e.css('background-color', this.options.highlight); - setTimeout(function(){ - if(bgColor === 'transparent') { - bgColor = ''; - } - $e.css('background-color', bgColor); - $e.addClass('editable-bg-transition'); - setTimeout(function(){ - $e.removeClass('editable-bg-transition'); - }, 1700); - }, 10); - } - - //set new value - this.setValue(params.newValue, false, params.response); - - /** - Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance - - @event save - @param {Object} event event object - @param {Object} params additional params - @param {mixed} params.newValue submitted value - @param {Object} params.response ajax response - @example - $('#username').on('save', function(e, params) { - alert('Saved value: ' + params.newValue); - }); - **/ - //event itself is triggered by editableContainer. Description here is only for documentation - }, - - validate: function () { - if (typeof this.options.validate === 'function') { - return this.options.validate.call(this, this.value); - } - }, - - /** - Sets new value of editable - @method setValue(value, convertStr) - @param {mixed} value new value - @param {boolean} convertStr whether to convert value from string to internal format - **/ - setValue: function(value, convertStr, response) { - if(convertStr) { - this.value = this.input.str2value(value); - } else { - this.value = value; - } - if(this.container) { - this.container.option('value', this.value); - } - $.when(this.render(response)) - .then($.proxy(function() { - this.handleEmpty(); - }, this)); - }, - - /** - Activates input of visible container (e.g. set focus) - @method activate() - **/ - activate: function() { - if(this.container) { - this.container.activate(); - } - }, - - /** - Removes editable feature from element - @method destroy() - **/ - destroy: function() { - this.disable(); - - if(this.container) { - this.container.destroy(); - } - - this.input.destroy(); - - if(this.options.toggle !== 'manual') { - this.$element.removeClass('editable-click'); - this.$element.off(this.options.toggle + '.editable'); - } - - this.$element.off("save.internal"); - - this.$element.removeClass('editable editable-open editable-disabled'); - this.$element.removeData('editable'); - } - }; - - /* EDITABLE PLUGIN DEFINITION - * ======================= */ - - /** - jQuery method to initialize editable element. - - @method $().editable(options) - @params {Object} options - @example - $('#username').editable({ - type: 'text', - url: '/post', - pk: 1 - }); - **/ - $.fn.editable = function (option) { - //special API methods returning non-jquery object - var result = {}, args = arguments, datakey = 'editable'; - switch (option) { - /** - Runs client-side validation for all matched editables - - @method validate() - @returns {Object} validation errors map - @example - $('#username, #fullname').editable('validate'); - // possible result: - { - username: "username is required", - fullname: "fullname should be minimum 3 letters length" - } - **/ - case 'validate': - this.each(function () { - var $this = $(this), data = $this.data(datakey), error; - if (data && (error = data.validate())) { - result[data.options.name] = error; - } - }); - return result; - - /** - Returns current values of editable elements. - Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements. - If value of some editable is `null` or `undefined` it is excluded from result object. - When param `isSingle` is set to **true** - it is supposed you have single element and will return value of editable instead of object. - - @method getValue() - @param {bool} isSingle whether to return just value of single element - @returns {Object} object of element names and values - @example - $('#username, #fullname').editable('getValue'); - //result: - { - username: "superuser", - fullname: "John" - } - //isSingle = true - $('#username').editable('getValue', true); - //result "superuser" - **/ - case 'getValue': - if(arguments.length === 2 && arguments[1] === true) { //isSingle = true - result = this.eq(0).data(datakey).value; - } else { - this.each(function () { - var $this = $(this), data = $this.data(datakey); - if (data && data.value !== undefined && data.value !== null) { - result[data.options.name] = data.input.value2submit(data.value); - } - }); - } - return result; - - /** - This method collects values from several editable elements and submit them all to server. - Internally it runs client-side validation for all fields and submits only in case of success. - See <a href="#newrecord">creating new records</a> for details. - Since 1.5.1 `submit` can be applied to single element to send data programmatically. In that case - `url`, `success` and `error` is taken from initial options and you can just call `$('#username').editable('submit')`. - - @method submit(options) - @param {object} options - @param {object} options.url url to submit data - @param {object} options.data additional data to submit - @param {object} options.ajaxOptions additional ajax options - @param {function} options.error(obj) error handler - @param {function} options.success(obj,config) success handler - @returns {Object} jQuery object - **/ - case 'submit': //collects value, validate and submit to server for creating new record - var config = arguments[1] || {}, - $elems = this, - errors = this.editable('validate'); - - // validation ok - if($.isEmptyObject(errors)) { - var ajaxOptions = {}; - - // for single element use url, success etc from options - if($elems.length === 1) { - var editable = $elems.data('editable'); - //standard params - var params = { - name: editable.options.name || '', - value: editable.input.value2submit(editable.value), - pk: (typeof editable.options.pk === 'function') ? - editable.options.pk.call(editable.options.scope) : - editable.options.pk - }; - - //additional params - if(typeof editable.options.params === 'function') { - params = editable.options.params.call(editable.options.scope, params); - } else { - //try parse json in single quotes (from data-params attribute) - editable.options.params = $.fn.editableutils.tryParseJson(editable.options.params, true); - $.extend(params, editable.options.params); - } - - ajaxOptions = { - url: editable.options.url, - data: params, - type: 'POST' - }; - - // use success / error from options - config.success = config.success || editable.options.success; - config.error = config.error || editable.options.error; - - // multiple elements - } else { - var values = this.editable('getValue'); - - ajaxOptions = { - url: config.url, - data: values, - type: 'POST' - }; - } - - // ajax success callabck (response 200 OK) - ajaxOptions.success = typeof config.success === 'function' ? function(response) { - config.success.call($elems, response, config); - } : $.noop; - - // ajax error callabck - ajaxOptions.error = typeof config.error === 'function' ? function() { - config.error.apply($elems, arguments); - } : $.noop; - - // extend ajaxOptions - if(config.ajaxOptions) { - $.extend(ajaxOptions, config.ajaxOptions); - } - - // extra data - if(config.data) { - $.extend(ajaxOptions.data, config.data); - } - - // perform ajax request - $.ajax(ajaxOptions); - } else { //client-side validation error - if(typeof config.error === 'function') { - config.error.call($elems, errors); - } - } - return this; - } - - //return jquery object - return this.each(function () { - var $this = $(this), - data = $this.data(datakey), - options = typeof option === 'object' && option; - - //for delegated targets do not store `editable` object for element - //it's allows several different selectors. - //see: https://github.com/vitalets/x-editable/issues/312 - if(options && options.selector) { - data = new Editable(this, options); - return; - } - - if (!data) { - $this.data(datakey, (data = new Editable(this, options))); - } - - if (typeof option === 'string') { //call method - data[option].apply(data, Array.prototype.slice.call(args, 1)); - } - }); - }; - - - $.fn.editable.defaults = { - /** - Type of input. Can be <code>text|textarea|select|date|checklist</code> and more - - @property type - @type string - @default 'text' - **/ - type: 'text', - /** - Sets disabled state of editable - - @property disabled - @type boolean - @default false - **/ - disabled: false, - /** - How to toggle editable. Can be <code>click|dblclick|mouseenter|manual</code>. - When set to <code>manual</code> you should manually call <code>show/hide</code> methods of editable. - **Note**: if you call <code>show</code> or <code>toggle</code> inside **click** handler of some DOM element, - you need to apply <code>e.stopPropagation()</code> because containers are being closed on any click on document. - - @example - $('#edit-button').click(function(e) { - e.stopPropagation(); - $('#username').editable('toggle'); - }); - - @property toggle - @type string - @default 'click' - **/ - toggle: 'click', - /** - Text shown when element is empty. - - @property emptytext - @type string - @default 'Empty' - **/ - emptytext: 'Empty', - /** - Allows to automatically set element's text based on it's value. Can be <code>auto|always|never</code>. Useful for select and date. - For example, if dropdown list is <code>{1: 'a', 2: 'b'}</code> and element's value set to <code>1</code>, it's html will be automatically set to <code>'a'</code>. - <code>auto</code> - text will be automatically set only if element is empty. - <code>always|never</code> - always(never) try to set element's text. - - @property autotext - @type string - @default 'auto' - **/ - autotext: 'auto', - /** - Initial value of input. If not set, taken from element's text. - Note, that if element's text is empty - text is automatically generated from value and can be customized (see `autotext` option). - For example, to display currency sign: - @example - <a id="price" data-type="text" data-value="100"></a> - <script> - $('#price').editable({ - ... - display: function(value) { - $(this).text(value + '$'); - } - }) - </script> - - @property value - @type mixed - @default element's text - **/ - value: null, - /** - Callback to perform custom displaying of value in element's text. - If `null`, default input's display used. - If `false`, no displaying methods will be called, element's text will never change. - Runs under element's scope. - _**Parameters:**_ - - * `value` current value to be displayed - * `response` server response (if display called after ajax submit), since 1.4.0 - - For _inputs with source_ (select, checklist) parameters are different: - - * `value` current value to be displayed - * `sourceData` array of items for current input (e.g. dropdown items) - * `response` server response (if display called after ajax submit), since 1.4.0 - - To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`. - - @property display - @type function|boolean - @default null - @since 1.2.0 - @example - display: function(value, sourceData) { - //display checklist as comma-separated values - var html = [], - checked = $.fn.editableutils.itemsByValue(value, sourceData); - - if(checked.length) { - $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); }); - $(this).html(html.join(', ')); - } else { - $(this).empty(); - } - } - **/ - display: null, - /** - Css class applied when editable text is empty. - - @property emptyclass - @type string - @since 1.4.1 - @default editable-empty - **/ - emptyclass: 'editable-empty', - /** - Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`). - You may set it to `null` if you work with editables locally and submit them together. - - @property unsavedclass - @type string - @since 1.4.1 - @default editable-unsaved - **/ - unsavedclass: 'editable-unsaved', - /** - If selector is provided, editable will be delegated to the specified targets. - Usefull for dynamically generated DOM elements. - **Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options, - as they actually become editable only after first click. - You should manually set class `editable-click` to these elements. - Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element: - - @property selector - @type string - @since 1.4.1 - @default null - @example - <div id="user"> - <!-- empty --> - <a href="#" data-name="username" data-type="text" class="editable-click editable-empty" data-value="" title="Username">Empty</a> - <!-- non-empty --> - <a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" class="editable-click" title="Group">Operator</a> - </div> - - <script> - $('#user').editable({ - selector: 'a', - url: '/post', - pk: 1 - }); - </script> - **/ - selector: null, - /** - Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers. - - @property highlight - @type string|boolean - @since 1.4.5 - @default #FFFF80 - **/ - highlight: '#FFFF80' - }; - -}(window.jQuery)); - -/** -AbstractInput - base class for all editable inputs. -It defines interface to be implemented by any input type. -To create your own input you can inherit from this class. - -@class abstractinput -**/ -(function ($) { - "use strict"; - - //types - $.fn.editabletypes = {}; - - var AbstractInput = function () { }; - - AbstractInput.prototype = { - /** - Initializes input - - @method init() - **/ - init: function(type, options, defaults) { - this.type = type; - this.options = $.extend({}, defaults, options); - }, - - /* - this method called before render to init $tpl that is inserted in DOM - */ - prerender: function() { - this.$tpl = $(this.options.tpl); //whole tpl as jquery object - this.$input = this.$tpl; //control itself, can be changed in render method - this.$clear = null; //clear button - this.error = null; //error message, if input cannot be rendered - }, - - /** - Renders input from tpl. Can return jQuery deferred object. - Can be overwritten in child objects - - @method render() - **/ - render: function() { - - }, - - /** - Sets element's html by value. - - @method value2html(value, element) - @param {mixed} value - @param {DOMElement} element - **/ - value2html: function(value, element) { - $(element)[this.options.escape ? 'text' : 'html']($.trim(value)); - }, - - /** - Converts element's html to value - - @method html2value(html) - @param {string} html - @returns {mixed} - **/ - html2value: function(html) { - return $('<div>').html(html).text(); - }, - - /** - Converts value to string (for internal compare). For submitting to server used value2submit(). - - @method value2str(value) - @param {mixed} value - @returns {string} - **/ - value2str: function(value) { - return value; - }, - - /** - Converts string received from server into value. Usually from `data-value` attribute. - - @method str2value(str) - @param {string} str - @returns {mixed} - **/ - str2value: function(str) { - return str; - }, - - /** - Converts value for submitting to server. Result can be string or object. - - @method value2submit(value) - @param {mixed} value - @returns {mixed} - **/ - value2submit: function(value) { - return value; - }, - - /** - Sets value of input. - - @method value2input(value) - @param {mixed} value - **/ - value2input: function(value) { - this.$input.val(value); - }, - - /** - Returns value of input. Value can be object (e.g. datepicker) - - @method input2value() - **/ - input2value: function() { - return this.$input.val(); - }, - - /** - Activates input. For text it sets focus. - - @method activate() - **/ - activate: function() { - if(this.$input.is(':visible')) { - this.$input.focus(); - } - }, - - /** - Creates input. - - @method clear() - **/ - clear: function() { - this.$input.val(null); - }, - - /** - method to escape html. - **/ - escape: function(str) { - return $('<div>').text(str).html(); - }, - - /** - attach handler to automatically submit form when value changed (useful when buttons not shown) - **/ - autosubmit: function() { - - }, - - /** - Additional actions when destroying element - **/ - destroy: function() { - }, - - // -------- helper functions -------- - setClass: function() { - if(this.options.inputclass) { - this.$input.addClass(this.options.inputclass); - } - }, - - setAttr: function(attr) { - if (this.options[attr] !== undefined && this.options[attr] !== null) { - this.$input.attr(attr, this.options[attr]); - } - }, - - option: function(key, value) { - this.options[key] = value; - } - - }; - - AbstractInput.defaults = { - /** - HTML template of input. Normally you should not change it. - - @property tpl - @type string - @default '' - **/ - tpl: '', - /** - CSS class automatically applied to input - - @property inputclass - @type string - @default null - **/ - inputclass: null, - - /** - If `true` - html will be escaped in content of element via $.text() method. - If `false` - html will not be escaped, $.html() used. - When you use own `display` function, this option obviosly has no effect. - - @property escape - @type boolean - @since 1.5.0 - @default true - **/ - escape: true, - - //scope for external methods (e.g. source defined as function) - //for internal use only - scope: null, - - //need to re-declare showbuttons here to get it's value from common config (passed only options existing in defaults) - showbuttons: true - }; - - $.extend($.fn.editabletypes, {abstractinput: AbstractInput}); - -}(window.jQuery)); - -/** -List - abstract class for inputs that have source option loaded from js array or via ajax - -@class list -@extends abstractinput -**/ -(function ($) { - "use strict"; - - var List = function (options) { - - }; - - $.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput); - - $.extend(List.prototype, { - render: function () { - - var deferred = $.Deferred(); - - this.error = null; - this.onSourceReady(function () { - this.renderList(); - deferred.resolve(); - }, function () { - this.error = this.options.sourceError; - deferred.resolve(); - }); - - return deferred.promise(); - }, - - html2value: function (html) { - return null; //can't set value by text - }, - - value2html: function (value, element, display, response) { - var deferred = $.Deferred(), - success = function () { - if(typeof display === 'function') { - //custom display method - display.call(element, value, this.sourceData, response); - } else { - this.value2htmlFinal(value, element); - } - deferred.resolve(); - }; - - //for null value just call success without loading source - if(value === null) { - success.call(this); - } else { - this.onSourceReady(success, function () { deferred.resolve(); }); - } - - return deferred.promise(); - }, - - // ------------- additional functions ------------ - - onSourceReady: function (success, error) { - //run source if it function - var source; - if ($.isFunction(this.options.source)) { - source = this.options.source.call(this.options.scope); - this.sourceData = null; - //note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed - } else { - source = this.options.source; - } - - //if allready loaded just call success - if(this.options.sourceCache && $.isArray(this.sourceData)) { - success.call(this); - return; - } - - //try parse json in single quotes (for double quotes jquery does automatically) - try { - source = $.fn.editableutils.tryParseJson(source, false); - } catch (e) { - error.call(this); - return; - } - - //loading from url - if (typeof source === 'string') { - //try to get sourceData from cache - if(this.options.sourceCache) { - var cacheID = source, - cache; - - if (!$(document).data(cacheID)) { - $(document).data(cacheID, {}); - } - cache = $(document).data(cacheID); - - //check for cached data - if (cache.loading === false && cache.sourceData) { //take source from cache - this.sourceData = cache.sourceData; - this.doPrepend(); - success.call(this); - return; - } else if (cache.loading === true) { //cache is loading, put callback in stack to be called later - cache.callbacks.push($.proxy(function () { - this.sourceData = cache.sourceData; - this.doPrepend(); - success.call(this); - }, this)); - - //also collecting error callbacks - cache.err_callbacks.push($.proxy(error, this)); - return; - } else { //no cache yet, activate it - cache.loading = true; - cache.callbacks = []; - cache.err_callbacks = []; - } - } - - //ajaxOptions for source. Can be overwritten bt options.sourceOptions - var ajaxOptions = $.extend({ - url: source, - type: 'get', - cache: false, - dataType: 'json', - success: $.proxy(function (data) { - if(cache) { - cache.loading = false; - } - this.sourceData = this.makeArray(data); - if($.isArray(this.sourceData)) { - if(cache) { - //store result in cache - cache.sourceData = this.sourceData; - //run success callbacks for other fields waiting for this source - $.each(cache.callbacks, function () { this.call(); }); - } - this.doPrepend(); - success.call(this); - } else { - error.call(this); - if(cache) { - //run error callbacks for other fields waiting for this source - $.each(cache.err_callbacks, function () { this.call(); }); - } - } - }, this), - error: $.proxy(function () { - error.call(this); - if(cache) { - cache.loading = false; - //run error callbacks for other fields - $.each(cache.err_callbacks, function () { this.call(); }); - } - }, this) - }, this.options.sourceOptions); - - //loading sourceData from server - $.ajax(ajaxOptions); - - } else { //options as json/array - this.sourceData = this.makeArray(source); - - if($.isArray(this.sourceData)) { - this.doPrepend(); - success.call(this); - } else { - error.call(this); - } - } - }, - - doPrepend: function () { - if(this.options.prepend === null || this.options.prepend === undefined) { - return; - } - - if(!$.isArray(this.prependData)) { - //run prepend if it is function (once) - if ($.isFunction(this.options.prepend)) { - this.options.prepend = this.options.prepend.call(this.options.scope); - } - - //try parse json in single quotes - this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true); - - //convert prepend from string to object - if (typeof this.options.prepend === 'string') { - this.options.prepend = {'': this.options.prepend}; - } - - this.prependData = this.makeArray(this.options.prepend); - } - - if($.isArray(this.prependData) && $.isArray(this.sourceData)) { - this.sourceData = this.prependData.concat(this.sourceData); - } - }, - - /* - renders input list - */ - renderList: function() { - // this method should be overwritten in child class - }, - - /* - set element's html by value - */ - value2htmlFinal: function(value, element) { - // this method should be overwritten in child class - }, - - /** - * convert data to array suitable for sourceData, e.g. [{value: 1, text: 'abc'}, {...}] - */ - makeArray: function(data) { - var count, obj, result = [], item, iterateItem; - if(!data || typeof data === 'string') { - return null; - } - - if($.isArray(data)) { //array - /* - function to iterate inside item of array if item is object. - Caclulates count of keys in item and store in obj. - */ - iterateItem = function (k, v) { - obj = {value: k, text: v}; - if(count++ >= 2) { - return false;// exit from `each` if item has more than one key. - } - }; - - for(var i = 0; i < data.length; i++) { - item = data[i]; - if(typeof item === 'object') { - count = 0; //count of keys inside item - $.each(item, iterateItem); - //case: [{val1: 'text1'}, {val2: 'text2} ...] - if(count === 1) { - result.push(obj); - //case: [{value: 1, text: 'text1'}, {value: 2, text: 'text2'}, ...] - } else if(count > 1) { - //removed check of existance: item.hasOwnProperty('value') && item.hasOwnProperty('text') - if(item.children) { - item.children = this.makeArray(item.children); - } - result.push(item); - } - } else { - //case: ['text1', 'text2' ...] - result.push({value: item, text: item}); - } - } - } else { //case: {val1: 'text1', val2: 'text2, ...} - $.each(data, function (k, v) { - result.push({value: k, text: v}); - }); - } - return result; - }, - - option: function(key, value) { - this.options[key] = value; - if(key === 'source') { - this.sourceData = null; - } - if(key === 'prepend') { - this.prependData = null; - } - } - - }); - - List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { - /** - Source data for list. - If **array** - it should be in format: `[{value: 1, text: "text1"}, {value: 2, text: "text2"}, ...]` - For compability, object format is also supported: `{"1": "text1", "2": "text2" ...}` but it does not guarantee elements order. - - If **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. - - If **function**, it should return data in format above (since 1.4.0). - - Since 1.4.1 key `children` supported to render OPTGROUP (for **select** input only). - `[{text: "group1", children: [{value: 1, text: "text1"}, {value: 2, text: "text2"}]}, ...]` - - - @property source - @type string | array | object | function - @default null - **/ - source: null, - /** - Data automatically prepended to the beginning of dropdown list. - - @property prepend - @type string | array | object | function - @default false - **/ - prepend: false, - /** - Error message when list cannot be loaded (e.g. ajax error) - - @property sourceError - @type string - @default Error when loading list - **/ - sourceError: 'Error when loading list', - /** - if <code>true</code> and source is **string url** - results will be cached for fields with the same source. - Usefull for editable column in grid to prevent extra requests. - - @property sourceCache - @type boolean - @default true - @since 1.2.0 - **/ - sourceCache: true, - /** - Additional ajax options to be used in $.ajax() when loading list from server. - Useful to send extra parameters (`data` key) or change request method (`type` key). - - @property sourceOptions - @type object|function - @default null - @since 1.5.0 - **/ - sourceOptions: null - }); - - $.fn.editabletypes.list = List; - -}(window.jQuery)); - -/** -Text input - -@class text -@extends abstractinput -@final -@example -<a href="#" id="username" data-type="text" data-pk="1">awesome</a> -<script> -$(function(){ - $('#username').editable({ - url: '/post', - title: 'Enter username' - }); -}); -</script> -**/ -(function ($) { - "use strict"; - - var Text = function (options) { - this.init('text', options, Text.defaults); - }; - - $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput); - - $.extend(Text.prototype, { - render: function() { - this.renderClear(); - this.setClass(); - this.setAttr('placeholder'); - }, - - activate: function() { - if(this.$input.is(':visible')) { - this.$input.focus(); - $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length); - if(this.toggleClear) { - this.toggleClear(); - } - } - }, - - //render clear button - renderClear: function() { - if (this.options.clear) { - this.$clear = $('<span class="editable-clear-x"></span>'); - this.$input.after(this.$clear) - .css('padding-right', 24) - .keyup($.proxy(function(e) { - //arrows, enter, tab, etc - if(~$.inArray(e.keyCode, [40,38,9,13,27])) { - return; - } - - clearTimeout(this.t); - var that = this; - this.t = setTimeout(function() { - that.toggleClear(e); - }, 100); - - }, this)) - .parent().css('position', 'relative'); - - this.$clear.click($.proxy(this.clear, this)); - } - }, - - postrender: function() { - /* - //now `clear` is positioned via css - if(this.$clear) { - //can position clear button only here, when form is shown and height can be calculated -// var h = this.$input.outerHeight(true) || 20, - var h = this.$clear.parent().height(), - delta = (h - this.$clear.height()) / 2; - - //this.$clear.css({bottom: delta, right: delta}); - } - */ - }, - - //show / hide clear button - toggleClear: function(e) { - if(!this.$clear) { - return; - } - - var len = this.$input.val().length, - visible = this.$clear.is(':visible'); - - if(len && !visible) { - this.$clear.show(); - } - - if(!len && visible) { - this.$clear.hide(); - } - }, - - clear: function() { - this.$clear.hide(); - this.$input.val('').focus(); - } - }); - - Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { - /** - @property tpl - @default <input type="text"> - **/ - tpl: '<input type="text">', - /** - Placeholder attribute of input. Shown when input is empty. - - @property placeholder - @type string - @default null - **/ - placeholder: null, - - /** - Whether to show `clear` button - - @property clear - @type boolean - @default true - **/ - clear: true - }); - - $.fn.editabletypes.text = Text; - -}(window.jQuery)); - -/** -Textarea input - -@class textarea -@extends abstractinput -@final -@example -<a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a> -<script> -$(function(){ - $('#comments').editable({ - url: '/post', - title: 'Enter comments', - rows: 10 - }); -}); -</script> -**/ -(function ($) { - "use strict"; - - var Textarea = function (options) { - this.init('textarea', options, Textarea.defaults); - }; - - $.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput); - - $.extend(Textarea.prototype, { - render: function () { - this.setClass(); - this.setAttr('placeholder'); - this.setAttr('rows'); - - //ctrl + enter - this.$input.keydown(function (e) { - if (e.ctrlKey && e.which === 13) { - $(this).closest('form').submit(); - } - }); - }, - - //using `white-space: pre-wrap` solves \n <--> BR conversion very elegant! - /* - value2html: function(value, element) { - var html = '', lines; - if(value) { - lines = value.split("\n"); - for (var i = 0; i < lines.length; i++) { - lines[i] = $('<div>').text(lines[i]).html(); - } - html = lines.join('<br>'); - } - $(element).html(html); - }, - - html2value: function(html) { - if(!html) { - return ''; - } - - var regex = new RegExp(String.fromCharCode(10), 'g'); - var lines = html.split(/<br\s*\/?>/i); - for (var i = 0; i < lines.length; i++) { - var text = $('<div>').html(lines[i]).text(); - - // Remove newline characters (\n) to avoid them being converted by value2html() method - // thus adding extra <br> tags - text = text.replace(regex, ''); - - lines[i] = text; - } - return lines.join("\n"); - }, - */ - activate: function() { - $.fn.editabletypes.text.prototype.activate.call(this); - } - }); - - Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { - /** - @property tpl - @default <textarea></textarea> - **/ - tpl:'<textarea></textarea>', - /** - @property inputclass - @default input-large - **/ - inputclass: 'input-large', - /** - Placeholder attribute of input. Shown when input is empty. - - @property placeholder - @type string - @default null - **/ - placeholder: null, - /** - Number of rows in textarea - - @property rows - @type integer - @default 7 - **/ - rows: 7 - }); - - $.fn.editabletypes.textarea = Textarea; - -}(window.jQuery)); - -/** -Select (dropdown) - -@class select -@extends list -@final -@example -<a href="#" id="status" data-type="select" data-pk="1" data-url="/post" data-title="Select status"></a> -<script> -$(function(){ - $('#status').editable({ - value: 2, - source: [ - {value: 1, text: 'Active'}, - {value: 2, text: 'Blocked'}, - {value: 3, text: 'Deleted'} - ] - }); -}); -</script> -**/ -(function ($) { - "use strict"; - - var Select = function (options) { - this.init('select', options, Select.defaults); - }; - - $.fn.editableutils.inherit(Select, $.fn.editabletypes.list); - - $.extend(Select.prototype, { - renderList: function() { - this.$input.empty(); - - var fillItems = function($el, data) { - var attr; - if($.isArray(data)) { - for(var i=0; i<data.length; i++) { - attr = {}; - if(data[i].children) { - attr.label = data[i].text; - $el.append(fillItems($('<optgroup>', attr), data[i].children)); - } else { - attr.value = data[i].value; - if(data[i].disabled) { - attr.disabled = true; - } - $el.append($('<option>', attr).text(data[i].text)); - } - } - } - return $el; - }; - - fillItems(this.$input, this.sourceData); - - this.setClass(); - - //enter submit - this.$input.on('keydown.editable', function (e) { - if (e.which === 13) { - $(this).closest('form').submit(); - } - }); - }, - - value2htmlFinal: function(value, element) { - var text = '', - items = $.fn.editableutils.itemsByValue(value, this.sourceData); - - if(items.length) { - text = items[0].text; - } - - //$(element).text(text); - $.fn.editabletypes.abstractinput.prototype.value2html.call(this, text, element); - }, - - autosubmit: function() { - this.$input.off('keydown.editable').on('change.editable', function(){ - $(this).closest('form').submit(); - }); - } - }); - - Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, { - /** - @property tpl - @default <select></select> - **/ - tpl:'<select></select>' - }); - - $.fn.editabletypes.select = Select; - -}(window.jQuery)); - -/** -List of checkboxes. -Internally value stored as javascript array of values. - -@class checklist -@extends list -@final -@example -<a href="#" id="options" data-type="checklist" data-pk="1" data-url="/post" data-title="Select options"></a> -<script> -$(function(){ - $('#options').editable({ - value: [2, 3], - source: [ - {value: 1, text: 'option1'}, - {value: 2, text: 'option2'}, - {value: 3, text: 'option3'} - ] - }); -}); -</script> -**/ -(function ($) { - "use strict"; - - var Checklist = function (options) { - this.init('checklist', options, Checklist.defaults); - }; - - $.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list); - - $.extend(Checklist.prototype, { - renderList: function() { - var $label, $div; - - this.$tpl.empty(); - - if(!$.isArray(this.sourceData)) { - return; - } - - for(var i=0; i<this.sourceData.length; i++) { - $label = $('<label>').append($('<input>', { - type: 'checkbox', - value: this.sourceData[i].value - })) - .append($('<span>').text(' '+this.sourceData[i].text)); - - $('<div>').append($label).appendTo(this.$tpl); - } - - this.$input = this.$tpl.find('input[type="checkbox"]'); - this.setClass(); - }, - - value2str: function(value) { - return $.isArray(value) ? value.sort().join($.trim(this.options.separator)) : ''; - }, - - //parse separated string - str2value: function(str) { - var reg, value = null; - if(typeof str === 'string' && str.length) { - reg = new RegExp('\\s*'+$.trim(this.options.separator)+'\\s*'); - value = str.split(reg); - } else if($.isArray(str)) { - value = str; - } else { - value = [str]; - } - return value; - }, - - //set checked on required checkboxes - value2input: function(value) { - this.$input.prop('checked', false); - if($.isArray(value) && value.length) { - this.$input.each(function(i, el) { - var $el = $(el); - // cannot use $.inArray as it performs strict comparison - $.each(value, function(j, val){ - /*jslint eqeq: true*/ - if($el.val() == val) { - /*jslint eqeq: false*/ - $el.prop('checked', true); - } - }); - }); - } - }, - - input2value: function() { - var checked = []; - this.$input.filter(':checked').each(function(i, el) { - checked.push($(el).val()); - }); - return checked; - }, - - //collect text of checked boxes - value2htmlFinal: function(value, element) { - var html = [], - checked = $.fn.editableutils.itemsByValue(value, this.sourceData), - escape = this.options.escape; - - if(checked.length) { - $.each(checked, function(i, v) { - var text = escape ? $.fn.editableutils.escape(v.text) : v.text; - html.push(text); - }); - $(element).html(html.join('<br>')); - } else { - $(element).empty(); - } - }, - - activate: function() { - this.$input.first().focus(); - }, - - autosubmit: function() { - this.$input.on('keydown', function(e){ - if (e.which === 13) { - $(this).closest('form').submit(); - } - }); - } - }); - - Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, { - /** - @property tpl - @default <div></div> - **/ - tpl:'<div class="editable-checklist"></div>', - - /** - @property inputclass - @type string - @default null - **/ - inputclass: null, - - /** - Separator of values when reading from `data-value` attribute - - @property separator - @type string - @default ',' - **/ - separator: ',' - }); - - $.fn.editabletypes.checklist = Checklist; - -}(window.jQuery)); - -/** -HTML5 input types. -Following types are supported: - -* password -* email -* url -* tel -* number -* range -* time - -Learn more about html5 inputs: -http://www.w3.org/wiki/HTML5_form_additions -To check browser compatibility please see: -https://developer.mozilla.org/en-US/docs/HTML/Element/Input - -@class html5types -@extends text -@final -@since 1.3.0 -@example -<a href="#" id="email" data-type="email" data-pk="1">admin@example.com</a> -<script> -$(function(){ - $('#email').editable({ - url: '/post', - title: 'Enter email' - }); -}); -</script> -**/ - -/** -@property tpl -@default depends on type -**/ - -/* -Password -*/ -(function ($) { - "use strict"; - - var Password = function (options) { - this.init('password', options, Password.defaults); - }; - $.fn.editableutils.inherit(Password, $.fn.editabletypes.text); - $.extend(Password.prototype, { - //do not display password, show '[hidden]' instead - value2html: function(value, element) { - if(value) { - $(element).text('[hidden]'); - } else { - $(element).empty(); - } - }, - //as password not displayed, should not set value by html - html2value: function(html) { - return null; - } - }); - Password.defaults = $.extend({}, $.fn.editabletypes.text.defaults, { - tpl: '<input type="password">' - }); - $.fn.editabletypes.password = Password; -}(window.jQuery)); - - -/* -Email -*/ -(function ($) { - "use strict"; - - var Email = function (options) { - this.init('email', options, Email.defaults); - }; - $.fn.editableutils.inherit(Email, $.fn.editabletypes.text); - Email.defaults = $.extend({}, $.fn.editabletypes.text.defaults, { - tpl: '<input type="email">' - }); - $.fn.editabletypes.email = Email; -}(window.jQuery)); - - -/* -Url -*/ -(function ($) { - "use strict"; - - var Url = function (options) { - this.init('url', options, Url.defaults); - }; - $.fn.editableutils.inherit(Url, $.fn.editabletypes.text); - Url.defaults = $.extend({}, $.fn.editabletypes.text.defaults, { - tpl: '<input type="url">' - }); - $.fn.editabletypes.url = Url; -}(window.jQuery)); - - -/* -Tel -*/ -(function ($) { - "use strict"; - - var Tel = function (options) { - this.init('tel', options, Tel.defaults); - }; - $.fn.editableutils.inherit(Tel, $.fn.editabletypes.text); - Tel.defaults = $.extend({}, $.fn.editabletypes.text.defaults, { - tpl: '<input type="tel">' - }); - $.fn.editabletypes.tel = Tel; -}(window.jQuery)); - - -/* -Number -*/ -(function ($) { - "use strict"; - - var NumberInput = function (options) { - this.init('number', options, NumberInput.defaults); - }; - $.fn.editableutils.inherit(NumberInput, $.fn.editabletypes.text); - $.extend(NumberInput.prototype, { - render: function () { - NumberInput.superclass.render.call(this); - this.setAttr('min'); - this.setAttr('max'); - this.setAttr('step'); - }, - postrender: function() { - if(this.$clear) { - //increase right ffset for up/down arrows - this.$clear.css({right: 24}); - /* - //can position clear button only here, when form is shown and height can be calculated - var h = this.$input.outerHeight(true) || 20, - delta = (h - this.$clear.height()) / 2; - - //add 12px to offset right for up/down arrows - this.$clear.css({top: delta, right: delta + 16}); - */ - } - } - }); - NumberInput.defaults = $.extend({}, $.fn.editabletypes.text.defaults, { - tpl: '<input type="number">', - inputclass: 'input-mini', - min: null, - max: null, - step: null - }); - $.fn.editabletypes.number = NumberInput; -}(window.jQuery)); - - -/* -Range (inherit from number) -*/ -(function ($) { - "use strict"; - - var Range = function (options) { - this.init('range', options, Range.defaults); - }; - $.fn.editableutils.inherit(Range, $.fn.editabletypes.number); - $.extend(Range.prototype, { - render: function () { - this.$input = this.$tpl.filter('input'); - - this.setClass(); - this.setAttr('min'); - this.setAttr('max'); - this.setAttr('step'); - - this.$input.on('input', function(){ - $(this).siblings('output').text($(this).val()); - }); - }, - activate: function() { - this.$input.focus(); - } - }); - Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, { - tpl: '<input type="range"><output style="width: 30px; display: inline-block"></output>', - inputclass: 'input-medium' - }); - $.fn.editabletypes.range = Range; -}(window.jQuery)); - -/* -Time -*/ -(function ($) { - "use strict"; - - var Time = function (options) { - this.init('time', options, Time.defaults); - }; - //inherit from abstract, as inheritance from text gives selection error. - $.fn.editableutils.inherit(Time, $.fn.editabletypes.abstractinput); - $.extend(Time.prototype, { - render: function() { - this.setClass(); - } - }); - Time.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { - tpl: '<input type="time">' - }); - $.fn.editabletypes.time = Time; -}(window.jQuery)); - -/** -Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2. -Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options. - -You should manually download and include select2 distributive: - - <link href="select2/select2.css" rel="stylesheet" type="text/css"></link> - <script src="select2/select2.js"></script> - -To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css): - - <link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link> - -**Note:** currently `autotext` feature does not work for select2 with `ajax` remote source. -You need initially put both `data-value` and element's text youself: - - <a href="#" data-type="select2" data-value="1">Text1</a> - - -@class select2 -@extends abstractinput -@since 1.4.1 -@final -@example -<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a> -<script> -$(function(){ - //local source - $('#country').editable({ - source: [ - {id: 'gb', text: 'Great Britain'}, - {id: 'us', text: 'United States'}, - {id: 'ru', text: 'Russia'} - ], - select2: { - multiple: true - } - }); - //remote source (simple) - $('#country').editable({ - source: '/getCountries', - select2: { - placeholder: 'Select Country', - minimumInputLength: 1 - } - }); - //remote source (advanced) - $('#country').editable({ - select2: { - placeholder: 'Select Country', - allowClear: true, - minimumInputLength: 3, - id: function (item) { - return item.CountryId; - }, - ajax: { - url: '/getCountries', - dataType: 'json', - data: function (term, page) { - return { query: term }; - }, - results: function (data, page) { - return { results: data }; - } - }, - formatResult: function (item) { - return item.CountryName; - }, - formatSelection: function (item) { - return item.CountryName; - }, - initSelection: function (element, callback) { - return $.get('/getCountryById', { query: element.val() }, function (data) { - callback(data); - }); - } - } - }); -}); -</script> -**/ -(function ($) { - "use strict"; - - var Constructor = function (options) { - this.init('select2', options, Constructor.defaults); - - options.select2 = options.select2 || {}; - - this.sourceData = null; - - //placeholder - if(options.placeholder) { - options.select2.placeholder = options.placeholder; - } - - //if not `tags` mode, use source - if(!options.select2.tags && options.source) { - var source = options.source; - //if source is function, call it (once!) - if ($.isFunction(options.source)) { - source = options.source.call(options.scope); - } - - if (typeof source === 'string') { - options.select2.ajax = options.select2.ajax || {}; - //some default ajax params - if(!options.select2.ajax.data) { - options.select2.ajax.data = function(term) {return { query:term };}; - } - if(!options.select2.ajax.results) { - options.select2.ajax.results = function(data) { return {results:data };}; - } - options.select2.ajax.url = source; - } else { - //check format and convert x-editable format to select2 format (if needed) - this.sourceData = this.convertSource(source); - options.select2.data = this.sourceData; - } - } - - //overriding objects in config (as by default jQuery extend() is not recursive) - this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2); - - //detect whether it is multi-valued - this.isMultiple = this.options.select2.tags || this.options.select2.multiple; - this.isRemote = ('ajax' in this.options.select2); - - //store function returning ID of item - //should be here as used inautotext for local source - this.idFunc = this.options.select2.id; - if (typeof(this.idFunc) !== "function") { - var idKey = this.idFunc || 'id'; - this.idFunc = function (e) { return e[idKey]; }; - } - - //store function that renders text in select2 - this.formatSelection = this.options.select2.formatSelection; - if (typeof(this.formatSelection) !== "function") { - this.formatSelection = function (e) { return e.text; }; - } - }; - - $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput); - - $.extend(Constructor.prototype, { - render: function() { - this.setClass(); - - //can not apply select2 here as it calls initSelection - //over input that does not have correct value yet. - //apply select2 only in value2input - //this.$input.select2(this.options.select2); - - //when data is loaded via ajax, we need to know when it's done to populate listData - if(this.isRemote) { - //listen to loaded event to populate data - this.$input.on('select2-loaded', $.proxy(function(e) { - this.sourceData = e.items.results; - }, this)); - } - - //trigger resize of editableform to re-position container in multi-valued mode - if(this.isMultiple) { - this.$input.on('change', function() { - $(this).closest('form').parent().triggerHandler('resize'); - }); - } - }, - - value2html: function(value, element) { - var text = '', data, - that = this; - - if(this.options.select2.tags) { //in tags mode just assign value - data = value; - //data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc); - } else if(this.sourceData) { - data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc); - } else { - //can not get list of possible values - //(e.g. autotext for select2 with ajax source) - } - - //data may be array (when multiple values allowed) - if($.isArray(data)) { - //collect selected data and show with separator - text = []; - $.each(data, function(k, v){ - text.push(v && typeof v === 'object' ? that.formatSelection(v) : v); - }); - } else if(data) { - text = that.formatSelection(data); - } - - text = $.isArray(text) ? text.join(this.options.viewseparator) : text; - - //$(element).text(text); - Constructor.superclass.value2html.call(this, text, element); - }, - - html2value: function(html) { - return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null; - }, - - value2input: function(value) { - // if value array => join it anyway - if($.isArray(value)) { - value = value.join(this.getSeparator()); - } - - //for remote source just set value, text is updated by initSelection - if(!this.$input.data('select2')) { - this.$input.val(value); - this.$input.select2(this.options.select2); - } else { - //second argument needed to separate initial change from user's click (for autosubmit) - this.$input.val(value).trigger('change', true); - - //Uncaught Error: cannot call val() if initSelection() is not defined - //this.$input.select2('val', value); - } - - // if defined remote source AND no multiple mode AND no user's initSelection provided --> - // we should somehow get text for provided id. - // The solution is to use element's text as text for that id (exclude empty) - if(this.isRemote && !this.isMultiple && !this.options.select2.initSelection) { - // customId and customText are methods to extract `id` and `text` from data object - // we can use this workaround only if user did not define these methods - // otherwise we cant construct data object - var customId = this.options.select2.id, - customText = this.options.select2.formatSelection; - - if(!customId && !customText) { - var $el = $(this.options.scope); - if (!$el.data('editable').isEmpty) { - var data = {id: value, text: $el.text()}; - this.$input.select2('data', data); - } - } - } - }, - - input2value: function() { - return this.$input.select2('val'); - }, - - str2value: function(str, separator) { - if(typeof str !== 'string' || !this.isMultiple) { - return str; - } - - separator = separator || this.getSeparator(); - - var val, i, l; - - if (str === null || str.length < 1) { - return null; - } - val = str.split(separator); - for (i = 0, l = val.length; i < l; i = i + 1) { - val[i] = $.trim(val[i]); - } - - return val; - }, - - autosubmit: function() { - this.$input.on('change', function(e, isInitial){ - if(!isInitial) { - $(this).closest('form').submit(); - } - }); - }, - - getSeparator: function() { - return this.options.select2.separator || $.fn.select2.defaults.separator; - }, - - /* - Converts source from x-editable format: {value: 1, text: "1"} to - select2 format: {id: 1, text: "1"} - */ - convertSource: function(source) { - if($.isArray(source) && source.length && source[0].value !== undefined) { - for(var i = 0; i<source.length; i++) { - if(source[i].value !== undefined) { - source[i].id = source[i].value; - delete source[i].value; - } - } - } - return source; - }, - - destroy: function() { - if(this.$input) { - if(this.$input.data('select2')) { - this.$input.select2('destroy'); - } - } - } - - }); - - Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { - /** - @property tpl - @default <input type="hidden"> - **/ - tpl:'<input type="hidden">', - /** - Configuration of select2. [Full list of options](http://ivaynberg.github.com/select2). - - @property select2 - @type object - @default null - **/ - select2: null, - /** - Placeholder attribute of select - - @property placeholder - @type string - @default null - **/ - placeholder: null, - /** - Source data for select. It will be assigned to select2 `data` property and kept here just for convenience. - Please note, that format is different from simple `select` input: use 'id' instead of 'value'. - E.g. `[{id: 1, text: "text1"}, {id: 2, text: "text2"}, ...]`. - - @property source - @type array|string|function - @default null - **/ - source: null, - /** - Separator used to display tags. - - @property viewseparator - @type string - @default ', ' - **/ - viewseparator: ', ' - }); - - $.fn.editabletypes.select2 = Constructor; - -}(window.jQuery)); - -/** -* Combodate - 1.0.5 -* Dropdown date and time picker. -* Converts text input into dropdowns to pick day, month, year, hour, minute and second. -* Uses momentjs as datetime library http://momentjs.com. -* For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang -* -* Confusion at noon and midnight - see http://en.wikipedia.org/wiki/12-hour_clock#Confusion_at_noon_and_midnight -* In combodate: -* 12:00 pm --> 12:00 (24-h format, midday) -* 12:00 am --> 00:00 (24-h format, midnight, start of day) -* -* Differs from momentjs parse rules: -* 00:00 pm, 12:00 pm --> 12:00 (24-h format, day not change) -* 00:00 am, 12:00 am --> 00:00 (24-h format, day not change) -* -* -* Author: Vitaliy Potapov -* Project page: http://github.com/vitalets/combodate -* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License. -**/ -(function ($) { - - var Combodate = function (element, options) { - this.$element = $(element); - if(!this.$element.is('input')) { - $.error('Combodate should be applied to INPUT element'); - return; - } - this.options = $.extend({}, $.fn.combodate.defaults, options, this.$element.data()); - this.init(); - }; - - Combodate.prototype = { - constructor: Combodate, - init: function () { - this.map = { - //key regexp moment.method - day: ['D', 'date'], - month: ['M', 'month'], - year: ['Y', 'year'], - hour: ['[Hh]', 'hours'], - minute: ['m', 'minutes'], - second: ['s', 'seconds'], - ampm: ['[Aa]', ''] - }; - - this.$widget = $('<span class="combodate"></span>').html(this.getTemplate()); - - this.initCombos(); - - //update original input on change - this.$widget.on('change', 'select', $.proxy(function(e) { - this.$element.val(this.getValue()).change(); - // update days count if month or year changes - if (this.options.smartDays) { - if ($(e.target).is('.month') || $(e.target).is('.year')) { - this.fillCombo('day'); - } - } - }, this)); - - this.$widget.find('select').css('width', 'auto'); - - // hide original input and insert widget - this.$element.hide().after(this.$widget); - - // set initial value - this.setValue(this.$element.val() || this.options.value); - }, - - /* - Replace tokens in template with <select> elements - */ - getTemplate: function() { - var tpl = this.options.template; - - //first pass - $.each(this.map, function(k, v) { - v = v[0]; - var r = new RegExp(v+'+'), - token = v.length > 1 ? v.substring(1, 2) : v; - - tpl = tpl.replace(r, '{'+token+'}'); - }); - - //replace spaces with - tpl = tpl.replace(/ /g, ' '); - - //second pass - $.each(this.map, function(k, v) { - v = v[0]; - var token = v.length > 1 ? v.substring(1, 2) : v; - - tpl = tpl.replace('{'+token+'}', '<select class="'+k+'"></select>'); - }); - - return tpl; - }, - - /* - Initialize combos that presents in template - */ - initCombos: function() { - for (var k in this.map) { - var $c = this.$widget.find('.'+k); - // set properties like this.$day, this.$month etc. - this['$'+k] = $c.length ? $c : null; - // fill with items - this.fillCombo(k); - } - }, - - /* - Fill combo with items - */ - fillCombo: function(k) { - var $combo = this['$'+k]; - if (!$combo) { - return; - } - - // define method name to fill items, e.g `fillDays` - var f = 'fill' + k.charAt(0).toUpperCase() + k.slice(1); - var items = this[f](); - var value = $combo.val(); - - $combo.empty(); - for(var i=0; i<items.length; i++) { - $combo.append('<option value="'+items[i][0]+'">'+items[i][1]+'</option>'); - } - - $combo.val(value); - }, - - /* - Initialize items of combos. Handles `firstItem` option - */ - fillCommon: function(key) { - var values = [], - relTime; - - if(this.options.firstItem === 'name') { - //need both to support moment ver < 2 and >= 2 - relTime = moment.relativeTime || moment.langData()._relativeTime; - var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key]; - //take last entry (see momentjs lang files structure) - header = header.split(' ').reverse()[0]; - values.push(['', header]); - } else if(this.options.firstItem === 'empty') { - values.push(['', '']); - } - return values; - }, - - - /* - fill day - */ - fillDay: function() { - var items = this.fillCommon('d'), name, i, - twoDigit = this.options.template.indexOf('DD') !== -1, - daysCount = 31; - - // detect days count (depends on month and year) - // originally https://github.com/vitalets/combodate/pull/7 - if (this.options.smartDays && this.$month && this.$year) { - var month = parseInt(this.$month.val(), 10); - var year = parseInt(this.$year.val(), 10); - - if (!isNaN(month) && !isNaN(year)) { - daysCount = moment([year, month]).daysInMonth(); - } - } - - for (i = 1; i <= daysCount; i++) { - name = twoDigit ? this.leadZero(i) : i; - items.push([i, name]); - } - return items; - }, - - /* - fill month - */ - fillMonth: function() { - var items = this.fillCommon('M'), name, i, - longNames = this.options.template.indexOf('MMMM') !== -1, - shortNames = this.options.template.indexOf('MMM') !== -1, - twoDigit = this.options.template.indexOf('MM') !== -1; - - for(i=0; i<=11; i++) { - if(longNames) { - //see https://github.com/timrwood/momentjs.com/pull/36 - name = moment().date(1).month(i).format('MMMM'); - } else if(shortNames) { - name = moment().date(1).month(i).format('MMM'); - } else if(twoDigit) { - name = this.leadZero(i+1); - } else { - name = i+1; - } - items.push([i, name]); - } - return items; - }, - - /* - fill year - */ - fillYear: function() { - var items = [], name, i, - longNames = this.options.template.indexOf('YYYY') !== -1; - - for(i=this.options.maxYear; i>=this.options.minYear; i--) { - name = longNames ? i : (i+'').substring(2); - items[this.options.yearDescending ? 'push' : 'unshift']([i, name]); - } - - items = this.fillCommon('y').concat(items); - - return items; - }, - - /* - fill hour - */ - fillHour: function() { - var items = this.fillCommon('h'), name, i, - h12 = this.options.template.indexOf('h') !== -1, - h24 = this.options.template.indexOf('H') !== -1, - twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1, - min = h12 ? 1 : 0, - max = h12 ? 12 : 23; - - for(i=min; i<=max; i++) { - name = twoDigit ? this.leadZero(i) : i; - items.push([i, name]); - } - return items; - }, - - /* - fill minute - */ - fillMinute: function() { - var items = this.fillCommon('m'), name, i, - twoDigit = this.options.template.indexOf('mm') !== -1; - - for(i=0; i<=59; i+= this.options.minuteStep) { - name = twoDigit ? this.leadZero(i) : i; - items.push([i, name]); - } - return items; - }, - - /* - fill second - */ - fillSecond: function() { - var items = this.fillCommon('s'), name, i, - twoDigit = this.options.template.indexOf('ss') !== -1; - - for(i=0; i<=59; i+= this.options.secondStep) { - name = twoDigit ? this.leadZero(i) : i; - items.push([i, name]); - } - return items; - }, - - /* - fill ampm - */ - fillAmpm: function() { - var ampmL = this.options.template.indexOf('a') !== -1, - ampmU = this.options.template.indexOf('A') !== -1, - items = [ - ['am', ampmL ? 'am' : 'AM'], - ['pm', ampmL ? 'pm' : 'PM'] - ]; - return items; - }, - - /* - Returns current date value from combos. - If format not specified - `options.format` used. - If format = `null` - Moment object returned. - */ - getValue: function(format) { - var dt, values = {}, - that = this, - notSelected = false; - - //getting selected values - $.each(this.map, function(k, v) { - if(k === 'ampm') { - return; - } - var def = k === 'day' ? 1 : 0; - - values[k] = that['$'+k] ? parseInt(that['$'+k].val(), 10) : def; - - if(isNaN(values[k])) { - notSelected = true; - return false; - } - }); - - //if at least one visible combo not selected - return empty string - if(notSelected) { - return ''; - } - - //convert hours 12h --> 24h - if(this.$ampm) { - //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day) - if(values.hour === 12) { - values.hour = this.$ampm.val() === 'am' ? 0 : 12; - } else { - values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12; - } - } - - dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]); - - //highlight invalid date - this.highlight(dt); - - format = format === undefined ? this.options.format : format; - if(format === null) { - return dt.isValid() ? dt : null; - } else { - return dt.isValid() ? dt.format(format) : ''; - } - }, - - setValue: function(value) { - if(!value) { - return; - } - - var dt = typeof value === 'string' ? moment(value, this.options.format) : moment(value), - that = this, - values = {}; - - //function to find nearest value in select options - function getNearest($select, value) { - var delta = {}; - $select.children('option').each(function(i, opt){ - var optValue = $(opt).attr('value'), - distance; - - if(optValue === '') return; - distance = Math.abs(optValue - value); - if(typeof delta.distance === 'undefined' || distance < delta.distance) { - delta = {value: optValue, distance: distance}; - } - }); - return delta.value; - } - - if(dt.isValid()) { - //read values from date object - $.each(this.map, function(k, v) { - if(k === 'ampm') { - return; - } - values[k] = dt[v[1]](); - }); - - if(this.$ampm) { - //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day) - if(values.hour >= 12) { - values.ampm = 'pm'; - if(values.hour > 12) { - values.hour -= 12; - } - } else { - values.ampm = 'am'; - if(values.hour === 0) { - values.hour = 12; - } - } - } - - $.each(values, function(k, v) { - //call val() for each existing combo, e.g. this.$hour.val() - if(that['$'+k]) { - - if(k === 'minute' && that.options.minuteStep > 1 && that.options.roundTime) { - v = getNearest(that['$'+k], v); - } - - if(k === 'second' && that.options.secondStep > 1 && that.options.roundTime) { - v = getNearest(that['$'+k], v); - } - - that['$'+k].val(v); - } - }); - - // update days count - if (this.options.smartDays) { - this.fillCombo('day'); - } - - this.$element.val(dt.format(this.options.format)).change(); - } - }, - - /* - highlight combos if date is invalid - */ - highlight: function(dt) { - if(!dt.isValid()) { - if(this.options.errorClass) { - this.$widget.addClass(this.options.errorClass); - } else { - //store original border color - if(!this.borderColor) { - this.borderColor = this.$widget.find('select').css('border-color'); - } - this.$widget.find('select').css('border-color', 'red'); - } - } else { - if(this.options.errorClass) { - this.$widget.removeClass(this.options.errorClass); - } else { - this.$widget.find('select').css('border-color', this.borderColor); - } - } - }, - - leadZero: function(v) { - return v <= 9 ? '0' + v : v; - }, - - destroy: function() { - this.$widget.remove(); - this.$element.removeData('combodate').show(); - } - - //todo: clear method - }; - - $.fn.combodate = function ( option ) { - var d, args = Array.apply(null, arguments); - args.shift(); - - //getValue returns date as string / object (not jQuery object) - if(option === 'getValue' && this.length && (d = this.eq(0).data('combodate'))) { - return d.getValue.apply(d, args); - } - - return this.each(function () { - var $this = $(this), - data = $this.data('combodate'), - options = typeof option == 'object' && option; - if (!data) { - $this.data('combodate', (data = new Combodate(this, options))); - } - if (typeof option == 'string' && typeof data[option] == 'function') { - data[option].apply(data, args); - } - }); - }; - - $.fn.combodate.defaults = { - //in this format value stored in original input - format: 'DD-MM-YYYY HH:mm', - //in this format items in dropdowns are displayed - template: 'D / MMM / YYYY H : mm', - //initial value, can be `new Date()` - value: null, - minYear: 1970, - maxYear: (new Date().getFullYear()), - yearDescending: true, - minuteStep: 5, - secondStep: 1, - firstItem: 'empty', //'name', 'empty', 'none' - errorClass: null, - roundTime: true, // whether to round minutes and seconds if step > 1 - smartDays: false // whether days in combo depend on selected month: 31, 30, 28 - }; - -}(window.jQuery)); - -/** -Combodate input - dropdown date and time picker. -Based on [combodate](http://vitalets.github.com/combodate) plugin (included). To use it you should manually include [momentjs](http://momentjs.com). - - <script src="js/moment.min.js"></script> - -Allows to input: - -* only date -* only time -* both date and time - -Please note, that format is taken from momentjs and **not compatible** with bootstrap-datepicker / jquery UI datepicker. -Internally value stored as `momentjs` object. - -@class combodate -@extends abstractinput -@final -@since 1.4.0 -@example -<a href="#" id="dob" data-type="combodate" data-pk="1" data-url="/post" data-value="1984-05-15" data-title="Select date"></a> -<script> -$(function(){ - $('#dob').editable({ - format: 'YYYY-MM-DD', - viewformat: 'DD.MM.YYYY', - template: 'D / MMMM / YYYY', - combodate: { - minYear: 2000, - maxYear: 2015, - minuteStep: 1 - } - } - }); -}); -</script> -**/ - -/*global moment*/ - -(function ($) { - "use strict"; - - var Constructor = function (options) { - this.init('combodate', options, Constructor.defaults); - - //by default viewformat equals to format - if(!this.options.viewformat) { - this.options.viewformat = this.options.format; - } - - //try parse combodate config defined as json string in data-combodate - options.combodate = $.fn.editableutils.tryParseJson(options.combodate, true); - - //overriding combodate config (as by default jQuery extend() is not recursive) - this.options.combodate = $.extend({}, Constructor.defaults.combodate, options.combodate, { - format: this.options.format, - template: this.options.template - }); - }; - - $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput); - - $.extend(Constructor.prototype, { - render: function () { - this.$input.combodate(this.options.combodate); - - if($.fn.editableform.engine === 'bs3') { - this.$input.siblings().find('select').addClass('form-control'); - } - - if(this.options.inputclass) { - this.$input.siblings().find('select').addClass(this.options.inputclass); - } - //"clear" link - /* - if(this.options.clear) { - this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){ - e.preventDefault(); - e.stopPropagation(); - this.clear(); - }, this)); - - this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear)); - } - */ - }, - - value2html: function(value, element) { - var text = value ? value.format(this.options.viewformat) : ''; - //$(element).text(text); - Constructor.superclass.value2html.call(this, text, element); - }, - - html2value: function(html) { - return html ? moment(html, this.options.viewformat) : null; - }, - - value2str: function(value) { - return value ? value.format(this.options.format) : ''; - }, - - str2value: function(str) { - return str ? moment(str, this.options.format) : null; - }, - - value2submit: function(value) { - return this.value2str(value); - }, - - value2input: function(value) { - this.$input.combodate('setValue', value); - }, - - input2value: function() { - return this.$input.combodate('getValue', null); - }, - - activate: function() { - this.$input.siblings('.combodate').find('select').eq(0).focus(); - }, - - /* - clear: function() { - this.$input.data('datepicker').date = null; - this.$input.find('.active').removeClass('active'); - }, - */ - - autosubmit: function() { - - } - - }); - - Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { - /** - @property tpl - @default <input type="text"> - **/ - tpl:'<input type="text">', - /** - @property inputclass - @default null - **/ - inputclass: null, - /** - Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br> - See list of tokens in [momentjs docs](http://momentjs.com/docs/#/parsing/string-format) - - @property format - @type string - @default YYYY-MM-DD - **/ - format:'YYYY-MM-DD', - /** - Format used for displaying date. Also applied when converting date from element's text on init. - If not specified equals to `format`. - - @property viewformat - @type string - @default null - **/ - viewformat: null, - /** - Template used for displaying dropdowns. - - @property template - @type string - @default D / MMM / YYYY - **/ - template: 'D / MMM / YYYY', - /** - Configuration of combodate. - Full list of options: http://vitalets.github.com/combodate/#docs - - @property combodate - @type object - @default null - **/ - combodate: null - - /* - (not implemented yet) - Text shown as clear date button. - If <code>false</code> clear button will not be rendered. - - @property clear - @type boolean|string - @default 'x clear' - */ - //clear: '× clear' - }); - - $.fn.editabletypes.combodate = Constructor; - -}(window.jQuery)); - -/* -Editableform based on Twitter Bootstrap 3 -*/ -(function ($) { - "use strict"; - - //store parent methods - const pInitInput = $.fn.editableform.Constructor.prototype.initInput; - - $.extend($.fn.editableform.Constructor.prototype, { - // initTemplate: function() { - // this.$form = $($.fn.editableform.template); - // this.$form.find('.control-group').addClass('form-group'); - // this.$form.find('.editable-error-block').addClass('help-block'); - // }, - initInput: function() { - pInitInput.apply(this); - - //for bs3 set default class `input-sm` to standard inputs - const emptyInputClass = this.input.options.inputclass === null || this.input.options.inputclass === false; - const defaultClass = 'input-sm'; - - //bs3 add `form-control` class to standard inputs - const stdtypes = 'text,select,textarea,password,email,url,tel,number,range,time,typeaheadjs'.split(','); - if(~$.inArray(this.input.type, stdtypes)) { - this.input.$input.addClass('form-control editable'); - if(emptyInputClass) { - this.input.options.inputclass = defaultClass; - this.input.$input.addClass(defaultClass); - } - } - // Automatically open select dropdown when clicked - if (this.input.type === 'select') { - setTimeout(() => { - this.input.$input.focus().click(); - }, 50); - } - - //apply bs3 size class also to buttons (to fit size of control) - const $btn = this.$form.find('.editable-buttons'); - const classes = emptyInputClass ? [defaultClass] : this.input.options.inputclass.split(' '); - for(let i=0; i<classes.length; i++) { - if(classes[i].toLowerCase() === 'input-lg') { - $btn.find('button').removeClass('btn-sm').addClass('btn-lg'); - } - } - } - }); - - //buttons - $.fn.editableform.buttons = - '<button type="submit" class="btn btn-primary btn-sm editable-submit">'+ - '<i class="bi bi-check"></i>'+ - '</button>'+ - '<button type="button" class="btn btn-secondary btn-sm editable-cancel">'+ - '<i class="bi bi-x"></i>'+ - '</button>'; - - //error classes - $.fn.editableform.errorGroupClass = 'has-error'; - $.fn.editableform.errorBlockClass = null; - //engine - $.fn.editableform.engine = 'bs3'; -}(window.jQuery)); -/** -* Editable Popover3 (for Bootstrap 3) -* --------------------- -* requires bootstrap-popover.js -*/ -(function ($) { - "use strict"; - - //extend methods - $.extend($.fn.editableContainer.Popup.prototype, { - containerName: 'popover', - containerDataName: 'bs.popover', - innerCss: '.popover-content', - defaults: $.fn.popover.Constructor.DEFAULTS, - - initContainer: function(){ - $.extend(this.containerOptions, { - trigger: 'manual', - selector: false, - content: ' ', - template: this.defaults.template - }); - - //as template property is used in inputs, hide it from popover - var t; - if(this.$element.data('template')) { - t = this.$element.data('template'); - this.$element.removeData('template'); - } - - this.call(this.containerOptions); - - if(t) { - //restore data('template') - this.$element.data('template', t); - } - }, - - /* show */ - innerShow: function () { - this.call('show'); - }, - - /* hide */ - innerHide: function () { - this.call('hide'); - }, - - /* destroy */ - innerDestroy: function() { - this.call('destroy'); - }, - - setContainerOption: function(key, value) { - this.container().options[key] = value; - }, - - /** - * move popover to new position. This function mainly copied from bootstrap-popover. - */ - /*jshint laxcomma: true, eqeqeq: false*/ - setPosition: function () { - - (function() { - /* - var $tip = this.tip() - , inside - , pos - , actualWidth - , actualHeight - , placement - , tp - , tpt - , tpb - , tpl - , tpr; - - placement = typeof this.options.placement === 'function' ? - this.options.placement.call(this, $tip[0], this.$element[0]) : - this.options.placement; - - inside = /in/.test(placement); - - $tip - // .detach() - //vitalets: remove any placement class because otherwise they dont influence on re-positioning of visible popover - .removeClass('top right bottom left') - .css({ top: 0, left: 0, display: 'block' }); - // .insertAfter(this.$element); - - pos = this.getPosition(inside); - - actualWidth = $tip[0].offsetWidth; - actualHeight = $tip[0].offsetHeight; - - placement = inside ? placement.split(' ')[1] : placement; - - tpb = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2}; - tpt = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2}; - tpl = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth}; - tpr = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width}; - - switch (placement) { - case 'bottom': - if ((tpb.top + actualHeight) > ($(window).scrollTop() + $(window).height())) { - if (tpt.top > $(window).scrollTop()) { - placement = 'top'; - } else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) { - placement = 'right'; - } else if (tpl.left > $(window).scrollLeft()) { - placement = 'left'; - } else { - placement = 'right'; - } - } - break; - case 'top': - if (tpt.top < $(window).scrollTop()) { - if ((tpb.top + actualHeight) < ($(window).scrollTop() + $(window).height())) { - placement = 'bottom'; - } else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) { - placement = 'right'; - } else if (tpl.left > $(window).scrollLeft()) { - placement = 'left'; - } else { - placement = 'right'; - } - } - break; - case 'left': - if (tpl.left < $(window).scrollLeft()) { - if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) { - placement = 'right'; - } else if (tpt.top > $(window).scrollTop()) { - placement = 'top'; - } else if (tpt.top > $(window).scrollTop()) { - placement = 'bottom'; - } else { - placement = 'right'; - } - } - break; - case 'right': - if ((tpr.left + actualWidth) > ($(window).scrollLeft() + $(window).width())) { - if (tpl.left > $(window).scrollLeft()) { - placement = 'left'; - } else if (tpt.top > $(window).scrollTop()) { - placement = 'top'; - } else if (tpt.top > $(window).scrollTop()) { - placement = 'bottom'; - } - } - break; - } - - switch (placement) { - case 'bottom': - tp = tpb; - break; - case 'top': - tp = tpt; - break; - case 'left': - tp = tpl; - break; - case 'right': - tp = tpr; - break; - } - - $tip - .offset(tp) - .addClass(placement) - .addClass('in'); - */ - - - var $tip = this.tip(); - - var placement = typeof this.options.placement == 'function' ? - this.options.placement.call(this, $tip[0], this.$element[0]) : - this.options.placement; - - var autoToken = /\s?auto?\s?/i; - var autoPlace = autoToken.test(placement); - if (autoPlace) { - placement = placement.replace(autoToken, '') || 'top'; - } - - - var pos = this.getPosition(); - var actualWidth = $tip[0].offsetWidth; - var actualHeight = $tip[0].offsetHeight; - - if (autoPlace) { - var $parent = this.$element.parent(); - - var orgPlacement = placement; - var docScroll = document.documentElement.scrollTop || document.body.scrollTop; - var parentWidth = this.options.container == 'body' ? window.innerWidth : $parent.outerWidth(); - var parentHeight = this.options.container == 'body' ? window.innerHeight : $parent.outerHeight(); - var parentLeft = this.options.container == 'body' ? 0 : $parent.offset().left; - - placement = placement == 'bottom' && pos.top + pos.height + actualHeight - docScroll > parentHeight ? 'top' : - placement == 'top' && pos.top - docScroll - actualHeight < 0 ? 'bottom' : - placement == 'right' && pos.right + actualWidth > parentWidth ? 'left' : - placement == 'left' && pos.left - actualWidth < parentLeft ? 'right' : - placement; - - $tip - .removeClass(orgPlacement) - .addClass(placement); - } - - - var calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight); - - this.applyPlacement(calculatedOffset, placement); - - - }).call(this.container()); - /*jshint laxcomma: false, eqeqeq: true*/ - } - }); - -}(window.jQuery)); - -/* ========================================================= - * bootstrap-datepicker.js - * http://www.eyecon.ro/bootstrap-datepicker - * ========================================================= - * Copyright 2012 Stefan Petre - * Improvements by Andrew Rowls - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * ========================================================= */ - -(function( $ ) { - - function UTCDate(){ - return new Date(Date.UTC.apply(Date, arguments)); - } - function UTCToday(){ - var today = new Date(); - return UTCDate(today.getUTCFullYear(), today.getUTCMonth(), today.getUTCDate()); - } - - // Picker object - - var Datepicker = function(element, options) { - var that = this; - - this._process_options(options); - - this.element = $(element); - this.isInline = false; - this.isInput = this.element.is('input'); - this.component = this.element.is('.date') ? this.element.find('.add-on, .btn') : false; - this.hasInput = this.component && this.element.find('input').length; - if(this.component && this.component.length === 0) - this.component = false; - - this.picker = $(DPGlobal.template); - this._buildEvents(); - this._attachEvents(); - - if(this.isInline) { - this.picker.addClass('datepicker-inline').appendTo(this.element); - } else { - this.picker.addClass('datepicker-dropdown dropdown-menu'); - } - - if (this.o.rtl){ - this.picker.addClass('datepicker-rtl'); - this.picker.find('.prev i, .next i') - .toggleClass('icon-arrow-left icon-arrow-right'); - } - - - this.viewMode = this.o.startView; - - if (this.o.calendarWeeks) - this.picker.find('tfoot th.today') - .attr('colspan', function(i, val){ - return parseInt(val) + 1; - }); - - this._allow_update = false; - - this.setStartDate(this.o.startDate); - this.setEndDate(this.o.endDate); - this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled); - - this.fillDow(); - this.fillMonths(); - - this._allow_update = true; - - this.update(); - this.showMode(); - - if(this.isInline) { - this.show(); - } - }; - - Datepicker.prototype = { - constructor: Datepicker, - - _process_options: function(opts){ - // Store raw options for reference - this._o = $.extend({}, this._o, opts); - // Processed options - var o = this.o = $.extend({}, this._o); - - // Check if "de-DE" style date is available, if not language should - // fallback to 2 letter code eg "de" - var lang = o.language; - if (!dates[lang]) { - lang = lang.split('-')[0]; - if (!dates[lang]) - lang = defaults.language; - } - o.language = lang; - - switch(o.startView){ - case 2: - case 'decade': - o.startView = 2; - break; - case 1: - case 'year': - o.startView = 1; - break; - default: - o.startView = 0; - } - - switch (o.minViewMode) { - case 1: - case 'months': - o.minViewMode = 1; - break; - case 2: - case 'years': - o.minViewMode = 2; - break; - default: - o.minViewMode = 0; - } - - o.startView = Math.max(o.startView, o.minViewMode); - - o.weekStart %= 7; - o.weekEnd = ((o.weekStart + 6) % 7); - - var format = DPGlobal.parseFormat(o.format) - if (o.startDate !== -Infinity) { - o.startDate = DPGlobal.parseDate(o.startDate, format, o.language); - } - if (o.endDate !== Infinity) { - o.endDate = DPGlobal.parseDate(o.endDate, format, o.language); - } - - o.daysOfWeekDisabled = o.daysOfWeekDisabled||[]; - if (!$.isArray(o.daysOfWeekDisabled)) - o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/); - o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function (d) { - return parseInt(d, 10); - }); - }, - _events: [], - _secondaryEvents: [], - _applyEvents: function(evs){ - for (var i=0, el, ev; i<evs.length; i++){ - el = evs[i][0]; - ev = evs[i][1]; - el.on(ev); - } - }, - _unapplyEvents: function(evs){ - for (var i=0, el, ev; i<evs.length; i++){ - el = evs[i][0]; - ev = evs[i][1]; - el.off(ev); - } - }, - _buildEvents: function(){ - if (this.isInput) { // single input - this._events = [ - [this.element, { - focus: $.proxy(this.show, this), - keyup: $.proxy(this.update, this), - keydown: $.proxy(this.keydown, this) - }] - ]; - } - else if (this.component && this.hasInput){ // component: input + button - this._events = [ - // For components that are not readonly, allow keyboard nav - [this.element.find('input'), { - focus: $.proxy(this.show, this), - keyup: $.proxy(this.update, this), - keydown: $.proxy(this.keydown, this) - }], - [this.component, { - click: $.proxy(this.show, this) - }] - ]; - } - else if (this.element.is('div')) { // inline datepicker - this.isInline = true; - } - else { - this._events = [ - [this.element, { - click: $.proxy(this.show, this) - }] - ]; - } - - this._secondaryEvents = [ - [this.picker, { - click: $.proxy(this.click, this) - }], - [$(window), { - resize: $.proxy(this.place, this) - }], - [$(document), { - mousedown: $.proxy(function (e) { - // Clicked outside the datepicker, hide it - if (!( - this.element.is(e.target) || - this.element.find(e.target).size() || - this.picker.is(e.target) || - this.picker.find(e.target).size() - )) { - this.hide(); - } - }, this) - }] - ]; - }, - _attachEvents: function(){ - this._detachEvents(); - this._applyEvents(this._events); - }, - _detachEvents: function(){ - this._unapplyEvents(this._events); - }, - _attachSecondaryEvents: function(){ - this._detachSecondaryEvents(); - this._applyEvents(this._secondaryEvents); - }, - _detachSecondaryEvents: function(){ - this._unapplyEvents(this._secondaryEvents); - }, - _trigger: function(event, altdate){ - var date = altdate || this.date, - local_date = new Date(date.getTime() + (date.getTimezoneOffset()*60000)); - - this.element.trigger({ - type: event, - date: local_date, - format: $.proxy(function(altformat){ - var format = altformat || this.o.format; - return DPGlobal.formatDate(date, format, this.o.language); - }, this) - }); - }, - - show: function(e) { - if (!this.isInline) - this.picker.appendTo('body'); - this.picker.show(); - this.height = this.component ? this.component.outerHeight() : this.element.outerHeight(); - this.place(); - this._attachSecondaryEvents(); - if (e) { - e.preventDefault(); - } - this._trigger('show'); - }, - - hide: function(e){ - if(this.isInline) return; - if (!this.picker.is(':visible')) return; - this.picker.hide().detach(); - this._detachSecondaryEvents(); - this.viewMode = this.o.startView; - this.showMode(); - - if ( - this.o.forceParse && - ( - this.isInput && this.element.val() || - this.hasInput && this.element.find('input').val() - ) - ) - this.setValue(); - this._trigger('hide'); - }, - - remove: function() { - this.hide(); - this._detachEvents(); - this._detachSecondaryEvents(); - this.picker.remove(); - delete this.element.data().datepicker; - if (!this.isInput) { - delete this.element.data().date; - } - }, - - getDate: function() { - var d = this.getUTCDate(); - return new Date(d.getTime() + (d.getTimezoneOffset()*60000)); - }, - - getUTCDate: function() { - return this.date; - }, - - setDate: function(d) { - this.setUTCDate(new Date(d.getTime() - (d.getTimezoneOffset()*60000))); - }, - - setUTCDate: function(d) { - this.date = d; - this.setValue(); - }, - - setValue: function() { - var formatted = this.getFormattedDate(); - if (!this.isInput) { - if (this.component){ - this.element.find('input').val(formatted); - } - } else { - this.element.val(formatted); - } - }, - - getFormattedDate: function(format) { - if (format === undefined) - format = this.o.format; - return DPGlobal.formatDate(this.date, format, this.o.language); - }, - - setStartDate: function(startDate){ - this._process_options({startDate: startDate}); - this.update(); - this.updateNavArrows(); - }, - - setEndDate: function(endDate){ - this._process_options({endDate: endDate}); - this.update(); - this.updateNavArrows(); - }, - - setDaysOfWeekDisabled: function(daysOfWeekDisabled){ - this._process_options({daysOfWeekDisabled: daysOfWeekDisabled}); - this.update(); - this.updateNavArrows(); - }, - - place: function(){ - if(this.isInline) return; - var zIndex = parseInt(this.element.parents().filter(function() { - return $(this).css('z-index') != 'auto'; - }).first().css('z-index'))+10; - var offset = this.component ? this.component.parent().offset() : this.element.offset(); - var height = this.component ? this.component.outerHeight(true) : this.element.outerHeight(true); - this.picker.css({ - top: offset.top + height, - left: offset.left, - zIndex: zIndex - }); - }, - - _allow_update: true, - update: function(){ - if (!this._allow_update) return; - - var date, fromArgs = false; - if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) { - date = arguments[0]; - fromArgs = true; - } else { - date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val(); - delete this.element.data().date; - } - - this.date = DPGlobal.parseDate(date, this.o.format, this.o.language); - - if(fromArgs) this.setValue(); - - if (this.date < this.o.startDate) { - this.viewDate = new Date(this.o.startDate); - } else if (this.date > this.o.endDate) { - this.viewDate = new Date(this.o.endDate); - } else { - this.viewDate = new Date(this.date); - } - this.fill(); - }, - - fillDow: function(){ - var dowCnt = this.o.weekStart, - html = '<tr>'; - if(this.o.calendarWeeks){ - var cell = '<th class="cw"> </th>'; - html += cell; - this.picker.find('.datepicker-days thead tr:first-child').prepend(cell); - } - while (dowCnt < this.o.weekStart + 7) { - html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>'; - } - html += '</tr>'; - this.picker.find('.datepicker-days thead').append(html); - }, - - fillMonths: function(){ - var html = '', - i = 0; - while (i < 12) { - html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>'; - } - this.picker.find('.datepicker-months td').html(html); - }, - - setRange: function(range){ - if (!range || !range.length) - delete this.range; - else - this.range = $.map(range, function(d){ return d.valueOf(); }); - this.fill(); - }, - - getClassNames: function(date){ - var cls = [], - year = this.viewDate.getUTCFullYear(), - month = this.viewDate.getUTCMonth(), - currentDate = this.date.valueOf(), - today = new Date(); - if (date.getUTCFullYear() < year || (date.getUTCFullYear() == year && date.getUTCMonth() < month)) { - cls.push('old'); - } else if (date.getUTCFullYear() > year || (date.getUTCFullYear() == year && date.getUTCMonth() > month)) { - cls.push('new'); - } - // Compare internal UTC date with local today, not UTC today - if (this.o.todayHighlight && - date.getUTCFullYear() == today.getFullYear() && - date.getUTCMonth() == today.getMonth() && - date.getUTCDate() == today.getDate()) { - cls.push('today'); - } - if (currentDate && date.valueOf() == currentDate) { - cls.push('active'); - } - if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate || - $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1) { - cls.push('disabled'); - } - if (this.range){ - if (date > this.range[0] && date < this.range[this.range.length-1]){ - cls.push('range'); - } - if ($.inArray(date.valueOf(), this.range) != -1){ - cls.push('selected'); - } - } - return cls; - }, - - fill: function() { - var d = new Date(this.viewDate), - year = d.getUTCFullYear(), - month = d.getUTCMonth(), - startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity, - startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity, - endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity, - endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity, - currentDate = this.date && this.date.valueOf(), - tooltip; - this.picker.find('.datepicker-days thead th.datepicker-switch') - .text(dates[this.o.language].months[month]+' '+year); - this.picker.find('tfoot th.today') - .text(dates[this.o.language].today) - .toggle(this.o.todayBtn !== false); - this.picker.find('tfoot th.clear') - .text(dates[this.o.language].clear) - .toggle(this.o.clearBtn !== false); - this.updateNavArrows(); - this.fillMonths(); - var prevMonth = UTCDate(year, month-1, 28,0,0,0,0), - day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth()); - prevMonth.setUTCDate(day); - prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7); - var nextMonth = new Date(prevMonth); - nextMonth.setUTCDate(nextMonth.getUTCDate() + 42); - nextMonth = nextMonth.valueOf(); - var html = []; - var clsName; - while(prevMonth.valueOf() < nextMonth) { - if (prevMonth.getUTCDay() == this.o.weekStart) { - html.push('<tr>'); - if(this.o.calendarWeeks){ - // ISO 8601: First week contains first thursday. - // ISO also states week starts on Monday, but we can be more abstract here. - var - // Start of current week: based on weekstart/current date - ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5), - // Thursday of this week - th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5), - // First Thursday of year, year from thursday - yth = new Date(+(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5), - // Calendar week: ms between thursdays, div ms per day, div 7 days - calWeek = (th - yth) / 864e5 / 7 + 1; - html.push('<td class="cw">'+ calWeek +'</td>'); - - } - } - clsName = this.getClassNames(prevMonth); - clsName.push('day'); - - var before = this.o.beforeShowDay(prevMonth); - if (before === undefined) - before = {}; - else if (typeof(before) === 'boolean') - before = {enabled: before}; - else if (typeof(before) === 'string') - before = {classes: before}; - if (before.enabled === false) - clsName.push('disabled'); - if (before.classes) - clsName = clsName.concat(before.classes.split(/\s+/)); - if (before.tooltip) - tooltip = before.tooltip; - - clsName = $.unique(clsName); - html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>'); - if (prevMonth.getUTCDay() == this.o.weekEnd) { - html.push('</tr>'); - } - prevMonth.setUTCDate(prevMonth.getUTCDate()+1); - } - this.picker.find('.datepicker-days tbody').empty().append(html.join('')); - var currentYear = this.date && this.date.getUTCFullYear(); - - var months = this.picker.find('.datepicker-months') - .find('th:eq(1)') - .text(year) - .end() - .find('span').removeClass('active'); - if (currentYear && currentYear == year) { - months.eq(this.date.getUTCMonth()).addClass('active'); - } - if (year < startYear || year > endYear) { - months.addClass('disabled'); - } - if (year == startYear) { - months.slice(0, startMonth).addClass('disabled'); - } - if (year == endYear) { - months.slice(endMonth+1).addClass('disabled'); - } - - html = ''; - year = parseInt(year/10, 10) * 10; - var yearCont = this.picker.find('.datepicker-years') - .find('th:eq(1)') - .text(year + '-' + (year + 9)) - .end() - .find('td'); - year -= 1; - for (var i = -1; i < 11; i++) { - html += '<span class="year'+(i == -1 ? ' old' : i == 10 ? ' new' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>'; - year += 1; - } - yearCont.html(html); - }, - - updateNavArrows: function() { - if (!this._allow_update) return; - - var d = new Date(this.viewDate), - year = d.getUTCFullYear(), - month = d.getUTCMonth(); - switch (this.viewMode) { - case 0: - if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()) { - this.picker.find('.prev').css({visibility: 'hidden'}); - } else { - this.picker.find('.prev').css({visibility: 'visible'}); - } - if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()) { - this.picker.find('.next').css({visibility: 'hidden'}); - } else { - this.picker.find('.next').css({visibility: 'visible'}); - } - break; - case 1: - case 2: - if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()) { - this.picker.find('.prev').css({visibility: 'hidden'}); - } else { - this.picker.find('.prev').css({visibility: 'visible'}); - } - if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()) { - this.picker.find('.next').css({visibility: 'hidden'}); - } else { - this.picker.find('.next').css({visibility: 'visible'}); - } - break; - } - }, - - click: function(e) { - e.preventDefault(); - var target = $(e.target).closest('span, td, th'); - if (target.length == 1) { - switch(target[0].nodeName.toLowerCase()) { - case 'th': - switch(target[0].className) { - case 'datepicker-switch': - this.showMode(1); - break; - case 'prev': - case 'next': - var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1); - switch(this.viewMode){ - case 0: - this.viewDate = this.moveMonth(this.viewDate, dir); - break; - case 1: - case 2: - this.viewDate = this.moveYear(this.viewDate, dir); - break; - } - this.fill(); - break; - case 'today': - var date = new Date(); - date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); - - this.showMode(-2); - var which = this.o.todayBtn == 'linked' ? null : 'view'; - this._setDate(date, which); - break; - case 'clear': - var element; - if (this.isInput) - element = this.element; - else if (this.component) - element = this.element.find('input'); - if (element) - element.val("").change(); - this._trigger('changeDate'); - this.update(); - if (this.o.autoclose) - this.hide(); - break; - } - break; - case 'span': - if (!target.is('.disabled')) { - this.viewDate.setUTCDate(1); - if (target.is('.month')) { - var day = 1; - var month = target.parent().find('span').index(target); - var year = this.viewDate.getUTCFullYear(); - this.viewDate.setUTCMonth(month); - this._trigger('changeMonth', this.viewDate); - if (this.o.minViewMode === 1) { - this._setDate(UTCDate(year, month, day,0,0,0,0)); - } - } else { - var year = parseInt(target.text(), 10)||0; - var day = 1; - var month = 0; - this.viewDate.setUTCFullYear(year); - this._trigger('changeYear', this.viewDate); - if (this.o.minViewMode === 2) { - this._setDate(UTCDate(year, month, day,0,0,0,0)); - } - } - this.showMode(-1); - this.fill(); - } - break; - case 'td': - if (target.is('.day') && !target.is('.disabled')){ - var day = parseInt(target.text(), 10)||1; - var year = this.viewDate.getUTCFullYear(), - month = this.viewDate.getUTCMonth(); - if (target.is('.old')) { - if (month === 0) { - month = 11; - year -= 1; - } else { - month -= 1; - } - } else if (target.is('.new')) { - if (month == 11) { - month = 0; - year += 1; - } else { - month += 1; - } - } - this._setDate(UTCDate(year, month, day,0,0,0,0)); - } - break; - } - } - }, - - _setDate: function(date, which){ - if (!which || which == 'date') - this.date = new Date(date); - if (!which || which == 'view') - this.viewDate = new Date(date); - this.fill(); - this.setValue(); - this._trigger('changeDate'); - var element; - if (this.isInput) { - element = this.element; - } else if (this.component){ - element = this.element.find('input'); - } - if (element) { - element.change(); - if (this.o.autoclose && (!which || which == 'date')) { - this.hide(); - } - } - }, - - moveMonth: function(date, dir){ - if (!dir) return date; - var new_date = new Date(date.valueOf()), - day = new_date.getUTCDate(), - month = new_date.getUTCMonth(), - mag = Math.abs(dir), - new_month, test; - dir = dir > 0 ? 1 : -1; - if (mag == 1){ - test = dir == -1 - // If going back one month, make sure month is not current month - // (eg, Mar 31 -> Feb 31 == Feb 28, not Mar 02) - ? function(){ return new_date.getUTCMonth() == month; } - // If going forward one month, make sure month is as expected - // (eg, Jan 31 -> Feb 31 == Feb 28, not Mar 02) - : function(){ return new_date.getUTCMonth() != new_month; }; - new_month = month + dir; - new_date.setUTCMonth(new_month); - // Dec -> Jan (12) or Jan -> Dec (-1) -- limit expected date to 0-11 - if (new_month < 0 || new_month > 11) - new_month = (new_month + 12) % 12; - } else { - // For magnitudes >1, move one month at a time... - for (var i=0; i<mag; i++) - // ...which might decrease the day (eg, Jan 31 to Feb 28, etc)... - new_date = this.moveMonth(new_date, dir); - // ...then reset the day, keeping it in the new month - new_month = new_date.getUTCMonth(); - new_date.setUTCDate(day); - test = function(){ return new_month != new_date.getUTCMonth(); }; - } - // Common date-resetting loop -- if date is beyond end of month, make it - // end of month - while (test()){ - new_date.setUTCDate(--day); - new_date.setUTCMonth(new_month); - } - return new_date; - }, - - moveYear: function(date, dir){ - return this.moveMonth(date, dir*12); - }, - - dateWithinRange: function(date){ - return date >= this.o.startDate && date <= this.o.endDate; - }, - - keydown: function(e){ - if (this.picker.is(':not(:visible)')){ - if (e.keyCode == 27) // allow escape to hide and re-show picker - this.show(); - return; - } - var dateChanged = false, - dir, day, month, - newDate, newViewDate; - switch(e.keyCode){ - case 27: // escape - this.hide(); - e.preventDefault(); - break; - case 37: // left - case 39: // right - if (!this.o.keyboardNavigation) break; - dir = e.keyCode == 37 ? -1 : 1; - if (e.ctrlKey){ - newDate = this.moveYear(this.date, dir); - newViewDate = this.moveYear(this.viewDate, dir); - } else if (e.shiftKey){ - newDate = this.moveMonth(this.date, dir); - newViewDate = this.moveMonth(this.viewDate, dir); - } else { - newDate = new Date(this.date); - newDate.setUTCDate(this.date.getUTCDate() + dir); - newViewDate = new Date(this.viewDate); - newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir); - } - if (this.dateWithinRange(newDate)){ - this.date = newDate; - this.viewDate = newViewDate; - this.setValue(); - this.update(); - e.preventDefault(); - dateChanged = true; - } - break; - case 38: // up - case 40: // down - if (!this.o.keyboardNavigation) break; - dir = e.keyCode == 38 ? -1 : 1; - if (e.ctrlKey){ - newDate = this.moveYear(this.date, dir); - newViewDate = this.moveYear(this.viewDate, dir); - } else if (e.shiftKey){ - newDate = this.moveMonth(this.date, dir); - newViewDate = this.moveMonth(this.viewDate, dir); - } else { - newDate = new Date(this.date); - newDate.setUTCDate(this.date.getUTCDate() + dir * 7); - newViewDate = new Date(this.viewDate); - newViewDate.setUTCDate(this.viewDate.getUTCDate() + dir * 7); - } - if (this.dateWithinRange(newDate)){ - this.date = newDate; - this.viewDate = newViewDate; - this.setValue(); - this.update(); - e.preventDefault(); - dateChanged = true; - } - break; - case 13: // enter - this.hide(); - e.preventDefault(); - break; - case 9: // tab - this.hide(); - break; - } - if (dateChanged){ - this._trigger('changeDate'); - var element; - if (this.isInput) { - element = this.element; - } else if (this.component){ - element = this.element.find('input'); - } - if (element) { - element.change(); - } - } - }, - - showMode: function(dir) { - if (dir) { - this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir)); - } - /* - vitalets: fixing bug of very special conditions: - jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover. - Method show() does not set display css correctly and datepicker is not shown. - Changed to .css('display', 'block') solve the problem. - See https://github.com/vitalets/x-editable/issues/37 - - In jquery 1.7.2+ everything works fine. - */ - //this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show(); - this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block'); - this.updateNavArrows(); - } - }; - - var DateRangePicker = function(element, options){ - this.element = $(element); - this.inputs = $.map(options.inputs, function(i){ return i.jquery ? i[0] : i; }); - delete options.inputs; - - $(this.inputs) - .datepicker(options) - .bind('changeDate', $.proxy(this.dateUpdated, this)); - - this.pickers = $.map(this.inputs, function(i){ return $(i).data('datepicker'); }); - this.updateDates(); - }; - DateRangePicker.prototype = { - updateDates: function(){ - this.dates = $.map(this.pickers, function(i){ return i.date; }); - this.updateRanges(); - }, - updateRanges: function(){ - var range = $.map(this.dates, function(d){ return d.valueOf(); }); - $.each(this.pickers, function(i, p){ - p.setRange(range); - }); - }, - dateUpdated: function(e){ - var dp = $(e.target).data('datepicker'), - new_date = dp.getUTCDate(), - i = $.inArray(e.target, this.inputs), - l = this.inputs.length; - if (i == -1) return; - - if (new_date < this.dates[i]){ - // Date being moved earlier/left - while (i>=0 && new_date < this.dates[i]){ - this.pickers[i--].setUTCDate(new_date); - } - } - else if (new_date > this.dates[i]){ - // Date being moved later/right - while (i<l && new_date > this.dates[i]){ - this.pickers[i++].setUTCDate(new_date); - } - } - this.updateDates(); - }, - remove: function(){ - $.map(this.pickers, function(p){ p.remove(); }); - delete this.element.data().datepicker; - } - }; - - function opts_from_el(el, prefix){ - // Derive options from element data-attrs - var data = $(el).data(), - out = {}, inkey, - replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'), - prefix = new RegExp('^' + prefix.toLowerCase()); - for (var key in data) - if (prefix.test(key)){ - inkey = key.replace(replace, function(_,a){ return a.toLowerCase(); }); - out[inkey] = data[key]; - } - return out; - } - - function opts_from_locale(lang){ - // Derive options from locale plugins - var out = {}; - // Check if "de-DE" style date is available, if not language should - // fallback to 2 letter code eg "de" - if (!dates[lang]) { - lang = lang.split('-')[0] - if (!dates[lang]) - return; - } - var d = dates[lang]; - $.each(locale_opts, function(i,k){ - if (k in d) - out[k] = d[k]; - }); - return out; - } - - var old = $.fn.datepicker; - var datepicker = $.fn.datepicker = function ( option ) { - var args = Array.apply(null, arguments); - args.shift(); - var internal_return, - this_return; - this.each(function () { - var $this = $(this), - data = $this.data('datepicker'), - options = typeof option == 'object' && option; - if (!data) { - var elopts = opts_from_el(this, 'date'), - // Preliminary otions - xopts = $.extend({}, defaults, elopts, options), - locopts = opts_from_locale(xopts.language), - // Options priority: js args, data-attrs, locales, defaults - opts = $.extend({}, defaults, locopts, elopts, options); - if ($this.is('.input-daterange') || opts.inputs){ - var ropts = { - inputs: opts.inputs || $this.find('input').toArray() - }; - $this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts)))); - } - else{ - $this.data('datepicker', (data = new Datepicker(this, opts))); - } - } - if (typeof option == 'string' && typeof data[option] == 'function') { - internal_return = data[option].apply(data, args); - if (internal_return !== undefined) - return false; - } - }); - if (internal_return !== undefined) - return internal_return; - else - return this; - }; - - var defaults = $.fn.datepicker.defaults = { - autoclose: false, - beforeShowDay: $.noop, - calendarWeeks: false, - clearBtn: false, - daysOfWeekDisabled: [], - endDate: Infinity, - forceParse: true, - format: 'mm/dd/yyyy', - keyboardNavigation: true, - language: 'en', - minViewMode: 0, - rtl: false, - startDate: -Infinity, - startView: 0, - todayBtn: false, - todayHighlight: false, - weekStart: 0 - }; - var locale_opts = $.fn.datepicker.locale_opts = [ - 'format', - 'rtl', - 'weekStart' - ]; - $.fn.datepicker.Constructor = Datepicker; - var dates = $.fn.datepicker.dates = { - en: { - days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"], - daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"], - daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"], - months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"], - monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"], - today: "Today", - clear: "Clear" - } - }; - - var DPGlobal = { - modes: [ - { - clsName: 'days', - navFnc: 'Month', - navStep: 1 - }, - { - clsName: 'months', - navFnc: 'FullYear', - navStep: 1 - }, - { - clsName: 'years', - navFnc: 'FullYear', - navStep: 10 - }], - isLeapYear: function (year) { - return (((year % 4 === 0) && (year % 100 !== 0)) || (year % 400 === 0)); - }, - getDaysInMonth: function (year, month) { - return [31, (DPGlobal.isLeapYear(year) ? 29 : 28), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month]; - }, - validParts: /dd?|DD?|mm?|MM?|yy(?:yy)?/g, - nonpunctuation: /[^ -\/:-@\[\u3400-\u9fff-`{-~\t\n\r]+/g, - parseFormat: function(format){ - // IE treats \0 as a string end in inputs (truncating the value), - // so it's a bad format delimiter, anyway - var separators = format.replace(this.validParts, '\0').split('\0'), - parts = format.match(this.validParts); - if (!separators || !separators.length || !parts || parts.length === 0){ - throw new Error("Invalid date format."); - } - return {separators: separators, parts: parts}; - }, - parseDate: function(date, format, language) { - if (date instanceof Date) return date; - if (typeof format === 'string') - format = DPGlobal.parseFormat(format); - if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) { - var part_re = /([\-+]\d+)([dmwy])/, - parts = date.match(/([\-+]\d+)([dmwy])/g), - part, dir; - date = new Date(); - for (var i=0; i<parts.length; i++) { - part = part_re.exec(parts[i]); - dir = parseInt(part[1]); - switch(part[2]){ - case 'd': - date.setUTCDate(date.getUTCDate() + dir); - break; - case 'm': - date = Datepicker.prototype.moveMonth.call(Datepicker.prototype, date, dir); - break; - case 'w': - date.setUTCDate(date.getUTCDate() + dir * 7); - break; - case 'y': - date = Datepicker.prototype.moveYear.call(Datepicker.prototype, date, dir); - break; - } - } - return UTCDate(date.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0); - } - var parts = date && date.match(this.nonpunctuation) || [], - date = new Date(), - parsed = {}, - setters_order = ['yyyy', 'yy', 'M', 'MM', 'm', 'mm', 'd', 'dd'], - setters_map = { - yyyy: function(d,v){ return d.setUTCFullYear(v); }, - yy: function(d,v){ return d.setUTCFullYear(2000+v); }, - m: function(d,v){ - v -= 1; - while (v<0) v += 12; - v %= 12; - d.setUTCMonth(v); - while (d.getUTCMonth() != v) - d.setUTCDate(d.getUTCDate()-1); - return d; - }, - d: function(d,v){ return d.setUTCDate(v); } - }, - val, filtered, part; - setters_map['M'] = setters_map['MM'] = setters_map['mm'] = setters_map['m']; - setters_map['dd'] = setters_map['d']; - date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0); - var fparts = format.parts.slice(); - // Remove noop parts - if (parts.length != fparts.length) { - fparts = $(fparts).filter(function(i,p){ - return $.inArray(p, setters_order) !== -1; - }).toArray(); - } - // Process remainder - if (parts.length == fparts.length) { - for (var i=0, cnt = fparts.length; i < cnt; i++) { - val = parseInt(parts[i], 10); - part = fparts[i]; - if (isNaN(val)) { - switch(part) { - case 'MM': - filtered = $(dates[language].months).filter(function(){ - var m = this.slice(0, parts[i].length), - p = parts[i].slice(0, m.length); - return m == p; - }); - val = $.inArray(filtered[0], dates[language].months) + 1; - break; - case 'M': - filtered = $(dates[language].monthsShort).filter(function(){ - var m = this.slice(0, parts[i].length), - p = parts[i].slice(0, m.length); - return m == p; - }); - val = $.inArray(filtered[0], dates[language].monthsShort) + 1; - break; - } - } - parsed[part] = val; - } - for (var i=0, s; i<setters_order.length; i++){ - s = setters_order[i]; - if (s in parsed && !isNaN(parsed[s])) - setters_map[s](date, parsed[s]); - } - } - return date; - }, - formatDate: function(date, format, language){ - if (typeof format === 'string') - format = DPGlobal.parseFormat(format); - var val = { - d: date.getUTCDate(), - D: dates[language].daysShort[date.getUTCDay()], - DD: dates[language].days[date.getUTCDay()], - m: date.getUTCMonth() + 1, - M: dates[language].monthsShort[date.getUTCMonth()], - MM: dates[language].months[date.getUTCMonth()], - yy: date.getUTCFullYear().toString().substring(2), - yyyy: date.getUTCFullYear() - }; - val.dd = (val.d < 10 ? '0' : '') + val.d; - val.mm = (val.m < 10 ? '0' : '') + val.m; - var date = [], - seps = $.extend([], format.separators); - for (var i=0, cnt = format.parts.length; i <= cnt; i++) { - if (seps.length) - date.push(seps.shift()); - date.push(val[format.parts[i]]); - } - return date.join(''); - }, - headTemplate: '<thead>'+ - '<tr>'+ - '<th class="prev"><i class="icon-arrow-left"/></th>'+ - '<th colspan="5" class="datepicker-switch"></th>'+ - '<th class="next"><i class="icon-arrow-right"/></th>'+ - '</tr>'+ - '</thead>', - contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>', - footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr><tr><th colspan="7" class="clear"></th></tr></tfoot>' - }; - DPGlobal.template = '<div class="datepicker">'+ - '<div class="datepicker-days">'+ - '<table class=" table-condensed">'+ - DPGlobal.headTemplate+ - '<tbody></tbody>'+ - DPGlobal.footTemplate+ - '</table>'+ - '</div>'+ - '<div class="datepicker-months">'+ - '<table class="table-condensed">'+ - DPGlobal.headTemplate+ - DPGlobal.contTemplate+ - DPGlobal.footTemplate+ - '</table>'+ - '</div>'+ - '<div class="datepicker-years">'+ - '<table class="table-condensed">'+ - DPGlobal.headTemplate+ - DPGlobal.contTemplate+ - DPGlobal.footTemplate+ - '</table>'+ - '</div>'+ - '</div>'; - - $.fn.datepicker.DPGlobal = DPGlobal; - - - /* DATEPICKER NO CONFLICT - * =================== */ - - $.fn.datepicker.noConflict = function(){ - $.fn.datepicker = old; - return this; - }; - - - /* DATEPICKER DATA-API - * ================== */ - - $(document).on( - 'focus.datepicker.data-api click.datepicker.data-api', - '[data-provide="datepicker"]', - function(e){ - var $this = $(this); - if ($this.data('datepicker')) return; - e.preventDefault(); - // component click requires us to explicitly show it - datepicker.call($this, 'show'); - } - ); - $(function(){ - //$('[data-provide="datepicker-inline"]').datepicker(); - //vit: changed to support noConflict() - datepicker.call($('[data-provide="datepicker-inline"]')); - }); - -}( window.jQuery )); - -/** -Bootstrap-datepicker. -Description and examples: https://github.com/eternicode/bootstrap-datepicker. -For **i18n** you should include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales -and set `language` option. -Since 1.4.0 date has different appearance in **popup** and **inline** modes. - -@class date -@extends abstractinput -@final -@example -<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-title="Select date">15/05/1984</a> -<script> -$(function(){ - $('#dob').editable({ - format: 'yyyy-mm-dd', - viewformat: 'dd/mm/yyyy', - datepicker: { - weekStart: 1 - } - } - }); -}); -</script> -**/ -(function ($) { - "use strict"; - - //store bootstrap-datepicker as bdateicker to exclude conflict with jQuery UI one - $.fn.bdatepicker = $.fn.datepicker.noConflict(); - if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name - $.fn.datepicker = $.fn.bdatepicker; - } - - var Date = function (options) { - this.init('date', options, Date.defaults); - this.initPicker(options, Date.defaults); - }; - - $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput); - - $.extend(Date.prototype, { - initPicker: function(options, defaults) { - //'format' is set directly from settings or data-* attributes - - //by default viewformat equals to format - if(!this.options.viewformat) { - this.options.viewformat = this.options.format; - } - - //try parse datepicker config defined as json string in data-datepicker - options.datepicker = $.fn.editableutils.tryParseJson(options.datepicker, true); - - //overriding datepicker config (as by default jQuery extend() is not recursive) - //since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only - this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, { - format: this.options.viewformat - }); - - //language - this.options.datepicker.language = this.options.datepicker.language || 'en'; - - //store DPglobal - this.dpg = $.fn.bdatepicker.DPGlobal; - - //store parsed formats - this.parsedFormat = this.dpg.parseFormat(this.options.format); - this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat); - }, - - render: function () { - this.$input.bdatepicker(this.options.datepicker); - - //"clear" link - if(this.options.clear) { - this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){ - e.preventDefault(); - e.stopPropagation(); - this.clear(); - }, this)); - - this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear)); - } - }, - - value2html: function(value, element) { - var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : ''; - Date.superclass.value2html.call(this, text, element); - }, - - html2value: function(html) { - return this.parseDate(html, this.parsedViewFormat); - }, - - value2str: function(value) { - return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : ''; - }, - - str2value: function(str) { - return this.parseDate(str, this.parsedFormat); - }, - - value2submit: function(value) { - return this.value2str(value); - }, - - value2input: function(value) { - this.$input.bdatepicker('update', value); - }, - - input2value: function() { - return this.$input.data('datepicker').date; - }, - - activate: function() { - }, - - clear: function() { - this.$input.data('datepicker').date = null; - this.$input.find('.active').removeClass('active'); - if(!this.options.showbuttons) { - this.$input.closest('form').submit(); - } - }, - - autosubmit: function() { - this.$input.on('mouseup', '.day', function(e){ - if($(e.currentTarget).is('.old') || $(e.currentTarget).is('.new')) { - return; - } - var $form = $(this).closest('form'); - setTimeout(function() { - $form.submit(); - }, 200); - }); - //changedate is not suitable as it triggered when showing datepicker. see #149 - /* - this.$input.on('changeDate', function(e){ - var $form = $(this).closest('form'); - setTimeout(function() { - $form.submit(); - }, 200); - }); - */ - }, - - /* - For incorrect date bootstrap-datepicker returns current date that is not suitable - for datefield. - This function returns null for incorrect date. - */ - parseDate: function(str, format) { - var date = null, formattedBack; - if(str) { - date = this.dpg.parseDate(str, format, this.options.datepicker.language); - if(typeof str === 'string') { - formattedBack = this.dpg.formatDate(date, format, this.options.datepicker.language); - if(str !== formattedBack) { - date = null; - } - } - } - return date; - } - - }); - - Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { - /** - @property tpl - @default <div></div> - **/ - tpl:'<div class="editable-date well"></div>', - /** - @property inputclass - @default null - **/ - inputclass: null, - /** - Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br> - Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code> - - @property format - @type string - @default yyyy-mm-dd - **/ - format:'yyyy-mm-dd', - /** - Format used for displaying date. Also applied when converting date from element's text on init. - If not specified equals to <code>format</code> - - @property viewformat - @type string - @default null - **/ - viewformat: null, - /** - Configuration of datepicker. - Full list of options: http://bootstrap-datepicker.readthedocs.org/en/latest/options.html - - @property datepicker - @type object - @default { - weekStart: 0, - startView: 0, - minViewMode: 0, - autoclose: false - } - **/ - datepicker:{ - weekStart: 0, - startView: 0, - minViewMode: 0, - autoclose: false - }, - /** - Text shown as clear date button. - If <code>false</code> clear button will not be rendered. - - @property clear - @type boolean|string - @default 'x clear' - **/ - clear: '× clear' - }); - - $.fn.editabletypes.date = Date; - -}(window.jQuery)); - -/** -Bootstrap datefield input - modification for inline mode. -Shows normal <input type="text"> and binds popup datepicker. -Automatically shown in inline mode. - -@class datefield -@extends date - -@since 1.4.0 -**/ -(function ($) { - "use strict"; - - var DateField = function (options) { - this.init('datefield', options, DateField.defaults); - this.initPicker(options, DateField.defaults); - }; - - $.fn.editableutils.inherit(DateField, $.fn.editabletypes.date); - - $.extend(DateField.prototype, { - render: function () { - this.$input = this.$tpl.find('input'); - this.setClass(); - this.setAttr('placeholder'); - - //bootstrap-datepicker is set `bdateicker` to exclude conflict with jQuery UI one. (in date.js) - this.$tpl.bdatepicker(this.options.datepicker); - - //need to disable original event handlers - this.$input.off('focus keydown'); - - //update value of datepicker - this.$input.keyup($.proxy(function(){ - this.$tpl.removeData('date'); - this.$tpl.bdatepicker('update'); - }, this)); - - }, - - value2input: function(value) { - this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : ''); - this.$tpl.bdatepicker('update'); - }, - - input2value: function() { - return this.html2value(this.$input.val()); - }, - - activate: function() { - $.fn.editabletypes.text.prototype.activate.call(this); - }, - - autosubmit: function() { - //reset autosubmit to empty - } - }); - - DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, { - /** - @property tpl - **/ - tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>', - /** - @property inputclass - @default 'input-small' - **/ - inputclass: 'input-small', - - /* datepicker config */ - datepicker: { - weekStart: 0, - startView: 0, - minViewMode: 0, - autoclose: true - } - }); - - $.fn.editabletypes.datefield = DateField; - -}(window.jQuery)); -/** -Bootstrap-datetimepicker. -Based on [smalot bootstrap-datetimepicker plugin](https://github.com/smalot/bootstrap-datetimepicker). -Before usage you should manually include dependent js and css: - - <link href="css/datetimepicker.css" rel="stylesheet" type="text/css"></link> - <script src="js/bootstrap-datetimepicker.js"></script> - -For **i18n** you should include js file from here: https://github.com/smalot/bootstrap-datetimepicker/tree/master/js/locales -and set `language` option. - -@class datetime -@extends abstractinput -@final -@since 1.4.4 -@example -<a href="#" id="last_seen" data-type="datetime" data-pk="1" data-url="/post" title="Select date & time">15/03/2013 12:45</a> -<script> -$(function(){ - $('#last_seen').editable({ - format: 'yyyy-mm-dd hh:ii', - viewformat: 'dd/mm/yyyy hh:ii', - datetimepicker: { - weekStart: 1 - } - } - }); -}); -</script> -**/ -(function ($) { - "use strict"; - - var DateTime = function (options) { - this.init('datetime', options, DateTime.defaults); - this.initPicker(options, DateTime.defaults); - }; - - $.fn.editableutils.inherit(DateTime, $.fn.editabletypes.abstractinput); - - $.extend(DateTime.prototype, { - initPicker: function(options, defaults) { - //'format' is set directly from settings or data-* attributes - - //by default viewformat equals to format - if(!this.options.viewformat) { - this.options.viewformat = this.options.format; - } - - //try parse datetimepicker config defined as json string in data-datetimepicker - options.datetimepicker = $.fn.editableutils.tryParseJson(options.datetimepicker, true); - - //overriding datetimepicker config (as by default jQuery extend() is not recursive) - //since 1.4 datetimepicker internally uses viewformat instead of format. Format is for submit only - this.options.datetimepicker = $.extend({}, defaults.datetimepicker, options.datetimepicker, { - format: this.options.viewformat - }); - - //language - this.options.datetimepicker.language = this.options.datetimepicker.language || 'en'; - - //store DPglobal - this.dpg = $.fn.datetimepicker.DPGlobal; - - //store parsed formats - this.parsedFormat = this.dpg.parseFormat(this.options.format, this.options.formatType); - this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat, this.options.formatType); - }, - - render: function () { - this.$input.datetimepicker(this.options.datetimepicker); - - //adjust container position when viewMode changes - //see https://github.com/smalot/bootstrap-datetimepicker/pull/80 - this.$input.on('changeMode', function(e) { - var f = $(this).closest('form').parent(); - //timeout here, otherwise container changes position before form has new size - setTimeout(function(){ - f.triggerHandler('resize'); - }, 0); - }); - - //"clear" link - if(this.options.clear) { - this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){ - e.preventDefault(); - e.stopPropagation(); - this.clear(); - }, this)); - - this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear)); - } - }, - - value2html: function(value, element) { - //formatDate works with UTCDate! - var text = value ? this.dpg.formatDate(this.toUTC(value), this.parsedViewFormat, this.options.datetimepicker.language, this.options.formatType) : ''; - if(element) { - DateTime.superclass.value2html.call(this, text, element); - } else { - return text; - } - }, - - html2value: function(html) { - //parseDate return utc date! - var value = this.parseDate(html, this.parsedViewFormat); - return value ? this.fromUTC(value) : null; - }, - - value2str: function(value) { - //formatDate works with UTCDate! - return value ? this.dpg.formatDate(this.toUTC(value), this.parsedFormat, this.options.datetimepicker.language, this.options.formatType) : ''; - }, - - str2value: function(str) { - //parseDate return utc date! - var value = this.parseDate(str, this.parsedFormat); - return value ? this.fromUTC(value) : null; - }, - - value2submit: function(value) { - return this.value2str(value); - }, - - value2input: function(value) { - if(value) { - this.$input.data('datetimepicker').setDate(value); - } - }, - - input2value: function() { - //date may be cleared, in that case getDate() triggers error - var dt = this.$input.data('datetimepicker'); - return dt.date ? dt.getDate() : null; - }, - - activate: function() { - }, - - clear: function() { - this.$input.data('datetimepicker').date = null; - this.$input.find('.active').removeClass('active'); - if(!this.options.showbuttons) { - this.$input.closest('form').submit(); - } - }, - - autosubmit: function() { - this.$input.on('mouseup', '.minute', function(e){ - var $form = $(this).closest('form'); - setTimeout(function() { - $form.submit(); - }, 200); - }); - }, - - //convert date from local to utc - toUTC: function(value) { - return value ? new Date(value.valueOf() - value.getTimezoneOffset() * 60000) : value; - }, - - //convert date from utc to local - fromUTC: function(value) { - return value ? new Date(value.valueOf() + value.getTimezoneOffset() * 60000) : value; - }, - - /* - For incorrect date bootstrap-datetimepicker returns current date that is not suitable - for datetimefield. - This function returns null for incorrect date. - */ - parseDate: function(str, format) { - var date = null, formattedBack; - if(str) { - date = this.dpg.parseDate(str, format, this.options.datetimepicker.language, this.options.formatType); - if(typeof str === 'string') { - formattedBack = this.dpg.formatDate(date, format, this.options.datetimepicker.language, this.options.formatType); - if(str !== formattedBack) { - date = null; - } - } - } - return date; - } - - }); - - DateTime.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { - /** - @property tpl - @default <div></div> - **/ - tpl:'<div class="editable-date well"></div>', - /** - @property inputclass - @default null - **/ - inputclass: null, - /** - Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br> - Possible tokens are: <code>d, dd, m, mm, yy, yyyy, h, i</code> - - @property format - @type string - @default yyyy-mm-dd hh:ii - **/ - format:'yyyy-mm-dd hh:ii', - formatType:'standard', - /** - Format used for displaying date. Also applied when converting date from element's text on init. - If not specified equals to <code>format</code> - - @property viewformat - @type string - @default null - **/ - viewformat: null, - /** - Configuration of datetimepicker. - Full list of options: https://github.com/smalot/bootstrap-datetimepicker - - @property datetimepicker - @type object - @default { } - **/ - datetimepicker:{ - todayHighlight: false, - autoclose: false - }, - /** - Text shown as clear date button. - If <code>false</code> clear button will not be rendered. - - @property clear - @type boolean|string - @default 'x clear' - **/ - clear: '× clear' - }); - - $.fn.editabletypes.datetime = DateTime; - -}(window.jQuery)); -/** -Bootstrap datetimefield input - datetime input for inline mode. -Shows normal <input type="text"> and binds popup datetimepicker. -Automatically shown in inline mode. - -@class datetimefield -@extends datetime - -**/ -(function ($) { - "use strict"; - - var DateTimeField = function (options) { - this.init('datetimefield', options, DateTimeField.defaults); - this.initPicker(options, DateTimeField.defaults); - }; - - $.fn.editableutils.inherit(DateTimeField, $.fn.editabletypes.datetime); - - $.extend(DateTimeField.prototype, { - render: function () { - this.$input = this.$tpl.find('input'); - this.setClass(); - this.setAttr('placeholder'); - - this.$tpl.datetimepicker(this.options.datetimepicker); - - //need to disable original event handlers - this.$input.off('focus keydown'); - - //update value of datepicker - this.$input.keyup($.proxy(function(){ - this.$tpl.removeData('date'); - this.$tpl.datetimepicker('update'); - }, this)); - - }, - - value2input: function(value) { - this.$input.val(this.value2html(value)); - this.$tpl.datetimepicker('update'); - }, - - input2value: function() { - return this.html2value(this.$input.val()); - }, - - activate: function() { - $.fn.editabletypes.text.prototype.activate.call(this); - }, - - autosubmit: function() { - //reset autosubmit to empty - } - }); - - DateTimeField.defaults = $.extend({}, $.fn.editabletypes.datetime.defaults, { - /** - @property tpl - **/ - tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>', - /** - @property inputclass - @default 'input-medium' - **/ - inputclass: 'input-medium', - - /* datetimepicker config */ - datetimepicker:{ - todayHighlight: false, - autoclose: true - } - }); - - $.fn.editabletypes.datetimefield = DateTimeField; - -}(window.jQuery)); \ No newline at end of file +!function(t){"use strict";const i=t.fn.editableform.Constructor.prototype.initInput;t.extend(t.fn.editableform.Constructor.prototype,{initInput:function(){i.apply(this);const n=null===this.input.options.inputclass||!1===this.input.options.inputclass,s="input-sm",e="text,select,textarea,password,email,url,tel,number,range,time,typeaheadjs".split(",");~t.inArray(this.input.type,e)&&(this.input.$input.addClass("form-control editable"),n&&(this.input.options.inputclass=s,this.input.$input.addClass(s))),"select"===this.input.type&&setTimeout((()=>{this.input.$input.focus().click()}),50);const o=this.$form.find(".editable-buttons"),l=n?[s]:this.input.options.inputclass.split(" ");for(let t=0;t<l.length;t++)"input-lg"===l[t].toLowerCase()&&o.find("button").removeClass("btn-sm").addClass("btn-lg")}}),t.fn.editableform.buttons='<button type="submit" class="btn btn-primary btn-sm editable-submit"><i class="bi bi-check"></i></button><button type="button" class="btn btn-secondary btn-sm editable-cancel"><i class="bi bi-x"></i></button>',t.fn.editableform.errorGroupClass="has-error",t.fn.editableform.errorBlockClass=null,t.fn.editableform.engine="bs3"}(window.jQuery),module.exports={}; \ No newline at end of file diff --git a/package.json b/package.json index e382d70..ac61006 100644 --- a/package.json +++ b/package.json @@ -1,26 +1,37 @@ { "name": "@24unix/x-editable", "title": "X-editable", - "description": "In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery", + "description": "A maintained fork of x-editable for Bootstrap 5 support.", "version": "1.5.1", - "homepage": "http://github.com/vitalets/x-editable", + "homepage": "https://git.24unix.net/tracer/x-editable", "author": { - "name": "Vitaliy Potapov", - "email": "noginsk@rambler.ru" + "name": "Micha Espey", + "email": "tracer@24unix.net" }, + "contributors": [ + { + "name": "Vitaliy Potapov", + "email": "noginsk@rambler.ru", + "url": "https://github.com/vitalets", + "contributions": "Original author of x-editable" + } + ], "repository": { "type": "git", - "url": "git://github.com/vitalets/x-editable.git" + "url": "https://git.24unix.net/tracer/x-editable.git" }, "bugs": { - "url": "https://github.com/vitalets/x-editable/issues" + "url": "https://git.24unix.net/tracer/x-editable/issues" }, "licenses": [ { "type": "MIT", - "url": "https://github.com/vitalets/x-editable/blob/master/LICENSE-MIT" + "url": "https://git.24unix.net/tracer/x-editable/blob/master/LICENSE-MIT" } ], + "main": "dist/bootstrap5-editable/js/bootstrap-editable.js", + "module": "dist/bootstrap5-editable/js/bootstrap-editable.esm.js", + "style": "dist/bootstrap5-editable/css/bootstrap-editable.css", "dependencies": { "bootstrap": "^5.3.3", "bootstrap-icons": "^1.11.3", @@ -43,5 +54,11 @@ "style-loader": "^4.0.0", "webpack-cli": "^6.0.1" }, - "keywords": [] -} + "keywords": [ + "editable", + "bootstrap5", + "jquery", + "in-place-editing", + "x-editable" + ] +} \ No newline at end of file