vendor/assets/javascripts/bootstrap-editable.js in bootstrap-editable-rails-0.0.4 vs vendor/assets/javascripts/bootstrap-editable.js in bootstrap-editable-rails-0.0.5

- old
+ new

@@ -1,6 +1,6 @@ -/*! X-editable - v1.4.1 +/*! X-editable - v1.4.3 * In-place editing with Twitter Bootstrap, jQuery UI or pure jQuery * http://github.com/vitalets/x-editable * Copyright (c) 2013 Vitaliy Potapov; Licensed MIT */ /** @@ -492,11 +492,11 @@ if(!response.success) return response.msg; } **/ success: null, /** - Additional options for ajax request. + Additional options for submit ajax request. List of values: http://api.jquery.com/jQuery.ajax @property ajaxOptions @type object @default null @@ -709,11 +709,11 @@ result = [], that = this; $.each(sourceData, function(i, o) { if(o.children) { - result = result.concat(that.itemsByValue(value, 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) { result.push(o); @@ -829,11 +829,12 @@ $('.editable-open').editableContainer('hide'); //todo: return focus on element } }); - //close containers when click outside + //close containers when click outside + //(mousedown could be better than click, it closes everything also on drag drop) $(document).on('click.editable', function(e) { var $target = $(e.target), i, exclude_classes = ['.editable-container', '.ui-datepicker-header', '.modal-backdrop', @@ -878,11 +879,11 @@ return this.container() ? this.container().$tip : null; }, /* returns container object */ container: function() { - return this.$element.data(this.containerName); + return this.$element.data(this.containerDataName || this.containerName); }, call: function() { this.$element[this.containerName].apply(this.$element, arguments); }, @@ -907,12 +908,12 @@ @event shown @param {Object} event event object @example $('#username').on('shown', function() { - var $tip = $(this).data('editableContainer').tip(); - $tip.find('input').val('overwriting value of input..'); + var editable = $(this).data('editable'); + editable.input.$input.val('overwriting value of input..'); }); **/ this.$element.triggerHandler('shown'); }, this) }) @@ -1209,13 +1210,13 @@ /** Animation speed (inline mode) @property anim @type string - @default 'fast' + @default false **/ - anim: 'fast', + anim: false, /** Mode of editable, can be `popup` or `inline` @property mode @@ -1318,11 +1319,13 @@ doAutotext, finalize; //name this.options.name = this.options.name || this.$element.attr('id'); - //create input of specified type. Input will be used for converting value, not in form + //create input of specified type. Input needed already here to convert value for initial display (e.g. show text by id for select) + //also we set scope option to have access to element inside input specific callbacks (e. g. source as function) + this.options.scope = this.$element[0]; this.input = $.fn.editableutils.createInput(this.options); if(!this.input) { return; } @@ -1369,13 +1372,23 @@ } else { this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually } //check conditions for autotext: - //if value was generated by text or value is empty, no sense to run autotext - doAutotext = !isValueByText && this.value !== null && this.value !== undefined; - doAutotext &= (this.options.autotext === 'always') || (this.options.autotext === 'auto' && !this.$element.text().length); + switch(this.options.autotext) { + case 'always': + doAutotext = true; + break; + case 'auto': + //if element text is empty and value is defined and value not generated by text --> run autotext + doAutotext = !$.trim(this.$element.text()).length && this.value !== null && this.value !== undefined && !isValueByText; + break; + default: + doAutotext = false; + } + + //depending on autotext run render() or just finilize init $.when(doAutotext ? this.render() : true).then($.proxy(function() { if(this.options.disabled) { this.disable(); } else { this.enable(); @@ -1407,10 +1420,15 @@ this.options.autotext = 'never'; //listen toggle events this.$element.on(this.options.toggle + '.editable', selector, $.proxy(function(e){ var $target = $(e.target); 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 + if($target.hasClass(this.options.emptyclass)) { + $target.empty(); + } $target.editable(this.options).trigger(e); } }, this)); }, @@ -1528,11 +1546,11 @@ handleEmpty: function (isEmpty) { //do not handle empty if we do not display anything if(this.options.display === false) { return; } - + this.isEmpty = isEmpty !== undefined ? isEmpty : $.trim(this.$element.text()) === ''; //emptytext shown only for enabled if(!this.options.disabled) { if (this.isEmpty) { @@ -1749,15 +1767,18 @@ } }); return result; /** - Returns current values of editable elements. If value is <code>null</code> or <code>undefined</code> it will not be returned + Returns current values of editable elements. + Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements. + If value of some editable is `null` or `undefined` it is excluded from result object. + @method getValue() @returns {Object} object of element names and values @example - $('#username, #fullname').editable('validate'); + $('#username, #fullname').editable('getValue'); // possible result: { username: "superuser", fullname: "John" } @@ -1890,28 +1911,40 @@ @type string @default 'auto' **/ autotext: 'auto', /** - Initial value of input. If not set, taken from element's text. - + Initial value of input. If not set, taken from element's text. + Note, that if element's text is empty - text is automatically generated from value and can be customized (see `autotext` option). + For example, to display currency sign: + @example + <a id="price" data-type="text" data-value="100"></a> + <script> + $('#price').editable({ + ... + display: function(value) { + $(this).text(value + '$'); + } + }) + </script> + @property value @type mixed @default element's text **/ value: null, /** Callback to perform custom displaying of value in element's text. If `null`, default input's display used. If `false`, no displaying methods will be called, element's text will never change. Runs under element's scope. - _Parameters:_ + _**Parameters:**_ * `value` current value to be displayed * `response` server response (if display called after ajax submit), since 1.4.0 - For **inputs with source** (select, checklist) parameters are different: + For _inputs with source_ (select, checklist) parameters are different: * `value` current value to be displayed * `sourceData` array of items for current input (e.g. dropdown items) * `response` server response (if display called after ajax submit), since 1.4.0 @@ -1954,23 +1987,27 @@ @since 1.4.1 @default editable-unsaved **/ unsavedclass: 'editable-unsaved', /** - If a css selector is provided, editable will be delegated to the specified targets. + If selector is provided, editable will be delegated to the specified targets. Usefull for dynamically generated DOM elements. - **Please note**, that delegated targets can't use `emptytext` and `autotext` options, - as they are initialized after first click. + **Please note**, that delegated targets can't be initialized with `emptytext` and `autotext` options, + as they actually become editable only after first click. + You should manually set class `editable-click` to these elements. + Also, if element originally empty you should add class `editable-empty`, set `data-value=""` and write emptytext into element: @property selector @type string @since 1.4.1 @default null @example <div id="user"> - <a href="#" data-name="username" data-type="text" title="Username">awesome</a> - <a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" title="Group">Operator</a> + <!-- empty --> + <a href="#" data-name="username" data-type="text" class="editable-click editable-empty" data-value="" title="Username">Empty</a> + <!-- non-empty --> + <a href="#" data-name="group" data-type="select" data-source="/groups" data-value="1" class="editable-click" title="Group">Operator</a> </div> <script> $('#user').editable({ selector: 'a', @@ -2143,11 +2180,11 @@ this.$input.addClass(this.options.inputclass); } }, setAttr: function(attr) { - if (this.options[attr]) { + if (this.options[attr] !== undefined && this.options[attr] !== null) { this.$input.attr(attr, this.options[attr]); } }, option: function(key, value) { @@ -2170,11 +2207,14 @@ @property inputclass @type string @default input-medium **/ - inputclass: 'input-medium' + inputclass: 'input-medium', + //scope for external methods (e.g. source defined as function) + //for internal use only + scope: null }; $.extend($.fn.editabletypes, {abstractinput: AbstractInput}); }(window.jQuery)); @@ -2249,16 +2289,23 @@ this.options.source = $.fn.editableutils.tryParseJson(this.options.source, false); } catch (e) { error.call(this); return; } + + var source = this.options.source; + + //run source if it function + if ($.isFunction(source)) { + source = source.call(this.options.scope); + } //loading from url - if (typeof this.options.source === 'string') { + if (typeof source === 'string') { //try to get from cache if(this.options.sourceCache) { - var cacheID = this.options.source, + var cacheID = source, cache; if (!$(document).data(cacheID)) { $(document).data(cacheID, {}); } @@ -2287,11 +2334,11 @@ } } //loading sourceData from server $.ajax({ - url: this.options.source, + url: source, type: 'get', cache: false, dataType: 'json', success: $.proxy(function (data) { if(cache) { @@ -2322,16 +2369,12 @@ //run error callbacks for other fields $.each(cache.err_callbacks, function () { this.call(); }); } }, this) }); - } else { //options as json/array/function - if ($.isFunction(this.options.source)) { - this.sourceData = this.makeArray(this.options.source()); - } else { - this.sourceData = this.makeArray(this.options.source); - } + } else { //options as json/array + this.sourceData = this.makeArray(source); if($.isArray(this.sourceData)) { this.doPrepend(); success.call(this); } else { @@ -2344,20 +2387,24 @@ if(this.options.prepend === null || this.options.prepend === undefined) { return; } if(!$.isArray(this.prependData)) { + //run prepend if it is function (once) + if ($.isFunction(this.options.prepend)) { + this.options.prepend = this.options.prepend.call(this.options.scope); + } + //try parse json in single quotes this.options.prepend = $.fn.editableutils.tryParseJson(this.options.prepend, true); + + //convert prepend from string to object if (typeof this.options.prepend === 'string') { this.options.prepend = {'': this.options.prepend}; - } - if (typeof this.options.prepend === 'function') { - this.prependData = this.makeArray(this.options.prepend()); - } else { - this.prependData = this.makeArray(this.options.prepend); } + + this.prependData = this.makeArray(this.options.prepend); } if($.isArray(this.prependData) && $.isArray(this.sourceData)) { this.sourceData = this.prependData.concat(this.sourceData); } @@ -2534,12 +2581,24 @@ //render clear button renderClear: function() { if (this.options.clear) { this.$clear = $('<span class="editable-clear-x"></span>'); this.$input.after(this.$clear) - .css('padding-right', 20) - .keyup($.proxy(this.toggleClear, this)) + .css('padding-right', 24) + .keyup($.proxy(function(e) { + //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)); } }, @@ -2553,23 +2612,28 @@ //workaround for plain-popup if(delta < 3) { delta = 3; } - this.$clear.css({top: delta, right: delta}); + this.$clear.css({bottom: delta, right: delta}); } }, //show / hide clear button - toggleClear: function() { + toggleClear: function(e) { if(!this.$clear) { return; } - if(this.$input.val().length) { + var len = this.$input.val().length, + visible = this.$clear.is(':visible'); + + if(len && !visible) { this.$clear.show(); - } else { + } + + if(!len && visible) { this.$clear.hide(); } }, clear: function() { @@ -2731,11 +2795,10 @@ source: [ {value: 1, text: 'Active'}, {value: 2, text: 'Blocked'}, {value: 3, text: 'Deleted'} ] - } }); }); </script> **/ (function ($) { @@ -2801,11 +2864,12 @@ tpl:'<select></select>' }); $.fn.editabletypes.select = Select; -}(window.jQuery)); +}(window.jQuery)); + /** List of checkboxes. Internally value stored as javascript array of values. @class checklist @@ -2820,11 +2884,10 @@ source: [ {value: 1, text: 'option1'}, {value: 2, text: 'option2'}, {value: 3, text: 'option3'} ] - } }); }); </script> **/ (function ($) { @@ -3130,10 +3193,13 @@ You should manually include select2 distributive: <link href="select2/select2.css" rel="stylesheet" type="text/css"></link> <script src="select2/select2.js"></script> +**Note:** currently `ajax` source for select2 is not supported, as it's not possible to load it in closed select2 state. +The solution is to load source manually and assign statically. + @class select2 @extends abstractinput @since 1.4.1 @final @example @@ -3159,25 +3225,42 @@ this.init('select2', options, Constructor.defaults); options.select2 = options.select2 || {}; var that = this, - mixin = { + mixin = { //mixin to select2 options placeholder: options.placeholder }; //detect whether it is multi-valued this.isMultiple = options.select2.tags || options.select2.multiple; - //if not `tags` mode, we need define init set data from source + //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) { @@ -3198,30 +3281,44 @@ render: function() { this.setClass(); //apply select2 this.$input.select2(this.options.select2); + //when data is loaded via ajax, we need to know when it's done + if('ajax' in this.options.select2) { + /* + console.log('attached'); + var original = this.$input.data('select2').postprocessResults; + this.$input.data('select2').postprocessResults = function(data, initial) { + this.element.triggerHandler('loaded', [data]); + original.apply(this, arguments); + } + */ + } + + //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; - if(this.$input) { //when submitting form + if(this.$input) { //called when submitting form and select2 already exists data = this.$input.select2('data'); } else { //on init (autotext) //here select2 instance not created yet and data may be even not loaded. //we can check data/tags property of select config and if exist lookup text if(this.options.select2.tags) { data = value; } else if(this.options.select2.data) { data = $.fn.editableutils.itemsByValue(value, this.options.select2.data, 'id'); + } else { + //if('ajax' in this.options.select2) { } } if($.isArray(data)) { //collect selected data and show with separator @@ -3241,11 +3338,11 @@ html2value: function(html) { return this.options.select2.tags ? this.str2value(html, this.options.viewseparator) : null; }, value2input: function(value) { - this.$input.val(value).trigger('change'); + this.$input.val(value).trigger('change', true); //second argument needed to separate initial change from user's click (for autosubmit) }, input2value: function() { return this.$input.select2('val'); }, @@ -3266,11 +3363,19 @@ for (i = 0, l = val.length; i < l; i = i + 1) { val[i] = $.trim(val[i]); } return val; - } + }, + + autosubmit: function() { + this.$input.on('change', function(e, isInitial){ + if(!isInitial) { + $(this).closest('form').submit(); + } + }); + } }); Constructor.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { /** @@ -3316,11 +3421,11 @@ $.fn.editabletypes.select2 = Constructor; }(window.jQuery)); /** -* Combodate - 1.0.1 +* Combodate - 1.0.2 * 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 * @@ -3419,13 +3524,17 @@ /* Initialize items of combos. Handles `firstItem` option */ initItems: function(key) { - var values = []; + var values = [], + relTime; + if(this.options.firstItem === 'name') { - var header = typeof moment.relativeTime[key] === 'function' ? moment.relativeTime[key](1, true, key, false) : moment.relativeTime[key]; + //need both to suuport moment ver < 2 and >= 2 + relTime = moment.relativeTime || moment.langData()._relativeTime; + var header = typeof relTime[key] === 'function' ? relTime[key](1, true, key, false) : relTime[key]; //take last entry (see momentjs lang files structure) header = header.split(' ').reverse()[0]; values.push(['', header]); } else if(this.options.firstItem === 'empty') { values.push(['', '']); @@ -3467,13 +3576,13 @@ shortNames = this.options.template.indexOf('MMM') !== -1, twoDigit = this.options.template.indexOf('MM') !== -1; for(i=0; i<=11; i++) { if(longNames) { - name = moment.months[i]; + name = moment().month(i).format('MMMM'); } else if(shortNames) { - name = moment.monthsShort[i]; + name = moment().month(i).format('MMM'); } else if(twoDigit) { name = this.leadZero(i+1); } else { name = i+1; } @@ -3715,11 +3824,11 @@ }; }(window.jQuery)); /** Combodate input - dropdown date and time picker. -Based on [combodate](http://vitalets.github.com/combodate) plugin. To use it you should manually include [momentjs](http://momentjs.com). +Based on [combodate](http://vitalets.github.com/combodate) plugin (included). To use it you should manually include [momentjs](http://momentjs.com). <script src="js/moment.min.js"></script> Allows to input: @@ -3763,10 +3872,13 @@ //by default viewformat equals to format if(!this.options.viewformat) { this.options.viewformat = this.options.format; } + //try parse combodate config defined as json string in data-combodate + options.combodate = $.fn.editableutils.tryParseJson(options.combodate, true); + //overriding combodate config (as by default jQuery extend() is not recursive) this.options.combodate = $.extend({}, Constructor.defaults.combodate, options.combodate, { format: this.options.format, template: this.options.template }); @@ -4147,16 +4259,28 @@ this.$input.data('datepicker').date = null; this.$input.find('.active').removeClass('active'); }, autosubmit: function() { + this.$input.on('mouseup', '.day', function(e){ + if($(e.currentTarget).is('.old') || $(e.currentTarget).is('.new')) { + return; + } + var $form = $(this).closest('form'); + setTimeout(function() { + $form.submit(); + }, 200); + }); + //changedate is not suitable as it triggered when showing datepicker. see #149 + /* this.$input.on('changeDate', function(e){ var $form = $(this).closest('form'); setTimeout(function() { $form.submit(); }, 200); }); + */ } }); Date.defaults = $.extend({}, $.fn.editabletypes.abstractinput.defaults, { @@ -4195,16 +4319,18 @@ @property datepicker @type object @default { weekStart: 0, startView: 0, + minViewMode: 0, autoclose: false } **/ datepicker:{ weekStart: 0, startView: 0, + minViewMode: 0, autoclose: false }, /** Text shown as clear date button. If <code>false</code> clear button will not be rendered. @@ -4289,10 +4415,11 @@ /* datepicker config */ datepicker: { weekStart: 0, startView: 0, + minViewMode: 0, autoclose: true } }); $.fn.editabletypes.datefield = DateField; @@ -4333,13 +4460,14 @@ var Datepicker = function(element, options) { var that = this; this.element = $(element); this.language = options.language||this.element.data('date-language')||"en"; + this.language = this.language in dates ? this.language : this.language.split('-')[0]; //Check if "de-DE" style date is available, if not language should fallback to 2 letter code eg "de" this.language = this.language in dates ? this.language : "en"; this.isRTL = dates[this.language].rtl||false; - this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||'mm/dd/yyyy'); + this.format = DPGlobal.parseFormat(options.format||this.element.data('date-format')||dates[this.language].format||'mm/dd/yyyy'); this.isInline = false; this.isInput = this.element.is('input'); this.component = this.element.is('.date') ? this.element.find('.add-on') : false; this.hasInput = this.component && this.element.find('input').length; if(this.component && this.component.length === 0) @@ -4351,12 +4479,12 @@ if ('forceParse' in options) { this.forceParse = options.forceParse; } else if ('dateForceParse' in this.element.data()) { this.forceParse = this.element.data('date-force-parse'); } - + this.picker = $(DPGlobal.template) .appendTo(this.isInline ? this.element : 'body') .on({ click: $.proxy(this.click, this), mousedown: $.proxy(this.mousedown, this) @@ -4372,11 +4500,11 @@ this.picker.find('.prev i, .next i') .toggleClass('icon-arrow-left icon-arrow-right'); } $(document).on('mousedown', function (e) { // Clicked outside the datepicker, hide it - if ($(e.target).closest('.datepicker').length === 0) { + if ($(e.target).closest('.datepicker.datepicker-inline, .datepicker.datepicker-dropdown').length === 0) { that.hide(); } }); this.autoclose = false; @@ -4403,13 +4531,42 @@ case 'year': this.viewMode = this.startViewMode = 1; break; } + this.minViewMode = options.minViewMode||this.element.data('date-min-view-mode')||0; + if (typeof this.minViewMode === 'string') { + switch (this.minViewMode) { + case 'months': + this.minViewMode = 1; + break; + case 'years': + this.minViewMode = 2; + break; + default: + this.minViewMode = 0; + break; + } + } + + this.viewMode = this.startViewMode = Math.max(this.startViewMode, this.minViewMode); + this.todayBtn = (options.todayBtn||this.element.data('date-today-btn')||false); this.todayHighlight = (options.todayHighlight||this.element.data('date-today-highlight')||false); + this.calendarWeeks = false; + if ('calendarWeeks' in options) { + this.calendarWeeks = options.calendarWeeks; + } else if ('dateCalendarWeeks' in this.element.data()) { + this.calendarWeeks = this.element.data('date-calendar-weeks'); + } + if (this.calendarWeeks) + this.picker.find('tfoot th.today') + .attr('colspan', function(i, val){ + return parseInt(val) + 1; + }); + this.weekStart = ((options.weekStart||this.element.data('date-weekstart')||dates[this.language].weekStart||0) % 7); this.weekEnd = ((this.weekStart + 6) % 7); this.startDate = -Infinity; this.endDate = Infinity; this.daysOfWeekDisabled = []; @@ -4495,10 +4652,11 @@ }); }, hide: function(e){ if(this.isInline) return; + if (!this.picker.is(':visible')) return; this.picker.hide(); $(window).off('resize', this.place); this.viewMode = this.startViewMode; this.showMode(); if (!this.isInput) { @@ -4616,31 +4774,28 @@ this.date = DPGlobal.parseDate(date, this.format, this.language); if(fromArgs) this.setValue(); - var oldViewDate = this.viewDate; if (this.date < this.startDate) { this.viewDate = new Date(this.startDate); } else if (this.date > this.endDate) { this.viewDate = new Date(this.endDate); } else { this.viewDate = new Date(this.date); } - - if (oldViewDate && oldViewDate.getTime() != this.viewDate.getTime()){ - this.element.trigger({ - type: 'changeDate', - date: this.viewDate - }); - } this.fill(); }, fillDow: function(){ var dowCnt = this.weekStart, html = '<tr>'; + if(this.calendarWeeks){ + var cell = '<th class="cw">&nbsp;</th>'; + html += cell; + this.picker.find('.datepicker-days thead tr:first-child').prepend(cell); + } while (dowCnt < this.weekStart + 7) { html += '<th class="dow">'+dates[this.language].daysMin[(dowCnt++)%7]+'</th>'; } html += '</tr>'; this.picker.find('.datepicker-days thead').append(html); @@ -4663,11 +4818,11 @@ startMonth = this.startDate !== -Infinity ? this.startDate.getUTCMonth() : -Infinity, endYear = this.endDate !== Infinity ? this.endDate.getUTCFullYear() : Infinity, endMonth = this.endDate !== Infinity ? this.endDate.getUTCMonth() : Infinity, currentDate = this.date && this.date.valueOf(), today = new Date(); - this.picker.find('.datepicker-days thead th:eq(1)') + this.picker.find('.datepicker-days thead th.switch') .text(dates[this.language].months[month]+' '+year); this.picker.find('tfoot th.today') .text(dates[this.language].today) .toggle(this.todayBtn !== false); this.updateNavArrows(); @@ -4682,10 +4837,25 @@ var html = []; var clsName; while(prevMonth.valueOf() < nextMonth) { if (prevMonth.getUTCDay() == this.weekStart) { html.push('<tr>'); + if(this.calendarWeeks){ + // ISO 8601: First week contains first thursday. + // ISO also states week starts on Monday, but we can be more abstract here. + var + // Start of current week: based on weekstart/current date + ws = new Date(+prevMonth + (this.weekStart - prevMonth.getUTCDay() - 7) % 7 * 864e5), + // Thursday of this week + th = new Date(+ws + (7 + 4 - ws.getUTCDay()) % 7 * 864e5), + // First Thursday of year, year from thursday + yth = new Date(+(yth = UTCDate(th.getUTCFullYear(), 0, 1)) + (7 + 4 - yth.getUTCDay())%7*864e5), + // Calendar week: ms between thursdays, div ms per day, div 7 days + calWeek = (th - yth) / 864e5 / 7 + 1; + html.push('<td class="cw">'+ calWeek +'</td>'); + + } } clsName = ''; if (prevMonth.getUTCFullYear() < year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() < month)) { clsName += ' old'; } else if (prevMonth.getUTCFullYear() > year || (prevMonth.getUTCFullYear() == year && prevMonth.getUTCMonth() > month)) { @@ -4817,23 +4987,33 @@ break; case 'span': if (!target.is('.disabled')) { this.viewDate.setUTCDate(1); if (target.is('.month')) { + var day = 1; var month = target.parent().find('span').index(target); + var year = this.viewDate.getUTCFullYear(); this.viewDate.setUTCMonth(month); this.element.trigger({ type: 'changeMonth', date: this.viewDate }); + if ( this.minViewMode == 1 ) { + this._setDate(UTCDate(year, month, day,0,0,0,0)); + } } else { var year = parseInt(target.text(), 10)||0; + var day = 1; + var month = 0; this.viewDate.setUTCFullYear(year); this.element.trigger({ type: 'changeYear', date: this.viewDate }); + if ( this.minViewMode == 2 ) { + this._setDate(UTCDate(year, month, day,0,0,0,0)); + } } this.showMode(-1); this.fill(); } break; @@ -5026,11 +5206,11 @@ } }, showMode: function(dir) { if (dir) { - this.viewMode = Math.max(0, Math.min(2, this.viewMode + dir)); + this.viewMode = Math.max(this.minViewMode, Math.min(2, this.viewMode + dir)); } /* vitalets: fixing bug of very special conditions: jquery 1.7.1 + webkit + show inline datepicker in bootstrap popover. Method show() does not set display css correctly and datepicker is not shown. @@ -5286,11 +5466,10 @@ source: [ {value: 'gb', text: 'Great Britain'}, {value: 'us', text: 'United States'}, {value: 'ru', text: 'Russia'} ] - } }); }); </script> **/ (function ($) { @@ -5318,12 +5497,15 @@ this.options.typeahead.source = this.sourceData; //apply typeahead this.$input.typeahead(this.options.typeahead); - //attach own render method - this.$input.data('typeahead').render = $.proxy(this.typeaheadRender, this.$input.data('typeahead')); + //patch some methods in typeahead + var ta = this.$input.data('typeahead'); + ta.render = $.proxy(this.typeaheadRender, ta); + ta.select = $.proxy(this.typeaheadSelect, ta); + ta.move = $.proxy(this.typeaheadMove, ta); this.renderClear(); this.setClass(); this.setAttr('placeholder'); }, @@ -5398,11 +5580,11 @@ /* Typeahead option methods used as defaults */ - /*jshint eqeqeq:false, curly: false, laxcomma: true*/ + /*jshint eqeqeq:false, curly: false, laxcomma: true, asi: true*/ matcher: function (item) { return $.fn.typeahead.Constructor.prototype.matcher.call(this, item.text); }, sorter: function (items) { var beginswith = [] @@ -5422,22 +5604,21 @@ }, highlighter: function (item) { return $.fn.typeahead.Constructor.prototype.highlighter.call(this, item.text); }, updater: function (item) { - item = this.$menu.find('.active').data('item'); this.$element.data('value', item.value); return item.text; }, /* Overwrite typeahead's render method to store objects. There are a lot of disscussion in bootstrap repo on this point and still no result. See https://github.com/twitter/bootstrap/issues/5967 - This function just store item in via jQuery data() method instead of attr('data-value') + This function just store item via jQuery data() method instead of attr('data-value') */ typeaheadRender: function (items) { var that = this; items = $(items).map(function (i, item) { @@ -5445,14 +5626,60 @@ i = $(that.options.item).data('item', item); i.find('a').html(that.highlighter(item)); return i[0]; }); - items.first().addClass('active'); + //add option to disable autoselect of first line + //see https://github.com/twitter/bootstrap/pull/4164 + if (this.options.autoSelect) { + items.first().addClass('active'); + } this.$menu.html(items); return this; + }, + + //add option to disable autoselect of first line + //see https://github.com/twitter/bootstrap/pull/4164 + typeaheadSelect: function () { + var val = this.$menu.find('.active').data('item') + if(this.options.autoSelect || val){ + this.$element + .val(this.updater(val)) + .change() + } + return this.hide() + }, + + /* + if autoSelect = false and nothing matched we need extra press onEnter that is not convinient. + This patch fixes it. + */ + typeaheadMove: function (e) { + if (!this.shown) return + + switch(e.keyCode) { + case 9: // tab + case 13: // enter + case 27: // escape + if (!this.$menu.find('.active').length) return + e.preventDefault() + break + + case 38: // up arrow + e.preventDefault() + this.prev() + break + + case 40: // down arrow + e.preventDefault() + this.next() + break + } + + e.stopPropagation() } - /*jshint eqeqeq: true, curly: true, laxcomma: false*/ + + /*jshint eqeqeq: true, curly: true, laxcomma: false, asi: false*/ }); Constructor.defaults = $.extend({}, $.fn.editabletypes.list.defaults, { /** \ No newline at end of file