vendor/assets/javascripts/editable/bootstrap-editable.js in x-editable-rails-1.0.1 vs vendor/assets/javascripts/editable/bootstrap-editable.js in x-editable-rails-1.0.2
- old
+ new
@@ -1,6 +1,6 @@
-/*! X-editable - v1.4.4
+/*! X-editable - v1.4.5
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
* http://github.com/vitalets/x-editable
* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
/**
@@ -63,10 +63,14 @@
}
//show loading state
this.showLoading();
+ //flag showing is form now saving value to server.
+ //It is needed to wait when closing form.
+ this.isSaving = false;
+
/**
Fired when rendering starts
@event rendering
@param {Object} event event object
**/
@@ -215,75 +219,87 @@
**/
this.$div.triggerHandler('nochange');
return;
}
+ //convert value for submitting to server
+ var submitValue = this.input.value2submit(newValue);
+
+ this.isSaving = true;
+
//sending data to server
- $.when(this.save(newValue))
+ $.when(this.save(submitValue))
.done($.proxy(function(response) {
+ this.isSaving = false;
+
//run success callback
var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null;
-
+
//if success callback returns false --> keep form open and do not activate input
if(res === false) {
this.error(false);
this.showForm(false);
return;
- }
-
+ }
+
//if success callback returns string --> keep form open, show error and activate input
if(typeof res === 'string') {
this.error(res);
this.showForm();
return;
- }
-
+ }
+
//if success callback returns object like {newValue: <something>} --> use that value instead of submitted
//it is usefull if you want to chnage value in url-function
if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
newValue = res.newValue;
- }
+ }
//clear error message
this.error(false);
this.value = newValue;
/**
Fired when form is submitted
@event save
@param {Object} event event object
@param {Object} params additional params
- @param {mixed} params.newValue submitted value
+ @param {mixed} params.newValue raw new value
+ @param {mixed} params.submitValue submitted value as string
@param {Object} params.response ajax response
@example
$('#form-div').on('save'), function(e, params){
if(params.newValue === 'username') {...}
- });
- **/
- this.$div.triggerHandler('save', {newValue: newValue, response: response});
+ });
+ **/
+ this.$div.triggerHandler('save', {newValue: newValue, submitValue: submitValue, response: response});
}, this))
.fail($.proxy(function(xhr) {
+ this.isSaving = false;
+
var msg;
if(typeof this.options.error === 'function') {
msg = this.options.error.call(this.options.scope, xhr, newValue);
} else {
msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!';
}
-
+
this.error(msg);
this.showForm();
}, this));
},
- save: function(newValue) {
- //convert value for submitting to server
- var submitValue = this.input.value2submit(newValue);
-
+ save: function(submitValue) {
//try parse composite pk defined as json string in data-pk
this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true);
var pk = (typeof this.options.pk === 'function') ? this.options.pk.call(this.options.scope) : this.options.pk,
+ /*
+ send on server in following cases:
+ 1. url is function
+ 2. url is string AND (pk defined OR send option = always)
+ */
send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))),
params;
if (send) { //send to server
this.showLoading();
@@ -814,10 +830,31 @@
return input;
} else {
$.error('Unknown type: '+ type);
return false;
}
+ },
+
+ //see http://stackoverflow.com/questions/7264899/detect-css-transitions-using-javascript-and-without-modernizr
+ supportsTransitions: function () {
+ var b = document.body || document.documentElement,
+ s = b.style,
+ p = 'transition',
+ v = ['Moz', 'Webkit', 'Khtml', 'O', 'ms'];
+
+ if(typeof s[p] === 'string') {
+ return true;
+ }
+
+ // Tests for vendor specific prop
+ p = p.charAt(0).toUpperCase() + p.substr(1);
+ for(var i=0; i<v.length; i++) {
+ if(typeof s[v[i] + p] === 'string') {
+ return true;
+ }
+ }
+ return false;
}
};
}(window.jQuery));
@@ -854,10 +891,13 @@
//set scope of form callbacks to element
this.formOptions.scope = this.$element[0];
this.initContainer();
+
+ //flag to hide container, when saving value will finish
+ this.delayedHide = false;
//bind 'destroyed' listener to destroy container when element is removed from dom
this.$element.on('destroyed', $.proxy(function(){
this.destroy();
}, this));
@@ -958,11 +998,18 @@
.editableform(this.formOptions)
.on({
save: $.proxy(this.save, this), //click on submit button (value changed)
nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)
cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on calcel button
- show: $.proxy(this.setPosition, this), //re-position container every time form is shown (occurs each time after loading state)
+ show: $.proxy(function() {
+ if(this.delayedHide) {
+ this.hide(this.delayedHide.reason);
+ this.delayedHide = false;
+ } else {
+ this.setPosition();
+ }
+ }, this), //re-position container every time form is shown (occurs each time after loading state)
rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed
rendered: $.proxy(function(){
/**
Fired when container is shown and form is rendered (for select will wait for loading dropdown options).
@@ -1002,15 +1049,15 @@
this.innerShow();
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().
+ The main reason is that we dont know, what will container do with content when closed:
+ remove(), detach() or just hide() - it depends on container.
Detaching form itself before hide and re-insert before show is good solution,
- but visually it looks ugly, as container changes size before hide.
+ but visually it looks ugly --> container changes size before hide.
*/
//if form already exist - delete previous data
if(this.$form) {
//todo: destroy prev data!
@@ -1039,14 +1086,22 @@
hide: function(reason) {
if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
return;
}
+ //if form is saving value, schedule hide
+ if(this.$form.data('editableform').isSaving) {
+ this.delayedHide = {reason: reason};
+ return;
+ } else {
+ this.delayedHide = false;
+ }
+
this.$element.removeClass('editable-open');
this.innerHide();
-
- /**
+
+ /**
Fired when container was hidden. It occurs on both save or cancel.
**Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one.
The workaround is to check `arguments.length` that is always `2` for x-editable.
@event hidden
@@ -1056,24 +1111,24 @@
$('#username').on('hidden', function(e, reason) {
if(reason === 'save' || reason === 'cancel') {
//auto-open next editable
$(this).closest('tr').next().find('.editable').editable('show');
}
- });
- **/
+ });
+ **/
this.$element.triggerHandler('hidden', reason || 'manual');
},
-
+
/* internal show method. To be overwritten in child classes */
innerShow: function () {
},
-
+
/* internal hide method. To be overwritten in child classes */
innerHide: function () {
-
- },
+
+ },
/**
Toggles container visibility (show / hide)
@method toggle()
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
@@ -1114,11 +1169,11 @@
}
});
**/
this.$element.triggerHandler('save', params);
- //hide must be after trigger, as saving value may require methods od plugin, applied to input
+ //hide must be after trigger, as saving value may require methods of plugin, applied to input
this.hide('save');
},
/**
Sets new option
@@ -1274,11 +1329,11 @@
@since 1.1.1
**/
onblur: 'cancel',
/**
- Animation speed (inline mode)
+ Animation speed (inline mode only)
@property anim
@type string
@default false
**/
anim: false,
@@ -1378,10 +1433,15 @@
if(this.options.selector) {
this.initLive();
} else {
this.init();
}
+
+ //check for transition support
+ if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) {
+ this.options.highlight = false;
+ }
};
Editable.prototype = {
constructor: Editable,
init: function () {
@@ -1422,29 +1482,37 @@
//attach handler activating editable. In disabled mode it just prevent default action (useful for links)
if(this.options.toggle !== 'manual') {
this.$element.addClass('editable-click');
this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
- //prevent following link
- e.preventDefault();
+ //prevent following link if editable enabled
+ if(!this.options.disabled) {
+ e.preventDefault();
+ }
//stop propagation not required because in document click handler it checks event target
//e.stopPropagation();
if(this.options.toggle === 'mouseenter') {
//for hover only show container
- this.show();
+ this.show();
} else {
//when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
var closeAll = (this.options.toggle !== 'click');
this.toggle(closeAll);
- }
+ }
}, this));
} else {
this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually
}
+ //if display is function it's far more convinient to have autotext = always to render correctly on init
+ //see https://github.com/vitalets/x-editable-yii/issues/34
+ if(typeof this.options.display === 'function') {
+ this.options.autotext = 'always';
+ }
+
//check conditions for autotext:
switch(this.options.autotext) {
case 'always':
doAutotext = true;
break;
@@ -1619,16 +1687,33 @@
//do not handle empty if we do not display anything
if(this.options.display === false) {
return;
}
- this.isEmpty = isEmpty !== undefined ? isEmpty : $.trim(this.$element.text()) === '';
+ /*
+ isEmpty may be set directly as param of method.
+ It is required when we enable/disable field and can't rely on content
+ as node content is text: "Empty" that is not empty %)
+ */
+ if(isEmpty !== undefined) {
+ this.isEmpty = isEmpty;
+ } else {
+ //detect empty
+ if($.trim(this.$element.html()) === '') {
+ this.isEmpty = true;
+ } else if($.trim(this.$element.text()) !== '') {
+ this.isEmpty = false;
+ } else {
+ //e.g. '<img>'
+ this.isEmpty = !this.$element.height() || !this.$element.width();
+ }
+ }
//emptytext shown only for enabled
if(!this.options.disabled) {
if (this.isEmpty) {
- this.$element.text(this.options.emptytext);
+ this.$element.html(this.options.emptytext);
if(this.options.emptyclass) {
this.$element.addClass(this.options.emptyclass);
}
} else if(this.options.emptyclass) {
this.$element.removeClass(this.options.emptyclass);
@@ -1719,10 +1804,25 @@
} else {
this.$element.addClass(this.options.unsavedclass);
}
}
+ //highlight when saving
+ if(this.options.highlight) {
+ var $e = this.$element,
+ $bgColor = $e.css('background-color');
+
+ $e.css('background-color', this.options.highlight);
+ setTimeout(function(){
+ $e.css('background-color', $bgColor);
+ $e.addClass('editable-bg-transition');
+ setTimeout(function(){
+ $e.removeClass('editable-bg-transition');
+ }, 1700);
+ }, 0);
+ }
+
//set new value
this.setValue(params.newValue, false, params.response);
/**
Fired when new value was submitted. You can use <code>$(this).data('editable')</code> to access to editable instance
@@ -1785,10 +1885,12 @@
this.disable();
if(this.container) {
this.container.destroy();
}
+
+ this.input.destroy();
if(this.options.toggle !== 'manual') {
this.$element.removeClass('editable-click');
this.$element.off(this.options.toggle + '.editable');
}
@@ -1842,32 +1944,41 @@
return result;
/**
Returns current values of editable elements.
Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements.
- If value of some editable is `null` or `undefined` it is excluded from result object.
+ If value of some editable is `null` or `undefined` it is excluded from result object.
+ When param `isSingle` is set to **true** - it is supposed you have single element and will return value of editable instead of object.
@method getValue()
+ @param {bool} isSingle whether to return just value of single element
@returns {Object} object of element names and values
@example
$('#username, #fullname').editable('getValue');
- // possible result:
+ //result:
{
username: "superuser",
fullname: "John"
}
+ //isSingle = true
+ $('#username').editable('getValue', true);
+ //result "superuser"
**/
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.value2submit(data.value);
- }
- });
+ if(arguments.length === 2 && arguments[1] === true) { //isSingle = true
+ result = this.eq(0).data(datakey).value;
+ } else {
+ this.each(function () {
+ var $this = $(this), data = $this.data(datakey);
+ if (data && data.value !== undefined && data.value !== null) {
+ result[data.options.name] = data.input.value2submit(data.value);
+ }
+ });
+ }
return result;
- /**
+ /**
This method collects values from several editable elements and submit them all to server.
Internally it runs client-side validation for all fields and submits only in case of success.
See <a href="#newrecord">creating new records</a> for details.
@method submit(options)
@@ -2087,11 +2198,20 @@
url: '/post',
pk: 1
});
</script>
**/
- selector: null
+ selector: null,
+ /**
+ Color used to highlight element after update. Implemented via CSS3 transition, works in modern browsers.
+
+ @property highlight
+ @type string|boolean
+ @since 1.4.5
+ @default #FFFF80
+ **/
+ highlight: '#FFFF80'
};
}(window.jQuery));
/**
@@ -2101,27 +2221,27 @@
@class abstractinput
**/
(function ($) {
"use strict";
-
+
//types
$.fn.editabletypes = {};
-
+
var AbstractInput = function () { };
AbstractInput.prototype = {
/**
Initializes input
-
+
@method init()
**/
init: function(type, options, defaults) {
this.type = type;
this.options = $.extend({}, defaults, options);
},
-
+
/*
this method called before render to init $tpl that is inserted in DOM
*/
prerender: function() {
this.$tpl = $(this.options.tpl); //whole tpl as jquery object
@@ -2131,132 +2251,138 @@
},
/**
Renders input from tpl. Can return jQuery deferred object.
Can be overwritten in child objects
-
- @method render()
- **/
+
+ @method render()
+ **/
render: function() {
},
/**
Sets element's html by value.
-
- @method value2html(value, element)
+
+ @method value2html(value, element)
@param {mixed} value
@param {DOMElement} element
- **/
+ **/
value2html: function(value, element) {
- $(element).text(value);
+ $(element).text($.trim(value));
},
-
+
/**
Converts element's html to value
-
- @method html2value(html)
+
+ @method html2value(html)
@param {string} html
@returns {mixed}
- **/
+ **/
html2value: function(html) {
return $('<div>').html(html).text();
},
-
+
/**
Converts value to string (for internal compare). For submitting to server used value2submit().
-
+
@method value2str(value)
@param {mixed} value
@returns {string}
- **/
+ **/
value2str: function(value) {
return value;
},
-
+
/**
Converts string received from server into value. Usually from `data-value` attribute.
-
- @method str2value(str)
+
+ @method str2value(str)
@param {string} str
@returns {mixed}
- **/
+ **/
str2value: function(str) {
return str;
},
/**
Converts value for submitting to server. Result can be string or object.
-
+
@method value2submit(value)
@param {mixed} value
@returns {mixed}
- **/
+ **/
value2submit: function(value) {
return value;
- },
-
+ },
+
/**
Sets value of input.
-
+
@method value2input(value)
@param {mixed} value
- **/
+ **/
value2input: function(value) {
this.$input.val(value);
},
-
+
/**
Returns value of input. Value can be object (e.g. datepicker)
-
+
@method input2value()
- **/
+ **/
input2value: function() {
return this.$input.val();
},
/**
Activates input. For text it sets focus.
-
+
@method activate()
- **/
+ **/
activate: function() {
if(this.$input.is(':visible')) {
this.$input.focus();
}
},
-
+
/**
Creates input.
-
+
@method clear()
**/
clear: function() {
this.$input.val(null);
},
-
+
/**
method to escape html.
**/
escape: function(str) {
return $('<div>').text(str).html();
},
/**
attach handler to automatically submit form when value changed (useful when buttons not shown)
- **/
+ **/
autosubmit: function() {
},
+ /**
+ Additional actions when destroying element
+ **/
+ destroy: function() {
+ },
+
// -------- helper functions --------
setClass: function() {
if(this.options.inputclass) {
this.$input.addClass(this.options.inputclass);
}
},
-
+
setAttr: function(attr) {
if (this.options[attr] !== undefined && this.options[attr] !== null) {
this.$input.attr(attr, this.options[attr]);
}
},
@@ -2354,34 +2480,37 @@
},
// ------------- additional functions ------------
onSourceReady: function (success, error) {
+ //run source if it function
+ var source;
+ if ($.isFunction(this.options.source)) {
+ source = this.options.source.call(this.options.scope);
+ this.sourceData = null;
+ //note: if function returns the same source as URL - sourceData will be taken from cahce and no extra request performed
+ } else {
+ source = this.options.source;
+ }
+
//if allready loaded just call success
- if($.isArray(this.sourceData)) {
+ if(this.options.sourceCache && $.isArray(this.sourceData)) {
success.call(this);
return;
}
- // try parse json in single quotes (for double quotes jquery does automatically)
+ //try parse json in single quotes (for double quotes jquery does automatically)
try {
- this.options.source = $.fn.editableutils.tryParseJson(this.options.source, false);
+ source = $.fn.editableutils.tryParseJson(source, false);
} catch (e) {
error.call(this);
return;
}
-
- var source = this.options.source;
-
- //run source if it function
- if ($.isFunction(source)) {
- source = source.call(this.options.scope);
- }
//loading from url
if (typeof source === 'string') {
- //try to get from cache
+ //try to get sourceData from cache
if(this.options.sourceCache) {
var cacheID = source,
cache;
if (!$(document).data(cacheID)) {
@@ -3297,29 +3426,33 @@
});
$.fn.editabletypes.range = Range;
}(window.jQuery));
/**
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
-Please see [original docs](http://ivaynberg.github.com/select2) for detailed description and options.
-You should manually include select2 distributive:
+Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
+Compatible **select2 version is 3.4.1**!
+You should manually download and include select2 distributive:
<link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="select2/select2.js"></script>
-For make it **Bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
+To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css):
<link href="select2-bootstrap.css" rel="stylesheet" type="text/css"></link>
-**Note:** currently `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.
+**Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.
+You need initially put both `data-value` and element's text youself:
+
+ <a href="#" data-type="select2" data-value="1">Text1</a>
+
@class select2
@extends abstractinput
@since 1.4.1
@final
@example
-<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-original-title="Select country"></a>
+<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a>
<script>
$(function(){
$('#country').editable({
source: [
{id: 'gb', text: 'Great Britain'},
@@ -3336,82 +3469,69 @@
(function ($) {
"use strict";
var Constructor = function (options) {
this.init('select2', options, Constructor.defaults);
-
+
options.select2 = options.select2 || {};
+
+ this.sourceData = null;
- var that = this,
- mixin = { //mixin to select2 options
- placeholder: options.placeholder
- };
+ //placeholder
+ if(options.placeholder) {
+ options.select2.placeholder = options.placeholder;
+ }
- //detect whether it is multi-valued
- this.isMultiple = options.select2.tags || options.select2.multiple;
-
- //if not `tags` mode, we need define initSelection to set data from source
- if(!options.select2.tags) {
- if(options.source) {
- mixin.data = options.source;
- }
+ //if not `tags` mode, use source
+ if(!options.select2.tags && options.source) {
+ var source = options.source;
+ //if source is function, call it (once!)
+ if ($.isFunction(options.source)) {
+ source = options.source.call(options.scope);
+ }
- //this function can be defaulted in seletc2. See https://github.com/ivaynberg/select2/issues/710
- mixin.initSelection = function (element, callback) {
- //temp: try update results
- /*
- if(options.select2 && options.select2.ajax) {
- console.log('attached');
- var original = $(element).data('select2').postprocessResults;
- console.log(original);
- $(element).data('select2').postprocessResults = function(data, initial) {
- console.log('postprocess');
- // this.element.triggerHandler('loaded', [data]);
- original.apply(this, arguments);
- }
-
- // $(element).on('loaded', function(){console.log('loaded');});
- $(element).data('select2').updateResults(true);
+ if (typeof source === 'string') {
+ options.select2.ajax = options.select2.ajax || {};
+ //some default ajax params
+ if(!options.select2.ajax.data) {
+ options.select2.ajax.data = function(term) {return { query:term };};
}
- */
-
- var val = that.str2value(element.val()),
- data = $.fn.editableutils.itemsByValue(val, mixin.data, 'id');
-
- //for single-valued mode should not use array. Take first element instead.
- if($.isArray(data) && data.length && !that.isMultiple) {
- data = data[0];
+ if(!options.select2.ajax.results) {
+ options.select2.ajax.results = function(data) { return {results:data };};
}
-
- callback(data);
- };
- }
+ options.select2.ajax.url = source;
+ } else {
+ //check format and convert x-editable format to select2 format (if needed)
+ this.sourceData = this.convertSource(source);
+ options.select2.data = this.sourceData;
+ }
+ }
//overriding objects in config (as by default jQuery extend() is not recursive)
- this.options.select2 = $.extend({}, Constructor.defaults.select2, mixin, options.select2);
+ this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2);
+
+ //detect whether it is multi-valued
+ this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
+ this.isRemote = ('ajax' in this.options.select2);
};
$.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
$.extend(Constructor.prototype, {
render: function() {
this.setClass();
+
//apply select2
this.$input.select2(this.options.select2);
- //when data is loaded via ajax, we need to know when it's done
- if('ajax' in this.options.select2) {
- /*
- console.log('attached');
- var original = this.$input.data('select2').postprocessResults;
- this.$input.data('select2').postprocessResults = function(data, initial) {
- this.element.triggerHandler('loaded', [data]);
- original.apply(this, arguments);
- }
- */
+ //when data is loaded via ajax, we need to know when it's done to populate listData
+ if(this.isRemote) {
+ //listen to loaded event to populate data
+ this.$input.on('select2-loaded', $.proxy(function(e) {
+ this.sourceData = e.items.results;
+ }, this));
}
-
//trigger resize of editableform to re-position container in multi-valued mode
if(this.isMultiple) {
this.$input.on('change', function() {
$(this).closest('form').parent().triggerHandler('resize');
@@ -3419,24 +3539,20 @@
}
},
value2html: function(value, element) {
var text = '', data;
- if(this.$input) { //called when submitting form and select2 already exists
- data = this.$input.select2('data');
- } else { //on init (autotext)
- //here select2 instance not created yet and data may be even not loaded.
- //we can check data/tags property of select config and if exist lookup text
- if(this.options.select2.tags) {
- data = value;
- } else if(this.options.select2.data) {
- data = $.fn.editableutils.itemsByValue(value, this.options.select2.data, 'id');
- } else {
- //if('ajax' in this.options.select2) {
- }
+
+ if(this.options.select2.tags) { //in tags mode just assign value
+ data = value;
+ } else if(this.sourceData) {
+ data = $.fn.editableutils.itemsByValue(value, this.sourceData, 'id');
+ } else {
+ //can not get list of possible values (e.g. autotext for select2 with ajax source)
}
+ //data may be array (when multiple values allowed)
if($.isArray(data)) {
//collect selected data and show with separator
text = [];
$.each(data, function(k, v){
text.push(v && typeof v === 'object' ? v.text : v);
@@ -3453,11 +3569,30 @@
html2value: function(html) {
return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;
},
value2input: function(value) {
- this.$input.val(value).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
+ //for remote source .val() is not working, need to look in sourceData
+ if(this.isRemote) {
+ //todo: check value for array
+ var item, items;
+ //if sourceData loaded, use it to get text for display
+ if(this.sourceData) {
+ items = $.fn.editableutils.itemsByValue(value, this.sourceData, 'id');
+ if(items.length) {
+ item = items[0];
+ }
+ }
+ //if item not found by sourceData, use element text (e.g. for the first show)
+ if(!item) {
+ item = {id: value, text: $(this.options.scope).text()};
+ }
+ //select2('data', ...) allows to set both id and text --> usefull for initial show when items are not loaded
+ this.$input.select2('data', item).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
+ } else {
+ this.$input.val(value).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
+ }
},
input2value: function() {
return this.$input.select2('val');
},
@@ -3486,10 +3621,26 @@
this.$input.on('change', function(e, isInitial){
if(!isInitial) {
$(this).closest('form').submit();
}
});
+ },
+
+ /*
+ Converts source from x-editable format: {value: 1, text: "1"} to
+ select2 format: {id: 1, text: "1"}
+ */
+ convertSource: function(source) {
+ if($.isArray(source) && source.length && source[0].value !== undefined) {
+ for(var i = 0; i<source.length; i++) {
+ if(source[i].value !== undefined) {
+ source[i].id = source[i].value;
+ delete source[i].value;
+ }
+ }
+ }
+ return source;
}
});
Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
@@ -3537,16 +3688,26 @@
$.fn.editabletypes.select2 = Constructor;
}(window.jQuery));
/**
-* Combodate - 1.0.3
+* Combodate - 1.0.4
* Dropdown date and time picker.
* Converts text input into dropdowns to pick day, month, year, hour, minute and second.
* Uses momentjs as datetime library http://momentjs.com.
* For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang
*
+* Confusion at noon and midnight - see http://en.wikipedia.org/wiki/12-hour_clock#Confusion_at_noon_and_midnight
+* In combodate:
+* 12:00 pm --> 12:00 (24-h format, midday)
+* 12:00 am --> 00:00 (24-h format, midnight, start of day)
+*
+* Differs from momentjs parse rules:
+* 00:00 pm, 12:00 pm --> 12:00 (24-h format, day not change)
+* 00:00 am, 12:00 am --> 00:00 (24-h format, day not change)
+*
+*
* Author: Vitaliy Potapov
* Project page: http://github.com/vitalets/combodate
* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
**/
(function ($) {
@@ -3692,13 +3853,14 @@
shortNames = this.options.template.indexOf('MMM') !== -1,
twoDigit = this.options.template.indexOf('MM') !== -1;
for(i=0; i<=11; i++) {
if(longNames) {
- name = moment().month(i).format('MMMM');
+ //see https://github.com/timrwood/momentjs.com/pull/36
+ name = moment().date(1).month(i).format('MMMM');
} else if(shortNames) {
- name = moment().month(i).format('MMM');
+ name = moment().date(1).month(i).format('MMM');
} else if(twoDigit) {
name = this.leadZero(i+1);
} else {
name = i+1;
}
@@ -3730,13 +3892,14 @@
fillHour: function() {
var items = this.initItems('h'), name, i,
h12 = this.options.template.indexOf('h') !== -1,
h24 = this.options.template.indexOf('H') !== -1,
twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
+ min = h12 ? 1 : 0,
max = h12 ? 12 : 23;
- for(i=0; i<=max; i++) {
+ for(i=min; i<=max; i++) {
name = twoDigit ? this.leadZero(i) : i;
items.push([i, name]);
}
return items;
},
@@ -3781,11 +3944,11 @@
];
return items;
},
/*
- Returns current date value.
+ Returns current date value from combos.
If format not specified - `options.format` used.
If format = `null` - Moment object returned.
*/
getValue: function(format) {
var dt, values = {},
@@ -3810,16 +3973,18 @@
//if at least one visible combo not selected - return empty string
if(notSelected) {
return '';
}
- //convert hours if 12h format
+ //convert hours 12h --> 24h
if(this.$ampm) {
- values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
- if(values.hour === 24) {
- values.hour = 0;
- }
+ //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
+ if(values.hour === 12) {
+ values.hour = this.$ampm.val() === 'am' ? 0 : 12;
+ } else {
+ values.hour = this.$ampm.val() === 'am' ? values.hour : values.hour+12;
+ }
}
dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
//highlight invalid date
@@ -3866,15 +4031,21 @@
}
values[k] = dt[v[1]]();
});
if(this.$ampm) {
- if(values.hour > 12) {
- values.hour -= 12;
+ //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
+ if(values.hour >= 12) {
values.ampm = 'pm';
+ if(values.hour > 12) {
+ values.hour -= 12;
+ }
} else {
- values.ampm = 'am';
+ values.ampm = 'am';
+ if(values.hour === 0) {
+ values.hour = 12;
+ }
}
}
$.each(values, function(k, v) {
//call val() for each existing combo, e.g. this.$hour.val()
@@ -4242,11 +4413,11 @@
/**
* move popover to new position. This function mainly copied from bootstrap-popover.
*/
/*jshint laxcomma: true*/
setPosition: function () {
-
+
(function() {
var $tip = this.tip()
, inside
, pos
, actualWidth
@@ -4296,292 +4467,10 @@
/*jshint laxcomma: false*/
}
});
}(window.jQuery));
-/**
-Bootstrap-datepicker.
-Description and examples: https://github.com/eternicode/bootstrap-datepicker.
-For **i18n** you should include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
-and set `language` option.
-Since 1.4.0 date has different appearance in **popup** and **inline** modes.
-
-@class date
-@extends abstractinput
-@final
-@example
-<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-original-title="Select date">15/05/1984</a>
-<script>
-$(function(){
- $('#dob').editable({
- format: 'yyyy-mm-dd',
- viewformat: 'dd/mm/yyyy',
- datepicker: {
- weekStart: 1
- }
- }
- });
-});
-</script>
-**/
-(function ($) {
- "use strict";
-
- var Date = function (options) {
- this.init('date', options, Date.defaults);
- this.initPicker(options, Date.defaults);
- };
-
- $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
-
- $.extend(Date.prototype, {
- initPicker: function(options, defaults) {
- //'format' is set directly from settings or data-* attributes
-
- //by default viewformat equals to format
- if(!this.options.viewformat) {
- this.options.viewformat = this.options.format;
- }
-
- //overriding datepicker config (as by default jQuery extend() is not recursive)
- //since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
- this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
- format: this.options.viewformat
- });
-
- //language
- this.options.datepicker.language = this.options.datepicker.language || 'en';
-
- //store DPglobal
- this.dpg = $.fn.datepicker.DPGlobal;
-
- //store parsed formats
- this.parsedFormat = this.dpg.parseFormat(this.options.format);
- this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
- },
-
- render: function () {
- this.$input.datepicker(this.options.datepicker);
-
- //"clear" link
- if(this.options.clear) {
- this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
- e.preventDefault();
- e.stopPropagation();
- this.clear();
- }, this));
-
- this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
- }
- },
-
- value2html: function(value, element) {
- var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
- Date.superclass.value2html(text, element);
- },
-
- html2value: function(html) {
- return html ? this.dpg.parseDate(html, this.parsedViewFormat, this.options.datepicker.language) : null;
- },
-
- value2str: function(value) {
- 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);
- },
-
- input2value: function() {
- return this.$input.data('datepicker').date;
- },
-
- activate: function() {
- },
-
- clear: function() {
- this.$input.data('datepicker').date = null;
- this.$input.find('.active').removeClass('active');
- if(!this.options.showbuttons) {
- this.$input.closest('form').submit();
- }
- },
-
- autosubmit: function() {
- this.$input.on('mouseup', '.day', function(e){
- if($(e.currentTarget).is('.old') || $(e.currentTarget).is('.new')) {
- return;
- }
- var $form = $(this).closest('form');
- setTimeout(function() {
- $form.submit();
- }, 200);
- });
- //changedate is not suitable as it triggered when showing datepicker. see #149
- /*
- this.$input.on('changeDate', function(e){
- var $form = $(this).closest('form');
- setTimeout(function() {
- $form.submit();
- }, 200);
- });
- */
- }
-
- });
-
- Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
- /**
- @property tpl
- @default <div></div>
- **/
- tpl:'<div class="editable-date well"></div>',
- /**
- @property inputclass
- @default null
- **/
- inputclass: null,
- /**
- Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
- Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code>
-
- @property format
- @type string
- @default yyyy-mm-dd
- **/
- format:'yyyy-mm-dd',
- /**
- Format used for displaying date. Also applied when converting date from element's text on init.
- If not specified equals to <code>format</code>
-
- @property viewformat
- @type string
- @default null
- **/
- viewformat: null,
- /**
- Configuration of datepicker.
- Full list of options: http://vitalets.github.com/bootstrap-datepicker
-
- @property datepicker
- @type object
- @default {
- weekStart: 0,
- startView: 0,
- minViewMode: 0,
- autoclose: false
- }
- **/
- datepicker:{
- weekStart: 0,
- startView: 0,
- minViewMode: 0,
- autoclose: false
- },
- /**
- Text shown as clear date button.
- If <code>false</code> clear button will not be rendered.
-
- @property clear
- @type boolean|string
- @default 'x clear'
- **/
- clear: '× clear'
- });
-
- $.fn.editabletypes.date = Date;
-
-}(window.jQuery));
-
-/**
-Bootstrap datefield input - modification for inline mode.
-Shows normal <input type="text"> and binds popup datepicker.
-Automatically shown in inline mode.
-
-@class datefield
-@extends date
-
-@since 1.4.0
-**/
-(function ($) {
- "use strict";
-
- var DateField = function (options) {
- this.init('datefield', options, DateField.defaults);
- this.initPicker(options, DateField.defaults);
- };
-
- $.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);
-
- $.extend(DateField.prototype, {
- render: function () {
- this.$input = this.$tpl.find('input');
- this.setClass();
- this.setAttr('placeholder');
-
- this.$tpl.datepicker(this.options.datepicker);
-
- //need to disable original event handlers
- this.$input.off('focus keydown');
-
- //update value of datepicker
- this.$input.keyup($.proxy(function(){
- this.$tpl.removeData('date');
- this.$tpl.datepicker('update');
- }, this));
-
- },
-
- value2input: function(value) {
- this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '');
- this.$tpl.datepicker('update');
- },
-
- input2value: function() {
- return this.html2value(this.$input.val());
- },
-
- activate: function() {
- $.fn.editabletypes.text.prototype.activate.call(this);
- },
-
- autosubmit: function() {
- //reset autosubmit to empty
- }
- });
-
- DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, {
- /**
- @property tpl
- **/
- tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',
- /**
- @property inputclass
- @default 'input-small'
- **/
- inputclass: 'input-small',
-
- /* datepicker config */
- datepicker: {
- weekStart: 0,
- startView: 0,
- minViewMode: 0,
- autoclose: true
- }
- });
-
- $.fn.editabletypes.datefield = DateField;
-
-}(window.jQuery));
/* =========================================================
* bootstrap-datepicker.js
* http://www.eyecon.ro/bootstrap-datepicker
* =========================================================
* Copyright 2012 Stefan Petre
@@ -4598,11 +4487,11 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* ========================================================= */
-!function( $ ) {
+(function( $ ) {
function UTCDate(){
return new Date(Date.UTC.apply(Date, arguments));
}
function UTCToday(){
@@ -4613,113 +4502,51 @@
// Picker object
var Datepicker = function(element, options) {
var that = this;
+ this._process_options(options);
+
this.element = $(element);
- this.language = options.language||this.element.data('date-language')||"en";
- this.language = this.language in dates ? this.language : this.language.split('-')[0]; //Check if "de-DE" style date is available, if not language should fallback to 2 letter code eg "de"
- 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, .btn') : false;
this.hasInput = this.component && this.element.find('input').length;
if(this.component && this.component.length === 0)
this.component = false;
- 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();
if(this.isInline) {
this.picker.addClass('datepicker-inline').appendTo(this.element);
} else {
this.picker.addClass('datepicker-dropdown dropdown-menu');
}
- if (this.isRTL){
+
+ if (this.o.rtl){
this.picker.addClass('datepicker-rtl');
this.picker.find('.prev i, .next i')
.toggleClass('icon-arrow-left icon-arrow-right');
}
- this.autoclose = false;
- if ('autoclose' in options) {
- this.autoclose = options.autoclose;
- } else if ('dateAutoclose' in this.element.data()) {
- this.autoclose = this.element.data('date-autoclose');
- }
- this.keyboardNavigation = true;
- if ('keyboardNavigation' in options) {
- this.keyboardNavigation = options.keyboardNavigation;
- } else if ('dateKeyboardNavigation' in this.element.data()) {
- this.keyboardNavigation = this.element.data('date-keyboard-navigation');
- }
+ this.viewMode = this.o.startView;
- this.viewMode = this.startViewMode = 0;
- switch(options.startView || this.element.data('date-start-view')){
- case 2:
- case 'decade':
- this.viewMode = this.startViewMode = 2;
- break;
- case 1:
- case 'year':
- this.viewMode = this.startViewMode = 1;
- break;
- }
-
- this.minViewMode = options.minViewMode||this.element.data('date-min-view-mode')||0;
- if (typeof this.minViewMode === 'string') {
- switch (this.minViewMode) {
- case 'months':
- this.minViewMode = 1;
- break;
- case 'years':
- this.minViewMode = 2;
- break;
- default:
- this.minViewMode = 0;
- break;
- }
- }
-
- this.viewMode = this.startViewMode = Math.max(this.startViewMode, this.minViewMode);
-
- this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false);
- this.todayHighlight = (options.todayHighlight||this.element.data('date-today-highlight')||false);
-
- this.calendarWeeks = false;
- if ('calendarWeeks' in options) {
- this.calendarWeeks = options.calendarWeeks;
- } else if ('dateCalendarWeeks' in this.element.data()) {
- this.calendarWeeks = this.element.data('date-calendar-weeks');
- }
- if (this.calendarWeeks)
+ if (this.o.calendarWeeks)
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.setStartDate(this.o.startDate);
+ this.setEndDate(this.o.endDate);
+ this.setDaysOfWeekDisabled(this.o.daysOfWeekDisabled);
+
this.fillDow();
this.fillMonths();
this._allow_update = true;
@@ -4732,10 +4559,72 @@
};
Datepicker.prototype = {
constructor: Datepicker,
+ _process_options: function(opts){
+ // Store raw options for reference
+ this._o = $.extend({}, this._o, opts);
+ // Processed options
+ var o = this.o = $.extend({}, this._o);
+
+ // Check if "de-DE" style date is available, if not language should
+ // fallback to 2 letter code eg "de"
+ var lang = o.language;
+ if (!dates[lang]) {
+ lang = lang.split('-')[0];
+ if (!dates[lang])
+ lang = defaults.language;
+ }
+ o.language = lang;
+
+ switch(o.startView){
+ case 2:
+ case 'decade':
+ o.startView = 2;
+ break;
+ case 1:
+ case 'year':
+ o.startView = 1;
+ break;
+ default:
+ o.startView = 0;
+ }
+
+ switch (o.minViewMode) {
+ case 1:
+ case 'months':
+ o.minViewMode = 1;
+ break;
+ case 2:
+ case 'years':
+ o.minViewMode = 2;
+ break;
+ default:
+ o.minViewMode = 0;
+ }
+
+ o.startView = Math.max(o.startView, o.minViewMode);
+
+ o.weekStart %= 7;
+ o.weekEnd = ((o.weekStart + 6) % 7);
+
+ var format = DPGlobal.parseFormat(o.format)
+ if (o.startDate !== -Infinity) {
+ o.startDate = DPGlobal.parseDate(o.startDate, format, o.language);
+ }
+ if (o.endDate !== Infinity) {
+ o.endDate = DPGlobal.parseDate(o.endDate, format, o.language);
+ }
+
+ o.daysOfWeekDisabled = o.daysOfWeekDisabled||[];
+ if (!$.isArray(o.daysOfWeekDisabled))
+ o.daysOfWeekDisabled = o.daysOfWeekDisabled.split(/[,\s]*/);
+ o.daysOfWeekDisabled = $.map(o.daysOfWeekDisabled, function (d) {
+ return parseInt(d, 10);
+ });
+ },
_events: [],
_secondaryEvents: [],
_applyEvents: function(evs){
for (var i=0, el, ev; i<evs.length; i++){
el = evs[i][0];
@@ -4792,11 +4681,16 @@
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) {
+ if (!(
+ this.element.is(e.target) ||
+ this.element.find(e.target).size() ||
+ this.picker.is(e.target) ||
+ this.picker.find(e.target).size()
+ )) {
this.hide();
}
}, this)
}]
];
@@ -4813,47 +4707,54 @@
this._applyEvents(this._secondaryEvents);
},
_detachSecondaryEvents: function(){
this._unapplyEvents(this._secondaryEvents);
},
+ _trigger: function(event, altdate){
+ var date = altdate || this.date,
+ local_date = new Date(date.getTime() + (date.getTimezoneOffset()*60000));
+ this.element.trigger({
+ type: event,
+ date: local_date,
+ format: $.proxy(function(altformat){
+ var format = altformat || this.o.format;
+ return DPGlobal.formatDate(date, format, this.o.language);
+ }, this)
+ });
+ },
+
show: function(e) {
if (!this.isInline)
this.picker.appendTo('body');
this.picker.show();
this.height = this.component ? this.component.outerHeight() : this.element.outerHeight();
this.place();
this._attachSecondaryEvents();
if (e) {
e.preventDefault();
}
- this.element.trigger({
- type: 'show',
- date: this.date
- });
+ this._trigger('show');
},
hide: function(e){
if(this.isInline) return;
if (!this.picker.is(':visible')) return;
this.picker.hide().detach();
this._detachSecondaryEvents();
- this.viewMode = this.startViewMode;
+ this.viewMode = this.o.startView;
this.showMode();
if (
- this.forceParse &&
+ this.o.forceParse &&
(
this.isInput && this.element.val() ||
this.hasInput && this.element.find('input').val()
)
)
this.setValue();
- this.element.trigger({
- type: 'hide',
- date: this.date
- });
+ this._trigger('hide');
},
remove: function() {
this.hide();
this._detachEvents();
@@ -4887,48 +4788,35 @@
var formatted = this.getFormattedDate();
if (!this.isInput) {
if (this.component){
this.element.find('input').val(formatted);
}
- this.element.data('date', formatted);
} else {
this.element.val(formatted);
}
},
getFormattedDate: function(format) {
if (format === undefined)
- format = this.format;
- return DPGlobal.formatDate(this.date, format, this.language);
+ format = this.o.format;
+ return DPGlobal.formatDate(this.date, format, this.o.language);
},
setStartDate: function(startDate){
- this.startDate = startDate||-Infinity;
- if (this.startDate !== -Infinity) {
- this.startDate = DPGlobal.parseDate(this.startDate, this.format, this.language);
- }
+ this._process_options({startDate: startDate});
this.update();
this.updateNavArrows();
},
setEndDate: function(endDate){
- this.endDate = endDate||Infinity;
- if (this.endDate !== Infinity) {
- this.endDate = DPGlobal.parseDate(this.endDate, this.format, this.language);
- }
+ this._process_options({endDate: endDate});
this.update();
this.updateNavArrows();
},
setDaysOfWeekDisabled: function(daysOfWeekDisabled){
- this.daysOfWeekDisabled = daysOfWeekDisabled||[];
- if (!$.isArray(this.daysOfWeekDisabled)) {
- this.daysOfWeekDisabled = this.daysOfWeekDisabled.split(/,\s*/);
- }
- this.daysOfWeekDisabled = $.map(this.daysOfWeekDisabled, function (d) {
- return parseInt(d, 10);
- });
+ this._process_options({daysOfWeekDisabled: daysOfWeekDisabled});
this.update();
this.updateNavArrows();
},
place: function(){
@@ -4953,117 +4841,163 @@
if(arguments && arguments.length && (typeof arguments[0] === 'string' || arguments[0] instanceof Date)) {
date = arguments[0];
fromArgs = true;
} else {
date = this.isInput ? this.element.val() : this.element.data('date') || this.element.find('input').val();
+ delete this.element.data().date;
}
- this.date = DPGlobal.parseDate(date, this.format, this.language);
+ this.date = DPGlobal.parseDate(date, this.o.format, this.o.language);
if(fromArgs) this.setValue();
- if (this.date < this.startDate) {
- this.viewDate = new Date(this.startDate);
- } else if (this.date > this.endDate) {
- this.viewDate = new Date(this.endDate);
+ if (this.date < this.o.startDate) {
+ this.viewDate = new Date(this.o.startDate);
+ } else if (this.date > this.o.endDate) {
+ this.viewDate = new Date(this.o.endDate);
} else {
this.viewDate = new Date(this.date);
}
this.fill();
},
fillDow: function(){
- var dowCnt = this.weekStart,
+ var dowCnt = this.o.weekStart,
html = '<tr>';
- if(this.calendarWeeks){
+ if(this.o.calendarWeeks){
var cell = '<th class="cw"> </th>';
html += cell;
this.picker.find('.datepicker-days thead tr:first-child').prepend(cell);
}
- while (dowCnt < this.weekStart + 7) {
- html += '<th class="dow">'+dates[this.language].daysMin[(dowCnt++)%7]+'</th>';
+ while (dowCnt < this.o.weekStart + 7) {
+ html += '<th class="dow">'+dates[this.o.language].daysMin[(dowCnt++)%7]+'</th>';
}
html += '</tr>';
this.picker.find('.datepicker-days thead').append(html);
},
fillMonths: function(){
var html = '',
i = 0;
while (i < 12) {
- html += '<span class="month">'+dates[this.language].monthsShort[i++]+'</span>';
+ html += '<span class="month">'+dates[this.o.language].monthsShort[i++]+'</span>';
}
this.picker.find('.datepicker-months td').html(html);
},
+ setRange: function(range){
+ if (!range || !range.length)
+ delete this.range;
+ else
+ this.range = $.map(range, function(d){ return d.valueOf(); });
+ this.fill();
+ },
+
+ getClassNames: function(date){
+ var cls = [],
+ year = this.viewDate.getUTCFullYear(),
+ month = this.viewDate.getUTCMonth(),
+ currentDate = this.date.valueOf(),
+ today = new Date();
+ if (date.getUTCFullYear() < year || (date.getUTCFullYear() == year && date.getUTCMonth() < month)) {
+ cls.push('old');
+ } else if (date.getUTCFullYear() > year || (date.getUTCFullYear() == year && date.getUTCMonth() > month)) {
+ cls.push('new');
+ }
+ // Compare internal UTC date with local today, not UTC today
+ if (this.o.todayHighlight &&
+ date.getUTCFullYear() == today.getFullYear() &&
+ date.getUTCMonth() == today.getMonth() &&
+ date.getUTCDate() == today.getDate()) {
+ cls.push('today');
+ }
+ if (currentDate && date.valueOf() == currentDate) {
+ cls.push('active');
+ }
+ if (date.valueOf() < this.o.startDate || date.valueOf() > this.o.endDate ||
+ $.inArray(date.getUTCDay(), this.o.daysOfWeekDisabled) !== -1) {
+ cls.push('disabled');
+ }
+ if (this.range){
+ if (date > this.range[0] && date < this.range[this.range.length-1]){
+ cls.push('range');
+ }
+ if ($.inArray(date.valueOf(), this.range) != -1){
+ cls.push('selected');
+ }
+ }
+ return cls;
+ },
+
fill: function() {
var d = new Date(this.viewDate),
year = d.getUTCFullYear(),
month = d.getUTCMonth(),
- startYear = this.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,
+ startYear = this.o.startDate !== -Infinity ? this.o.startDate.getUTCFullYear() : -Infinity,
+ startMonth = this.o.startDate !== -Infinity ? this.o.startDate.getUTCMonth() : -Infinity,
+ endYear = this.o.endDate !== Infinity ? this.o.endDate.getUTCFullYear() : Infinity,
+ endMonth = this.o.endDate !== Infinity ? this.o.endDate.getUTCMonth() : Infinity,
currentDate = this.date && this.date.valueOf(),
- today = new Date();
- this.picker.find('.datepicker-days thead th.switch')
- .text(dates[this.language].months[month]+' '+year);
+ tooltip;
+ this.picker.find('.datepicker-days thead th.datepicker-switch')
+ .text(dates[this.o.language].months[month]+' '+year);
this.picker.find('tfoot th.today')
- .text(dates[this.language].today)
- .toggle(this.todayBtn !== false);
+ .text(dates[this.o.language].today)
+ .toggle(this.o.todayBtn !== false);
+ this.picker.find('tfoot th.clear')
+ .text(dates[this.o.language].clear)
+ .toggle(this.o.clearBtn !== false);
this.updateNavArrows();
this.fillMonths();
var prevMonth = UTCDate(year, month-1, 28,0,0,0,0),
day = DPGlobal.getDaysInMonth(prevMonth.getUTCFullYear(), prevMonth.getUTCMonth());
prevMonth.setUTCDate(day);
- prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.weekStart + 7)%7);
+ prevMonth.setUTCDate(day - (prevMonth.getUTCDay() - this.o.weekStart + 7)%7);
var nextMonth = new Date(prevMonth);
nextMonth.setUTCDate(nextMonth.getUTCDate() + 42);
nextMonth = nextMonth.valueOf();
var html = [];
var clsName;
while(prevMonth.valueOf() < nextMonth) {
- if (prevMonth.getUTCDay() == this.weekStart) {
+ if (prevMonth.getUTCDay() == this.o.weekStart) {
html.push('<tr>');
- if(this.calendarWeeks){
+ if(this.o.calendarWeeks){
// ISO 8601: First week contains first thursday.
// ISO also states week starts on Monday, but we can be more abstract here.
var
// Start of current week: based on weekstart/current date
- ws = new Date(+prevMonth + (this.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
+ ws = new Date(+prevMonth + (this.o.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5),
// Thursday of this week
th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5),
// First Thursday of year, year from thursday
yth = new Date(+(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5),
// Calendar week: ms between thursdays, div ms per day, div 7 days
calWeek = (th - yth) / 864e5 / 7 + 1;
html.push('<td class="cw">'+ calWeek +'</td>');
}
}
- clsName = '';
- if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) {
- clsName += ' old';
- } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) {
- clsName += ' new';
- }
- // Compare internal UTC date with local today, not UTC today
- if (this.todayHighlight &&
- prevMonth.getUTCFullYear() == today.getFullYear() &&
- prevMonth.getUTCMonth() == today.getMonth() &&
- prevMonth.getUTCDate() == today.getDate()) {
- clsName += ' today';
- }
- if (currentDate && prevMonth.valueOf() == currentDate) {
- clsName += ' active';
- }
- if (prevMonth.valueOf() < this.startDate || prevMonth.valueOf() > this.endDate ||
- $.inArray(prevMonth.getUTCDay(), this.daysOfWeekDisabled) !== -1) {
- clsName += ' disabled';
- }
- html.push('<td class="day'+clsName+'">'+prevMonth.getUTCDate() + '</td>');
- if (prevMonth.getUTCDay() == this.weekEnd) {
+ clsName = this.getClassNames(prevMonth);
+ clsName.push('day');
+
+ var before = this.o.beforeShowDay(prevMonth);
+ if (before === undefined)
+ before = {};
+ else if (typeof(before) === 'boolean')
+ before = {enabled: before};
+ else if (typeof(before) === 'string')
+ before = {classes: before};
+ if (before.enabled === false)
+ clsName.push('disabled');
+ if (before.classes)
+ clsName = clsName.concat(before.classes.split(/\s+/));
+ if (before.tooltip)
+ tooltip = before.tooltip;
+
+ clsName = $.unique(clsName);
+ html.push('<td class="'+clsName.join(' ')+'"' + (tooltip ? ' title="'+tooltip+'"' : '') + '>'+prevMonth.getUTCDate() + '</td>');
+ if (prevMonth.getUTCDay() == this.o.weekEnd) {
html.push('</tr>');
}
prevMonth.setUTCDate(prevMonth.getUTCDate()+1);
}
this.picker.find('.datepicker-days tbody').empty().append(html.join(''));
@@ -5094,11 +5028,11 @@
.text(year + '-' + (year + 9))
.end()
.find('td');
year -= 1;
for (var i = -1; i < 11; i++) {
- html += '<span class="year'+(i == -1 || i == 10 ? ' old' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
+ html += '<span class="year'+(i == -1 ? ' old' : i == 10 ? ' new' : '')+(currentYear == year ? ' active' : '')+(year < startYear || year > endYear ? ' disabled' : '')+'">'+year+'</span>';
year += 1;
}
yearCont.html(html);
},
@@ -5108,29 +5042,29 @@
var d = new Date(this.viewDate),
year = d.getUTCFullYear(),
month = d.getUTCMonth();
switch (this.viewMode) {
case 0:
- if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear() && month <= this.startDate.getUTCMonth()) {
+ if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear() && month <= this.o.startDate.getUTCMonth()) {
this.picker.find('.prev').css({visibility: 'hidden'});
} else {
this.picker.find('.prev').css({visibility: 'visible'});
}
- if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear() && month >= this.endDate.getUTCMonth()) {
+ if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear() && month >= this.o.endDate.getUTCMonth()) {
this.picker.find('.next').css({visibility: 'hidden'});
} else {
this.picker.find('.next').css({visibility: 'visible'});
}
break;
case 1:
case 2:
- if (this.startDate !== -Infinity && year <= this.startDate.getUTCFullYear()) {
+ if (this.o.startDate !== -Infinity && year <= this.o.startDate.getUTCFullYear()) {
this.picker.find('.prev').css({visibility: 'hidden'});
} else {
this.picker.find('.prev').css({visibility: 'visible'});
}
- if (this.endDate !== Infinity && year >= this.endDate.getUTCFullYear()) {
+ if (this.o.endDate !== Infinity && year >= this.o.endDate.getUTCFullYear()) {
this.picker.find('.next').css({visibility: 'hidden'});
} else {
this.picker.find('.next').css({visibility: 'visible'});
}
break;
@@ -5142,11 +5076,11 @@
var target = $(e.target).closest('span, td, th');
if (target.length == 1) {
switch(target[0].nodeName.toLowerCase()) {
case 'th':
switch(target[0].className) {
- case 'switch':
+ case 'datepicker-switch':
this.showMode(1);
break;
case 'prev':
case 'next':
var dir = DPGlobal.modes[this.viewMode].navStep * (target[0].className == 'prev' ? -1 : 1);
@@ -5164,40 +5098,47 @@
case 'today':
var date = new Date();
date = UTCDate(date.getFullYear(), date.getMonth(), date.getDate(), 0, 0, 0);
this.showMode(-2);
- var which = this.todayBtn == 'linked' ? null : 'view';
+ var which = this.o.todayBtn == 'linked' ? null : 'view';
this._setDate(date, which);
break;
+ case 'clear':
+ var element;
+ if (this.isInput)
+ element = this.element;
+ else if (this.component)
+ element = this.element.find('input');
+ if (element)
+ element.val("").change();
+ this._trigger('changeDate');
+ this.update();
+ if (this.o.autoclose)
+ this.hide();
+ break;
}
break;
case 'span':
if (!target.is('.disabled')) {
this.viewDate.setUTCDate(1);
if (target.is('.month')) {
var day = 1;
var month = target.parent().find('span').index(target);
var year = this.viewDate.getUTCFullYear();
this.viewDate.setUTCMonth(month);
- this.element.trigger({
- type: 'changeMonth',
- date: this.viewDate
- });
- if ( this.minViewMode == 1 ) {
+ this._trigger('changeMonth', this.viewDate);
+ if (this.o.minViewMode === 1) {
this._setDate(UTCDate(year, month, day,0,0,0,0));
}
} else {
var year = parseInt(target.text(), 10)||0;
var day = 1;
var month = 0;
this.viewDate.setUTCFullYear(year);
- this.element.trigger({
- type: 'changeYear',
- date: this.viewDate
- });
- if ( this.minViewMode == 2 ) {
+ this._trigger('changeYear', this.viewDate);
+ if (this.o.minViewMode === 2) {
this._setDate(UTCDate(year, month, day,0,0,0,0));
}
}
this.showMode(-1);
this.fill();
@@ -5230,28 +5171,25 @@
}
},
_setDate: function(date, which){
if (!which || which == 'date')
- this.date = date;
+ this.date = new Date(date);
if (!which || which == 'view')
- this.viewDate = date;
+ this.viewDate = new Date(date);
this.fill();
this.setValue();
- this.element.trigger({
- type: 'changeDate',
- date: this.date
- });
+ this._trigger('changeDate');
var element;
if (this.isInput) {
element = this.element;
} else if (this.component){
element = this.element.find('input');
}
if (element) {
element.change();
- if (this.autoclose && (!which || which == 'date')) {
+ if (this.o.autoclose && (!which || which == 'date')) {
this.hide();
}
}
},
@@ -5298,11 +5236,11 @@
moveYear: function(date, dir){
return this.moveMonth(date, dir*12);
},
dateWithinRange: function(date){
- return date >= this.startDate && date <= this.endDate;
+ return date >= this.o.startDate && date <= this.o.endDate;
},
keydown: function(e){
if (this.picker.is(':not(:visible)')){
if (e.keyCode == 27) // allow escape to hide and re-show picker
@@ -5317,11 +5255,11 @@
this.hide();
e.preventDefault();
break;
case 37: // left
case 39: // right
- if (!this.keyboardNavigation) break;
+ if (!this.o.keyboardNavigation) break;
dir = e.keyCode == 37 ? -1 : 1;
if (e.ctrlKey){
newDate = this.moveYear(this.date, dir);
newViewDate = this.moveYear(this.viewDate, dir);
} else if (e.shiftKey){
@@ -5342,11 +5280,11 @@
dateChanged = true;
}
break;
case 38: // up
case 40: // down
- if (!this.keyboardNavigation) break;
+ if (!this.o.keyboardNavigation) break;
dir = e.keyCode == 38 ? -1 : 1;
if (e.ctrlKey){
newDate = this.moveYear(this.date, dir);
newViewDate = this.moveYear(this.viewDate, dir);
} else if (e.shiftKey){
@@ -5374,14 +5312,11 @@
case 9: // tab
this.hide();
break;
}
if (dateChanged){
- this.element.trigger({
- type: 'changeDate',
- date: this.date
- });
+ this._trigger('changeDate');
var element;
if (this.isInput) {
element = this.element;
} else if (this.component){
element = this.element.find('input');
@@ -5392,11 +5327,11 @@
}
},
showMode: function(dir) {
if (dir) {
- this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir));
+ this.viewMode = Math.max(this.o.minViewMode, Math.min(2, this.viewMode + dir));
}
/*
vitalets: fixing bug of very special conditions:
jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover.
Method show() does not set display css correctly and datepicker is not shown.
@@ -5409,37 +5344,165 @@
this.picker.find('>div').hide().filter('.datepicker-'+DPGlobal.modes[this.viewMode].clsName).css('display', 'block');
this.updateNavArrows();
}
};
- $.fn.datepicker = function ( option ) {
+ var DateRangePicker = function(element, options){
+ this.element = $(element);
+ this.inputs = $.map(options.inputs, function(i){ return i.jquery ? i[0] : i; });
+ delete options.inputs;
+
+ $(this.inputs)
+ .datepicker(options)
+ .bind('changeDate', $.proxy(this.dateUpdated, this));
+
+ this.pickers = $.map(this.inputs, function(i){ return $(i).data('datepicker'); });
+ this.updateDates();
+ };
+ DateRangePicker.prototype = {
+ updateDates: function(){
+ this.dates = $.map(this.pickers, function(i){ return i.date; });
+ this.updateRanges();
+ },
+ updateRanges: function(){
+ var range = $.map(this.dates, function(d){ return d.valueOf(); });
+ $.each(this.pickers, function(i, p){
+ p.setRange(range);
+ });
+ },
+ dateUpdated: function(e){
+ var dp = $(e.target).data('datepicker'),
+ new_date = dp.getUTCDate(),
+ i = $.inArray(e.target, this.inputs),
+ l = this.inputs.length;
+ if (i == -1) return;
+
+ if (new_date < this.dates[i]){
+ // Date being moved earlier/left
+ while (i>=0 && new_date < this.dates[i]){
+ this.pickers[i--].setUTCDate(new_date);
+ }
+ }
+ else if (new_date > this.dates[i]){
+ // Date being moved later/right
+ while (i<l && new_date > this.dates[i]){
+ this.pickers[i++].setUTCDate(new_date);
+ }
+ }
+ this.updateDates();
+ },
+ remove: function(){
+ $.map(this.pickers, function(p){ p.remove(); });
+ delete this.element.data().datepicker;
+ }
+ };
+
+ function opts_from_el(el, prefix){
+ // Derive options from element data-attrs
+ var data = $(el).data(),
+ out = {}, inkey,
+ replace = new RegExp('^' + prefix.toLowerCase() + '([A-Z])'),
+ prefix = new RegExp('^' + prefix.toLowerCase());
+ for (var key in data)
+ if (prefix.test(key)){
+ inkey = key.replace(replace, function(_,a){ return a.toLowerCase(); });
+ out[inkey] = data[key];
+ }
+ return out;
+ }
+
+ function opts_from_locale(lang){
+ // Derive options from locale plugins
+ var out = {};
+ // Check if "de-DE" style date is available, if not language should
+ // fallback to 2 letter code eg "de"
+ if (!dates[lang]) {
+ lang = lang.split('-')[0]
+ if (!dates[lang])
+ return;
+ }
+ var d = dates[lang];
+ $.each(locale_opts, function(i,k){
+ if (k in d)
+ out[k] = d[k];
+ });
+ return out;
+ }
+
+ var old = $.fn.datepicker;
+ var datepicker = $.fn.datepicker = function ( option ) {
var args = Array.apply(null, arguments);
args.shift();
- return this.each(function () {
+ var internal_return,
+ this_return;
+ this.each(function () {
var $this = $(this),
data = $this.data('datepicker'),
options = typeof option == 'object' && option;
if (!data) {
- $this.data('datepicker', (data = new Datepicker(this, $.extend({}, $.fn.datepicker.defaults,options))));
+ var elopts = opts_from_el(this, 'date'),
+ // Preliminary otions
+ xopts = $.extend({}, defaults, elopts, options),
+ locopts = opts_from_locale(xopts.language),
+ // Options priority: js args, data-attrs, locales, defaults
+ opts = $.extend({}, defaults, locopts, elopts, options);
+ if ($this.is('.input-daterange') || opts.inputs){
+ var ropts = {
+ inputs: opts.inputs || $this.find('input').toArray()
+ };
+ $this.data('datepicker', (data = new DateRangePicker(this, $.extend(opts, ropts))));
+ }
+ else{
+ $this.data('datepicker', (data = new Datepicker(this, opts)));
+ }
}
if (typeof option == 'string' && typeof data[option] == 'function') {
- data[option].apply(data, args);
+ internal_return = data[option].apply(data, args);
+ if (internal_return !== undefined)
+ return false;
}
});
+ if (internal_return !== undefined)
+ return internal_return;
+ else
+ return this;
};
- $.fn.datepicker.defaults = {
+ var defaults = $.fn.datepicker.defaults = {
+ autoclose: false,
+ beforeShowDay: $.noop,
+ calendarWeeks: false,
+ clearBtn: false,
+ daysOfWeekDisabled: [],
+ endDate: Infinity,
+ forceParse: true,
+ format: 'mm/dd/yyyy',
+ keyboardNavigation: true,
+ language: 'en',
+ minViewMode: 0,
+ rtl: false,
+ startDate: -Infinity,
+ startView: 0,
+ todayBtn: false,
+ todayHighlight: false,
+ weekStart: 0
};
+ var locale_opts = $.fn.datepicker.locale_opts = [
+ 'format',
+ 'rtl',
+ 'weekStart'
+ ];
$.fn.datepicker.Constructor = Datepicker;
var dates = $.fn.datepicker.dates = {
en: {
days: ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", "Friday", "Saturday", "Sunday"],
daysShort: ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"],
daysMin: ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa", "Su"],
months: ["January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December"],
monthsShort: ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"],
- today: "Today"
+ today: "Today",
+ clear: "Clear"
}
};
var DPGlobal = {
modes: [
@@ -5476,10 +5539,12 @@
}
return {separators: separators, parts: parts};
},
parseDate: function(date, format, language) {
if (date instanceof Date) return date;
+ if (typeof format === 'string')
+ format = DPGlobal.parseFormat(format);
if (/^[\-+]\d+[dmwy]([\s,]+[\-+]\d+[dmwy])*$/.test(date)) {
var part_re = /([\-+]\d+)([dmwy])/,
parts = date.match(/([\-+]\d+)([dmwy])/g),
part, dir;
date = new Date();
@@ -5566,10 +5631,12 @@
}
}
return date;
},
formatDate: function(date, format, language){
+ if (typeof format === 'string')
+ format = DPGlobal.parseFormat(format);
var val = {
d: date.getUTCDate(),
D: dates[language].daysShort[date.getUTCDay()],
DD: dates[language].days[date.getUTCDay()],
m: date.getUTCMonth() + 1,
@@ -5580,26 +5647,26 @@
};
val.dd = (val.d < 10 ? '0' : '') + val.d;
val.mm = (val.m < 10 ? '0' : '') + val.m;
var date = [],
seps = $.extend([], format.separators);
- for (var i=0, cnt = format.parts.length; i < cnt; i++) {
+ for (var i=0, cnt = format.parts.length; i <= cnt; i++) {
if (seps.length)
date.push(seps.shift());
date.push(val[format.parts[i]]);
}
return date.join('');
},
headTemplate: '<thead>'+
'<tr>'+
'<th class="prev"><i class="icon-arrow-left"/></th>'+
- '<th colspan="5" class="switch"></th>'+
+ '<th colspan="5" class="datepicker-switch"></th>'+
'<th class="next"><i class="icon-arrow-right"/></th>'+
'</tr>'+
'</thead>',
contTemplate: '<tbody><tr><td colspan="7"></td></tr></tbody>',
- footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr></tfoot>'
+ footTemplate: '<tfoot><tr><th colspan="7" class="today"></th></tr><tr><th colspan="7" class="clear"></th></tr></tfoot>'
};
DPGlobal.template = '<div class="datepicker">'+
'<div class="datepicker-days">'+
'<table class=" table-condensed">'+
DPGlobal.headTemplate+
@@ -5623,20 +5690,358 @@
'</div>'+
'</div>';
$.fn.datepicker.DPGlobal = DPGlobal;
-}( window.jQuery );
+
+ /* DATEPICKER NO CONFLICT
+ * =================== */
+
+ $.fn.datepicker.noConflict = function(){
+ $.fn.datepicker = old;
+ return this;
+ };
+
+
+ /* DATEPICKER DATA-API
+ * ================== */
+
+ $(document).on(
+ 'focus.datepicker.data-api click.datepicker.data-api',
+ '[data-provide="datepicker"]',
+ function(e){
+ var $this = $(this);
+ if ($this.data('datepicker')) return;
+ e.preventDefault();
+ // component click requires us to explicitly show it
+ datepicker.call($this, 'show');
+ }
+ );
+ $(function(){
+ //$('[data-provide="datepicker-inline"]').datepicker();
+ //vit: changed to support noConflict()
+ datepicker.call($('[data-provide="datepicker-inline"]'));
+ });
+
+}( window.jQuery ));
/**
+Bootstrap-datepicker.
+Description and examples: https://github.com/eternicode/bootstrap-datepicker.
+For **i18n** you should include js file from here: https://github.com/eternicode/bootstrap-datepicker/tree/master/js/locales
+and set `language` option.
+Since 1.4.0 date has different appearance in **popup** and **inline** modes.
+
+@class date
+@extends abstractinput
+@final
+@example
+<a href="#" id="dob" data-type="date" data-pk="1" data-url="/post" data-original-title="Select date">15/05/1984</a>
+<script>
+$(function(){
+ $('#dob').editable({
+ format: 'yyyy-mm-dd',
+ viewformat: 'dd/mm/yyyy',
+ datepicker: {
+ weekStart: 1
+ }
+ }
+ });
+});
+</script>
+**/
+(function ($) {
+ "use strict";
+
+ //store bootstrap-datepicker as bdateicker to exclude conflict with jQuery UI one
+ $.fn.bdatepicker = $.fn.datepicker.noConflict();
+ if(!$.fn.datepicker) { //if there were no other datepickers, keep also original name
+ $.fn.datepicker = $.fn.bdatepicker;
+ }
+
+ var Date = function (options) {
+ this.init('date', options, Date.defaults);
+ this.initPicker(options, Date.defaults);
+ };
+
+ $.fn.editableutils.inherit(Date, $.fn.editabletypes.abstractinput);
+
+ $.extend(Date.prototype, {
+ initPicker: function(options, defaults) {
+ //'format' is set directly from settings or data-* attributes
+
+ //by default viewformat equals to format
+ if(!this.options.viewformat) {
+ this.options.viewformat = this.options.format;
+ }
+
+ //overriding datepicker config (as by default jQuery extend() is not recursive)
+ //since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
+ this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
+ format: this.options.viewformat
+ });
+
+ //language
+ this.options.datepicker.language = this.options.datepicker.language || 'en';
+
+ //store DPglobal
+ this.dpg = $.fn.bdatepicker.DPGlobal;
+
+ //store parsed formats
+ this.parsedFormat = this.dpg.parseFormat(this.options.format);
+ this.parsedViewFormat = this.dpg.parseFormat(this.options.viewformat);
+ },
+
+ render: function () {
+ this.$input.bdatepicker(this.options.datepicker);
+
+ //"clear" link
+ if(this.options.clear) {
+ this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
+ e.preventDefault();
+ e.stopPropagation();
+ this.clear();
+ }, this));
+
+ this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
+ }
+ },
+
+ value2html: function(value, element) {
+ var text = value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '';
+ Date.superclass.value2html(text, element);
+ },
+
+ html2value: function(html) {
+ return this.parseDate(html, this.parsedViewFormat);
+ },
+
+ value2str: function(value) {
+ return value ? this.dpg.formatDate(value, this.parsedFormat, this.options.datepicker.language) : '';
+ },
+
+ str2value: function(str) {
+ return this.parseDate(str, this.parsedFormat);
+ },
+
+ value2submit: function(value) {
+ return this.value2str(value);
+ },
+
+ value2input: function(value) {
+ this.$input.bdatepicker('update', value);
+ },
+
+ input2value: function() {
+ return this.$input.data('datepicker').date;
+ },
+
+ activate: function() {
+ },
+
+ clear: function() {
+ this.$input.data('datepicker').date = null;
+ this.$input.find('.active').removeClass('active');
+ if(!this.options.showbuttons) {
+ this.$input.closest('form').submit();
+ }
+ },
+
+ autosubmit: function() {
+ this.$input.on('mouseup', '.day', function(e){
+ if($(e.currentTarget).is('.old') || $(e.currentTarget).is('.new')) {
+ return;
+ }
+ var $form = $(this).closest('form');
+ setTimeout(function() {
+ $form.submit();
+ }, 200);
+ });
+ //changedate is not suitable as it triggered when showing datepicker. see #149
+ /*
+ this.$input.on('changeDate', function(e){
+ var $form = $(this).closest('form');
+ setTimeout(function() {
+ $form.submit();
+ }, 200);
+ });
+ */
+ },
+
+ /*
+ For incorrect date bootstrap-datepicker returns current date that is not suitable
+ for datefield.
+ This function returns null for incorrect date.
+ */
+ parseDate: function(str, format) {
+ var date = null, formattedBack;
+ if(str) {
+ date = this.dpg.parseDate(str, format, this.options.datepicker.language);
+ if(typeof str === 'string') {
+ formattedBack = this.dpg.formatDate(date, format, this.options.datepicker.language);
+ if(str !== formattedBack) {
+ date = null;
+ }
+ }
+ }
+ return date;
+ }
+
+ });
+
+ Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
+ /**
+ @property tpl
+ @default <div></div>
+ **/
+ tpl:'<div class="editable-date well"></div>',
+ /**
+ @property inputclass
+ @default null
+ **/
+ inputclass: null,
+ /**
+ Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
+ Possible tokens are: <code>d, dd, m, mm, yy, yyyy</code>
+
+ @property format
+ @type string
+ @default yyyy-mm-dd
+ **/
+ format:'yyyy-mm-dd',
+ /**
+ Format used for displaying date. Also applied when converting date from element's text on init.
+ If not specified equals to <code>format</code>
+
+ @property viewformat
+ @type string
+ @default null
+ **/
+ viewformat: null,
+ /**
+ Configuration of datepicker.
+ Full list of options: http://vitalets.github.com/bootstrap-datepicker
+
+ @property datepicker
+ @type object
+ @default {
+ weekStart: 0,
+ startView: 0,
+ minViewMode: 0,
+ autoclose: false
+ }
+ **/
+ datepicker:{
+ weekStart: 0,
+ startView: 0,
+ minViewMode: 0,
+ autoclose: false
+ },
+ /**
+ Text shown as clear date button.
+ If <code>false</code> clear button will not be rendered.
+
+ @property clear
+ @type boolean|string
+ @default 'x clear'
+ **/
+ clear: '× clear'
+ });
+
+ $.fn.editabletypes.date = Date;
+
+}(window.jQuery));
+
+/**
+Bootstrap datefield input - modification for inline mode.
+Shows normal <input type="text"> and binds popup datepicker.
+Automatically shown in inline mode.
+
+@class datefield
+@extends date
+
+@since 1.4.0
+**/
+(function ($) {
+ "use strict";
+
+ var DateField = function (options) {
+ this.init('datefield', options, DateField.defaults);
+ this.initPicker(options, DateField.defaults);
+ };
+
+ $.fn.editableutils.inherit(DateField, $.fn.editabletypes.date);
+
+ $.extend(DateField.prototype, {
+ render: function () {
+ this.$input = this.$tpl.find('input');
+ this.setClass();
+ this.setAttr('placeholder');
+
+ //bootstrap-datepicker is set `bdateicker` to exclude conflict with jQuery UI one. (in date.js)
+ this.$tpl.bdatepicker(this.options.datepicker);
+
+ //need to disable original event handlers
+ this.$input.off('focus keydown');
+
+ //update value of datepicker
+ this.$input.keyup($.proxy(function(){
+ this.$tpl.removeData('date');
+ this.$tpl.bdatepicker('update');
+ }, this));
+
+ },
+
+ value2input: function(value) {
+ this.$input.val(value ? this.dpg.formatDate(value, this.parsedViewFormat, this.options.datepicker.language) : '');
+ this.$tpl.bdatepicker('update');
+ },
+
+ input2value: function() {
+ return this.html2value(this.$input.val());
+ },
+
+ activate: function() {
+ $.fn.editabletypes.text.prototype.activate.call(this);
+ },
+
+ autosubmit: function() {
+ //reset autosubmit to empty
+ }
+ });
+
+ DateField.defaults = $.extend({}, $.fn.editabletypes.date.defaults, {
+ /**
+ @property tpl
+ **/
+ tpl:'<div class="input-append date"><input type="text"/><span class="add-on"><i class="icon-th"></i></span></div>',
+ /**
+ @property inputclass
+ @default 'input-small'
+ **/
+ inputclass: 'input-small',
+
+ /* datepicker config */
+ datepicker: {
+ weekStart: 0,
+ startView: 0,
+ minViewMode: 0,
+ autoclose: true
+ }
+ });
+
+ $.fn.editabletypes.datefield = DateField;
+
+}(window.jQuery));
+/**
Bootstrap-datetimepicker.
Based on [smalot bootstrap-datetimepicker plugin](https://github.com/smalot/bootstrap-datetimepicker).
Before usage you should manually include dependent js and css:
<link href="css/datetimepicker.css" rel="stylesheet" type="text/css"></link>
<script src="js/bootstrap-datetimepicker.js"></script>
-
+
For **i18n** you should include js file from here: https://github.com/smalot/bootstrap-datetimepicker/tree/master/js/locales
and set `language` option.
@class datetime
@extends abstractinput
@@ -5657,149 +6062,173 @@
});
</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);
-
+
+ //adjust container position when viewMode changes
+ //see https://github.com/smalot/bootstrap-datetimepicker/pull/80
+ this.$input.on('changeMode', function(e) {
+ var f = $(this).closest('form').parent();
+ //timeout here, otherwise container changes position before form has new size
+ setTimeout(function(){
+ f.triggerHandler('resize');
+ }, 0);
+ });
+
//"clear" link
if(this.options.clear) {
this.$clear = $('<a href="#"></a>').html(this.options.clear).click($.proxy(function(e){
e.preventDefault();
e.stopPropagation();
this.clear();
}, this));
-
+
this.$tpl.parent().append($('<div class="editable-clear">').append(this.$clear));
- }
+ }
},
-
+
value2html: function(value, element) {
//formatDate works with UTCDate!
var text = value ? this.dpg.formatDate(this.toUTC(value), this.parsedViewFormat, this.options.datetimepicker.language, this.options.formatType) : '';
if(element) {
DateTime.superclass.value2html(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;
+ var value = this.parseDate(html, this.parsedViewFormat);
return value ? this.fromUTC(value) : null;
- },
-
+ },
+
value2str: function(value) {
//formatDate works with UTCDate!
return value ? this.dpg.formatDate(this.toUTC(value), this.parsedFormat, this.options.datetimepicker.language, this.options.formatType) : '';
- },
-
+ },
+
str2value: function(str) {
//parseDate return utc date!
- var value = str ? this.dpg.parseDate(str, this.parsedFormat, this.options.datetimepicker.language, this.options.formatType) : null;
+ var value = this.parseDate(str, this.parsedFormat);
return value ? this.fromUTC(value) : null;
- },
-
+ },
+
value2submit: function(value) {
return this.value2str(value);
- },
+ },
value2input: function(value) {
if(value) {
this.$input.data('datetimepicker').setDate(value);
}
},
-
+
input2value: function() {
//date may be cleared, in that case getDate() triggers error
var dt = this.$input.data('datetimepicker');
return dt.date ? dt.getDate() : null;
- },
-
+ },
+
activate: function() {
},
-
+
clear: function() {
this.$input.data('datetimepicker').date = null;
this.$input.find('.active').removeClass('active');
if(!this.options.showbuttons) {
this.$input.closest('form').submit();
}
},
-
+
autosubmit: function() {
this.$input.on('mouseup', '.minute', function(e){
var $form = $(this).closest('form');
setTimeout(function() {
$form.submit();
}, 200);
});
},
-
+
//convert date from local to utc
toUTC: function(value) {
return value ? new Date(value.valueOf() - value.getTimezoneOffset() * 60000) : value;
},
-
+
//convert date from utc to local
fromUTC: function(value) {
return value ? new Date(value.valueOf() + value.getTimezoneOffset() * 60000) : value;
+ },
+
+ /*
+ For incorrect date bootstrap-datetimepicker returns current date that is not suitable
+ for datetimefield.
+ This function returns null for incorrect date.
+ */
+ parseDate: function(str, format) {
+ var date = null, formattedBack;
+ if(str) {
+ date = this.dpg.parseDate(str, format, this.options.datetimepicker.language, this.options.formatType);
+ if(typeof str === 'string') {
+ formattedBack = this.dpg.formatDate(date, format, this.options.datetimepicker.language, this.options.formatType);
+ if(str !== formattedBack) {
+ date = null;
+ }
+ }
+ }
+ return date;
}
});
-
+
DateTime.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
/**
@property tpl
@default <div></div>
**/
tpl:'<div class="editable-date well"></div>',
/**
@property inputclass
@default null
- **/
+ **/
inputclass: null,
/**
Format used for sending value to server. Also applied when converting date from <code>data-value</code> attribute.<br>
Possible tokens are: <code>d, dd, m, mm, yy, yyyy, h, i</code>
@@ -5814,16 +6243,16 @@
If not specified equals to <code>format</code>
@property viewformat
@type string
@default null
- **/
- viewformat: null,
+ **/
+ viewformat: null,
/**
Configuration of datetimepicker.
Full list of options: https://github.com/smalot/bootstrap-datetimepicker
-
+
@property datetimepicker
@type object
@default { }
**/
datetimepicker:{
@@ -5831,16 +6260,16 @@
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'
+ @default 'x clear'
**/
clear: '× clear'
- });
+ });
$.fn.editabletypes.datetime = DateTime;
}(window.jQuery));
/**
\ No newline at end of file