vendor/assets/javascripts/bootstrap-editable.js in bootstrap-x-editable-rails-1.4.3 vs vendor/assets/javascripts/bootstrap-editable.js in bootstrap-x-editable-rails-1.4.4
- old
+ new
@@ -1,6 +1,6 @@
-/*! X-editable - v1.4.3
+/*! X-editable - v1.4.4
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
* http://github.com/vitalets/x-editable
* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
/**
@@ -11,11 +11,12 @@
@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;
@@ -35,11 +36,15 @@
},
initTemplate: function() {
this.$form = $($.fn.editableform.template);
},
initButtons: function() {
- this.$form.find('.editable-buttons').append($.fn.editableform.buttons);
+ 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
@@ -255,12 +260,19 @@
});
**/
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();
+ 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(newValue) {
//convert value for submitting to server
@@ -268,11 +280,11 @@
//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)))),
+ 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();
@@ -476,11 +488,11 @@
}
**/
validate: null,
/**
Success callback. Called when value successfully sent on server and **response status = 200**.
- Useful to work with json response. For example, if your backend response can be <code>{success: true}</code>
+ 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.
@@ -492,10 +504,29 @@
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
@@ -507,15 +538,15 @@
dataType: 'json'
}
**/
ajaxOptions: null,
/**
- Whether to show buttons or not.
+ Where to show buttons: left(true)|bottom|false
Form without buttons is auto-submitted.
@property showbuttons
- @type boolean
+ @type boolean|string
@default true
@since 1.1.1
**/
showbuttons: true,
/**
@@ -535,11 +566,11 @@
@property savenochange
@type boolean
@default false
@since 1.2.0
**/
- savenochange: false
+ savenochange: false
};
/*
Note: following params could redefined in engine: bootstrap or jqueryui:
Classes 'control-group' and 'editable-error-block' must always present!
@@ -562,14 +593,17 @@
$.fn.editableform.errorGroupClass = null;
//error class attached to editable-error-block
$.fn.editableform.errorBlockClass = 'editable-error';
}(window.jQuery));
+
/**
* EditableForm utilites
*/
(function ($) {
+ "use strict";
+
//utils
$.fn.editableutils = {
/**
* classic JS inheritance function
*/
@@ -759,10 +793,15 @@
//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';
}
@@ -790,10 +829,11 @@
@class editableContainer
@uses editableform
**/
(function ($) {
+ "use strict";
var Popup = function (element, options) {
this.init(element, options);
};
@@ -803,10 +843,11 @@
//methods
Popup.prototype = {
containerName: null, //tbd in child class
innerCss: null, //tbd in child class
+ containerClass: 'editable-container editable-popup', //css class applied to container element
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();
@@ -835,14 +876,28 @@
//(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'];
+ '.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;
}
@@ -858,10 +913,15 @@
//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?');
+ }
+
var cDef = $.fn[this.containerName].defaults;
//keys defined in container defaults go to container, others go to form
for(var k in this.options) {
if(k in cDef) {
this.containerOptions[k] = this.options[k];
@@ -882,10 +942,11 @@
/* returns container object */
container: function() {
return this.$element.data(this.containerDataName || this.containerName);
},
+ /* call native method of underlying container, e.g. this.$element.popover('method') */
call: function() {
this.$element[this.containerName].apply(this.$element, arguments);
},
initContainer: function(){
@@ -902,21 +963,25 @@
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
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)
+ 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() {
- var editable = $(this).data('editable');
+ $('#username').on('shown', function(e, editable) {
editable.input.$input.val('overwriting value of input..');
});
**/
- this.$element.triggerHandler('shown');
+ /*
+ 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);
}, this)
})
.editableform('render');
},
@@ -933,11 +998,11 @@
this.closeOthers(this.$element[0]);
}
//show container itself
this.innerShow();
- this.tip().addClass('editable-container');
+ this.tip().addClass(this.containerClass);
/*
Currently, form is re-rendered on every show.
The main reason is that we dont know, what container will do with content when closed:
remove(), detach() or just hide().
@@ -978,24 +1043,26 @@
this.$element.removeClass('editable-open');
this.innerHide();
/**
- Fired when container was hidden. It occurs on both save or cancel.
+ 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|undefined (=manual)</code>
+ @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);
+ this.$element.triggerHandler('hidden', reason || 'manual');
},
/* internal show method. To be overwritten in child classes */
innerShow: function () {
@@ -1244,20 +1311,22 @@
/**
* 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>').addClass('editable-inline');
+ this.$tip = $('<span></span>');
//convert anim to miliseconds (int)
if(!this.options.anim) {
this.options.anim = 0;
}
@@ -1298,10 +1367,11 @@
@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));
@@ -1392,20 +1462,22 @@
this.disable();
} else {
this.enable();
}
/**
- Fired when element was initialized by editable method.
+ 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));
},
@@ -1708,10 +1780,12 @@
/**
Removes editable feature from element
@method destroy()
**/
destroy: function() {
+ this.disable();
+
if(this.container) {
this.container.destroy();
}
if(this.options.toggle !== 'manual') {
@@ -1719,12 +1793,11 @@
this.$element.off(this.options.toggle + '.editable');
}
this.$element.off("save.internal");
- this.$element.removeClass('editable');
- this.$element.removeClass('editable-open');
+ this.$element.removeClass('editable editable-open editable-disabled');
this.$element.removeData('editable');
}
};
/* EDITABLE PLUGIN DEFINITION
@@ -2027,11 +2100,12 @@
To create your own input you can inherit from this class.
@class abstractinput
**/
(function ($) {
-
+ "use strict";
+
//types
$.fn.editabletypes = {};
var AbstractInput = function () { };
@@ -2210,11 +2284,14 @@
@default input-medium
**/
inputclass: 'input-medium',
//scope for external methods (e.g. source defined as function)
//for internal use only
- scope: null
+ 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));
@@ -2224,11 +2301,12 @@
@class list
@extends abstractinput
**/
(function ($) {
-
+ "use strict";
+
var List = function (options) {
};
$.fn.editableutils.inherit(List, $.fn.editabletypes.abstractinput);
@@ -2553,10 +2631,12 @@
});
});
</script>
**/
(function ($) {
+ "use strict";
+
var Text = function (options) {
this.init('text', options, Text.defaults);
};
$.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
@@ -2602,22 +2682,21 @@
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() || 20,
+// var h = this.$input.outerHeight(true) || 20,
+ var h = this.$clear.parent().height(),
delta = (h - this.$clear.height()) / 2;
-
- //workaround for plain-popup
- if(delta < 3) {
- delta = 3;
- }
- this.$clear.css({bottom: delta, right: delta});
- }
+ //this.$clear.css({bottom: delta, right: delta});
+ }
+ */
},
//show / hide clear button
toggleClear: function(e) {
if(!this.$clear) {
@@ -2688,11 +2767,12 @@
});
});
</script>
**/
(function ($) {
-
+ "use strict";
+
var Textarea = function (options) {
this.init('textarea', options, Textarea.defaults);
};
$.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput);
@@ -2800,11 +2880,12 @@
});
});
</script>
**/
(function ($) {
-
+ "use strict";
+
var Select = function (options) {
this.init('select', options, Select.defaults);
};
$.fn.editableutils.inherit(Select, $.fn.editabletypes.list);
@@ -2889,11 +2970,12 @@
});
});
</script>
**/
(function ($) {
-
+ "use strict";
+
var Checklist = function (options) {
this.init('checklist', options, Checklist.defaults);
};
$.fn.editableutils.inherit(Checklist, $.fn.editabletypes.list);
@@ -2932,10 +3014,12 @@
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
@@ -3057,10 +3141,12 @@
/*
Password
*/
(function ($) {
+ "use strict";
+
var Password = function (options) {
this.init('password', options, Password.defaults);
};
$.fn.editableutils.inherit(Password, $.fn.editabletypes.text);
$.extend(Password.prototype, {
@@ -3086,10 +3172,12 @@
/*
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, {
@@ -3101,10 +3189,12 @@
/*
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, {
@@ -3116,10 +3206,12 @@
/*
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, {
@@ -3131,21 +3223,37 @@
/*
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,
@@ -3158,10 +3266,12 @@
/*
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, {
@@ -3193,10 +3303,14 @@
You should manually include select2 distributive:
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="select2/select2.js"></script>
+For 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 `ajax` source for select2 is not supported, as it's not possible to load it in closed select2 state.
The solution is to load source manually and assign statically.
@class select2
@extends abstractinput
@@ -3218,11 +3332,12 @@
});
});
</script>
**/
(function ($) {
-
+ "use strict";
+
var Constructor = function (options) {
this.init('select2', options, Constructor.defaults);
options.select2 = options.select2 || {};
@@ -3420,12 +3535,13 @@
});
$.fn.editabletypes.select2 = Constructor;
}(window.jQuery));
+
/**
-* Combodate - 1.0.2
+* Combodate - 1.0.3
* 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
*
@@ -3528,11 +3644,11 @@
initItems: function(key) {
var values = [],
relTime;
if(this.options.firstItem === 'name') {
- //need both to suuport moment ver < 2 and >= 2
+ //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]);
@@ -3593,17 +3709,20 @@
/*
fill year
*/
fillYear: function() {
- var items = this.initItems('y'), name, i,
+ 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.push([i, name]);
- }
+ items[this.options.yearDescending ? 'push' : 'unshift']([i, name]);
+ }
+
+ items = this.initItems('y').concat(items);
+
return items;
},
/*
fill hour
@@ -3721,10 +3840,26 @@
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;
@@ -3740,11 +3875,21 @@
values.ampm = 'am';
}
}
$.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);
}
});
this.$element.val(dt.format(this.options.format));
@@ -3815,14 +3960,16 @@
template: 'D / MMM / YYYY H : mm',
//initial value, can be `new Date()`
value: null,
minYear: 1970,
maxYear: 2015,
+ yearDescending: true,
minuteStep: 5,
secondStep: 1,
firstItem: 'empty', //'name', 'empty', 'none'
- errorClass: null
+ errorClass: null,
+ roundTime: true //whether to round minutes and seconds if step > 1
};
}(window.jQuery));
/**
Combodate input - dropdown date and time picker.
@@ -3863,11 +4010,12 @@
**/
/*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) {
@@ -4015,12 +4163,13 @@
/*
Editableform based on Twitter Bootstrap
*/
(function ($) {
+ "use strict";
- $.extend($.fn.editableform.Constructor.prototype, {
+ $.extend($.fn.editableform.Constructor.prototype, {
initTemplate: function() {
this.$form = $($.fn.editableform.template);
this.$form.find('.editable-error-block').addClass('help-block');
}
});
@@ -4038,16 +4187,17 @@
* Editable Popover
* ---------------------
* requires bootstrap-popover.js
*/
(function ($) {
+ "use strict";
//extend methods
$.extend($.fn.editableContainer.Popup.prototype, {
containerName: 'popover',
//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',
+ innerCss: $.fn.popover && $($.fn.popover.defaults.template).find('p').length ? '.popover-content p' : '.popover-content',
initContainer: function(){
$.extend(this.containerOptions, {
trigger: 'manual',
selector: false,
@@ -4172,11 +4322,12 @@
});
});
</script>
**/
(function ($) {
-
+ "use strict";
+
var Date = function (options) {
this.init('date', options, Date.defaults);
this.initPicker(options, Date.defaults);
};
@@ -4256,10 +4407,13 @@
},
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')) {
@@ -4355,11 +4509,12 @@
@extends date
@since 1.4.0
**/
(function ($) {
-
+ "use strict";
+
var DateField = function (options) {
this.init('datefield', options, DateField.defaults);
this.initPicker(options, DateField.defaults);
};
@@ -4466,48 +4621,36 @@
this.language = this.language in dates ? this.language : "en";
this.isRTL = dates[this.language].rtl||false;
this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||dates[this.language].format||'mm/dd/yyyy');
this.isInline = false;
this.isInput = this.element.is('input');
- this.component = this.element.is('.date') ? this.element.find('.add-on') : false;
+ 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._attachEvents();
-
this.forceParse = true;
if ('forceParse' in options) {
this.forceParse = options.forceParse;
} else if ('dateForceParse' in this.element.data()) {
this.forceParse = this.element.data('date-force-parse');
}
+ this.picker = $(DPGlobal.template);
+ this._buildEvents();
+ this._attachEvents();
- this.picker = $(DPGlobal.template)
- .appendTo(this.isInline ? this.element : 'body')
- .on({
- click: $.proxy(this.click, this),
- mousedown: $.proxy(this.mousedown, this)
- });
-
if(this.isInline) {
- this.picker.addClass('datepicker-inline');
+ this.picker.addClass('datepicker-inline').appendTo(this.element);
} else {
this.picker.addClass('datepicker-dropdown dropdown-menu');
}
if (this.isRTL){
this.picker.addClass('datepicker-rtl');
this.picker.find('.prev i, .next i')
.toggleClass('icon-arrow-left icon-arrow-right');
}
- $(document).on('mousedown', function (e) {
- // Clicked outside the datepicker, hide it
- if ($(e.target).closest('.datepicker.datepicker-inline, .datepicker.datepicker-dropdown').length === 0) {
- that.hide();
- }
- });
this.autoclose = false;
if ('autoclose' in options) {
this.autoclose = options.autoclose;
} else if ('dateAutoclose' in this.element.data()) {
@@ -4563,20 +4706,25 @@
this.picker.find('tfoot th.today')
.attr('colspan', function(i, val){
return parseInt(val) + 1;
});
+ this._allow_update = false;
+
this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||dates[this.language].weekStart||0) % 7);
this.weekEnd = ((this.weekStart + 6) % 7);
this.startDate = -Infinity;
this.endDate = Infinity;
this.daysOfWeekDisabled = [];
this.setStartDate(options.startDate||this.element.data('date-startdate'));
this.setEndDate(options.endDate||this.element.data('date-enddate'));
this.setDaysOfWeekDisabled(options.daysOfWeekDisabled||this.element.data('date-days-of-week-disabled'));
this.fillDow();
this.fillMonths();
+
+ this._allow_update = true;
+
this.update();
this.showMode();
if(this.isInline) {
this.show();
@@ -4585,12 +4733,26 @@
Datepicker.prototype = {
constructor: Datepicker,
_events: [],
- _attachEvents: function(){
- this._detachEvents();
+ _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),
@@ -4609,43 +4771,61 @@
[this.component, {
click: $.proxy(this.show, this)
}]
];
}
- else if (this.element.is('div')) { // inline datepicker
- this.isInline = true;
- }
+ else if (this.element.is('div')) { // inline datepicker
+ this.isInline = true;
+ }
else {
this._events = [
[this.element, {
click: $.proxy(this.show, this)
}]
];
}
- for (var i=0, el, ev; i<this._events.length; i++){
- el = this._events[i][0];
- ev = this._events[i][1];
- el.on(ev);
- }
+
+ 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 ($(e.target).closest('.datepicker.datepicker-inline, .datepicker.datepicker-dropdown').length === 0) {
+ this.hide();
+ }
+ }, this)
+ }]
+ ];
},
+ _attachEvents: function(){
+ this._detachEvents();
+ this._applyEvents(this._events);
+ },
_detachEvents: function(){
- for (var i=0, el, ev; i<this._events.length; i++){
- el = this._events[i][0];
- ev = this._events[i][1];
- el.off(ev);
- }
- this._events = [];
+ this._unapplyEvents(this._events);
},
+ _attachSecondaryEvents: function(){
+ this._detachSecondaryEvents();
+ this._applyEvents(this._secondaryEvents);
+ },
+ _detachSecondaryEvents: function(){
+ this._unapplyEvents(this._secondaryEvents);
+ },
show: function(e) {
+ if (!this.isInline)
+ this.picker.appendTo('body');
this.picker.show();
this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
- this.update();
this.place();
- $(window).on('resize', $.proxy(this.place, this));
- if (e ) {
- e.stopPropagation();
+ this._attachSecondaryEvents();
+ if (e) {
e.preventDefault();
}
this.element.trigger({
type: 'show',
date: this.date
@@ -4653,17 +4833,14 @@
},
hide: function(e){
if(this.isInline) return;
if (!this.picker.is(':visible')) return;
- this.picker.hide();
- $(window).off('resize', this.place);
+ this.picker.hide().detach();
+ this._detachSecondaryEvents();
this.viewMode = this.startViewMode;
this.showMode();
- if (!this.isInput) {
- $(document).off('mousedown', this.hide);
- }
if (
this.forceParse &&
(
this.isInput && this.element.val() ||
@@ -4676,13 +4853,18 @@
date: this.date
});
},
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));
@@ -4752,20 +4934,23 @@
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.offset() : this.element.offset();
+ 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 {
@@ -4916,10 +5101,12 @@
}
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:
@@ -4949,11 +5136,10 @@
break;
}
},
click: function(e) {
- e.stopPropagation();
e.preventDefault();
var target = $(e.target).closest('span, td, th');
if (target.length == 1) {
switch(target[0].nodeName.toLowerCase()) {
case 'th':
@@ -5440,10 +5626,304 @@
$.fn.datepicker.DPGlobal = DPGlobal;
}( 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;
+ }
+
+ //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);
+
+ //
+ this.options.datetimepicker.startView = this.options.startView;
+ this.options.datetimepicker.minView = this.options.minView;
+ this.options.datetimepicker.maxView = this.options.maxView;
+ },
+
+ render: function () {
+ this.$input.datetimepicker(this.options.datetimepicker);
+
+ //"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(text, element);
+ } else {
+ return text;
+ }
+ },
+
+ html2value: function(html) {
+ //parseDate return utc date!
+ var value = html ? this.dpg.parseDate(html, this.parsedViewFormat, this.options.datetimepicker.language, this.options.formatType) : null;
+ 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 = str ? this.dpg.parseDate(str, this.parsedFormat, this.options.datetimepicker.language, this.options.formatType) : null;
+ 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;
+ }
+
+ });
+
+ 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));
+/**
Typeahead input (bootstrap only). Based on Twitter Bootstrap [typeahead](http://twitter.github.com/bootstrap/javascript.html#typeahead).
Depending on `source` format typeahead operates in two modes:
* **strings**:
When `source` defined as array of strings, e.g. `['text1', 'text2', 'text3' ...]`.
@@ -5471,10 +5951,11 @@
});
});
</script>
**/
(function ($) {
-
+ "use strict";
+
var Constructor = function (options) {
this.init('typeahead', options, Constructor.defaults);
//overriding objects in config (as by default jQuery extend() is not recursive)
this.options.typeahead = $.extend({}, Constructor.defaults.typeahead, {
\ No newline at end of file