diff --git a/InvenTree/part/templates/part/bom.html b/InvenTree/part/templates/part/bom.html index bfa1432f23..137bf6e2d6 100644 --- a/InvenTree/part/templates/part/bom.html +++ b/InvenTree/part/templates/part/bom.html @@ -9,7 +9,7 @@ {% include 'part/tabs.html' with tab='bom' %} -
text|textarea|select|date|checklist
-
- @property type
- @type string
- @default 'text'
- **/
- type: 'text',
- /**
- Url for submit, e.g. '/post'
- If function - it will be called instead of ajax. Function should return deferred object to run fail/done callbacks.
-
- @property url
- @type string|function
- @default null
- @example
- url: function(params) {
- var d = new $.Deferred;
- if(params.value === 'abc') {
- return d.reject('error message'); //returning error via deferred object
- } else {
- //async saving data in js model
- someModel.asyncSaveMethod({
- ...,
- success: function(){
- d.resolve();
- }
- });
- return d.promise();
- }
- }
- **/
- url:null,
- /**
- Additional params for submit. If defined as object
- it is **appended** to original ajax data (pk, name and value).
- If defined as function
- returned object **overwrites** original ajax data.
- @example
- params: function(params) {
- //originally params contain pk, name and value
- params.a = 1;
- return params;
- }
-
- @property params
- @type object|function
- @default null
- **/
- params:null,
- /**
- Name of field. Will be submitted on server. Can be taken from id
attribute
-
- @property name
- @type string
- @default null
- **/
- name: null,
- /**
- Primary key of editable object (e.g. record id in database). For composite keys use object, e.g. {id: 1, lang: 'en'}
.
- Can be calculated dynamically via function.
-
- @property pk
- @type string|object|function
- @default null
- **/
- pk: null,
- /**
- Initial value. If not defined - will be taken from element's content.
- For __select__ type should be defined (as it is ID of shown text).
-
- @property value
- @type string|object
- @default null
- **/
- value: null,
- /**
- 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',
- /**
- Function for client-side validation. If returns string - means validation not passed and string showed as error.
- Since 1.5.1 you can modify submitted value by returning object from `validate`:
- `{newValue: '...'}` or `{newValue: '...', msg: '...'}`
-
- @property validate
- @type function
- @default null
- @example
- validate: function(value) {
- if($.trim(value) == '') {
- return 'This field is required';
- }
- }
- **/
- validate: null,
- /**
- Success callback. Called when value successfully sent on server and **response status = 200**.
- Usefull to work with json response. For example, if your backend response can be {success: true}
- or {success: false, msg: "server error"}
you can check it inside this callback.
- If it returns **string** - means error occured and string is shown as error message.
- If it returns **object like** {newValue: <something>}
- it overwrites value, submitted by user.
- Otherwise newValue simply rendered into element.
-
- @property success
- @type function
- @default null
- @example
- success: function(response, newValue) {
- if(!response.success) return response.msg;
- }
- **/
- success: null,
- /**
- Error callback. Called when request failed (response status != 200).
- Usefull when you want to parse error response and display a custom message.
- Must return **string** - the message to be displayed in the error block.
-
- @property error
- @type function
- @default null
- @since 1.4.4
- @example
- error: function(response, newValue) {
- if(response.status === 500) {
- return 'Service unavailable. Please try later.';
- } else {
- return response.responseText;
- }
- }
- **/
- error: null,
- /**
- Additional options for submit ajax request.
- List of values: http://api.jquery.com/jQuery.ajax
-
- @property ajaxOptions
- @type object
- @default null
- @since 1.1.1
- @example
- ajaxOptions: {
- type: 'put',
- dataType: 'json'
- }
- **/
- ajaxOptions: null,
- /**
- Where to show buttons: left(true)|bottom|false
- Form without buttons is auto-submitted.
-
- @property showbuttons
- @type boolean|string
- @default true
- @since 1.1.1
- **/
- showbuttons: true,
- /**
- Scope for callback methods (success, validate).
- If null
means editableform instance itself.
-
- @property scope
- @type DOMElement|object
- @default null
- @since 1.2.0
- @private
- **/
- scope: null,
- /**
- Whether to save or cancel value when it was not changed but form was submitted
-
- @property savenochange
- @type boolean
- @default false
- @since 1.2.0
- **/
- savenochange: false
- };
-
- /*
- Note: following params could redefined in engine: bootstrap or jqueryui:
- Classes 'control-group' and 'editable-error-block' must always present!
- */
- $.fn.editableform.template = '';
-
- //loading div
- $.fn.editableform.loading = '';
-
- //buttons
- $.fn.editableform.buttons = ''+
- '';
-
- //error class attached to control-group
- $.fn.editableform.errorGroupClass = null;
-
- //error class attached to editable-error-block
- $.fn.editableform.errorBlockClass = 'editable-error';
-
- //engine
- $.fn.editableform.engine = 'jquery';
-}(window.jQuery));
-
-/**
-* EditableForm utilites
-*/
-(function ($) {
- "use strict";
-
- //utils
- $.fn.editableutils = {
- /**
- * classic JS inheritance function
- */
- inherit: function (Child, Parent) {
- var F = function() { };
- F.prototype = Parent.prototype;
- Child.prototype = new F();
- Child.prototype.constructor = Child;
- Child.superclass = Parent.prototype;
- },
-
- /**
- * set caret position in input
- * see http://stackoverflow.com/questions/499126/jquery-set-cursor-position-in-text-area
- */
- setCursorPosition: function(elem, pos) {
- if (elem.setSelectionRange) {
- elem.setSelectionRange(pos, pos);
- } else if (elem.createTextRange) {
- var range = elem.createTextRange();
- range.collapse(true);
- range.moveEnd('character', pos);
- range.moveStart('character', pos);
- range.select();
- }
- },
-
- /**
- * function to parse JSON in *single* quotes. (jquery automatically parse only double quotes)
- * That allows such code as:
- * safe = true --> means no exception will be thrown
- * for details see http://stackoverflow.com/questions/7410348/how-to-set-json-format-to-html5-data-attributes-in-the-jquery
- */
- tryParseJson: function(s, safe) {
- if (typeof s === 'string' && s.length && s.match(/^[\{\[].*[\}\]]$/)) {
- if (safe) {
- try {
- /*jslint evil: true*/
- s = (new Function('return ' + s))();
- /*jslint evil: false*/
- } catch (e) {} finally {
- return s;
- }
- } else {
- /*jslint evil: true*/
- s = (new Function('return ' + s))();
- /*jslint evil: false*/
- }
- }
- return s;
- },
-
- /**
- * slice object by specified keys
- */
- sliceObj: function(obj, keys, caseSensitive /* default: false */) {
- var key, keyLower, newObj = {};
-
- if (!$.isArray(keys) || !keys.length) {
- return newObj;
- }
-
- for (var i = 0; i < keys.length; i++) {
- key = keys[i];
- if (obj.hasOwnProperty(key)) {
- newObj[key] = obj[key];
- }
-
- if(caseSensitive === true) {
- continue;
- }
-
- //when getting data-* attributes via $.data() it's converted to lowercase.
- //details: http://stackoverflow.com/questions/7602565/using-data-attributes-with-jquery
- //workaround is code below.
- keyLower = key.toLowerCase();
- if (obj.hasOwnProperty(keyLower)) {
- newObj[key] = obj[keyLower];
- }
- }
-
- return newObj;
- },
-
- /*
- exclude complex objects from $.data() before pass to config
- */
- getConfigData: function($element) {
- var data = {};
- $.each($element.data(), function(k, v) {
- if(typeof v !== 'object' || (v && typeof v === 'object' && (v.constructor === Object || v.constructor === Array))) {
- data[k] = v;
- }
- });
- return data;
- },
-
- /*
- returns keys of object
- */
- objectKeys: function(o) {
- if (Object.keys) {
- return Object.keys(o);
- } else {
- if (o !== Object(o)) {
- throw new TypeError('Object.keys called on a non-object');
- }
- var k=[], p;
- for (p in o) {
- if (Object.prototype.hasOwnProperty.call(o,p)) {
- k.push(p);
- }
- }
- return k;
- }
-
- },
-
- /**
- method to escape html.
- **/
- escape: function(str) {
- return $('$().editable()
. You should subscribe on it's events (save / cancel) to get profit of it.save|cancel|onblur|nochange|undefined (=manual)
- **/
- hide: function(reason) {
- if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
- return;
- }
-
- //if form is saving value, schedule hide
- if(this.$form.data('editableform').isSaving) {
- this.delayedHide = {reason: reason};
- return;
- } else {
- this.delayedHide = false;
- }
-
- this.$element.removeClass('editable-open');
- this.innerHide();
-
- /**
- Fired when container was hidden. It occurs on both save or cancel.
- **Note:** Bootstrap popover has own `hidden` event that now cannot be separated from x-editable's one.
- The workaround is to check `arguments.length` that is always `2` for x-editable.
-
- @event hidden
- @param {object} event event object
- @param {string} reason Reason caused hiding. Can be save|cancel|onblur|nochange|manual
- @example
- $('#username').on('hidden', function(e, reason) {
- if(reason === 'save' || reason === 'cancel') {
- //auto-open next editable
- $(this).closest('tr').next().find('.editable').editable('show');
- }
- });
- **/
- this.$element.triggerHandler('hidden', reason || 'manual');
- },
-
- /* internal show method. To be overwritten in child classes */
- innerShow: function () {
-
- },
-
- /* internal hide method. To be overwritten in child classes */
- innerHide: function () {
-
- },
-
- /**
- Toggles container visibility (show / hide)
- @method toggle()
- @param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
- **/
- toggle: function(closeAll) {
- if(this.container() && this.tip() && this.tip().is(':visible')) {
- this.hide();
- } else {
- this.show(closeAll);
- }
- },
-
- /*
- Updates the position of container when content changed.
- @method setPosition()
- */
- setPosition: function() {
- //tbd in child class
- },
-
- save: function(e, params) {
- /**
- Fired when new value was submitted. You can use $(this).data('editableContainer')
inside handler to access to editableContainer instance
-
- @event save
- @param {Object} event event object
- @param {Object} params additional params
- @param {mixed} params.newValue submitted value
- @param {Object} params.response ajax response
- @example
- $('#username').on('save', function(e, params) {
- //assuming server response: '{success: true}'
- var pk = $(this).data('editableContainer').options.pk;
- if(params.response && params.response.success) {
- alert('value: ' + params.newValue + ' with pk: ' + pk + ' saved!');
- } else {
- alert('error!');
- }
- });
- **/
- this.$element.triggerHandler('save', params);
-
- //hide must be after trigger, as saving value may require methods of plugin, applied to input
- this.hide('save');
- },
-
- /**
- Sets new option
-
- @method option(key, value)
- @param {string} key
- @param {mixed} value
- **/
- option: function(key, value) {
- this.options[key] = value;
- if(key in this.containerOptions) {
- this.containerOptions[key] = value;
- this.setContainerOption(key, value);
- } else {
- this.formOptions[key] = value;
- if(this.$form) {
- this.$form.editableform('option', key, value);
- }
- }
- },
-
- setContainerOption: function(key, value) {
- this.call('option', key, value);
- },
-
- /**
- Destroys the container instance
- @method destroy()
- **/
- destroy: function() {
- this.hide();
- this.innerDestroy();
- this.$element.off('destroyed');
- this.$element.removeData('editableContainer');
- },
-
- /* to be overwritten in child classes */
- innerDestroy: function() {
-
- },
-
- /*
- Closes other containers except one related to passed element.
- Other containers can be cancelled or submitted (depends on onblur option)
- */
- closeOthers: function(element) {
- $('.editable-open').each(function(i, el){
- //do nothing with passed element and it's children
- if(el === element || $(el).find(element).length) {
- return;
- }
-
- //otherwise cancel or submit all open containers
- var $el = $(el),
- ec = $el.data('editableContainer');
-
- if(!ec) {
- return;
- }
-
- if(ec.options.onblur === 'cancel') {
- $el.data('editableContainer').hide('onblur');
- } else if(ec.options.onblur === 'submit') {
- $el.data('editableContainer').tip().find('form').submit();
- }
- });
-
- },
-
- /**
- Activates input of visible container (e.g. set focus)
- @method activate()
- **/
- activate: function() {
- if(this.tip && this.tip().is(':visible') && this.$form) {
- this.$form.data('editableform').input.activate();
- }
- }
-
- };
-
- /**
- jQuery method to initialize editableContainer.
-
- @method $().editableContainer(options)
- @params {Object} options
- @example
- $('#edit').editableContainer({
- type: 'text',
- url: '/post',
- pk: 1,
- value: 'hello'
- });
- **/
- $.fn.editableContainer = function (option) {
- var args = arguments;
- return this.each(function () {
- var $this = $(this),
- dataKey = 'editableContainer',
- data = $this.data(dataKey),
- options = typeof option === 'object' && option,
- Constructor = (options.mode === 'inline') ? Inline : Popup;
-
- if (!data) {
- $this.data(dataKey, (data = new Constructor(this, options)));
- }
-
- if (typeof option === 'string') { //call method
- data[option].apply(data, Array.prototype.slice.call(args, 1));
- }
- });
- };
-
- //store constructors
- $.fn.editableContainer.Popup = Popup;
- $.fn.editableContainer.Inline = Inline;
-
- //defaults
- $.fn.editableContainer.defaults = {
- /**
- Initial value of form input
-
- @property value
- @type mixed
- @default null
- @private
- **/
- value: null,
- /**
- Placement of container relative to element. Can be top|right|bottom|left
. Not used for inline container.
-
- @property placement
- @type string
- @default 'top'
- **/
- placement: 'top',
- /**
- Whether to hide container on save/cancel.
-
- @property autohide
- @type boolean
- @default true
- @private
- **/
- autohide: true,
- /**
- Action when user clicks outside the container. Can be cancel|submit|ignore
.
- Setting ignore
allows to have several containers open.
-
- @property onblur
- @type string
- @default 'cancel'
- @since 1.1.1
- **/
- onblur: 'cancel',
-
- /**
- Animation speed (inline mode only)
- @property anim
- @type string
- @default false
- **/
- anim: false,
-
- /**
- Mode of editable, can be `popup` or `inline`
-
- @property mode
- @type string
- @default 'popup'
- @since 1.4.0
- **/
- mode: 'popup'
- };
-
- /*
- * workaround to have 'destroyed' event to destroy popover when element is destroyed
- * see http://stackoverflow.com/questions/2200494/jquery-trigger-event-when-an-element-is-removed-from-the-dom
- */
- jQuery.event.special.destroyed = {
- remove: function(o) {
- if (o.handler) {
- o.handler();
- }
- }
- };
-
-}(window.jQuery));
-
-/**
-* Editable Inline
-* ---------------------
-*/
-(function ($) {
- "use strict";
-
- //copy prototype from EditableContainer
- //extend methods
- $.extend($.fn.editableContainer.Inline.prototype, $.fn.editableContainer.Popup.prototype, {
- containerName: 'editableform',
- innerCss: '.editable-inline',
- containerClass: 'editable-container editable-inline', //css class applied to container element
-
- initContainer: function(){
- //container is element
- this.$tip = $('');
-
- //convert anim to miliseconds (int)
- if(!this.options.anim) {
- this.options.anim = 0;
- }
- },
-
- splitOptions: function() {
- //all options are passed to form
- this.containerOptions = {};
- this.formOptions = this.options;
- },
-
- tip: function() {
- return this.$tip;
- },
-
- innerShow: function () {
- this.$element.hide();
- this.tip().insertAfter(this.$element).show();
- },
-
- innerHide: function () {
- this.$tip.hide(this.options.anim, $.proxy(function() {
- this.$element.show();
- this.innerDestroy();
- }, this));
- },
-
- innerDestroy: function() {
- if(this.tip()) {
- this.tip().empty().remove();
- }
- }
- });
-
-}(window.jQuery));
-/**
-Makes editable any HTML element on the page. Applied as jQuery method.
-
-@class editable
-@uses editableContainer
-**/
-(function ($) {
- "use strict";
-
- var Editable = function (element, options) {
- this.$element = $(element);
- //data-* has more priority over js options: because dynamically created elements may change data-*
- this.options = $.extend({}, $.fn.editable.defaults, options, $.fn.editableutils.getConfigData(this.$element));
- if(this.options.selector) {
- this.initLive();
- } else {
- this.init();
- }
-
- //check for transition support
- if(this.options.highlight && !$.fn.editableutils.supportsTransitions()) {
- this.options.highlight = false;
- }
- };
-
- Editable.prototype = {
- constructor: Editable,
- init: function () {
- var isValueByText = false,
- doAutotext, finalize;
-
- //name
- this.options.name = this.options.name || this.$element.attr('id');
-
- //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;
- }
-
- //set value from settings or by element's text
- if (this.options.value === undefined || this.options.value === null) {
- this.value = this.input.html2value($.trim(this.$element.html()));
- isValueByText = true;
- } else {
- /*
- value can be string when received from 'data-value' attribute
- for complext objects value can be set as json string in data-value attribute,
- e.g. data-value="{city: 'Moscow', street: 'Lenina'}"
- */
- this.options.value = $.fn.editableutils.tryParseJson(this.options.value, true);
- if(typeof this.options.value === 'string') {
- this.value = this.input.str2value(this.options.value);
- } else {
- this.value = this.options.value;
- }
- }
-
- //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
- if(!this.options.disabled) {
- e.preventDefault();
- }
-
- //stop propagation not required because in document click handler it checks event target
- //e.stopPropagation();
-
- if(this.options.toggle === 'mouseenter') {
- //for hover only show container
- this.show();
- } else {
- //when toggle='click' we should not close all other containers as they will be closed automatically in document click listener
- var closeAll = (this.options.toggle !== 'click');
- this.toggle(closeAll);
- }
- }, this));
- } else {
- this.$element.attr('tabindex', -1); //do not stop focus on element when toggled manually
- }
-
- //if display is function it's far more convinient to have autotext = always to render correctly on init
- //see https://github.com/vitalets/x-editable-yii/issues/34
- if(typeof this.options.display === 'function') {
- this.options.autotext = 'always';
- }
-
- //check conditions for autotext:
- switch(this.options.autotext) {
- case 'always':
- doAutotext = true;
- break;
- 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();
- }
- /**
- Fired when element was initialized by `$().editable()` method.
- Please note that you should setup `init` handler **before** applying `editable`.
-
- @event init
- @param {Object} event event object
- @param {Object} editable editable instance (as here it cannot accessed via data('editable'))
- @since 1.2.0
- @example
- $('#username').on('init', function(e, editable) {
- alert('initialized ' + editable.options.name);
- });
- $('#username').editable();
- **/
- this.$element.triggerHandler('init', this);
- }, this));
- },
-
- /*
- Initializes parent element for live editables
- */
- initLive: function() {
- //store selector
- var selector = this.options.selector;
- //modify options for child elements
- this.options.selector = false;
- 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));
- },
-
- /*
- Renders value into element's text.
- Can call custom display method from options.
- Can return deferred object.
- @method render()
- @param {mixed} response server response (if exist) to pass into display function
- */
- render: function(response) {
- //do not display anything
- if(this.options.display === false) {
- return;
- }
-
- //if input has `value2htmlFinal` method, we pass callback in third param to be called when source is loaded
- if(this.input.value2htmlFinal) {
- return this.input.value2html(this.value, this.$element[0], this.options.display, response);
- //if display method defined --> use it
- } else if(typeof this.options.display === 'function') {
- return this.options.display.call(this.$element[0], this.value, response);
- //else use input's original value2html() method
- } else {
- return this.input.value2html(this.value, this.$element[0]);
- }
- },
-
- /**
- Enables editable
- @method enable()
- **/
- enable: function() {
- this.options.disabled = false;
- this.$element.removeClass('editable-disabled');
- this.handleEmpty(this.isEmpty);
- if(this.options.toggle !== 'manual') {
- if(this.$element.attr('tabindex') === '-1') {
- this.$element.removeAttr('tabindex');
- }
- }
- },
-
- /**
- Disables editable
- @method disable()
- **/
- disable: function() {
- this.options.disabled = true;
- this.hide();
- this.$element.addClass('editable-disabled');
- this.handleEmpty(this.isEmpty);
- //do not stop focus on this element
- this.$element.attr('tabindex', -1);
- },
-
- /**
- Toggles enabled / disabled state of editable element
- @method toggleDisabled()
- **/
- toggleDisabled: function() {
- if(this.options.disabled) {
- this.enable();
- } else {
- this.disable();
- }
- },
-
- /**
- Sets new option
-
- @method option(key, value)
- @param {string|object} key option name or object with several options
- @param {mixed} value option new value
- @example
- $('.editable').editable('option', 'pk', 2);
- **/
- option: function(key, value) {
- //set option(s) by object
- if(key && typeof key === 'object') {
- $.each(key, $.proxy(function(k, v){
- this.option($.trim(k), v);
- }, this));
- return;
- }
-
- //set option by string
- this.options[key] = value;
-
- //disabled
- if(key === 'disabled') {
- return value ? this.disable() : this.enable();
- }
-
- //value
- if(key === 'value') {
- this.setValue(value);
- }
-
- //transfer new option to container!
- if(this.container) {
- this.container.option(key, value);
- }
-
- //pass option to input directly (as it points to the same in form)
- if(this.input.option) {
- this.input.option(key, value);
- }
-
- },
-
- /*
- * set emptytext if element is empty
- */
- handleEmpty: function (isEmpty) {
- //do not handle empty if we do not display anything
- if(this.options.display === false) {
- return;
- }
-
- /*
- 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
- //for some inputs we need more smart check
- //e.g. wysihtml5 may have $(this).data('editable')
to access to editable instance
-
- @event save
- @param {Object} event event object
- @param {Object} params additional params
- @param {mixed} params.newValue submitted value
- @param {Object} params.response ajax response
- @example
- $('#username').on('save', function(e, params) {
- alert('Saved value: ' + params.newValue);
- });
- **/
- //event itself is triggered by editableContainer. Description here is only for documentation
- },
-
- validate: function () {
- if (typeof this.options.validate === 'function') {
- return this.options.validate.call(this, this.value);
- }
- },
-
- /**
- Sets new value of editable
- @method setValue(value, convertStr)
- @param {mixed} value new value
- @param {boolean} convertStr whether to convert value from string to internal format
- **/
- setValue: function(value, convertStr, response) {
- if(convertStr) {
- this.value = this.input.str2value(value);
- } else {
- this.value = value;
- }
- if(this.container) {
- this.container.option('value', this.value);
- }
- $.when(this.render(response))
- .then($.proxy(function() {
- this.handleEmpty();
- }, this));
- },
-
- /**
- Activates input of visible container (e.g. set focus)
- @method activate()
- **/
- activate: function() {
- if(this.container) {
- this.container.activate();
- }
- },
-
- /**
- Removes editable feature from element
- @method destroy()
- **/
- destroy: function() {
- this.disable();
-
- if(this.container) {
- this.container.destroy();
- }
-
- this.input.destroy();
-
- if(this.options.toggle !== 'manual') {
- this.$element.removeClass('editable-click');
- this.$element.off(this.options.toggle + '.editable');
- }
-
- this.$element.off("save.internal");
-
- this.$element.removeClass('editable editable-open editable-disabled');
- this.$element.removeData('editable');
- }
- };
-
- /* EDITABLE PLUGIN DEFINITION
- * ======================= */
-
- /**
- jQuery method to initialize editable element.
-
- @method $().editable(options)
- @params {Object} options
- @example
- $('#username').editable({
- type: 'text',
- url: '/post',
- pk: 1
- });
- **/
- $.fn.editable = function (option) {
- //special API methods returning non-jquery object
- var result = {}, args = arguments, datakey = 'editable';
- switch (option) {
- /**
- Runs client-side validation for all matched editables
-
- @method validate()
- @returns {Object} validation errors map
- @example
- $('#username, #fullname').editable('validate');
- // possible result:
- {
- username: "username is required",
- fullname: "fullname should be minimum 3 letters length"
- }
- **/
- case 'validate':
- this.each(function () {
- var $this = $(this), data = $this.data(datakey), error;
- if (data && (error = data.validate())) {
- result[data.options.name] = error;
- }
- });
- return result;
-
- /**
- Returns current values of editable elements.
- Note that it returns an **object** with name-value pairs, not a value itself. It allows to get data from several elements.
- If value of some editable is `null` or `undefined` it is excluded from result object.
- When param `isSingle` is set to **true** - it is supposed you have single element and will return value of editable instead of object.
-
- @method getValue()
- @param {bool} isSingle whether to return just value of single element
- @returns {Object} object of element names and values
- @example
- $('#username, #fullname').editable('getValue');
- //result:
- {
- username: "superuser",
- fullname: "John"
- }
- //isSingle = true
- $('#username').editable('getValue', true);
- //result "superuser"
- **/
- case 'getValue':
- if(arguments.length === 2 && arguments[1] === true) { //isSingle = true
- result = this.eq(0).data(datakey).value;
- } else {
- this.each(function () {
- var $this = $(this), data = $this.data(datakey);
- if (data && data.value !== undefined && data.value !== null) {
- result[data.options.name] = data.input.value2submit(data.value);
- }
- });
- }
- return result;
-
- /**
- This method collects values from several editable elements and submit them all to server.
- Internally it runs client-side validation for all fields and submits only in case of success.
- See creating new records for details.
- Since 1.5.1 `submit` can be applied to single element to send data programmatically. In that case
- `url`, `success` and `error` is taken from initial options and you can just call `$('#username').editable('submit')`.
-
- @method submit(options)
- @param {object} options
- @param {object} options.url url to submit data
- @param {object} options.data additional data to submit
- @param {object} options.ajaxOptions additional ajax options
- @param {function} options.error(obj) error handler
- @param {function} options.success(obj,config) success handler
- @returns {Object} jQuery object
- **/
- case 'submit': //collects value, validate and submit to server for creating new record
- var config = arguments[1] || {},
- $elems = this,
- errors = this.editable('validate');
-
- // validation ok
- if($.isEmptyObject(errors)) {
- var ajaxOptions = {};
-
- // for single element use url, success etc from options
- if($elems.length === 1) {
- var editable = $elems.data('editable');
- //standard params
- var params = {
- name: editable.options.name || '',
- value: editable.input.value2submit(editable.value),
- pk: (typeof editable.options.pk === 'function') ?
- editable.options.pk.call(editable.options.scope) :
- editable.options.pk
- };
-
- //additional params
- if(typeof editable.options.params === 'function') {
- params = editable.options.params.call(editable.options.scope, params);
- } else {
- //try parse json in single quotes (from data-params attribute)
- editable.options.params = $.fn.editableutils.tryParseJson(editable.options.params, true);
- $.extend(params, editable.options.params);
- }
-
- ajaxOptions = {
- url: editable.options.url,
- data: params,
- type: 'POST'
- };
-
- // use success / error from options
- config.success = config.success || editable.options.success;
- config.error = config.error || editable.options.error;
-
- // multiple elements
- } else {
- var values = this.editable('getValue');
-
- ajaxOptions = {
- url: config.url,
- data: values,
- type: 'POST'
- };
- }
-
- // ajax success callabck (response 200 OK)
- ajaxOptions.success = typeof config.success === 'function' ? function(response) {
- config.success.call($elems, response, config);
- } : $.noop;
-
- // ajax error callabck
- ajaxOptions.error = typeof config.error === 'function' ? function() {
- config.error.apply($elems, arguments);
- } : $.noop;
-
- // extend ajaxOptions
- if(config.ajaxOptions) {
- $.extend(ajaxOptions, config.ajaxOptions);
- }
-
- // extra data
- if(config.data) {
- $.extend(ajaxOptions.data, config.data);
- }
-
- // perform ajax request
- $.ajax(ajaxOptions);
- } else { //client-side validation error
- if(typeof config.error === 'function') {
- config.error.call($elems, errors);
- }
- }
- return this;
- }
-
- //return jquery object
- 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
- data[option].apply(data, Array.prototype.slice.call(args, 1));
- }
- });
- };
-
-
- $.fn.editable.defaults = {
- /**
- Type of input. Can be text|textarea|select|date|checklist
and more
-
- @property type
- @type string
- @default 'text'
- **/
- type: 'text',
- /**
- Sets disabled state of editable
-
- @property disabled
- @type boolean
- @default false
- **/
- disabled: false,
- /**
- How to toggle editable. Can be click|dblclick|mouseenter|manual
.
- When set to manual
you should manually call show/hide
methods of editable.
- **Note**: if you call show
or toggle
inside **click** handler of some DOM element,
- you need to apply e.stopPropagation()
because containers are being closed on any click on document.
-
- @example
- $('#edit-button').click(function(e) {
- e.stopPropagation();
- $('#username').editable('toggle');
- });
-
- @property toggle
- @type string
- @default 'click'
- **/
- toggle: 'click',
- /**
- Text shown when element is empty.
-
- @property emptytext
- @type string
- @default 'Empty'
- **/
- emptytext: 'Empty',
- /**
- Allows to automatically set element's text based on it's value. Can be auto|always|never
. Useful for select and date.
- For example, if dropdown list is {1: 'a', 2: 'b'}
and element's value set to 1
, it's html will be automatically set to 'a'
.
- auto
- text will be automatically set only if element is empty.
- always|never
- always(never) try to set element's text.
-
- @property autotext
- @type string
- @default 'auto'
- **/
- autotext: 'auto',
- /**
- 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
-
-
-
- @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:**_
-
- * `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:
-
- * `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
-
- To get currently selected items use `$.fn.editableutils.itemsByValue(value, sourceData)`.
-
- @property display
- @type function|boolean
- @default null
- @since 1.2.0
- @example
- display: function(value, sourceData) {
- //display checklist as comma-separated values
- var html = [],
- checked = $.fn.editableutils.itemsByValue(value, sourceData);
-
- if(checked.length) {
- $.each(checked, function(i, v) { html.push($.fn.editableutils.escape(v.text)); });
- $(this).html(html.join(', '));
- } else {
- $(this).empty();
- }
- }
- **/
- display: null,
- /**
- Css class applied when editable text is empty.
-
- @property emptyclass
- @type string
- @since 1.4.1
- @default editable-empty
- **/
- emptyclass: 'editable-empty',
- /**
- Css class applied when value was stored but not sent to server (`pk` is empty or `send = 'never'`).
- You may set it to `null` if you work with editables locally and submit them together.
-
- @property unsavedclass
- @type string
- @since 1.4.1
- @default editable-unsaved
- **/
- unsavedclass: 'editable-unsaved',
- /**
- 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 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
-
-
-
- **/
- 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));
-
-/**
-AbstractInput - base class for all editable inputs.
-It defines interface to be implemented by any input type.
-To create your own input you can inherit from this class.
-
-@class abstractinput
-**/
-(function ($) {
- "use strict";
-
- //types
- $.fn.editabletypes = {};
-
- var AbstractInput = function () { };
-
- AbstractInput.prototype = {
- /**
- Initializes input
-
- @method init()
- **/
- init: function(type, options, defaults) {
- this.type = type;
- this.options = $.extend({}, defaults, options);
- },
-
- /*
- this method called before render to init $tpl that is inserted in DOM
- */
- prerender: function() {
- this.$tpl = $(this.options.tpl); //whole tpl as jquery object
- this.$input = this.$tpl; //control itself, can be changed in render method
- this.$clear = null; //clear button
- this.error = null; //error message, if input cannot be rendered
- },
-
- /**
- Renders input from tpl. Can return jQuery deferred object.
- Can be overwritten in child objects
-
- @method render()
- **/
- render: function() {
-
- },
-
- /**
- Sets element's html by value.
-
- @method value2html(value, element)
- @param {mixed} value
- @param {DOMElement} element
- **/
- value2html: function(value, element) {
- $(element)[this.options.escape ? 'text' : 'html']($.trim(value));
- },
-
- /**
- Converts element's html to value
-
- @method html2value(html)
- @param {string} html
- @returns {mixed}
- **/
- html2value: function(html) {
- return $('true
and source is **string url** - results will be cached for fields with the same source.
- Usefull for editable column in grid to prevent extra requests.
-
- @property sourceCache
- @type boolean
- @default true
- @since 1.2.0
- **/
- sourceCache: true,
- /**
- Additional ajax options to be used in $.ajax() when loading list from server.
- Useful to send extra parameters (`data` key) or change request method (`type` key).
-
- @property sourceOptions
- @type object|function
- @default null
- @since 1.5.0
- **/
- sourceOptions: null
- });
-
- $.fn.editabletypes.list = List;
-
-}(window.jQuery));
-
-/**
-Text input
-
-@class text
-@extends abstractinput
-@final
-@example
-awesome
-
-**/
-(function ($) {
- "use strict";
-
- var Text = function (options) {
- this.init('text', options, Text.defaults);
- };
-
- $.fn.editableutils.inherit(Text, $.fn.editabletypes.abstractinput);
-
- $.extend(Text.prototype, {
- render: function() {
- this.renderClear();
- this.setClass();
- this.setAttr('placeholder');
- },
-
- activate: function() {
- if(this.$input.is(':visible')) {
- this.$input.focus();
- $.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) {
- this.$clear = $('');
- this.$input.after(this.$clear)
- .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));
- }
- },
-
- postrender: function() {
- /*
- //now `clear` is positioned via css
- if(this.$clear) {
- //can position clear button only here, when form is shown and height can be calculated
-// 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
- @default
- **/
- tpl: '',
- /**
- Placeholder attribute of input. Shown when input is empty.
-
- @property placeholder
- @type string
- @default null
- **/
- placeholder: null,
-
- /**
- Whether to show `clear` button
-
- @property clear
- @type boolean
- @default true
- **/
- clear: true
- });
-
- $.fn.editabletypes.text = Text;
-
-}(window.jQuery));
-
-/**
-Textarea input
-
-@class textarea
-@extends abstractinput
-@final
-@example
-awesome comment!
-
-**/
-(function ($) {
- "use strict";
-
- var Textarea = function (options) {
- this.init('textarea', options, Textarea.defaults);
- };
-
- $.fn.editableutils.inherit(Textarea, $.fn.editabletypes.abstractinput);
-
- $.extend(Textarea.prototype, {
- render: function () {
- this.setClass();
- this.setAttr('placeholder');
- this.setAttr('rows');
-
- //ctrl + enter
- this.$input.keydown(function (e) {
- if (e.ctrlKey && e.which === 13) {
- $(this).closest('form').submit();
- }
- });
- },
-
- //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] = $('