diff --git a/dist/README.md b/dist/README.md index b26fdcf5..53a58b46 100644 --- a/dist/README.md +++ b/dist/README.md @@ -1,19 +1,24 @@ # X-editable - In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery. -## Project status -Unfortunately, **project is currently frozen**, as I don't have enough time for it. -You could try use it as is, but there may be some bugs with newer versions of dependend libraries (e.g. bootstrap). -I would really appreciate if someone take care of it.. See [#610](https://github.com/vitalets/x-editable/issues/610). -Vitalets. - -## Live demo +## Live Demo **http://vitalets.github.io/x-editable/demo.html** +## Pull Requests +Please submit all Pull Requests to the `develop` branch: https://github.com/vitalets/x-editable/tree/develop + +## Issue Tracker +Please report all issues here: https://github.com/vitalets/x-editable/issues + +## User Support +Unfortunately, due to this project being supported by volunteers we cannot provide user support at this time. Please try a site like Stack Overflow: http://stackoverflow.com/questions/tagged/x-editable + ## Documentation **http://vitalets.github.io/x-editable** +## Project Status +Actively Maintained + ## How to get it ### Manual download @@ -26,25 +31,25 @@ bower install x-editable ### CDN Bootstrap 3 build: -````js +````html ```` Bootstrap 2 build: -````js +````html ```` jQuery UI build: -````js +````html ```` jQuery only build: -````js +````html ```` diff --git a/dist/bootstrap-editable/css/bootstrap-editable.css b/dist/bootstrap-editable/css/bootstrap-editable.css index ff7ea50f..97d0d093 100644 --- a/dist/bootstrap-editable/css/bootstrap-editable.css +++ b/dist/bootstrap-editable/css/bootstrap-editable.css @@ -1,7 +1,7 @@ /*! X-editable - v1.5.3 * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery * http://github.com/vitalets/x-editable -* Copyright (c) 2015 Vitaliy Potapov; Licensed MIT */ +* Copyright (c) 2017 Vitaliy Potapov; Licensed MIT */ .editableform { margin-bottom: 0; /* overwrites bootstrap margin */ } @@ -12,6 +12,16 @@ line-height: 20px; /* overwriting bootstrap line-height. See #133 */ } +/* + BS3 fix: stop css from breaking when the form is inside a popup and inside a form with the class .form-horizontal + See: https://github.com/vitalets/x-editable/issues/682 +*/ +.form-horizontal .editable-popup .editableform .form-group { + margin-left:0; + margin-right:0; +} + + /* BS3 width:1005 for inputs breaks editable form in popup See: https://github.com/vitalets/x-editable/issues/393 @@ -145,6 +155,7 @@ .editable-pre-wrapped { white-space: pre-wrap; } + .editable-container.editable-popup { max-width: none !important; /* without this rule poshytip/tooltip does not stretch */ } diff --git a/dist/bootstrap-editable/js/bootstrap-editable.js b/dist/bootstrap-editable/js/bootstrap-editable.js index 5aab86a5..b1b46ae0 100644 --- a/dist/bootstrap-editable/js/bootstrap-editable.js +++ b/dist/bootstrap-editable/js/bootstrap-editable.js @@ -1,7 +1,7 @@ /*! X-editable - v1.5.3 * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery * http://github.com/vitalets/x-editable -* Copyright (c) 2015 Vitaliy Potapov; Licensed MIT */ +* Copyright (c) 2017 Vitaliy Potapov; Licensed MIT */ /** Form with single input element, two buttons and two states: normal/loading. Applied as jQuery method to DIV tag (not to form tag!). This is because form can be in loading state when spinner shown. @@ -664,7 +664,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc. */ setCursorPosition: function(elem, pos) { if (elem.setSelectionRange) { - elem.setSelectionRange(pos, pos); + try { elem.setSelectionRange(pos, pos); } catch (e) {} } else if (elem.createTextRange) { var range = elem.createTextRange(); range.collapse(true); @@ -736,7 +736,7 @@ Editableform is linked with one of input types, e.g. 'text', 'select' etc. */ getConfigData: function($element) { var data = {}; - $.each($element.data(), function(k, v) { + $.each($element[0].dataset, function(k, v) { if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) { data[k] = v; } @@ -941,7 +941,7 @@ Applied as jQuery method. //close all on escape $(document).on('keyup.editable', function (e) { if (e.which === 27) { - $('.editable-open').editableContainer('hide'); + $('.editable-open').editableContainer('hide', 'cancel'); //todo: return focus on element } }); @@ -950,7 +950,8 @@ Applied as jQuery method. //(mousedown could be better than click, it closes everything also on drag drop) $(document).on('click.editable', function(e) { var $target = $(e.target), i, - exclude_classes = ['.editable-container', + exclude_classes = ['.editable-container', + '.select2-container', '.ui-datepicker-header', '.datepicker', //in inline mode datepicker is rendered into body '.modal-backdrop', @@ -1613,7 +1614,7 @@ Makes editable any HTML element on the page. Applied as jQuery method. this.options.autotext = 'never'; //listen toggle events this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){ - var $target = $(e.target); + var $target = $(e.target).closest(selector); if(!$target.data('editable')) { //if delegated element initially empty, we need to clear it's text (that was manually set to `empty` by user) //see https://github.com/vitalets/x-editable/issues/137 @@ -2400,7 +2401,7 @@ To create your own input you can inherit from this class. @returns {string} **/ value2str: function(value) { - return value; + return String(value); }, /** @@ -2913,7 +2914,7 @@ $(function(){ **/ (function ($) { "use strict"; - + var Text = function (options) { this.init('text', options, Text.defaults); }; @@ -2925,21 +2926,22 @@ $(function(){ this.renderClear(); this.setClass(); this.setAttr('placeholder'); - this.setAttr('maxlength'); }, - + activate: function() { if(this.$input.is(':visible')) { this.$input.focus(); +// if (this.$input.is('input,textarea') && !this.$input.is('[type="checkbox"],[type="range"],[type="number"],[type="email"]')) { if (this.$input.is('input,textarea') && !this.$input.is('[type="checkbox"],[type="range"]')) { $.fn.editableutils.setCursorPosition(this.$input.get(0), this.$input.val().length); } + if(this.toggleClear) { this.toggleClear(); } } }, - + //render clear button renderClear: function() { if (this.options.clear) { @@ -2950,21 +2952,21 @@ $(function(){ //arrows, enter, tab, etc if(~$.inArray(e.keyCode, [40,38,9,13,27])) { return; - } + } clearTimeout(this.t); var that = this; this.t = setTimeout(function() { that.toggleClear(e); }, 100); - + }, this)) .parent().css('position', 'relative'); - - this.$clear.click($.proxy(this.clear, this)); - } + + this.$clear.click($.proxy(this.clear, this)); + } }, - + postrender: function() { /* //now `clear` is positioned via css @@ -2973,66 +2975,57 @@ $(function(){ // var h = this.$input.outerHeight(true) || 20, var h = this.$clear.parent().height(), delta = (h - this.$clear.height()) / 2; - + //this.$clear.css({bottom: delta, right: delta}); } - */ + */ }, - + //show / hide clear button toggleClear: function(e) { if(!this.$clear) { return; } - + var len = this.$input.val().length, visible = this.$clear.is(':visible'); - + if(len && !visible) { this.$clear.show(); - } - + } + if(!len && visible) { this.$clear.hide(); - } + } }, - + clear: function() { this.$clear.hide(); this.$input.val('').focus(); - } + } }); Text.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { /** - @property tpl + @property tpl @default - **/ + **/ tpl: '', /** Placeholder attribute of input. Shown when input is empty. - @property placeholder - @type string - @default null - **/ - placeholder: null, - - /** - maxlength attribute of input. - - @property placeholder + @property placeholder @type string @default null **/ - maxlength: null, - + placeholder: null, + /** - Whether to show `clear` button - - @property clear + Whether to show `clear` button + + @property clear @type boolean - @default true + @default true **/ clear: true }); @@ -3072,8 +3065,7 @@ $(function(){ render: function () { this.setClass(); this.setAttr('placeholder'); - this.setAttr('rows'); - this.setAttr('maxlength'); + this.setAttr('rows'); //ctrl + enter this.$input.keydown(function (e) { @@ -3140,16 +3132,6 @@ $(function(){ @default null **/ placeholder: null, - - /** - maxlength attribute of input. - - @property placeholder - @type string - @default null - **/ - - maxlength: null, /** Number of rows in textarea @@ -3643,24 +3625,24 @@ 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. - -You should manually download and include select2 distributive: +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. - - - -To make it **bootstrap-styled** you can use css from [here](https://github.com/t0m/select2-bootstrap-css): +You should manually download and include select2 distributive: - - -**Note:** currently `autotext` feature does not work for select2 with `ajax` remote source. -You need initially put both `data-value` and element's text youself: + + + +To make it **bootstrap-styled** you can use css from [here](https://github.com/fk/select2-bootstrap-theme): + + + +**Note:** currently `autotext` feature does not work for select2 with `ajax` remote source. +You need initially put both `data-value` and element's text youself: Text1 - - + + @class select2 @extends abstractinput @since 1.4.1 @@ -3717,57 +3699,72 @@ $(function(){ return $.get('/getCountryById', { query: element.val() }, function (data) { callback(data); }); - } - } + } + } }); }); **/ (function ($) { "use strict"; - + var Constructor = function (options) { this.init('select2', options, Constructor.defaults); options.select2 = options.select2 || {}; - this.sourceData = null; - - //placeholder - if(options.placeholder) { + // placeholder + if (options.placeholder) { options.select2.placeholder = options.placeholder; } - - //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; + // Automatically recognize the old `tags` behaviour and convert it into + // `tags` + `data`, which is what Select2 4.0.0 expects. + // + // Also defaults to being a multiple selection, like older versions of + // Select2. + if ($.isArray(options.select2.tags)) { + options.select2.data = options.select2.tags; + options.select2.tags = true; + options.select2.multiple = true; + } + + if (options.select2.formatSelection) { + options.select2.templateSelection = options.select2.formatSelection; + } + + if (options.select2.formatResult) { + options.select2.templateResult = options.select2.formatResult; + } + + if (options.select2.ajax) { + if (options.select2.ajax.results) { + options.select2.ajax.processResults = options.select2.ajax.results; } - } + + if (options.select2.ajax.processResults) { + var processResults = options.select2.ajax.processResults; + + options.select2.ajax.processResults = $.proxy(function (data) { + var results = processResults(data); + + results.results = this.convertSource(results.results); + + return results; + }, this); + } + } + + if (options.select2.initSelection) { + this.options.initFunction = options.select2.initSelection; + delete options.select2.initSelection; + } //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.isMultiple = this.options.select2.multiple; this.isRemote = ('ajax' in this.options.select2); //store function returning ID of item @@ -3785,128 +3782,190 @@ $(function(){ } }; - $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.abstractinput); + $.fn.editableutils.inherit(Constructor, $.fn.editabletypes.select); $.extend(Constructor.prototype, { - render: function() { - this.setClass(); + render: function () { + console.log('render'); + if (!this.$input.data('select2')) { + this.$input.select2(this.options.select2); + } + console.log(this.$input.html()) - //can not apply select2 here as it calls initSelection - //over input that does not have correct value yet. - //apply select2 only in value2input - //this.$input.select2(this.options.select2); + if (this.options.initFunction) { + this.options.initFunction(this.$input, $.proxy(function (initial) { + console.log('initFunction', initial); + if ($.isArray(initial)) { + + } else { + var id = this.idFunc(initial); + initial.id = id; + var option = new Option(initial.text, id); + option.selected = true; - //when data is loaded via ajax, we need to know when it's done to populate listData - if(this.isRemote) { - //listen to loaded event to populate data - this.$input.on('select2-loaded', $.proxy(function(e) { - this.sourceData = e.items.results; + $(option).data('data', initial); + + this.$input.append(option); + this.$input.trigger('change'); + } }, this)); + + delete this.options.initFunction; } + return Constructor.superclass.render.call(this); + }, + + renderList: function() { + //console.log('renderList', arguments) + var $options = this.$input.children(); + Constructor.superclass.renderList.apply(this, arguments); + this.$input.prepend($options); + + //can not apply select2 here as it calls initSelection + //over input that does not have correct value yet. + //apply select2 only in value2input + //this.$input.select2(this.options.select2); + //trigger resize of editableform to re-position container in multi-valued mode - if(this.isMultiple) { + if (this.isMultiple) { this.$input.on('change', function() { $(this).closest('form').parent().triggerHandler('resize'); }); } }, - value2html: function(value, element) { - 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, this.idFunc); - } else { - //can not get list of possible values - //(e.g. autotext for select2 with ajax source) + /** + * Used to convert a value (`data-value` or `options.value`) to the actual + * selected value that can be processed by x-editable. + * + * This is needed because x-editable does not support multiple selections + * by default. + */ + str2value: function (str) { + //console.log('str2value', str); + + if ($.isArray(str)) { + return str; } - //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' ? that.formatSelection(v) : v); - }); - } else if(data) { - text = that.formatSelection(data); + if (this.isMultiple) { + return str.split(this.getSeparator()); } - text = $.isArray(text) ? text.join(this.options.viewseparator) : text; + return str; + }, + + /** + * Called when no value is supplied, used to determine the value based on the text. + */ + html2value: function (html) { + //console.log('html2value', html, this.isMultiple); + if (!this.isMultiple) { + return html; + } - //$(element).text(text); - Constructor.superclass.value2html.call(this, text, element); + return html.split(this.options.viewseparator); }, - html2value: function(html) { - return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null; + /** + * Used to update the text in the link based on the selected value + */ + value2html: function (value, element) { + Constructor.superclass.value2html.apply(this, arguments); }, - value2input: function(value) { - // if value array => join it anyway - if($.isArray(value)) { - value = value.join(this.getSeparator()); + /** + * Used to convert the value to the text representation of it. + * + * Superclass doesn't support multiple selects, so we need to override this. + */ + value2htmlFinal: function (value, element) { + // The select input type can handle single selects fine + // We have to special case multiple selects, which aren't supported + // by default. + if (!$.isArray(value)) { + return Constructor.superclass.value2htmlFinal.call(this, value, element); } - //for remote source just set value, text is updated by initSelection - if(!this.$input.data('select2')) { - this.$input.val(value); - this.$input.select2(this.options.select2); - } else { - //second argument needed to separate initial change from user's click (for autosubmit) - this.$input.val(value).trigger('change', true); + var results = []; - //Uncaught Error: cannot call val() if initSelection() is not defined - //this.$input.select2('val', value); - } + // Convert all of the values into their text + for (var v = 0; v < value.length; v++) { + var val = value[v]; - // if defined remote source AND no multiple mode AND no user's initSelection provided --> - // we should somehow get text for provided id. - // The solution is to use element's text as text for that id (exclude empty) - if(this.isRemote && !this.isMultiple && !this.options.select2.initSelection) { - // customId and customText are methods to extract `id` and `text` from data object - // we can use this workaround only if user did not define these methods - // otherwise we cant construct data object - var customId = this.options.select2.id, - customText = this.options.select2.formatSelection; - - if(!customId && !customText) { - var $el = $(this.options.scope); - if (!$el.data('editable').isEmpty) { - var data = {id: value, text: $el.text()}; - this.$input.select2('data', data); - } + var items = $.fn.editableutils.itemsByValue(val, this.sourceData); + + // There are no items in cases like tagging + // So just assume that the tag value is also the text + if (items.length === 0) { + results.push(value[v]); + } else { + results.push(items[0].text); } } - }, - - input2value: function() { - return this.$input.select2('val'); + + //console.log('results', results); + + // The output is the text joined by the viewseparator (comma by default) + results = results.join(this.options.viewseparator); + + + $(element)[this.options.escape ? 'text' : 'html']($.trim(results)); }, - str2value: function(str, separator) { - if(typeof str !== 'string' || !this.isMultiple) { - return str; - } + /** + * Used to set the value of Select2 based on the current x-editable selections. + */ + value2input: function (value) { + //console.log('value2input', value) - separator = separator || this.getSeparator(); + // The value for a multiple select can be passed in as a single string + // This will convert it from a string to an array of data values + if (value && !$.isArray(value) && this.isMultiple) { + value = this.str2value(value); + } - var val, i, l; + if (!value) { + return; + } - if (str === null || str.length < 1) { - return null; - } - val = str.split(separator); - for (i = 0, l = val.length; i < l; i = i + 1) { - val[i] = $.trim(val[i]); - } + // Branch off based on whether or not it's a multiple select + // Either way, we are adding `