vendor/assets/javascripts/bootstrap-editable.js in bootstrap-editable-rails-0.0.1 vs vendor/assets/javascripts/bootstrap-editable.js in bootstrap-editable-rails-0.0.2
- old
+ new
@@ -1,36 +1,39 @@
-/*! X-editable - v1.1.1
+/*! X-editable - v1.3.0
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
* http://github.com/vitalets/x-editable
* Copyright (c) 2012 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!)
-Editableform is linked with one of input types, e.g. 'text' or 'select'.
+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 ($) {
- var EditableForm = function (element, options) {
+ var EditableForm = function (div, options) {
this.options = $.extend({}, $.fn.editableform.defaults, options);
- this.$element = $(element); //div, containing form. Not form tag! Not editable-element.
+ this.$div = $(div); //div, containing form. Not form tag! Not editable-element.
+ if(!this.options.scope) {
+ this.options.scope = this;
+ }
this.initInput();
};
EditableForm.prototype = {
constructor: EditableForm,
initInput: function() { //called once
var TypeConstructor, typeOptions;
//create input of specified type
- if(typeof $.fn.editableform.types[this.options.type] === 'function') {
- TypeConstructor = $.fn.editableform.types[this.options.type];
- typeOptions = $.fn.editableform.utils.sliceObj(this.options, $.fn.editableform.utils.objectKeys(TypeConstructor.defaults));
+ if(typeof $.fn.editabletypes[this.options.type] === 'function') {
+ TypeConstructor = $.fn.editabletypes[this.options.type];
+ typeOptions = $.fn.editableutils.sliceObj(this.options, $.fn.editableutils.objectKeys(TypeConstructor.defaults));
this.input = new TypeConstructor(typeOptions);
} else {
$.error('Unknown type: '+ this.options.type);
return;
}
@@ -48,11 +51,11 @@
@method render
**/
render: function() {
this.$loading = $($.fn.editableform.loading);
- this.$element.empty().append(this.$loading);
+ this.$div.empty().append(this.$loading);
this.showLoading();
//init form template and buttons
this.initTemplate();
if(this.options.showbuttons) {
@@ -64,11 +67,11 @@
/**
Fired when rendering starts
@event rendering
@param {Object} event event object
**/
- this.$element.triggerHandler('rendering');
+ this.$div.triggerHandler('rendering');
//render input
$.when(this.input.render())
.then($.proxy(function () {
//input
@@ -83,45 +86,47 @@
if(this.input.$clear) {
this.$form.find('div.editable-input').append($('<div class="editable-clear">').append(this.input.$clear));
}
//append form to container
- this.$element.append(this.$form);
-
+ this.$div.append(this.$form);
+
//attach 'cancel' handler
this.$form.find('.editable-cancel').click($.proxy(this.cancel, this));
- // this.$form.find('.editable-buttons button').eq(1).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');
this.input.value2input(this.value);
+ //attach submit handler
this.$form.submit($.proxy(this.submit, this));
}
/**
Fired when form is rendered
@event rendered
@param {Object} event event object
**/
- this.$element.triggerHandler('rendered');
+ this.$div.triggerHandler('rendered');
this.showForm();
}, this));
},
cancel: function() {
/**
Fired when form was cancelled by user
@event cancel
@param {Object} event event object
**/
- this.$element.triggerHandler('cancel');
+ this.$div.triggerHandler('cancel');
},
showLoading: function() {
var w;
if(this.$form) {
//set loading size equal to form
@@ -136,20 +141,22 @@
}
}
this.$loading.show();
},
- showForm: function() {
+ showForm: function(activate) {
this.$loading.hide();
this.$form.show();
- this.input.activate();
+ if(activate !== false) {
+ this.input.activate();
+ }
/**
Fired when form is shown
@event show
@param {Object} event event object
**/
- this.$element.triggerHandler('show');
+ this.$div.triggerHandler('show');
},
error: function(msg) {
var $group = this.$form.find('.control-group'),
$block = this.$form.find('.editable-error-block');
@@ -166,39 +173,47 @@
submit: function(e) {
e.stopPropagation();
e.preventDefault();
var error,
- newValue = this.input.input2value(), //get new value from input
- newValueStr;
+ newValue = this.input.input2value(); //get new value from input
//validation
if (error = this.validate(newValue)) {
this.error(error);
this.showForm();
return;
}
- //value as string
- newValueStr = this.input.value2str(newValue);
-
- //if value not changed --> cancel
+ //if value not changed --> trigger 'nochange' event and return
/*jslint eqeq: true*/
- if (newValueStr == this.input.value2str(this.value)) {
+ if (!this.options.savenochange && this.input.value2str(newValue) == this.input.value2str(this.value)) {
/*jslint eqeq: false*/
- this.cancel();
+ /**
+ Fired when value not changed but form is submitted. Requires savenochange = false.
+ @event nochange
+ @param {Object} event event object
+ **/
+ this.$div.triggerHandler('nochange');
return;
}
//sending data to server
- $.when(this.save(newValueStr))
+ $.when(this.save(newValue))
.done($.proxy(function(response) {
//run success callback
- var res = typeof this.options.success === 'function' ? this.options.success.call(this, response, newValue) : null;
+ var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;
- //if success callback returns string --> show error
- if(res && typeof res === 'string') {
+ //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;
}
@@ -221,63 +236,67 @@
@example
$('#form-div').on('save'), function(e, params){
if(params.newValue === 'username') {...}
});
**/
- this.$element.triggerHandler('save', {newValue: newValue, response: response});
+ this.$div.triggerHandler('save', {newValue: newValue, response: response});
}, this))
.fail($.proxy(function(xhr) {
this.error(typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!');
this.showForm();
}, this));
},
- save: function(value) {
- var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this) : this.options.pk,
+ save: function(newValue) {
+ //convert value for submitting to server
+ var submitValue = this.input.value2submit(newValue);
+
+ //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 = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk)))),
- params, ajaxOptions;
+ params;
if (send) { //send to server
this.showLoading();
//standard params
params = {
name: this.options.name || '',
- value: value,
+ value: submitValue,
pk: pk
};
//additional params
if(typeof this.options.params === 'function') {
- $.extend(params, this.options.params.call(this, params));
+ params = this.options.params.call(this.options.scope, params);
} else {
//try parse json in single quotes (from data-params attribute)
- this.options.params = $.fn.editableform.utils.tryParseJson(this.options.params, true);
+ 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, params);
- } else { //send ajax to server and return deferred object
- ajaxOptions = $.extend({
+ 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',
- dataType: 'json'
- }, this.options.ajaxOptions);
-
- return $.ajax(ajaxOptions);
+ 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, value);
+ return this.options.validate.call(this.options.scope, value);
}
},
option: function(key, value) {
this.options[key] = value;
@@ -359,14 +378,17 @@
}
}
**/
url:null,
/**
- Additional params for submit. Function can be used to calculate params dynamically
+ 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) {
- return { a: 1 };
+ //originally params contain pk, name and value
+ params.a = 1;
+ return params;
}
@property params
@type object|function
@default null
@@ -380,11 +402,11 @@
@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 dinamically via function.
+ Can be calculated dynamically via function.
@property pk
@type string|object|function
@default null
**/
@@ -421,11 +443,11 @@
}
**/
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>
+ Useful 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.
@@ -435,40 +457,56 @@
@example
success: function(response, newValue) {
if(!response.success) return response.msg;
}
**/
- success: function(response, newValue) {},
+ success: null,
/**
Additional options for ajax request.
List of values: http://api.jquery.com/jQuery.ajax
@property ajaxOptions
@type object
@default null
+ @since 1.1.1
**/
ajaxOptions: null,
/**
- Wether to show buttons or not.
+ Whether to show buttons or not.
Form without buttons can be auto-submitted by input or by onblur = 'submit'.
+ @example
+ ajaxOptions: {
+ method: 'PUT',
+ dataType: 'xml'
+ }
@property showbuttons
@type boolean
@default true
+ @since 1.1.1
**/
- showbuttons: true
-
- /*todo:
- Submit strategy. Can be <code>normal|never</code>
- <code>submitmode='never'</code> usefull for turning into classic form several inputs and submitting them together manually.
- Works pretty with <code>showbuttons=false</code>
+ showbuttons: true,
+ /**
+ Scope for callback methods (success, validate).
+ If <code>null</code> means editableform instance itself.
- @property submitmode
- @type string
- @default normal
- */
-// submitmode: 'normal'
+ @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!
@@ -485,30 +523,25 @@
//buttons
$.fn.editableform.buttons = '<button type="submit" class="editable-submit">ok</button>'+
'<button type="button" class="editable-cancel">cancel</button>';
- //error class attahced to control-group
+ //error class attached to control-group
$.fn.editableform.errorGroupClass = null;
- //error class attahced to editable-error-block
+ //error class attached to editable-error-block
$.fn.editableform.errorBlockClass = 'editable-error';
-
- //input types
- $.fn.editableform.types = {};
- //utils
- $.fn.editableform.utils = {};
-
}(window.jQuery));
/**
* EditableForm utilites
*/
(function ($) {
- $.fn.editableform.utils = {
+ //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;
@@ -615,11 +648,18 @@
}
}
return k;
}
- }
+ },
+
+ /**
+ method to escape html.
+ **/
+ escape: function(str) {
+ return $('<div>').text(str).html();
+ }
};
}(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>
@@ -640,11 +680,11 @@
containerName: null, //tbd in child class
innerCss: null, //tbd in child class
init: function(element, options) {
this.$element = $(element);
//todo: what is in priority: data or js?
- this.options = $.extend({}, $.fn.editableContainer.defaults, $.fn.editableform.utils.getConfigData(this.$element), options);
+ this.options = $.extend({}, $.fn.editableContainer.defaults, $.fn.editableutils.getConfigData(this.$element), options);
this.splitOptions();
this.initContainer();
//bind 'destroyed' listener to destroy container when element is removed from dom
this.$element.on('destroyed', $.proxy(function(){
@@ -695,16 +735,18 @@
initContainer: function(){
this.call(this.containerOptions);
},
initForm: function() {
+ this.formOptions.scope = this.$element[0]; //set scope of form callbacks to element
this.$form = $('<div>')
.editableform(this.formOptions)
.on({
save: $.proxy(this.save, this),
- cancel: $.proxy(this.cancel, this),
- show: $.proxy(this.setPosition, this), //re-position container every time form is shown (after loading state)
+ cancel: $.proxy(function(){ this.hide('cancel'); }, this),
+ nochange: $.proxy(function(){ this.hide('nochange'); }, this),
+ show: $.proxy(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
rendered: $.proxy(function(){
/**
Fired when container is shown and form is rendered (for select will wait for loading dropdown options)
@@ -739,11 +781,11 @@
},
/**
Shows container with form
@method show()
- @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true.
+ @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
**/
show: function (closeAll) {
this.$element.addClass('editable-open');
if(closeAll !== false) {
//close all open containers (except this)
@@ -763,35 +805,44 @@
},
/**
Hides container with form
@method hide()
+ @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
**/
- hide: function() {
+ hide: function(reason) {
if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
return;
}
this.$element.removeClass('editable-open');
this.innerHide();
/**
Fired when container was hidden. It occurs on both save or cancel.
@event hidden
- @param {Object} event event object
+ @param {object} event event object
+ @param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=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');
+ this.$element.triggerHandler('hidden', reason);
},
/* internal hide method. To be overwritten in child classes */
innerHide: function () {
this.call('hide');
},
/**
Toggles container visibility (show / hide)
@method toggle()
- @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true.
+ @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
**/
toggle: function(closeAll) {
if(this.tip && this.tip().is(':visible')) {
this.hide();
} else {
@@ -805,27 +856,12 @@
*/
setPosition: function() {
//tbd in child class
},
- cancel: function() {
- if(this.options.autohide) {
- this.hide();
- }
- /**
- Fired when form was cancelled by user
-
- @event cancel
- @param {Object} event event object
- **/
- this.$element.triggerHandler('cancel');
- },
-
save: function(e, params) {
- if(this.options.autohide) {
- this.hide();
- }
+ this.hide('save');
/**
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
@@ -882,12 +918,12 @@
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
- if(el === element) {
+ //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),
@@ -896,11 +932,11 @@
if(!ec) {
return;
}
if(ec.options.onblur === 'cancel') {
- $el.data('editableContainer').hide();
+ $el.data('editableContainer').hide('onblur');
} else if(ec.options.onblur === 'submit') {
$el.data('editableContainer').tip().find('form').submit();
}
});
@@ -970,11 +1006,11 @@
@type string
@default 'top'
**/
placement: 'top',
/**
- Wether to hide container on save/cancel.
+ Whether to hide container on save/cancel.
@property autohide
@type boolean
@default true
@private
@@ -985,10 +1021,11 @@
Setting <code>ignore</code> allows to have several containers open.
@property onblur
@type string
@default 'cancel'
+ @since 1.1.1
**/
onblur: 'cancel'
};
/*
@@ -1013,11 +1050,11 @@
**/
(function ($) {
var Editable = function (element, options) {
this.$element = $(element);
- this.options = $.extend({}, $.fn.editable.defaults, $.fn.editableform.utils.getConfigData(this.$element), options);
+ this.options = $.extend({}, $.fn.editable.defaults, $.fn.editableutils.getConfigData(this.$element), options);
this.init();
};
Editable.prototype = {
constructor: Editable,
@@ -1025,26 +1062,23 @@
var TypeConstructor,
isValueByText = false,
doAutotext,
finalize;
- //initialization flag
- this.isInit = true;
-
//editableContainer must be defined
if(!$.fn.editableContainer) {
$.error('You must define $.fn.editableContainer via including corresponding file (e.g. editable-popover.js)');
return;
}
//name
this.options.name = this.options.name || this.$element.attr('id');
//create input of specified type. Input will be used for converting value, not in form
- if(typeof $.fn.editableform.types[this.options.type] === 'function') {
- TypeConstructor = $.fn.editableform.types[this.options.type];
- this.typeOptions = $.fn.editableform.utils.sliceObj(this.options, $.fn.editableform.utils.objectKeys(TypeConstructor.defaults));
+ if(typeof $.fn.editabletypes[this.options.type] === 'function') {
+ TypeConstructor = $.fn.editabletypes[this.options.type];
+ this.typeOptions = $.fn.editableutils.sliceObj(this.options, $.fn.editableutils.objectKeys(TypeConstructor.defaults));
this.input = new TypeConstructor(this.typeOptions);
} else {
$.error('Unknown type: '+ this.options.type);
return;
}
@@ -1052,17 +1086,24 @@
//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.options.value = $.trim(this.options.value);
+ this.value = this.input.str2value(this.options.value);
+ } else {
+ this.value = this.options.value;
}
- this.value = this.input.str2value(this.options.value);
}
- //add 'editable' class
+ //add 'editable' class to every editable element
this.$element.addClass('editable');
//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');
@@ -1086,33 +1127,50 @@
//check conditions for autotext:
//if value was generated by text or value is empty, no sense to run autotext
doAutotext = !isValueByText && this.value !== null && this.value !== undefined;
doAutotext &= (this.options.autotext === 'always') || (this.options.autotext === 'auto' && !this.$element.text().length);
- $.when(doAutotext ? this.input.value2html(this.value, this.$element) : true).then($.proxy(function() {
+ $.when(doAutotext ? this.render() : true).then($.proxy(function() {
if(this.options.disabled) {
this.disable();
} else {
this.enable();
}
/**
- Fired each time when element's text is rendered. Occurs on initialization and on each update of value.
- Can be used for display customization.
+ Fired when element was initialized by editable method.
- @event render
+ @event init
@param {Object} event event object
@param {Object} editable editable instance
- @example
- $('#action').on('render', function(e, editable) {
- var colors = {0: "gray", 1: "green", 2: "blue", 3: "red"};
- $(this).css("color", colors[editable.value]);
- });
+ @since 1.2.0
**/
- this.$element.triggerHandler('render', this);
- this.isInit = false;
+ this.$element.triggerHandler('init', this);
}, this));
},
+
+ /*
+ Renders value into element's text.
+ Can call custom display method from options.
+ Can return deferred object.
+ @method render()
+ */
+ render: function() {
+ //do not display anything
+ if(this.options.display === false) {
+ return;
+ }
+ //if it is input with source, we pass callback in third param to be called when source is loaded
+ if(this.input.options.hasOwnProperty('source')) {
+ return this.input.value2html(this.value, this.$element[0], this.options.display);
+ //if display method defined --> use it
+ } else if(typeof this.options.display === 'function') {
+ return this.options.display.call(this.$element[0], this.value);
+ //else use input's original value2html() method
+ } else {
+ return this.input.value2html(this.value, this.$element[0]);
+ }
+ },
/**
Enables editable
@method enable()
**/
@@ -1196,10 +1254,15 @@
/*
* set emptytext if element is empty (reverse: remove emptytext if needed)
*/
handleEmpty: function () {
+ //do not handle empty if we do not display anything
+ if(this.options.display === false) {
+ return;
+ }
+
var emptyClass = 'editable-empty';
//emptytext shown only for enabled
if(!this.options.disabled) {
if ($.trim(this.$element.text()) === '') {
this.$element.addClass(emptyClass).text(this.options.emptytext);
@@ -1216,28 +1279,24 @@
},
/**
Shows container with form
@method show()
- @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true.
+ @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,
- autohide: false //element will take care to show/hide container
+ value: this.value
});
this.$element.editableContainer(containerOptions);
- this.$element.on({
- save: $.proxy(this.save, this),
- cancel: $.proxy(this.hide, this)
- });
+ this.$element.on("save.internal", $.proxy(this.save, this));
this.container = this.$element.data('editableContainer');
} else if(this.container.tip().is(':visible')) {
return;
}
@@ -1251,21 +1310,16 @@
**/
hide: function () {
if(this.container) {
this.container.hide();
}
-
- //return focus on element
- if (this.options.enablefocus && this.options.toggle === 'click') {
- this.$element.focus();
- }
},
/**
Toggles container visibility (show / hide)
@method toggle()
- @param {boolean} closeAll Wether to close all other editable containers when showing this one. Default true.
+ @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 {
@@ -1276,17 +1330,17 @@
/*
* called when form was submitted
*/
save: function(e, params) {
//if url is not user's function and value was not sent to server and value changed --> mark element with unsaved css.
- if(typeof this.options.url !== 'function' && params.response === undefined && this.input.value2str(this.value) !== this.input.value2str(params.newValue)) {
+ if(typeof this.options.url !== 'function' && this.options.display !== false && params.response === undefined && this.input.value2str(this.value) !== this.input.value2str(params.newValue)) {
this.$element.addClass('editable-unsaved');
} else {
this.$element.removeClass('editable-unsaved');
}
- this.hide();
+ // this.hide();
this.setValue(params.newValue);
/**
Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance
@@ -1317,25 +1371,24 @@
/**
Sets new value of editable
@method setValue(value, convertStr)
@param {mixed} value new value
- @param {boolean} convertStr wether to convert value from string to internal format
+ @param {boolean} convertStr whether to convert value from string to internal format
**/
setValue: function(value, convertStr) {
if(convertStr) {
this.value = this.input.str2value(value);
} else {
this.value = value;
}
if(this.container) {
this.container.option('value', this.value);
}
- $.when(this.input.value2html(this.value, this.$element))
+ $.when(this.render())
.then($.proxy(function() {
this.handleEmpty();
- this.$element.triggerHandler('render', this);
}, this));
},
/**
Activates input of visible container (e.g. set focus)
@@ -1343,11 +1396,11 @@
**/
activate: function() {
if(this.container) {
this.container.activate();
}
- }
+ }
};
/* EDITABLE PLUGIN DEFINITION
* ======================= */
@@ -1374,11 +1427,11 @@
@returns {Object} validation errors map
@example
$('#username, #fullname').editable('validate');
// possible result:
{
- username: "username is requied",
+ username: "username is required",
fullname: "fullname should be minimum 3 letters length"
}
**/
case 'validate':
this.each(function () {
@@ -1403,62 +1456,61 @@
**/
case 'getValue':
this.each(function () {
var $this = $(this), data = $this.data(datakey);
if (data && data.value !== undefined && data.value !== null) {
- result[data.options.name] = data.input.value2str(data.value);
+ 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.
- It is designed mainly for <a href="#newrecord">creating new records</a>.
+ 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.
@method submit(options)
@param {object} options
@param {object} options.url url to submit data
@param {object} options.data additional data to submit
- @param {function} options.error(obj) error handler (called on both client-side and server-side validation errors)
- @param {function} options.success(obj) success handler
+ @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'),
values;
- if(typeof config.error !== 'function') {
- config.error = function() {};
- }
-
if($.isEmptyObject(errors)) {
values = this.editable('getValue');
if(config.data) {
$.extend(values, config.data);
- }
- $.ajax({
- type: 'POST',
+ }
+
+ $.ajax($.extend({
url: config.url,
data: values,
- dataType: 'json'
- }).success(function(response) {
- if(typeof response === 'object' && response.id) {
- $elems.editable('option', 'pk', response.id);
- $elems.removeClass('editable-unsaved');
- if(typeof config.success === 'function') {
- config.success.apply($elems, arguments);
- }
- } else { //server-side validation error
+ type: 'POST'
+ }, config.ajaxOptions))
+ .success(function(response) {
+ //successful response 200 OK
+ if(typeof config.success === 'function') {
+ config.success.call($elems, response, config);
+ }
+ })
+ .error(function(){ //ajax error
+ if(typeof config.error === 'function') {
config.error.apply($elems, arguments);
}
- }).error(function(){ //ajax error
- config.error.apply($elems, arguments);
});
} else { //client-side validation error
- config.error.call($elems, {errors: errors});
+ if(typeof config.error === 'function') {
+ config.error.call($elems, errors);
+ }
}
return this;
}
//return jquery object
@@ -1510,63 +1562,76 @@
@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>. Usefull for select and date.
+ 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',
/**
- Wether to return focus on element after form is closed.
- This allows fully keyboard input.
-
- @property enablefocus
- @type boolean
- @default false
- **/
- enablefocus: false,
- /**
Initial value of input. Taken from <code>data-value</code> or element's text.
@property value
@type mixed
@default element's text
**/
- value: null
+ value: null,
+ /**
+ Callback to perform custom displaying of value in element's text.
+ If <code>null</code>, default input's value2html() will be called.
+ If <code>false</code>, no displaying methods will be called, element's text will no change.
+ Runs under element's scope.
+ Second parameter __sourceData__ is passed for inputs with source (select, checklist).
+
+ @property display
+ @type function|boolean
+ @default null
+ @since 1.2.0
+ @example
+ display: function(value, sourceData) {
+ var escapedValue = $('<div>').text(value).html();
+ $(this).html('<b>'+escapedValue+'</b>');
+ }
+ **/
+ display: null
};
-}(window.jQuery));
+}(window.jQuery));
+
/**
-Abstract editable input class.
-To create your own input you should inherit from this class.
+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 abstract
+@class abstractinput
**/
(function ($) {
- var Abstract = function () { };
+ //types
+ $.fn.editabletypes = {};
+
+ var AbstractInput = function () { };
- Abstract.prototype = {
+ AbstractInput.prototype = {
/**
- Iinitializes input
+ Initializes input
@method init()
**/
init: function(type, options, defaults) {
this.type = type;
@@ -1575,11 +1640,11 @@
this.$clear = null;
this.error = null;
},
/**
- Renders input. Can return jQuery deferred object.
+ Renders input from tpl. Can return jQuery deferred object.
@method render()
**/
render: function() {
this.$input = $(this.options.tpl);
@@ -1598,12 +1663,11 @@
@method value2html(value, element)
@param {mixed} value
@param {DOMElement} element
**/
value2html: function(value, element) {
- var html = this.escape(value);
- $(element).html(html);
+ $(element).text(value);
},
/**
Converts element's html to value
@@ -1614,11 +1678,11 @@
html2value: function(html) {
return $('<div>').html(html).text();
},
/**
- Converts value to string (for submiting to server)
+ Converts value to string (for internal compare). For submitting to server used value2submit().
@method value2str(value)
@param {mixed} value
@returns {string}
**/
@@ -1636,10 +1700,21 @@
str2value: function(str) {
return str;
},
/**
+ Converts value for submitting to server
+
+ @method value2submit(value)
+ @param {mixed} value
+ @returns {mixed}
+ **/
+ value2submit: function(value) {
+ return value;
+ },
+
+ /**
Sets value of input.
@method value2input(value)
@param {mixed} value
**/
@@ -1666,15 +1741,15 @@
this.$input.focus();
}
},
/**
- Creares input.
+ Creates input.
@method clear()
**/
- clear: function() {
+ clear: function() {
this.$input.val(null);
},
/**
method to escape html.
@@ -1682,60 +1757,61 @@
escape: function(str) {
return $('<div>').text(str).html();
},
/**
- attach handler to automatically submit form when value changed (usefull when buttons not shown)
+ attach handler to automatically submit form when value changed (useful when buttons not shown)
**/
autosubmit: function() {
}
};
- Abstract.defaults = {
+ 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 span2
+ @default input-medium
**/
- inputclass: 'span2',
+ inputclass: 'input-medium',
/**
Name attribute of input
@property name
@type string
@default null
**/
name: null
};
- $.extend($.fn.editableform.types, {abstract: Abstract});
+ $.extend($.fn.editabletypes, {abstractinput: AbstractInput});
-}(window.jQuery));
+}(window.jQuery));
+
/**
List - abstract class for inputs that have source option loaded from js array or via ajax
@class list
-@extends abstract
+@extends abstractinput
**/
(function ($) {
var List = function (options) {
};
- $.fn.editableform.utils.inherit(List, $.fn.editableform.types.abstract);
+ $.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput);
$.extend(List.prototype, {
render: function () {
List.superclass.render.call(this);
var deferred = $.Deferred();
@@ -1755,17 +1831,22 @@
html2value: function (html) {
return null; //can't set value by text
},
- value2html: function (value, element) {
+ value2html: function (value, element, display) {
var deferred = $.Deferred();
this.onSourceReady(function () {
- this.value2htmlFinal(value, element);
+ if(typeof display === 'function') {
+ //custom display method
+ display.call(element, value, this.sourceData);
+ } else {
+ this.value2htmlFinal(value, element);
+ }
deferred.resolve();
}, function () {
- List.superclass.value2html(this.options.sourceError, element);
+ //do nothing with element
deferred.resolve();
});
return deferred.promise();
},
@@ -1779,71 +1860,83 @@
return;
}
// try parse json in single quotes (for double quotes jquery does automatically)
try {
- this.options.source = $.fn.editableform.utils.tryParseJson(this.options.source, false);
+ this.options.source = $.fn.editableutils.tryParseJson(this.options.source, false);
} catch (e) {
error.call(this);
return;
}
//loading from url
if (typeof this.options.source === 'string') {
- var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''),
- cache;
+ //try to get from cache
+ if(this.options.sourceCache) {
+ var cacheID = this.options.source + (this.options.name ? '-' + this.options.name : ''),
+ cache;
- if (!$(document).data(cacheID)) {
- $(document).data(cacheID, {});
- }
- cache = $(document).data(cacheID);
+ 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;
- 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 () {
+ //check for cached data
+ if (cache.loading === false && cache.sourceData) { //take source from cache
this.sourceData = cache.sourceData;
success.call(this);
- }, 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;
+ 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 = [];
+ //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 = [];
+ }
}
//loading sourceData from server
$.ajax({
url: this.options.source,
type: 'get',
cache: false,
data: this.options.name ? {name: this.options.name} : {},
dataType: 'json',
success: $.proxy(function (data) {
- cache.loading = false;
+ if(cache) {
+ cache.loading = false;
+ }
this.sourceData = this.makeArray(data);
if($.isArray(this.sourceData)) {
this.doPrepend();
- //store result in cache
- cache.sourceData = this.sourceData;
success.call(this);
- $.each(cache.callbacks, function () { this.call(); }); //run success callbacks for other fields
+ if(cache) {
+ //store result in cache
+ cache.sourceData = this.sourceData;
+ $.each(cache.callbacks, function () { this.call(); }); //run success callbacks for other fields
+ }
} else {
error.call(this);
- $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields
+ if(cache) {
+ $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields
+ }
}
}, this),
error: $.proxy(function () {
- cache.loading = false;
error.call(this);
- $.each(cache.err_callbacks, function () { this.call(); }); //run error callbacks for other fields
+ if(cache) {
+ cache.loading = false;
+ //run error callbacks for other fields
+ $.each(cache.err_callbacks, function () { this.call(); });
+ }
}, this)
});
} else { //options as json/array
this.sourceData = this.makeArray(this.options.source);
if($.isArray(this.sourceData)) {
@@ -1860,11 +1953,11 @@
return;
}
if(!$.isArray(this.prependData)) {
//try parse json in single quotes
- this.options.prepend = $.fn.editableform.utils.tryParseJson(this.options.prepend, true);
+ this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true);
if (typeof this.options.prepend === 'string') {
this.options.prepend = {'': this.options.prepend};
}
this.prependData = this.makeArray(this.options.prepend);
}
@@ -1941,23 +2034,24 @@
}
}
});
- List.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
+ List.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
/**
Source data for list. If string - considered ajax url to load items. Otherwise should be an array.
Array format is: <code>[{value: 1, text: "text"}, {...}]</code><br>
For compability it also supports format <code>{value1: "text1", value2: "text2" ...}</code> but it does not guarantee elements order.
-
+ If source is **string**, results will be cached for fields with the same source and name. See also <code>sourceCache</code> option.
+
@property source
@type string|array|object
@default null
**/
source:null,
/**
- Data automatically prepended to the begining of dropdown list.
+ Data automatically prepended to the beginning of dropdown list.
@property prepend
@type string|array|object
@default false
**/
@@ -1967,21 +2061,31 @@
@property sourceError
@type string
@default Error when loading list
**/
- sourceError: '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 and name.
+ Usefull for editable grids.
+
+ @property sourceCache
+ @type boolean
+ @default true
+ @since 1.2.0
+ **/
+ sourceCache: true
});
- $.fn.editableform.types.list = List;
+ $.fn.editabletypes.list = List;
}(window.jQuery));
/**
Text input
@class text
-@extends abstract
+@extends abstractinput
@final
@example
<a href="#" id="username" data-type="text" data-pk="1">awesome</a>
<script>
$(function(){
@@ -1995,22 +2099,22 @@
(function ($) {
var Text = function (options) {
this.init('text', options, Text.defaults);
};
- $.fn.editableform.utils.inherit(Text, $.fn.editableform.types.abstract);
+ $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
$.extend(Text.prototype, {
activate: function() {
if(this.$input.is(':visible')) {
this.$input.focus();
- $.fn.editableform.utils.setCursorPosition(this.$input.get(0), this.$input.val().length);
+ $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
}
}
});
- Text.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
+ Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
/**
@property tpl
@default <input type="text">
**/
tpl: '<input type="text">',
@@ -2022,19 +2126,19 @@
@default null
**/
placeholder: null
});
- $.fn.editableform.types.text = Text;
+ $.fn.editabletypes.text = Text;
}(window.jQuery));
/**
Textarea input
@class textarea
-@extends abstract
+@extends abstractinput
@final
@example
<a href="#" id="comments" data-type="textarea" data-pk="1">awesome comment!</a>
<script>
$(function(){
@@ -2049,11 +2153,11 @@
var Textarea = function (options) {
this.init('textarea', options, Textarea.defaults);
};
- $.fn.editableform.utils.inherit(Textarea, $.fn.editableform.types.abstract);
+ $.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput);
$.extend(Textarea.prototype, {
render: function () {
Textarea.superclass.render.call(this);
@@ -2088,40 +2192,41 @@
return lines.join("\n");
},
activate: function() {
if(this.$input.is(':visible')) {
- $.fn.editableform.utils.setCursorPosition(this.$input.get(0), this.$input.val().length);
+ $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length);
this.$input.focus();
}
}
});
- Textarea.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
+ Textarea.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
/**
@property tpl
@default <textarea></textarea>
**/
tpl:'<textarea></textarea>',
/**
@property inputclass
- @default span3
+ @default input-large
**/
- inputclass:'span3',
+ inputclass: 'input-large',
/**
Placeholder attribute of input. Shown when input is empty.
@property placeholder
@type string
@default null
**/
- placeholder: null
+ placeholder: null
});
- $.fn.editableform.types.textarea = Textarea;
+ $.fn.editabletypes.textarea = Textarea;
-}(window.jQuery));
+}(window.jQuery));
+
/**
Select (dropdown)
@class select
@extends list
@@ -2146,21 +2251,28 @@
var Select = function (options) {
this.init('select', options, Select.defaults);
};
- $.fn.editableform.utils.inherit(Select, $.fn.editableform.types.list);
+ $.fn.editableutils.inherit(Select, $.fn.editabletypes.list);
$.extend(Select.prototype, {
renderList: function() {
if(!$.isArray(this.sourceData)) {
return;
}
for(var i=0; i<this.sourceData.length; i++) {
this.$input.append($('<option>', {value: this.sourceData[i].value}).text(this.sourceData[i].text));
}
+
+ //enter submit
+ this.$input.on('keydown.editable', function (e) {
+ if (e.which === 13) {
+ $(this).closest('form').submit();
+ }
+ });
},
value2htmlFinal: function(value, element) {
var text = '', item = this.itemByVal(value);
if(item) {
@@ -2168,25 +2280,25 @@
}
Select.superclass.constructor.superclass.value2html(text, element);
},
autosubmit: function() {
- this.$input.on('change', function(){
+ this.$input.off('keydown.editable').on('change.editable', function(){
$(this).closest('form').submit();
});
}
});
- Select.defaults = $.extend({}, $.fn.editableform.types.list.defaults, {
+ Select.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
/**
@property tpl
@default <select></select>
**/
tpl:'<select></select>'
});
- $.fn.editableform.types.select = Select;
+ $.fn.editabletypes.select = Select;
}(window.jQuery));
/**
List of checkboxes.
Internally value stored as javascript array of values.
@@ -2214,11 +2326,11 @@
var Checklist = function (options) {
this.init('checklist', options, Checklist.defaults);
};
- $.fn.editableform.utils.inherit(Checklist, $.fn.editableform.types.list);
+ $.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list);
$.extend(Checklist.prototype, {
renderList: function() {
var $label, $div;
if(!$.isArray(this.sourceData)) {
@@ -2236,14 +2348,12 @@
$('<div>').append($label).appendTo(this.$input);
}
},
value2str: function(value) {
- return $.isArray(value) ? value.join($.trim(this.options.separator)) : '';
- //it is also possible to sent as array
- //return 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) {
@@ -2282,23 +2392,22 @@
return checked;
},
//collect text of checked boxes
value2htmlFinal: function(value, element) {
- var selected = [], item, i, html = '';
- if($.isArray(value) && value.length <= this.options.limit) {
- for(i=0; i<value.length; i++){
- item = this.itemByVal(value[i]);
- if(item) {
- selected.push($('<div>').text(item.text).html());
- }
- }
- html = selected.join(this.options.viewseparator);
- } else {
- html = this.options.limitText.replace('{checked}', $.isArray(value) ? value.length : 0).replace('{count}', this.sourceData.length);
+ var html = [],
+ /*jslint eqeq: true*/
+ checked = $.grep(this.sourceData, function(o){
+ return $.grep(value, function(v){ return v == o.value; }).length;
+ });
+ /*jslint eqeq: false*/
+ if(checked.length) {
+ $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
+ $(element).html(html.join('<br>'));
+ } else {
+ $(element).empty();
}
- $(element).html(html);
},
activate: function() {
this.$input.find('input[type="checkbox"]').first().focus();
},
@@ -2310,65 +2419,227 @@
}
});
}
});
- Checklist.defaults = $.extend({}, $.fn.editableform.types.list.defaults, {
+ Checklist.defaults = $.extend({}, $.fn.editabletypes.list.defaults, {
/**
@property tpl
@default <div></div>
**/
tpl:'<div></div>',
/**
@property inputclass
@type string
- @default span2 editable-checklist
+ @default editable-checklist
**/
- inputclass: 'span2 editable-checklist',
+ inputclass: 'editable-checklist',
/**
- Separator of values in string when sending to server
+ Separator of values when reading from 'data-value' string
@property separator
@type string
@default ', '
**/
- separator: ',',
- /**
- Separator of text when display as element content.
+ separator: ','
+ });
- @property viewseparator
- @type string
- @default '<br>'
- **/
- viewseparator: '<br>',
- /**
- Maximum number of items shown as element content.
- If checked more items - <code>limitText</code> will be shown.
+ $.fn.editabletypes.checklist = Checklist;
- @property limit
- @type integer
- @default 4
- **/
- limit: 4,
- /**
- Text shown when count of checked items is greater than <code>limit</code> parameter.
- You can use <code>{checked}</code> and <code>{count}</code> placeholders.
+}(window.jQuery));
+
+/**
+HTML5 input types.
+Following types are supported:
- @property limitText
- @type string
- @default 'Selected {checked} of {count}'
- **/
- limitText: 'Selected {checked} of {count}'
+* password
+* email
+* url
+* tel
+* number
+* range
+
+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>
+**/
- $.fn.editableform.types.checklist = Checklist;
+/**
+@property tpl
+@default depends on type
+**/
+/*
+Password
+*/
+(function ($) {
+ 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 ($) {
+ 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 ($) {
+ 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 ($) {
+ 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 ($) {
+ 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);
+
+ if (this.options.min !== null) {
+ this.$input.attr('min', this.options.min);
+ }
+
+ if (this.options.max !== null) {
+ this.$input.attr('max', this.options.max);
+ }
+
+ if (this.options.step !== null) {
+ this.$input.attr('step', this.options.step);
+ }
+ }
+ });
+ 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 ($) {
+ 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.options.tpl);
+ var $slider = this.$input.filter('input');
+ if(this.options.inputclass) {
+ $slider.addClass(this.options.inputclass);
+ }
+ if (this.options.min !== null) {
+ $slider.attr('min', this.options.min);
+ }
+
+ if (this.options.max !== null) {
+ $slider.attr('max', this.options.max);
+ }
+
+ if (this.options.step !== null) {
+ $slider.attr('step', this.options.step);
+ }
+
+ $slider.on('input', function(){
+ $(this).siblings('output').text($(this).val());
+ });
+ },
+ activate: function() {
+ this.$input.filter('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));
+/*
Editableform based on Twitter Bootstrap
*/
(function ($) {
$.extend($.fn.editableform.Constructor.prototype, {
@@ -2395,17 +2666,18 @@
(function ($) {
//extend methods
$.extend($.fn.editableContainer.Constructor.prototype, {
containerName: 'popover',
- innerCss: '.popover-content p',
+ //for compatibility with bootstrap <= 2.2.1 (content inserted into <p> instead of directly .popover-content)
+ innerCss: $($.fn.popover.defaults.template).find('p').length ? '.popover-content p' : '.popover-content',
initContainer: function(){
$.extend(this.containerOptions, {
trigger: 'manual',
selector: false,
- content: 'dfgh'
+ content: ' '
});
this.call(this.containerOptions);
},
setContainerOption: function(key, value) {
@@ -2482,11 +2754,11 @@
Bootstrap-datepicker.
Description and examples: http://vitalets.github.com/bootstrap-datepicker.
For localization you can include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
@class date
-@extends abstract
+@extends abstractinput
@final
@example
<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-original-title="Select date">15/05/1984</a>
<script>
$(function(){
@@ -2505,11 +2777,11 @@
var Date = function (options) {
this.init('date', options, Date.defaults);
//set popular options directly from settings or data-* attributes
- var directOptions = $.fn.editableform.utils.sliceObj(this.options, ['format']);
+ var directOptions = $.fn.editableutils.sliceObj(this.options, ['format']);
//overriding datepicker config (as by default jQuery extend() is not recursive)
this.options.datepicker = $.extend({}, Date.defaults.datepicker, directOptions, options.datepicker);
//by default viewformat equals to format
@@ -2526,11 +2798,11 @@
//store parsed formats
this.parsedFormat = this.dpg.parseFormat(this.options.datepicker.format);
this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
};
- $.fn.editableform.utils.inherit(Date, $.fn.editableform.types.abstract);
+ $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
$.extend(Date.prototype, {
render: function () {
Date.superclass.render.call(this);
this.$input.datepicker(this.options.datepicker);
@@ -2557,11 +2829,15 @@
return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';
},
str2value: function(str) {
return str ? this.dpg.parseDate(str, this.parsedFormat, this.options.datepicker.language) : null;
- },
+ },
+
+ value2submit: function(value) {
+ return this.value2str(value);
+ },
value2input: function(value) {
this.$input.datepicker('update', value);
},
@@ -2586,11 +2862,11 @@
});
}
});
- Date.defaults = $.extend({}, $.fn.editableform.types.abstract.defaults, {
+ Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
/**
@property tpl
@default <div></div>
**/
tpl:'<div></div>',
@@ -2643,11 +2919,11 @@
@default 'x clear'
**/
clear: '× clear'
});
- $.fn.editableform.types.date = Date;
+ $.fn.editabletypes.date = Date;
}(window.jQuery));
/* =========================================================
* bootstrap-datepicker.js
@@ -2935,17 +3211,17 @@
month = d.getUTCMonth(),
startYear = this.startDate !== -Infinity ? this.startDate.getUTCFullYear() : -Infinity,
startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity,
endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity,
endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity,
- currentDate = this.date.valueOf(),
+ currentDate = this.date && this.date.valueOf(),
today = new Date();
this.picker.find('.datepicker-days thead th:eq(1)')
.text(dates[this.language].months[month]+' '+year);
this.picker.find('tfoot th.today')
.text(dates[this.language].today)
- .toggle(this.todayBtn);
+ .toggle(this.todayBtn !== 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);
@@ -2970,11 +3246,11 @@
prevMonth.getUTCFullYear() == today.getFullYear() &&
prevMonth.getUTCMonth() == today.getMonth() &&
prevMonth.getUTCDate() == today.getDate()) {
clsName += ' today';
}
- if (prevMonth.valueOf() == currentDate) {
+ if (currentDate && prevMonth.valueOf() == currentDate) {
clsName += ' active';
}
if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate) {
clsName += ' disabled';
}
@@ -2983,18 +3259,18 @@
html.push('</tr>');
}
prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
}
this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
- var currentYear = this.date.getUTCFullYear();
+ 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 == year) {
+ if (currentYear && currentYear == year) {
months.eq(this.date.getUTCMonth()).addClass('active');
}
if (year < startYear || year > endYear) {
months.addClass('disabled');
}
@@ -3078,14 +3354,11 @@
}
this.fill();
break;
case 'today':
var date = new Date();
- date.setUTCHours(0);
- date.setUTCMinutes(0);
- date.setUTCSeconds(0);
- date.setUTCMilliseconds(0);
+ date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
this.showMode(-2);
var which = this.todayBtn == 'linked' ? null : 'view';
this._setDate(date, which);
break;
@@ -3304,11 +3577,21 @@
showMode: function(dir) {
if (dir) {
this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir));
}
- this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).show();
+ /*
+ 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();
}
};
$.fn.datepicker = function ( option ) {
@@ -3422,10 +3705,10 @@
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.getUTCFullYear(), date.getUTCMonth(), date.getUTCDate(), 0, 0, 0);
+ date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
if (parts.length == format.parts.length) {
for (var i=0, cnt = format.parts.length; i < cnt; i++) {
val = parseInt(parts[i], 10);
part = format.parts[i];
if (isNaN(val)) {