vendor/assets/javascripts/editable/bootstrap-editable.js in x-editable-rails-1.0.2 vs vendor/assets/javascripts/editable/bootstrap-editable.js in x-editable-rails-1.0.3
- old
+ new
@@ -1,6 +1,6 @@
-/*! X-editable - v1.4.5
+/*! X-editable - v1.4.6
* In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery
* http://github.com/vitalets/x-editable
* Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */
/**
@@ -105,11 +105,12 @@
this.$form.submit(function(e){ e.preventDefault(); });
} else {
this.error(false);
this.input.$input.removeAttr('disabled');
this.$form.find('.editable-submit').removeAttr('disabled');
- this.input.value2input(this.value);
+ var value = (this.value === null || this.value === undefined || this.value === '') ? this.options.defaultValue : this.value;
+ this.input.value2input(value);
//attach submit handler
this.$form.submit($.proxy(this.submit, this));
}
/**
@@ -480,13 +481,22 @@
@type string|object
@default null
**/
value: null,
/**
- Strategy for sending data on server. Can be <code>auto|always|never</code>.
- When 'auto' data will be sent on server only if pk defined, otherwise new value will be stored in element.
+ Value that will be displayed in input if original field value is empty (`null|undefined|''`).
+ @property defaultValue
+ @type string|object
+ @default null
+ @since 1.4.6
+ **/
+ defaultValue: null,
+ /**
+ Strategy for sending data on server. Can be `auto|always|never`.
+ When 'auto' data will be sent on server **only if pk and url defined**, otherwise new value will be stored locally.
+
@property send
@type string
@default 'auto'
**/
send: 'auto',
@@ -751,11 +761,14 @@
itemsByValue: function(value, sourceData, valueProp) {
if(!sourceData || value === null) {
return [];
}
- valueProp = valueProp || 'value';
+ if (typeof(valueProp) !== "function") {
+ var idKey = valueProp || 'value';
+ valueProp = function (e) { return e[idKey]; };
+ }
var isValArray = $.isArray(value),
result = [],
that = this;
@@ -763,15 +776,16 @@
if(o.children) {
result = result.concat(that.itemsByValue(value, o.children, valueProp));
} else {
/*jslint eqeq: true*/
if(isValArray) {
- if($.grep(value, function(v){ return v == (o && typeof o === 'object' ? o[valueProp] : o); }).length) {
+ if($.grep(value, function(v){ return v == (o && typeof o === 'object' ? valueProp(o) : o); }).length) {
result.push(o);
}
} else {
- if(value == (o && typeof o === 'object' ? o[valueProp] : o)) {
+ var itemValue = (o && (typeof o === 'object')) ? valueProp(o) : o;
+ if(value == itemValue) {
result.push(o);
}
}
/*jslint eqeq: false*/
}
@@ -878,11 +892,12 @@
this.init(element, options);
};
//methods
Popup.prototype = {
- containerName: null, //tbd in child class
+ containerName: null, //method to call container on element
+ containerDataName: null, //object name in element's .data()
innerCss: null, //tbd in child class
containerClass: 'editable-container editable-popup', //css class applied to container element
init: function(element, options) {
this.$element = $(element);
//since 1.4.1 container do not use data-* directly as they already merged into options.
@@ -979,11 +994,20 @@
return this.container() ? this.container().$tip : null;
},
/* returns container object */
container: function() {
- return this.$element.data(this.containerDataName || this.containerName);
+ var container;
+ //first, try get it by `containerDataName`
+ if(this.containerDataName) {
+ if(container = this.$element.data(this.containerDataName)) {
+ return container;
+ }
+ }
+ //second, try `containerName`
+ container = this.$element.data(this.containerName);
+ return container;
},
/* call native method of underlying container, e.g. this.$element.popover('method') */
call: function() {
this.$element[this.containerName].apply(this.$element, arguments);
@@ -1024,11 +1048,11 @@
});
**/
/*
TODO: added second param mainly to distinguish from bootstrap's shown event. It's a hotfix that will be solved in future versions via namespaced events.
*/
- this.$element.triggerHandler('shown', this);
+ this.$element.triggerHandler('shown', $(this.options.scope).data('editable'));
}, this)
})
.editableform('render');
},
@@ -1478,10 +1502,15 @@
}
//add 'editable' class to every editable element
this.$element.addClass('editable');
+ //specifically for "textarea" add class .editable-pre-wrapped to keep linebreaks
+ if(this.input.type === 'textarea') {
+ this.$element.addClass('editable-pre-wrapped');
+ }
+
//attach handler activating editable. In disabled mode it just prevent default action (useful for links)
if(this.options.toggle !== 'manual') {
this.$element.addClass('editable-click');
this.$element.on(this.options.toggle + '.editable', $.proxy(function(e){
//prevent following link if editable enabled
@@ -1807,20 +1836,23 @@
}
//highlight when saving
if(this.options.highlight) {
var $e = this.$element,
- $bgColor = $e.css('background-color');
+ bgColor = $e.css('background-color');
$e.css('background-color', this.options.highlight);
setTimeout(function(){
- $e.css('background-color', $bgColor);
+ if(bgColor === 'transparent') {
+ bgColor = '';
+ }
+ $e.css('background-color', bgColor);
$e.addClass('editable-bg-transition');
setTimeout(function(){
$e.removeClass('editable-bg-transition');
}, 1700);
- }, 0);
+ }, 10);
}
//set new value
this.setValue(params.newValue, false, params.response);
@@ -2030,10 +2062,18 @@
return this.each(function () {
var $this = $(this),
data = $this.data(datakey),
options = typeof option === 'object' && option;
+ //for delegated targets do not store `editable` object for element
+ //it's allows several different selectors.
+ //see: https://github.com/vitalets/x-editable/issues/312
+ if(options && options.selector) {
+ data = new Editable(this, options);
+ return;
+ }
+
if (!data) {
$this.data(datakey, (data = new Editable(this, options)));
}
if (typeof option === 'string') { //call method
@@ -2369,11 +2409,11 @@
},
/**
Additional actions when destroying element
**/
- destroy: function() {
+ destroy: function() {
},
// -------- helper functions --------
setClass: function() {
if(this.options.inputclass) {
@@ -2917,23 +2957,25 @@
if (e.ctrlKey && e.which === 13) {
$(this).closest('form').submit();
}
});
},
-
- value2html: function(value, element) {
+
+ //using `white-space: pre-wrap` solves \n <--> BR conversion very elegant!
+ /*
+ value2html: function(value, element) {
var html = '', lines;
if(value) {
lines = value.split("\n");
for (var i = 0; i < lines.length; i++) {
lines[i] = $('<div>').text(lines[i]).html();
}
html = lines.join('<br>');
}
$(element).html(html);
},
-
+
html2value: function(html) {
if(!html) {
return '';
}
@@ -2948,11 +2990,11 @@
lines[i] = text;
}
return lines.join("\n");
},
-
+ */
activate: function() {
$.fn.editabletypes.text.prototype.activate.call(this);
}
});
@@ -3022,16 +3064,23 @@
$.extend(Select.prototype, {
renderList: function() {
this.$input.empty();
var fillItems = function($el, data) {
+ var attr;
if($.isArray(data)) {
for(var i=0; i<data.length; i++) {
+ attr = {};
if(data[i].children) {
- $el.append(fillItems($('<optgroup>', {label: data[i].text}), data[i].children));
+ attr.label = data[i].text;
+ $el.append(fillItems($('<optgroup>', attr), data[i].children));
} else {
- $el.append($('<option>', {value: data[i].value}).text(data[i].text));
+ attr.value = data[i].value;
+ if(data[i].disabled) {
+ attr.disabled = true;
+ }
+ $el.append($('<option>', attr).text(data[i].text));
}
}
}
return $el;
};
@@ -3239,10 +3288,11 @@
* email
* url
* tel
* number
* range
+* time
Learn more about html5 inputs:
http://www.w3.org/wiki/HTML5_form_additions
To check browser compatibility please see:
https://developer.mozilla.org/en-US/docs/HTML/Element/Input
@@ -3423,11 +3473,34 @@
Range.defaults = $.extend({}, $.fn.editabletypes.number.defaults, {
tpl: '<input type="range"><output style="width: 30px; display: inline-block"></output>',
inputclass: 'input-medium'
});
$.fn.editabletypes.range = Range;
-}(window.jQuery));
+}(window.jQuery));
+
+/*
+Time
+*/
+(function ($) {
+ "use strict";
+
+ var Time = function (options) {
+ this.init('time', options, Time.defaults);
+ };
+ //inherit from abstract, as inheritance from text gives selection error.
+ $.fn.editableutils.inherit(Time, $.fn.editabletypes.abstractinput);
+ $.extend(Time.prototype, {
+ render: function() {
+ this.setClass();
+ }
+ });
+ Time.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
+ tpl: '<input type="time">'
+ });
+ $.fn.editabletypes.time = Time;
+}(window.jQuery));
+
/**
Select2 input. Based on amazing work of Igor Vaynberg https://github.com/ivaynberg/select2.
Please see [original select2 docs](http://ivaynberg.github.com/select2) for detailed description and options.
Compatible **select2 version is 3.4.1**!
You should manually download and include select2 distributive:
@@ -3451,20 +3524,57 @@
@final
@example
<a href="#" id="country" data-type="select2" data-pk="1" data-value="ru" data-url="/post" data-title="Select country"></a>
<script>
$(function(){
+ //local source
$('#country').editable({
source: [
{id: 'gb', text: 'Great Britain'},
{id: 'us', text: 'United States'},
{id: 'ru', text: 'Russia'}
],
select2: {
multiple: true
}
});
+ //remote source (simple)
+ $('#country').editable({
+ source: '/getCountries'
+ });
+ //remote source (advanced)
+ $('#country').editable({
+ select2: {
+ placeholder: 'Select Country',
+ allowClear: true,
+ minimumInputLength: 3,
+ id: function (item) {
+ return item.CountryId;
+ },
+ ajax: {
+ url: '/getCountries',
+ dataType: 'json',
+ data: function (term, page) {
+ return { query: term };
+ },
+ results: function (data, page) {
+ return { results: data };
+ }
+ },
+ formatResult: function (item) {
+ return item.CountryName;
+ },
+ formatSelection: function (item) {
+ return item.CountryName;
+ },
+ initSelection: function (element, callback) {
+ return $.get('/getCountryById', { query: element.val() }, function (data) {
+ callback(data);
+ });
+ }
+ }
+ });
});
</script>
**/
(function ($) {
"use strict";
@@ -3509,11 +3619,25 @@
//overriding objects in config (as by default jQuery extend() is not recursive)
this.options.select2 = $.extend({}, Constructor.defaults.select2, options.select2);
//detect whether it is multi-valued
this.isMultiple = this.options.select2.tags || this.options.select2.multiple;
- this.isRemote = ('ajax' in this.options.select2);
+ this.isRemote = ('ajax' in this.options.select2);
+
+ //store function returning ID of item
+ //should be here as used inautotext for local source
+ this.idFunc = this.options.select2.id;
+ if (typeof(this.idFunc) !== "function") {
+ var idKey = this.idFunc || 'id';
+ this.idFunc = function (e) { return e[idKey]; };
+ }
+
+ //store function that renders text in select2
+ this.formatSelection = this.options.select2.formatSelection;
+ if (typeof(this.formatSelection) !== "function") {
+ this.formatSelection = function (e) { return e.text; };
+ }
};
$.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput);
$.extend(Constructor.prototype, {
@@ -3534,33 +3658,35 @@
//trigger resize of editableform to re-position container in multi-valued mode
if(this.isMultiple) {
this.$input.on('change', function() {
$(this).closest('form').parent().triggerHandler('resize');
});
- }
+ }
},
value2html: function(value, element) {
- var text = '', data;
+ var text = '', data,
+ that = this;
if(this.options.select2.tags) { //in tags mode just assign value
data = value;
+ //data = $.fn.editableutils.itemsByValue(value, this.options.select2.tags, this.idFunc);
} else if(this.sourceData) {
- data = $.fn.editableutils.itemsByValue(value, this.sourceData, 'id');
+ data = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc);
} else {
//can not get list of possible values (e.g. autotext for select2 with ajax source)
}
//data may be array (when multiple values allowed)
if($.isArray(data)) {
//collect selected data and show with separator
text = [];
$.each(data, function(k, v){
- text.push(v && typeof v === 'object' ? v.text : v);
+ text.push(v && typeof v === 'object' ? that.formatSelection(v) : v);
});
} else if(data) {
- text = data.text;
+ text = that.formatSelection(data);
}
text = $.isArray(text) ? text.join(this.options.viewseparator) : text;
$(element).text(text);
@@ -3569,29 +3695,32 @@
html2value: function(html) {
return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null;
},
value2input: function(value) {
- //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)
+ //for local source use data directly from source (to allow autotext)
+ /*
+ if(!this.isRemote && !this.isMultiple) {
+ var items = $.fn.editableutils.itemsByValue(value, this.sourceData, this.idFunc);
+ if(items.length) {
+ this.$input.select2('data', items[0]);
+ return;
+ }
+ }
+ */
+
+ //for remote source just set value, text is updated by initSelection
+ this.$input.val(value).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit)
+
+ //if remote source AND no user's initSelection provided --> try to use element's text
+ if(this.isRemote && !this.isMultiple && !this.options.select2.initSelection) {
+ var customId = this.options.select2.id,
+ customText = this.options.select2.formatSelection;
+ if(!customId && !customText) {
+ var data = {id: value, text: $(this.options.scope).text()};
+ this.$input.select2('data', data);
+ }
}
},
input2value: function() {
return this.$input.select2('val');
@@ -3637,10 +3766,16 @@
delete source[i].value;
}
}
}
return source;
+ },
+
+ destroy: function() {
+ if(this.$input.data('select2')) {
+ this.$input.select2('destroy');
+ }
}
});
Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, {
@@ -3692,11 +3827,11 @@
/**
* 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
+* For internalization 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)
@@ -4421,11 +4556,15 @@
, inside
, pos
, actualWidth
, actualHeight
, placement
- , tp;
+ , tp
+ , tpt
+ , tpb
+ , tpl
+ , tpr;
placement = typeof this.options.placement === 'function' ?
this.options.placement.call(this, $tip[0], this.$element[0]) :
this.options.placement;
@@ -4441,36 +4580,97 @@
pos = this.getPosition(inside);
actualWidth = $tip[0].offsetWidth;
actualHeight = $tip[0].offsetHeight;
- switch (inside ? placement.split(' ')[1] : placement) {
+ placement = inside ? placement.split(' ')[1] : placement;
+
+ tpb = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2};
+ tpt = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2};
+ tpl = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth};
+ tpr = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width};
+
+ switch (placement) {
case 'bottom':
- tp = {top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2};
+ if ((tpb.top + actualHeight) > ($(window).scrollTop() + $(window).height())) {
+ if (tpt.top > $(window).scrollTop()) {
+ placement = 'top';
+ } else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
+ placement = 'right';
+ } else if (tpl.left > $(window).scrollLeft()) {
+ placement = 'left';
+ } else {
+ placement = 'right';
+ }
+ }
break;
case 'top':
- tp = {top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2};
+ if (tpt.top < $(window).scrollTop()) {
+ if ((tpb.top + actualHeight) < ($(window).scrollTop() + $(window).height())) {
+ placement = 'bottom';
+ } else if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
+ placement = 'right';
+ } else if (tpl.left > $(window).scrollLeft()) {
+ placement = 'left';
+ } else {
+ placement = 'right';
+ }
+ }
break;
case 'left':
- tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth};
+ if (tpl.left < $(window).scrollLeft()) {
+ if ((tpr.left + actualWidth) < ($(window).scrollLeft() + $(window).width())) {
+ placement = 'right';
+ } else if (tpt.top > $(window).scrollTop()) {
+ placement = 'top';
+ } else if (tpt.top > $(window).scrollTop()) {
+ placement = 'bottom';
+ } else {
+ placement = 'right';
+ }
+ }
break;
case 'right':
- tp = {top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width};
+ if ((tpr.left + actualWidth) > ($(window).scrollLeft() + $(window).width())) {
+ if (tpl.left > $(window).scrollLeft()) {
+ placement = 'left';
+ } else if (tpt.top > $(window).scrollTop()) {
+ placement = 'top';
+ } else if (tpt.top > $(window).scrollTop()) {
+ placement = 'bottom';
+ }
+ }
break;
}
+ switch (placement) {
+ case 'bottom':
+ tp = tpb;
+ break;
+ case 'top':
+ tp = tpt;
+ break;
+ case 'left':
+ tp = tpl;
+ break;
+ case 'right':
+ tp = tpr;
+ break;
+ }
+
$tip
.offset(tp)
.addClass(placement)
.addClass('in');
}).call(this.container());
/*jshint laxcomma: false*/
}
});
-}(window.jQuery));
+}(window.jQuery));
+
/* =========================================================
* bootstrap-datepicker.js
* http://www.eyecon.ro/bootstrap-datepicker
* =========================================================
* Copyright 2012 Stefan Petre
@@ -5772,10 +5972,13 @@
//by default viewformat equals to format
if(!this.options.viewformat) {
this.options.viewformat = this.options.format;
}
+ //try parse datepicker config defined as json string in data-datepicker
+ options.datepicker = $.fn.editableutils.tryParseJson(options.datepicker, true);
+
//overriding datepicker config (as by default jQuery extend() is not recursive)
//since 1.4 datepicker internally uses viewformat instead of format. Format is for submit only
this.options.datepicker = $.extend({}, defaults.datepicker, options.datepicker, {
format: this.options.viewformat
});
@@ -6078,9 +6281,12 @@
//by default viewformat equals to format
if(!this.options.viewformat) {
this.options.viewformat = this.options.format;
}
+
+ //try parse datetimepicker config defined as json string in data-datetimepicker
+ options.datetimepicker = $.fn.editableutils.tryParseJson(options.datetimepicker, true);
//overriding datetimepicker config (as by default jQuery extend() is not recursive)
//since 1.4 datetimepicker internally uses viewformat instead of format. Format is for submit only
this.options.datetimepicker = $.extend({}, defaults.datetimepicker, options.datetimepicker, {
format: this.options.viewformat
\ No newline at end of file