This commit is contained in:
vitalets 2013-06-23 17:40:34 +04:00
parent 1e05597d40
commit 9136f8d34a
11 changed files with 1838 additions and 861 deletions

20
dist/CHANGELOG.txt vendored

@ -1,6 +1,26 @@
X-editable changelog X-editable changelog
============================= =============================
Version 1.4.5 Jun 23, 2013
----------------------------
[enh #245] highlight element after update (vitalets)
[enh] select2 now works with ajax source (vitalets)
[bug] fix datefield (datetimefield) to return null for incorrect dates (vitalets)
[bug #224] do not close popup when it is saving value (vitalets)
[enh] added `submitValue` to `save` event params (vitalets)
[enh #259] allow `getValue` method to return value itself, not object (vitalets)
[enh] add `destroy` method to inputs (vitalets)
[enh #164] allow emptytext to be html (vitalets)
[enh #78] allow html in editable content (vitalets)
[enh] update container position when datetimepicker viewMode changes (vitalets)
[enh #255] remove xxxView options from first level config of datetimepicker (vitalets)
[enh] if `display` defined as function call it on init (vitalets)
[enh #218] sourceCache now disables cache totally (vitalets)
[bug #156] solve conflict of bootstrap datepicker and jQuery UI datepicker (vitalets)
[enh] update bootstrap-datepicker to 1.1.2 (vitalets)
[enh] allow follow links in disabled state (vitalets)
[enh] update combodate to 1.0.4, fix #222 (vitalets)
Version 1.4.4 May 4, 2013 Version 1.4.4 May 4, 2013
---------------------------- ----------------------------

35
dist/README.md vendored

@ -1,16 +1,35 @@
# X-editable # X-editable
In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery. In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery.
It is a new life of [bootstrap-editable plugin](http://github.com/vitalets/bootstrap-editable) that was strongly refactored and improved.
## Demo + Docs + Download ## Live demo
See **http://vitalets.github.com/x-editable** **http://vitalets.github.io/x-editable/demo.html**
## Installation
### Manual download
Use **http://vitalets.github.io/x-editable**
### Bower
````
bower install x-editable
````
### CDN
````js
<link href="//cdnjs.cloudflare.com/ajax/libs/x-editable/1.4.5/bootstrap-editable/css/bootstrap-editable.css" rel="stylesheet"/>
<script src="//cdnjs.cloudflare.com/ajax/libs/x-editable/1.4.5/bootstrap-editable/js/bootstrap-editable.min.js"></script>
````
## Documentation
**http://vitalets.github.io/x-editable**
## Reporting issues ## Reporting issues
When creating issues please provide [jsFiddle](http://jsfiddle.net) example. You can easily fork one of **jsFiddle templates**: When creating issues please provide [jsFiddle](http://jsfiddle.net) example. You can easily fork one of following:
1. [bootstrap](http://jsfiddle.net/xBB5x/195) 1. [jsFiddle bootstrap template](http://jsfiddle.net/xBB5x/195)
2. [jqueryui](http://jsfiddle.net/xBB5x/196) 2. [jsFiddle jqueryui template](http://jsfiddle.net/xBB5x/196)
3. [plain](http://jsfiddle.net/xBB5x/197) 3. [jsFiddle jquery template](http://jsfiddle.net/xBB5x/197)
Your feedback is very appreciated! Your feedback is very appreciated!
## Contribution ## Contribution
@ -68,4 +87,4 @@ Thanks for your support!
## License ## License
Copyright (c) 2012 Vitaliy Potapov Copyright (c) 2012 Vitaliy Potapov
Licensed under the MIT licenses. Licensed under the MIT license.

@ -1,4 +1,4 @@
/*! X-editable - v1.4.4 /*! X-editable - v1.4.5
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
* http://github.com/vitalets/x-editable * http://github.com/vitalets/x-editable
* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */ * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
@ -184,6 +184,14 @@ a.editable-click.editable-disabled:hover {
/* content: '*'*/ /* content: '*'*/
} }
.editable-bg-transition {
-webkit-transition: background-color 1400ms ease-in;
-moz-transition: background-color 1400ms ease-in;
-o-transition: background-color 1400ms ease-in;
-ms-transition: background-color 1400ms ease-in;
transition: background-color 1400ms ease-in;
}
/*see https://github.com/vitalets/x-editable/issues/139 */ /*see https://github.com/vitalets/x-editable/issues/139 */
.form-horizontal .editable .form-horizontal .editable
{ {
@ -305,7 +313,7 @@ a.editable-click.editable-disabled:hover {
border-color: #fdf59a #fdf59a #fbed50; border-color: #fdf59a #fdf59a #fbed50;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
color: #000 !important; color: #000;
} }
.datepicker table tr td.today:hover, .datepicker table tr td.today:hover,
.datepicker table tr td.today:hover:hover, .datepicker table tr td.today:hover:hover,
@ -339,6 +347,124 @@ a.editable-click.editable-disabled:hover {
.datepicker table tr td.today.disabled:hover.active { .datepicker table tr td.today.disabled:hover.active {
background-color: #fbf069 \9; background-color: #fbf069 \9;
} }
.datepicker table tr td.today:hover:hover {
color: #000;
}
.datepicker table tr td.today.active:hover {
color: #fff;
}
.datepicker table tr td.range,
.datepicker table tr td.range:hover,
.datepicker table tr td.range.disabled,
.datepicker table tr td.range.disabled:hover {
background: #eeeeee;
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.datepicker table tr td.range.today,
.datepicker table tr td.range.today:hover,
.datepicker table tr td.range.today.disabled,
.datepicker table tr td.range.today.disabled:hover {
background-color: #f3d17a;
background-image: -moz-linear-gradient(top, #f3c17a, #f3e97a);
background-image: -ms-linear-gradient(top, #f3c17a, #f3e97a);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#f3c17a), to(#f3e97a));
background-image: -webkit-linear-gradient(top, #f3c17a, #f3e97a);
background-image: -o-linear-gradient(top, #f3c17a, #f3e97a);
background-image: linear-gradient(top, #f3c17a, #f3e97a);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#f3c17a', endColorstr='#f3e97a', GradientType=0);
border-color: #f3e97a #f3e97a #edde34;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
-webkit-border-radius: 0;
-moz-border-radius: 0;
border-radius: 0;
}
.datepicker table tr td.range.today:hover,
.datepicker table tr td.range.today:hover:hover,
.datepicker table tr td.range.today.disabled:hover,
.datepicker table tr td.range.today.disabled:hover:hover,
.datepicker table tr td.range.today:active,
.datepicker table tr td.range.today:hover:active,
.datepicker table tr td.range.today.disabled:active,
.datepicker table tr td.range.today.disabled:hover:active,
.datepicker table tr td.range.today.active,
.datepicker table tr td.range.today:hover.active,
.datepicker table tr td.range.today.disabled.active,
.datepicker table tr td.range.today.disabled:hover.active,
.datepicker table tr td.range.today.disabled,
.datepicker table tr td.range.today:hover.disabled,
.datepicker table tr td.range.today.disabled.disabled,
.datepicker table tr td.range.today.disabled:hover.disabled,
.datepicker table tr td.range.today[disabled],
.datepicker table tr td.range.today:hover[disabled],
.datepicker table tr td.range.today.disabled[disabled],
.datepicker table tr td.range.today.disabled:hover[disabled] {
background-color: #f3e97a;
}
.datepicker table tr td.range.today:active,
.datepicker table tr td.range.today:hover:active,
.datepicker table tr td.range.today.disabled:active,
.datepicker table tr td.range.today.disabled:hover:active,
.datepicker table tr td.range.today.active,
.datepicker table tr td.range.today:hover.active,
.datepicker table tr td.range.today.disabled.active,
.datepicker table tr td.range.today.disabled:hover.active {
background-color: #efe24b \9;
}
.datepicker table tr td.selected,
.datepicker table tr td.selected:hover,
.datepicker table tr td.selected.disabled,
.datepicker table tr td.selected.disabled:hover {
background-color: #9e9e9e;
background-image: -moz-linear-gradient(top, #b3b3b3, #808080);
background-image: -ms-linear-gradient(top, #b3b3b3, #808080);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#b3b3b3), to(#808080));
background-image: -webkit-linear-gradient(top, #b3b3b3, #808080);
background-image: -o-linear-gradient(top, #b3b3b3, #808080);
background-image: linear-gradient(top, #b3b3b3, #808080);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#b3b3b3', endColorstr='#808080', GradientType=0);
border-color: #808080 #808080 #595959;
border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25);
filter: progid:DXImageTransform.Microsoft.gradient(enabled=false);
color: #fff;
text-shadow: 0 -1px 0 rgba(0, 0, 0, 0.25);
}
.datepicker table tr td.selected:hover,
.datepicker table tr td.selected:hover:hover,
.datepicker table tr td.selected.disabled:hover,
.datepicker table tr td.selected.disabled:hover:hover,
.datepicker table tr td.selected:active,
.datepicker table tr td.selected:hover:active,
.datepicker table tr td.selected.disabled:active,
.datepicker table tr td.selected.disabled:hover:active,
.datepicker table tr td.selected.active,
.datepicker table tr td.selected:hover.active,
.datepicker table tr td.selected.disabled.active,
.datepicker table tr td.selected.disabled:hover.active,
.datepicker table tr td.selected.disabled,
.datepicker table tr td.selected:hover.disabled,
.datepicker table tr td.selected.disabled.disabled,
.datepicker table tr td.selected.disabled:hover.disabled,
.datepicker table tr td.selected[disabled],
.datepicker table tr td.selected:hover[disabled],
.datepicker table tr td.selected.disabled[disabled],
.datepicker table tr td.selected.disabled:hover[disabled] {
background-color: #808080;
}
.datepicker table tr td.selected:active,
.datepicker table tr td.selected:hover:active,
.datepicker table tr td.selected.disabled:active,
.datepicker table tr td.selected.disabled:hover:active,
.datepicker table tr td.selected.active,
.datepicker table tr td.selected:hover.active,
.datepicker table tr td.selected.disabled.active,
.datepicker table tr td.selected.disabled:hover.active {
background-color: #666666 \9;
}
.datepicker table tr td.active, .datepicker table tr td.active,
.datepicker table tr td.active:hover, .datepicker table tr td.active:hover,
.datepicker table tr td.active.disabled, .datepicker table tr td.active.disabled,
@ -462,18 +588,19 @@ a.editable-click.editable-disabled:hover {
.datepicker table tr td span.active.disabled:hover.active { .datepicker table tr td span.active.disabled:hover.active {
background-color: #003399 \9; background-color: #003399 \9;
} }
.datepicker table tr td span.old { .datepicker table tr td span.old,
.datepicker table tr td span.new {
color: #999999; color: #999999;
} }
.datepicker th.switch { .datepicker th.datepicker-switch {
width: 145px; width: 145px;
} }
.datepicker thead tr:first-child th, .datepicker thead tr:first-child th,
.datepicker tfoot tr:first-child th { .datepicker tfoot tr th {
cursor: pointer; cursor: pointer;
} }
.datepicker thead tr:first-child th:hover, .datepicker thead tr:first-child th:hover,
.datepicker tfoot tr:first-child th:hover { .datepicker tfoot tr th:hover {
background: #eeeeee; background: #eeeeee;
} }
.datepicker .cw { .datepicker .cw {
@ -493,3 +620,32 @@ a.editable-click.editable-disabled:hover {
width: 16px; width: 16px;
height: 16px; height: 16px;
} }
.input-daterange input {
text-align: center;
}
.input-daterange input:first-child {
-webkit-border-radius: 3px 0 0 3px;
-moz-border-radius: 3px 0 0 3px;
border-radius: 3px 0 0 3px;
}
.input-daterange input:last-child {
-webkit-border-radius: 0 3px 3px 0;
-moz-border-radius: 0 3px 3px 0;
border-radius: 0 3px 3px 0;
}
.input-daterange .add-on {
display: inline-block;
width: auto;
min-width: 16px;
height: 18px;
padding: 4px 5px;
font-weight: normal;
line-height: 18px;
text-align: center;
text-shadow: 0 1px 0 #ffffff;
vertical-align: middle;
background-color: #eeeeee;
border: 1px solid #ccc;
margin-left: -5px;
margin-right: -5px;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

@ -1,4 +1,4 @@
/*! X-editable - v1.4.4 /*! X-editable - v1.4.5
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
* http://github.com/vitalets/x-editable * http://github.com/vitalets/x-editable
* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */ * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
@ -184,6 +184,14 @@ a.editable-click.editable-disabled:hover {
/* content: '*'*/ /* content: '*'*/
} }
.editable-bg-transition {
-webkit-transition: background-color 1400ms ease-in;
-moz-transition: background-color 1400ms ease-in;
-o-transition: background-color 1400ms ease-in;
-ms-transition: background-color 1400ms ease-in;
transition: background-color 1400ms ease-in;
}
/*see https://github.com/vitalets/x-editable/issues/139 */ /*see https://github.com/vitalets/x-editable/issues/139 */
.form-horizontal .editable .form-horizontal .editable
{ {

@ -1,4 +1,4 @@
/*! X-editable - v1.4.4 /*! X-editable - v1.4.5
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
* http://github.com/vitalets/x-editable * http://github.com/vitalets/x-editable
* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */ * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
@ -65,6 +65,10 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
//show loading state //show loading state
this.showLoading(); 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 Fired when rendering starts
@event rendering @event rendering
@ -217,31 +221,38 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
return; return;
} }
//convert value for submitting to server
var submitValue = this.input.value2submit(newValue);
this.isSaving = true;
//sending data to server //sending data to server
$.when(this.save(newValue)) $.when(this.save(submitValue))
.done($.proxy(function(response) { .done($.proxy(function(response) {
this.isSaving = false;
//run success callback //run success callback
var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null; 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 success callback returns false --> keep form open and do not activate input
if(res === false) { if(res === false) {
this.error(false); this.error(false);
this.showForm(false); this.showForm(false);
return; return;
} }
//if success callback returns string --> keep form open, show error and activate input //if success callback returns string --> keep form open, show error and activate input
if(typeof res === 'string') { if(typeof res === 'string') {
this.error(res); this.error(res);
this.showForm(); this.showForm();
return; return;
} }
//if success callback returns object like {newValue: <something>} --> use that value instead of submitted //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 //it is usefull if you want to chnage value in url-function
if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) { if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
newValue = res.newValue; newValue = res.newValue;
} }
//clear error message //clear error message
this.error(false); this.error(false);
@ -251,37 +262,42 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
@event save @event save
@param {Object} event event object @param {Object} event event object
@param {Object} params additional params @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 @param {Object} params.response ajax response
@example @example
$('#form-div').on('save'), function(e, params){ $('#form-div').on('save'), function(e, params){
if(params.newValue === 'username') {...} if(params.newValue === 'username') {...}
}); });
**/ **/
this.$div.triggerHandler('save', {newValue: newValue, response: response}); this.$div.triggerHandler('save', {newValue: newValue, submitValue: submitValue, response: response});
}, this)) }, this))
.fail($.proxy(function(xhr) { .fail($.proxy(function(xhr) {
this.isSaving = false;
var msg; var msg;
if(typeof this.options.error === 'function') { if(typeof this.options.error === 'function') {
msg = this.options.error.call(this.options.scope, xhr, newValue); msg = this.options.error.call(this.options.scope, xhr, newValue);
} else { } else {
msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!'; msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!';
} }
this.error(msg); this.error(msg);
this.showForm(); this.showForm();
}, this)); }, this));
}, },
save: function(newValue) { save: function(submitValue) {
//convert value for submitting to server
var submitValue = this.input.value2submit(newValue);
//try parse composite pk defined as json string in data-pk //try parse composite pk defined as json string in data-pk
this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true); 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, 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)))), send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))),
params; params;
@ -816,6 +832,27 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
$.error('Unknown type: '+ type); $.error('Unknown type: '+ type);
return false; 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;
} }
}; };
@ -856,6 +893,9 @@ Applied as jQuery method.
this.formOptions.scope = this.$element[0]; this.formOptions.scope = this.$element[0];
this.initContainer(); 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 //bind 'destroyed' listener to destroy container when element is removed from dom
this.$element.on('destroyed', $.proxy(function(){ this.$element.on('destroyed', $.proxy(function(){
@ -960,7 +1000,14 @@ Applied as jQuery method.
save: $.proxy(this.save, this), //click on submit button (value changed) 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) 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 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 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 resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed
rendered: $.proxy(function(){ rendered: $.proxy(function(){
@ -1004,11 +1051,11 @@ Applied as jQuery method.
/* /*
Currently, form is re-rendered on every show. Currently, form is re-rendered on every show.
The main reason is that we dont know, what container will do with content when closed: The main reason is that we dont know, what will container do with content when closed:
remove(), detach() or just hide(). remove(), detach() or just hide() - it depends on container.
Detaching form itself before hide and re-insert before show is good solution, 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 form already exist - delete previous data
@ -1041,10 +1088,18 @@ Applied as jQuery method.
return; 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.$element.removeClass('editable-open');
this.innerHide(); 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. **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. The workaround is to check `arguments.length` that is always `2` for x-editable.
@ -1058,20 +1113,20 @@ Applied as jQuery method.
//auto-open next editable //auto-open next editable
$(this).closest('tr').next().find('.editable').editable('show'); $(this).closest('tr').next().find('.editable').editable('show');
} }
}); });
**/ **/
this.$element.triggerHandler('hidden', reason || 'manual'); this.$element.triggerHandler('hidden', reason || 'manual');
}, },
/* internal show method. To be overwritten in child classes */ /* internal show method. To be overwritten in child classes */
innerShow: function () { innerShow: function () {
}, },
/* internal hide method. To be overwritten in child classes */ /* internal hide method. To be overwritten in child classes */
innerHide: function () { innerHide: function () {
}, },
/** /**
Toggles container visibility (show / hide) Toggles container visibility (show / hide)
@ -1116,7 +1171,7 @@ Applied as jQuery method.
**/ **/
this.$element.triggerHandler('save', params); 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'); this.hide('save');
}, },
@ -1276,7 +1331,7 @@ Applied as jQuery method.
onblur: 'cancel', onblur: 'cancel',
/** /**
Animation speed (inline mode) Animation speed (inline mode only)
@property anim @property anim
@type string @type string
@default false @default false
@ -1380,6 +1435,11 @@ Makes editable any HTML element on the page. Applied as jQuery method.
} else { } else {
this.init(); this.init();
} }
//check for transition support
if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) {
this.options.highlight = false;
}
}; };
Editable.prototype = { Editable.prototype = {
@ -1424,25 +1484,33 @@ Makes editable any HTML element on the page. Applied as jQuery method.
if(this.options.toggle !== 'manual') { if(this.options.toggle !== 'manual') {
this.$element.addClass('editable-click'); this.$element.addClass('editable-click');
this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){ this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
//prevent following link //prevent following link if editable enabled
e.preventDefault(); if(!this.options.disabled) {
e.preventDefault();
}
//stop propagation not required because in document click handler it checks event target //stop propagation not required because in document click handler it checks event target
//e.stopPropagation(); //e.stopPropagation();
if(this.options.toggle === 'mouseenter') { if(this.options.toggle === 'mouseenter') {
//for hover only show container //for hover only show container
this.show(); this.show();
} else { } else {
//when toggle='click' we should not close all other containers as they will be closed automatically in document click listener //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'); var closeAll = (this.options.toggle !== 'click');
this.toggle(closeAll); this.toggle(closeAll);
} }
}, this)); }, this));
} else { } else {
this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually 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: //check conditions for autotext:
switch(this.options.autotext) { switch(this.options.autotext) {
case 'always': case 'always':
@ -1621,12 +1689,29 @@ Makes editable any HTML element on the page. Applied as jQuery method.
return; 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 //emptytext shown only for enabled
if(!this.options.disabled) { if(!this.options.disabled) {
if (this.isEmpty) { if (this.isEmpty) {
this.$element.text(this.options.emptytext); this.$element.html(this.options.emptytext);
if(this.options.emptyclass) { if(this.options.emptyclass) {
this.$element.addClass(this.options.emptyclass); this.$element.addClass(this.options.emptyclass);
} }
@ -1721,6 +1806,21 @@ Makes editable any HTML element on the page. Applied as jQuery method.
} }
} }
//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');
}, 1500);
}, 0);
}
//set new value //set new value
this.setValue(params.newValue, false, params.response); this.setValue(params.newValue, false, params.response);
@ -1787,6 +1887,8 @@ Makes editable any HTML element on the page. Applied as jQuery method.
if(this.container) { if(this.container) {
this.container.destroy(); this.container.destroy();
} }
this.input.destroy();
if(this.options.toggle !== 'manual') { if(this.options.toggle !== 'manual') {
this.$element.removeClass('editable-click'); this.$element.removeClass('editable-click');
@ -1844,28 +1946,37 @@ Makes editable any HTML element on the page. Applied as jQuery method.
/** /**
Returns current values of editable elements. 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. 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() @method getValue()
@param {bool} isSingle whether to return just value of single element
@returns {Object} object of element names and values @returns {Object} object of element names and values
@example @example
$('#username, #fullname').editable('getValue'); $('#username, #fullname').editable('getValue');
// possible result: //result:
{ {
username: "superuser", username: "superuser",
fullname: "John" fullname: "John"
} }
//isSingle = true
$('#username').editable('getValue', true);
//result "superuser"
**/ **/
case 'getValue': case 'getValue':
this.each(function () { if(arguments.length === 2 && arguments[1] === true) { //isSingle = true
var $this = $(this), data = $this.data(datakey); result = this.eq(0).data(datakey).value;
if (data && data.value !== undefined && data.value !== null) { } else {
result[data.options.name] = data.input.value2submit(data.value); 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; return result;
/** /**
This method collects values from several editable elements and submit them all to server. 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. 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. See <a href="#newrecord">creating new records</a> for details.
@ -2089,7 +2200,16 @@ Makes editable any HTML element on the page. Applied as jQuery method.
}); });
</script> </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)); }(window.jQuery));
@ -2103,23 +2223,23 @@ To create your own input you can inherit from this class.
**/ **/
(function ($) { (function ($) {
"use strict"; "use strict";
//types //types
$.fn.editabletypes = {}; $.fn.editabletypes = {};
var AbstractInput = function () { }; var AbstractInput = function () { };
AbstractInput.prototype = { AbstractInput.prototype = {
/** /**
Initializes input Initializes input
@method init() @method init()
**/ **/
init: function(type, options, defaults) { init: function(type, options, defaults) {
this.type = type; this.type = type;
this.options = $.extend({}, defaults, options); this.options = $.extend({}, defaults, options);
}, },
/* /*
this method called before render to init $tpl that is inserted in DOM this method called before render to init $tpl that is inserted in DOM
*/ */
@ -2133,107 +2253,107 @@ To create your own input you can inherit from this class.
/** /**
Renders input from tpl. Can return jQuery deferred object. Renders input from tpl. Can return jQuery deferred object.
Can be overwritten in child objects Can be overwritten in child objects
@method render() @method render()
**/ **/
render: function() { render: function() {
}, },
/** /**
Sets element's html by value. Sets element's html by value.
@method value2html(value, element) @method value2html(value, element)
@param {mixed} value @param {mixed} value
@param {DOMElement} element @param {DOMElement} element
**/ **/
value2html: function(value, element) { value2html: function(value, element) {
$(element).text(value); $(element).text($.trim(value));
}, },
/** /**
Converts element's html to value Converts element's html to value
@method html2value(html) @method html2value(html)
@param {string} html @param {string} html
@returns {mixed} @returns {mixed}
**/ **/
html2value: function(html) { html2value: function(html) {
return $('<div>').html(html).text(); return $('<div>').html(html).text();
}, },
/** /**
Converts value to string (for internal compare). For submitting to server used value2submit(). Converts value to string (for internal compare). For submitting to server used value2submit().
@method value2str(value) @method value2str(value)
@param {mixed} value @param {mixed} value
@returns {string} @returns {string}
**/ **/
value2str: function(value) { value2str: function(value) {
return value; return value;
}, },
/** /**
Converts string received from server into value. Usually from `data-value` attribute. Converts string received from server into value. Usually from `data-value` attribute.
@method str2value(str) @method str2value(str)
@param {string} str @param {string} str
@returns {mixed} @returns {mixed}
**/ **/
str2value: function(str) { str2value: function(str) {
return str; return str;
}, },
/** /**
Converts value for submitting to server. Result can be string or object. Converts value for submitting to server. Result can be string or object.
@method value2submit(value) @method value2submit(value)
@param {mixed} value @param {mixed} value
@returns {mixed} @returns {mixed}
**/ **/
value2submit: function(value) { value2submit: function(value) {
return value; return value;
}, },
/** /**
Sets value of input. Sets value of input.
@method value2input(value) @method value2input(value)
@param {mixed} value @param {mixed} value
**/ **/
value2input: function(value) { value2input: function(value) {
this.$input.val(value); this.$input.val(value);
}, },
/** /**
Returns value of input. Value can be object (e.g. datepicker) Returns value of input. Value can be object (e.g. datepicker)
@method input2value() @method input2value()
**/ **/
input2value: function() { input2value: function() {
return this.$input.val(); return this.$input.val();
}, },
/** /**
Activates input. For text it sets focus. Activates input. For text it sets focus.
@method activate() @method activate()
**/ **/
activate: function() { activate: function() {
if(this.$input.is(':visible')) { if(this.$input.is(':visible')) {
this.$input.focus(); this.$input.focus();
} }
}, },
/** /**
Creates input. Creates input.
@method clear() @method clear()
**/ **/
clear: function() { clear: function() {
this.$input.val(null); this.$input.val(null);
}, },
/** /**
method to escape html. method to escape html.
**/ **/
@ -2243,18 +2363,24 @@ To create your own input you can inherit from this class.
/** /**
attach handler to automatically submit form when value changed (useful when buttons not shown) attach handler to automatically submit form when value changed (useful when buttons not shown)
**/ **/
autosubmit: function() { autosubmit: function() {
}, },
/**
Additional actions when destroying element
**/
destroy: function() {
},
// -------- helper functions -------- // -------- helper functions --------
setClass: function() { setClass: function() {
if(this.options.inputclass) { if(this.options.inputclass) {
this.$input.addClass(this.options.inputclass); this.$input.addClass(this.options.inputclass);
} }
}, },
setAttr: function(attr) { setAttr: function(attr) {
if (this.options[attr] !== undefined && this.options[attr] !== null) { if (this.options[attr] !== undefined && this.options[attr] !== null) {
this.$input.attr(attr, this.options[attr]); this.$input.attr(attr, this.options[attr]);
@ -2356,30 +2482,33 @@ List - abstract class for inputs that have source option loaded from js array or
// ------------- additional functions ------------ // ------------- additional functions ------------
onSourceReady: function (success, error) { 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 allready loaded just call success
if($.isArray(this.sourceData)) { if(this.options.sourceCache && $.isArray(this.sourceData)) {
success.call(this); success.call(this);
return; 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 { try {
this.options.source = $.fn.editableutils.tryParseJson(this.options.source, false); source = $.fn.editableutils.tryParseJson(source, false);
} catch (e) { } catch (e) {
error.call(this); error.call(this);
return; return;
} }
var source = this.options.source;
//run source if it function
if ($.isFunction(source)) {
source = source.call(this.options.scope);
}
//loading from url //loading from url
if (typeof source === 'string') { if (typeof source === 'string') {
//try to get from cache //try to get sourceData from cache
if(this.options.sourceCache) { if(this.options.sourceCache) {
var cacheID = source, var cacheID = source,
cache; cache;
@ -3299,25 +3428,29 @@ Range (inherit from number)
}(window.jQuery)); }(window.jQuery));
/** /**
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2. 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. Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
You should manually include select2 distributive: 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> <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="select2/select2.js"></script> <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> <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. **Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.
The solution is to load source manually and assign statically. You need initially put both `data-value` and element's text youself:
<a href="#" data-type="select2" data-value="1">Text1</a>
@class select2 @class select2
@extends abstractinput @extends abstractinput
@since 1.4.1 @since 1.4.1
@final @final
@example @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> <script>
$(function(){ $(function(){
$('#country').editable({ $('#country').editable({
@ -3338,56 +3471,47 @@ $(function(){
var Constructor = function (options) { var Constructor = function (options) {
this.init('select2', options, Constructor.defaults); this.init('select2', options, Constructor.defaults);
options.select2 = options.select2 || {}; options.select2 = options.select2 || {};
this.sourceData = null;
var that = this, //placeholder
mixin = { //mixin to select2 options if(options.placeholder) {
placeholder: 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;
}
//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);
}
*/
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];
}
callback(data);
};
} }
//if not `tags` mode, use source
if(!options.select2.tags && options.source) {
var source = options.source;
//if source is function, call it (once!)
if ($.isFunction(options.source)) {
source = options.source.call(options.scope);
}
if (typeof source === 'string') {
options.select2.ajax = options.select2.ajax || {};
//some default ajax params
if(!options.select2.ajax.data) {
options.select2.ajax.data = function(term) {return { query:term };};
}
if(!options.select2.ajax.results) {
options.select2.ajax.results = function(data) { return {results:data };};
}
options.select2.ajax.url = source;
} else {
//check format and convert x-editable format to select2 format (if needed)
this.sourceData = this.convertSource(source);
options.select2.data = this.sourceData;
}
}
//overriding objects in config (as by default jQuery extend() is not recursive) //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); $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
@ -3395,21 +3519,17 @@ $(function(){
$.extend(Constructor.prototype, { $.extend(Constructor.prototype, {
render: function() { render: function() {
this.setClass(); this.setClass();
//apply select2 //apply select2
this.$input.select2(this.options.select2); this.$input.select2(this.options.select2);
//when data is loaded via ajax, we need to know when it's done //when data is loaded via ajax, we need to know when it's done to populate listData
if('ajax' in this.options.select2) { if(this.isRemote) {
/* //listen to loaded event to populate data
console.log('attached'); this.$input.on('select2-loaded', $.proxy(function(e) {
var original = this.$input.data('select2').postprocessResults; this.sourceData = e.items.results;
this.$input.data('select2').postprocessResults = function(data, initial) { }, this));
this.element.triggerHandler('loaded', [data]);
original.apply(this, arguments);
}
*/
} }
//trigger resize of editableform to re-position container in multi-valued mode //trigger resize of editableform to re-position container in multi-valued mode
if(this.isMultiple) { if(this.isMultiple) {
@ -3421,20 +3541,16 @@ $(function(){
value2html: function(value, element) { value2html: function(value, element) {
var text = '', data; var text = '', data;
if(this.$input) { //called when submitting form and select2 already exists
data = this.$input.select2('data'); if(this.options.select2.tags) { //in tags mode just assign value
} else { //on init (autotext) data = value;
//here select2 instance not created yet and data may be even not loaded. } else if(this.sourceData) {
//we can check data/tags property of select config and if exist lookup text data = $.fn.editableutils.itemsByValue(value, this.sourceData, 'id');
if(this.options.select2.tags) { } else {
data = value; //can not get list of possible values (e.g. autotext for select2 with ajax source)
} else if(this.options.select2.data) {
data = $.fn.editableutils.itemsByValue(value, this.options.select2.data, 'id');
} else {
//if('ajax' in this.options.select2) {
}
} }
//data may be array (when multiple values allowed)
if($.isArray(data)) { if($.isArray(data)) {
//collect selected data and show with separator //collect selected data and show with separator
text = []; text = [];
@ -3455,7 +3571,26 @@ $(function(){
}, },
value2input: function(value) { 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() { input2value: function() {
@ -3488,6 +3623,22 @@ $(function(){
$(this).closest('form').submit(); $(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;
} }
}); });
@ -3539,12 +3690,22 @@ $(function(){
}(window.jQuery)); }(window.jQuery));
/** /**
* Combodate - 1.0.3 * Combodate - 1.0.4
* Dropdown date and time picker. * Dropdown date and time picker.
* Converts text input into dropdowns to pick day, month, year, hour, minute and second. * Converts text input into dropdowns to pick day, month, year, hour, minute and second.
* Uses momentjs as datetime library http://momentjs.com. * Uses momentjs as datetime library http://momentjs.com.
* For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang * 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 * Author: Vitaliy Potapov
* Project page: http://github.com/vitalets/combodate * Project page: http://github.com/vitalets/combodate
* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License. * Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
@ -3694,9 +3855,10 @@ $(function(){
for(i=0; i<=11; i++) { for(i=0; i<=11; i++) {
if(longNames) { 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) { } else if(shortNames) {
name = moment().month(i).format('MMM'); name = moment().date(1).month(i).format('MMM');
} else if(twoDigit) { } else if(twoDigit) {
name = this.leadZero(i+1); name = this.leadZero(i+1);
} else { } else {
@ -3732,9 +3894,10 @@ $(function(){
h12 = this.options.template.indexOf('h') !== -1, h12 = this.options.template.indexOf('h') !== -1,
h24 = this.options.template.indexOf('H') !== -1, h24 = this.options.template.indexOf('H') !== -1,
twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1, twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
min = h12 ? 1 : 0,
max = h12 ? 12 : 23; max = h12 ? 12 : 23;
for(i=0; i<=max; i++) { for(i=min; i<=max; i++) {
name = twoDigit ? this.leadZero(i) : i; name = twoDigit ? this.leadZero(i) : i;
items.push([i, name]); items.push([i, name]);
} }
@ -3783,7 +3946,7 @@ $(function(){
}, },
/* /*
Returns current date value. Returns current date value from combos.
If format not specified - `options.format` used. If format not specified - `options.format` used.
If format = `null` - Moment object returned. If format = `null` - Moment object returned.
*/ */
@ -3812,12 +3975,14 @@ $(function(){
return ''; return '';
} }
//convert hours if 12h format //convert hours 12h --> 24h
if(this.$ampm) { if(this.$ampm) {
values.hour = this.$ampm.val() === 'am' ? values.hour : 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 === 24) { if(values.hour === 12) {
values.hour = 0; 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]); dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
@ -3868,11 +4033,17 @@ $(function(){
}); });
if(this.$ampm) { if(this.$ampm) {
if(values.hour > 12) { //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
values.hour -= 12; if(values.hour >= 12) {
values.ampm = 'pm'; values.ampm = 'pm';
if(values.hour > 12) {
values.hour -= 12;
}
} else { } else {
values.ampm = 'am'; values.ampm = 'am';
if(values.hour === 0) {
values.hour = 12;
}
} }
} }

File diff suppressed because one or more lines are too long

@ -1,4 +1,4 @@
/*! X-editable - v1.4.4 /*! X-editable - v1.4.5
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
* http://github.com/vitalets/x-editable * http://github.com/vitalets/x-editable
* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */ * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
@ -184,6 +184,14 @@ a.editable-click.editable-disabled:hover {
/* content: '*'*/ /* content: '*'*/
} }
.editable-bg-transition {
-webkit-transition: background-color 1400ms ease-in;
-moz-transition: background-color 1400ms ease-in;
-o-transition: background-color 1400ms ease-in;
-ms-transition: background-color 1400ms ease-in;
transition: background-color 1400ms ease-in;
}
/*see https://github.com/vitalets/x-editable/issues/139 */ /*see https://github.com/vitalets/x-editable/issues/139 */
.form-horizontal .editable .form-horizontal .editable
{ {

@ -1,4 +1,4 @@
/*! X-editable - v1.4.4 /*! X-editable - v1.4.5
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
* http://github.com/vitalets/x-editable * http://github.com/vitalets/x-editable
* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */ * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
@ -65,6 +65,10 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
//show loading state //show loading state
this.showLoading(); 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 Fired when rendering starts
@event rendering @event rendering
@ -217,31 +221,38 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
return; return;
} }
//convert value for submitting to server
var submitValue = this.input.value2submit(newValue);
this.isSaving = true;
//sending data to server //sending data to server
$.when(this.save(newValue)) $.when(this.save(submitValue))
.done($.proxy(function(response) { .done($.proxy(function(response) {
this.isSaving = false;
//run success callback //run success callback
var res = typeof this.options.success === 'function' ? this.options.success.call(this.options.scope, response, newValue) : null; 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 success callback returns false --> keep form open and do not activate input
if(res === false) { if(res === false) {
this.error(false); this.error(false);
this.showForm(false); this.showForm(false);
return; return;
} }
//if success callback returns string --> keep form open, show error and activate input //if success callback returns string --> keep form open, show error and activate input
if(typeof res === 'string') { if(typeof res === 'string') {
this.error(res); this.error(res);
this.showForm(); this.showForm();
return; return;
} }
//if success callback returns object like {newValue: <something>} --> use that value instead of submitted //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 //it is usefull if you want to chnage value in url-function
if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) { if(res && typeof res === 'object' && res.hasOwnProperty('newValue')) {
newValue = res.newValue; newValue = res.newValue;
} }
//clear error message //clear error message
this.error(false); this.error(false);
@ -251,37 +262,42 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
@event save @event save
@param {Object} event event object @param {Object} event event object
@param {Object} params additional params @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 @param {Object} params.response ajax response
@example @example
$('#form-div').on('save'), function(e, params){ $('#form-div').on('save'), function(e, params){
if(params.newValue === 'username') {...} if(params.newValue === 'username') {...}
}); });
**/ **/
this.$div.triggerHandler('save', {newValue: newValue, response: response}); this.$div.triggerHandler('save', {newValue: newValue, submitValue: submitValue, response: response});
}, this)) }, this))
.fail($.proxy(function(xhr) { .fail($.proxy(function(xhr) {
this.isSaving = false;
var msg; var msg;
if(typeof this.options.error === 'function') { if(typeof this.options.error === 'function') {
msg = this.options.error.call(this.options.scope, xhr, newValue); msg = this.options.error.call(this.options.scope, xhr, newValue);
} else { } else {
msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!'; msg = typeof xhr === 'string' ? xhr : xhr.responseText || xhr.statusText || 'Unknown error!';
} }
this.error(msg); this.error(msg);
this.showForm(); this.showForm();
}, this)); }, this));
}, },
save: function(newValue) { save: function(submitValue) {
//convert value for submitting to server
var submitValue = this.input.value2submit(newValue);
//try parse composite pk defined as json string in data-pk //try parse composite pk defined as json string in data-pk
this.options.pk = $.fn.editableutils.tryParseJson(this.options.pk, true); 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, 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)))), send = !!(typeof this.options.url === 'function' || (this.options.url && ((this.options.send === 'always') || (this.options.send === 'auto' && pk !== null && pk !== undefined)))),
params; params;
@ -816,6 +832,27 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc.
$.error('Unknown type: '+ type); $.error('Unknown type: '+ type);
return false; 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;
} }
}; };
@ -856,6 +893,9 @@ Applied as jQuery method.
this.formOptions.scope = this.$element[0]; this.formOptions.scope = this.$element[0];
this.initContainer(); 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 //bind 'destroyed' listener to destroy container when element is removed from dom
this.$element.on('destroyed', $.proxy(function(){ this.$element.on('destroyed', $.proxy(function(){
@ -960,7 +1000,14 @@ Applied as jQuery method.
save: $.proxy(this.save, this), //click on submit button (value changed) 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) 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 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 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 resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed
rendered: $.proxy(function(){ rendered: $.proxy(function(){
@ -1004,11 +1051,11 @@ Applied as jQuery method.
/* /*
Currently, form is re-rendered on every show. Currently, form is re-rendered on every show.
The main reason is that we dont know, what container will do with content when closed: The main reason is that we dont know, what will container do with content when closed:
remove(), detach() or just hide(). remove(), detach() or just hide() - it depends on container.
Detaching form itself before hide and re-insert before show is good solution, 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 form already exist - delete previous data
@ -1041,10 +1088,18 @@ Applied as jQuery method.
return; 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.$element.removeClass('editable-open');
this.innerHide(); 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. **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. The workaround is to check `arguments.length` that is always `2` for x-editable.
@ -1058,20 +1113,20 @@ Applied as jQuery method.
//auto-open next editable //auto-open next editable
$(this).closest('tr').next().find('.editable').editable('show'); $(this).closest('tr').next().find('.editable').editable('show');
} }
}); });
**/ **/
this.$element.triggerHandler('hidden', reason || 'manual'); this.$element.triggerHandler('hidden', reason || 'manual');
}, },
/* internal show method. To be overwritten in child classes */ /* internal show method. To be overwritten in child classes */
innerShow: function () { innerShow: function () {
}, },
/* internal hide method. To be overwritten in child classes */ /* internal hide method. To be overwritten in child classes */
innerHide: function () { innerHide: function () {
}, },
/** /**
Toggles container visibility (show / hide) Toggles container visibility (show / hide)
@ -1116,7 +1171,7 @@ Applied as jQuery method.
**/ **/
this.$element.triggerHandler('save', params); 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'); this.hide('save');
}, },
@ -1276,7 +1331,7 @@ Applied as jQuery method.
onblur: 'cancel', onblur: 'cancel',
/** /**
Animation speed (inline mode) Animation speed (inline mode only)
@property anim @property anim
@type string @type string
@default false @default false
@ -1380,6 +1435,11 @@ Makes editable any HTML element on the page. Applied as jQuery method.
} else { } else {
this.init(); this.init();
} }
//check for transition support
if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) {
this.options.highlight = false;
}
}; };
Editable.prototype = { Editable.prototype = {
@ -1424,25 +1484,33 @@ Makes editable any HTML element on the page. Applied as jQuery method.
if(this.options.toggle !== 'manual') { if(this.options.toggle !== 'manual') {
this.$element.addClass('editable-click'); this.$element.addClass('editable-click');
this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){ this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
//prevent following link //prevent following link if editable enabled
e.preventDefault(); if(!this.options.disabled) {
e.preventDefault();
}
//stop propagation not required because in document click handler it checks event target //stop propagation not required because in document click handler it checks event target
//e.stopPropagation(); //e.stopPropagation();
if(this.options.toggle === 'mouseenter') { if(this.options.toggle === 'mouseenter') {
//for hover only show container //for hover only show container
this.show(); this.show();
} else { } else {
//when toggle='click' we should not close all other containers as they will be closed automatically in document click listener //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'); var closeAll = (this.options.toggle !== 'click');
this.toggle(closeAll); this.toggle(closeAll);
} }
}, this)); }, this));
} else { } else {
this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually 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: //check conditions for autotext:
switch(this.options.autotext) { switch(this.options.autotext) {
case 'always': case 'always':
@ -1621,12 +1689,29 @@ Makes editable any HTML element on the page. Applied as jQuery method.
return; 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 //emptytext shown only for enabled
if(!this.options.disabled) { if(!this.options.disabled) {
if (this.isEmpty) { if (this.isEmpty) {
this.$element.text(this.options.emptytext); this.$element.html(this.options.emptytext);
if(this.options.emptyclass) { if(this.options.emptyclass) {
this.$element.addClass(this.options.emptyclass); this.$element.addClass(this.options.emptyclass);
} }
@ -1721,6 +1806,21 @@ Makes editable any HTML element on the page. Applied as jQuery method.
} }
} }
//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');
}, 1500);
}, 0);
}
//set new value //set new value
this.setValue(params.newValue, false, params.response); this.setValue(params.newValue, false, params.response);
@ -1787,6 +1887,8 @@ Makes editable any HTML element on the page. Applied as jQuery method.
if(this.container) { if(this.container) {
this.container.destroy(); this.container.destroy();
} }
this.input.destroy();
if(this.options.toggle !== 'manual') { if(this.options.toggle !== 'manual') {
this.$element.removeClass('editable-click'); this.$element.removeClass('editable-click');
@ -1844,28 +1946,37 @@ Makes editable any HTML element on the page. Applied as jQuery method.
/** /**
Returns current values of editable elements. 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. 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() @method getValue()
@param {bool} isSingle whether to return just value of single element
@returns {Object} object of element names and values @returns {Object} object of element names and values
@example @example
$('#username, #fullname').editable('getValue'); $('#username, #fullname').editable('getValue');
// possible result: //result:
{ {
username: "superuser", username: "superuser",
fullname: "John" fullname: "John"
} }
//isSingle = true
$('#username').editable('getValue', true);
//result "superuser"
**/ **/
case 'getValue': case 'getValue':
this.each(function () { if(arguments.length === 2 && arguments[1] === true) { //isSingle = true
var $this = $(this), data = $this.data(datakey); result = this.eq(0).data(datakey).value;
if (data && data.value !== undefined && data.value !== null) { } else {
result[data.options.name] = data.input.value2submit(data.value); 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; return result;
/** /**
This method collects values from several editable elements and submit them all to server. 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. 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. See <a href="#newrecord">creating new records</a> for details.
@ -2089,7 +2200,16 @@ Makes editable any HTML element on the page. Applied as jQuery method.
}); });
</script> </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)); }(window.jQuery));
@ -2103,23 +2223,23 @@ To create your own input you can inherit from this class.
**/ **/
(function ($) { (function ($) {
"use strict"; "use strict";
//types //types
$.fn.editabletypes = {}; $.fn.editabletypes = {};
var AbstractInput = function () { }; var AbstractInput = function () { };
AbstractInput.prototype = { AbstractInput.prototype = {
/** /**
Initializes input Initializes input
@method init() @method init()
**/ **/
init: function(type, options, defaults) { init: function(type, options, defaults) {
this.type = type; this.type = type;
this.options = $.extend({}, defaults, options); this.options = $.extend({}, defaults, options);
}, },
/* /*
this method called before render to init $tpl that is inserted in DOM this method called before render to init $tpl that is inserted in DOM
*/ */
@ -2133,107 +2253,107 @@ To create your own input you can inherit from this class.
/** /**
Renders input from tpl. Can return jQuery deferred object. Renders input from tpl. Can return jQuery deferred object.
Can be overwritten in child objects Can be overwritten in child objects
@method render() @method render()
**/ **/
render: function() { render: function() {
}, },
/** /**
Sets element's html by value. Sets element's html by value.
@method value2html(value, element) @method value2html(value, element)
@param {mixed} value @param {mixed} value
@param {DOMElement} element @param {DOMElement} element
**/ **/
value2html: function(value, element) { value2html: function(value, element) {
$(element).text(value); $(element).text($.trim(value));
}, },
/** /**
Converts element's html to value Converts element's html to value
@method html2value(html) @method html2value(html)
@param {string} html @param {string} html
@returns {mixed} @returns {mixed}
**/ **/
html2value: function(html) { html2value: function(html) {
return $('<div>').html(html).text(); return $('<div>').html(html).text();
}, },
/** /**
Converts value to string (for internal compare). For submitting to server used value2submit(). Converts value to string (for internal compare). For submitting to server used value2submit().
@method value2str(value) @method value2str(value)
@param {mixed} value @param {mixed} value
@returns {string} @returns {string}
**/ **/
value2str: function(value) { value2str: function(value) {
return value; return value;
}, },
/** /**
Converts string received from server into value. Usually from `data-value` attribute. Converts string received from server into value. Usually from `data-value` attribute.
@method str2value(str) @method str2value(str)
@param {string} str @param {string} str
@returns {mixed} @returns {mixed}
**/ **/
str2value: function(str) { str2value: function(str) {
return str; return str;
}, },
/** /**
Converts value for submitting to server. Result can be string or object. Converts value for submitting to server. Result can be string or object.
@method value2submit(value) @method value2submit(value)
@param {mixed} value @param {mixed} value
@returns {mixed} @returns {mixed}
**/ **/
value2submit: function(value) { value2submit: function(value) {
return value; return value;
}, },
/** /**
Sets value of input. Sets value of input.
@method value2input(value) @method value2input(value)
@param {mixed} value @param {mixed} value
**/ **/
value2input: function(value) { value2input: function(value) {
this.$input.val(value); this.$input.val(value);
}, },
/** /**
Returns value of input. Value can be object (e.g. datepicker) Returns value of input. Value can be object (e.g. datepicker)
@method input2value() @method input2value()
**/ **/
input2value: function() { input2value: function() {
return this.$input.val(); return this.$input.val();
}, },
/** /**
Activates input. For text it sets focus. Activates input. For text it sets focus.
@method activate() @method activate()
**/ **/
activate: function() { activate: function() {
if(this.$input.is(':visible')) { if(this.$input.is(':visible')) {
this.$input.focus(); this.$input.focus();
} }
}, },
/** /**
Creates input. Creates input.
@method clear() @method clear()
**/ **/
clear: function() { clear: function() {
this.$input.val(null); this.$input.val(null);
}, },
/** /**
method to escape html. method to escape html.
**/ **/
@ -2243,18 +2363,24 @@ To create your own input you can inherit from this class.
/** /**
attach handler to automatically submit form when value changed (useful when buttons not shown) attach handler to automatically submit form when value changed (useful when buttons not shown)
**/ **/
autosubmit: function() { autosubmit: function() {
}, },
/**
Additional actions when destroying element
**/
destroy: function() {
},
// -------- helper functions -------- // -------- helper functions --------
setClass: function() { setClass: function() {
if(this.options.inputclass) { if(this.options.inputclass) {
this.$input.addClass(this.options.inputclass); this.$input.addClass(this.options.inputclass);
} }
}, },
setAttr: function(attr) { setAttr: function(attr) {
if (this.options[attr] !== undefined && this.options[attr] !== null) { if (this.options[attr] !== undefined && this.options[attr] !== null) {
this.$input.attr(attr, this.options[attr]); this.$input.attr(attr, this.options[attr]);
@ -2356,30 +2482,33 @@ List - abstract class for inputs that have source option loaded from js array or
// ------------- additional functions ------------ // ------------- additional functions ------------
onSourceReady: function (success, error) { 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 allready loaded just call success
if($.isArray(this.sourceData)) { if(this.options.sourceCache && $.isArray(this.sourceData)) {
success.call(this); success.call(this);
return; 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 { try {
this.options.source = $.fn.editableutils.tryParseJson(this.options.source, false); source = $.fn.editableutils.tryParseJson(source, false);
} catch (e) { } catch (e) {
error.call(this); error.call(this);
return; return;
} }
var source = this.options.source;
//run source if it function
if ($.isFunction(source)) {
source = source.call(this.options.scope);
}
//loading from url //loading from url
if (typeof source === 'string') { if (typeof source === 'string') {
//try to get from cache //try to get sourceData from cache
if(this.options.sourceCache) { if(this.options.sourceCache) {
var cacheID = source, var cacheID = source,
cache; cache;
@ -3299,25 +3428,29 @@ Range (inherit from number)
}(window.jQuery)); }(window.jQuery));
/** /**
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2. 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. Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
You should manually include select2 distributive: 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> <link href="select2/select2.css" rel="stylesheet" type="text/css"></link>
<script src="select2/select2.js"></script> <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> <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. **Note:** currently `autotext` feature does not work for select2 with `ajax` remote source.
The solution is to load source manually and assign statically. You need initially put both `data-value` and element's text youself:
<a href="#" data-type="select2" data-value="1">Text1</a>
@class select2 @class select2
@extends abstractinput @extends abstractinput
@since 1.4.1 @since 1.4.1
@final @final
@example @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> <script>
$(function(){ $(function(){
$('#country').editable({ $('#country').editable({
@ -3338,56 +3471,47 @@ $(function(){
var Constructor = function (options) { var Constructor = function (options) {
this.init('select2', options, Constructor.defaults); this.init('select2', options, Constructor.defaults);
options.select2 = options.select2 || {}; options.select2 = options.select2 || {};
this.sourceData = null;
var that = this, //placeholder
mixin = { //mixin to select2 options if(options.placeholder) {
placeholder: 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;
}
//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);
}
*/
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];
}
callback(data);
};
} }
//if not `tags` mode, use source
if(!options.select2.tags && options.source) {
var source = options.source;
//if source is function, call it (once!)
if ($.isFunction(options.source)) {
source = options.source.call(options.scope);
}
if (typeof source === 'string') {
options.select2.ajax = options.select2.ajax || {};
//some default ajax params
if(!options.select2.ajax.data) {
options.select2.ajax.data = function(term) {return { query:term };};
}
if(!options.select2.ajax.results) {
options.select2.ajax.results = function(data) { return {results:data };};
}
options.select2.ajax.url = source;
} else {
//check format and convert x-editable format to select2 format (if needed)
this.sourceData = this.convertSource(source);
options.select2.data = this.sourceData;
}
}
//overriding objects in config (as by default jQuery extend() is not recursive) //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); $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
@ -3395,21 +3519,17 @@ $(function(){
$.extend(Constructor.prototype, { $.extend(Constructor.prototype, {
render: function() { render: function() {
this.setClass(); this.setClass();
//apply select2 //apply select2
this.$input.select2(this.options.select2); this.$input.select2(this.options.select2);
//when data is loaded via ajax, we need to know when it's done //when data is loaded via ajax, we need to know when it's done to populate listData
if('ajax' in this.options.select2) { if(this.isRemote) {
/* //listen to loaded event to populate data
console.log('attached'); this.$input.on('select2-loaded', $.proxy(function(e) {
var original = this.$input.data('select2').postprocessResults; this.sourceData = e.items.results;
this.$input.data('select2').postprocessResults = function(data, initial) { }, this));
this.element.triggerHandler('loaded', [data]);
original.apply(this, arguments);
}
*/
} }
//trigger resize of editableform to re-position container in multi-valued mode //trigger resize of editableform to re-position container in multi-valued mode
if(this.isMultiple) { if(this.isMultiple) {
@ -3421,20 +3541,16 @@ $(function(){
value2html: function(value, element) { value2html: function(value, element) {
var text = '', data; var text = '', data;
if(this.$input) { //called when submitting form and select2 already exists
data = this.$input.select2('data'); if(this.options.select2.tags) { //in tags mode just assign value
} else { //on init (autotext) data = value;
//here select2 instance not created yet and data may be even not loaded. } else if(this.sourceData) {
//we can check data/tags property of select config and if exist lookup text data = $.fn.editableutils.itemsByValue(value, this.sourceData, 'id');
if(this.options.select2.tags) { } else {
data = value; //can not get list of possible values (e.g. autotext for select2 with ajax source)
} else if(this.options.select2.data) {
data = $.fn.editableutils.itemsByValue(value, this.options.select2.data, 'id');
} else {
//if('ajax' in this.options.select2) {
}
} }
//data may be array (when multiple values allowed)
if($.isArray(data)) { if($.isArray(data)) {
//collect selected data and show with separator //collect selected data and show with separator
text = []; text = [];
@ -3455,7 +3571,26 @@ $(function(){
}, },
value2input: function(value) { 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() { input2value: function() {
@ -3488,6 +3623,22 @@ $(function(){
$(this).closest('form').submit(); $(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;
} }
}); });
@ -3539,12 +3690,22 @@ $(function(){
}(window.jQuery)); }(window.jQuery));
/** /**
* Combodate - 1.0.3 * Combodate - 1.0.4
* Dropdown date and time picker. * Dropdown date and time picker.
* Converts text input into dropdowns to pick day, month, year, hour, minute and second. * Converts text input into dropdowns to pick day, month, year, hour, minute and second.
* Uses momentjs as datetime library http://momentjs.com. * Uses momentjs as datetime library http://momentjs.com.
* For i18n include corresponding file from https://github.com/timrwood/moment/tree/master/lang * 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 * Author: Vitaliy Potapov
* Project page: http://github.com/vitalets/combodate * Project page: http://github.com/vitalets/combodate
* Copyright (c) 2012 Vitaliy Potapov. Released under MIT License. * Copyright (c) 2012 Vitaliy Potapov. Released under MIT License.
@ -3694,9 +3855,10 @@ $(function(){
for(i=0; i<=11; i++) { for(i=0; i<=11; i++) {
if(longNames) { 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) { } else if(shortNames) {
name = moment().month(i).format('MMM'); name = moment().date(1).month(i).format('MMM');
} else if(twoDigit) { } else if(twoDigit) {
name = this.leadZero(i+1); name = this.leadZero(i+1);
} else { } else {
@ -3732,9 +3894,10 @@ $(function(){
h12 = this.options.template.indexOf('h') !== -1, h12 = this.options.template.indexOf('h') !== -1,
h24 = this.options.template.indexOf('H') !== -1, h24 = this.options.template.indexOf('H') !== -1,
twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1, twoDigit = this.options.template.toLowerCase().indexOf('hh') !== -1,
min = h12 ? 1 : 0,
max = h12 ? 12 : 23; max = h12 ? 12 : 23;
for(i=0; i<=max; i++) { for(i=min; i<=max; i++) {
name = twoDigit ? this.leadZero(i) : i; name = twoDigit ? this.leadZero(i) : i;
items.push([i, name]); items.push([i, name]);
} }
@ -3783,7 +3946,7 @@ $(function(){
}, },
/* /*
Returns current date value. Returns current date value from combos.
If format not specified - `options.format` used. If format not specified - `options.format` used.
If format = `null` - Moment object returned. If format = `null` - Moment object returned.
*/ */
@ -3812,12 +3975,14 @@ $(function(){
return ''; return '';
} }
//convert hours if 12h format //convert hours 12h --> 24h
if(this.$ampm) { if(this.$ampm) {
values.hour = this.$ampm.val() === 'am' ? values.hour : 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 === 24) { if(values.hour === 12) {
values.hour = 0; 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]); dt = moment([values.year, values.month, values.day, values.hour, values.minute, values.second]);
@ -3868,11 +4033,17 @@ $(function(){
}); });
if(this.$ampm) { if(this.$ampm) {
if(values.hour > 12) { //12:00 pm --> 12:00 (24-h format, midday), 12:00 am --> 00:00 (24-h format, midnight, start of day)
values.hour -= 12; if(values.hour >= 12) {
values.ampm = 'pm'; values.ampm = 'pm';
if(values.hour > 12) {
values.hour -= 12;
}
} else { } else {
values.ampm = 'am'; values.ampm = 'am';
if(values.hour === 0) {
values.hour = 12;
}
} }
} }

File diff suppressed because one or more lines are too long