datepicker working
This commit is contained in:
@@ -1,22 +1,22 @@
|
||||
/**
|
||||
Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br>
|
||||
This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>
|
||||
Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.<br>
|
||||
Applied as jQuery method.
|
||||
Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.<br>
|
||||
This method applied internally in <code>$().editable()</code>. You should subscribe on it's events (save / cancel) to get profit of it.<br>
|
||||
Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.<br>
|
||||
Applied as jQuery method.
|
||||
|
||||
@class editableContainer
|
||||
@uses editableform
|
||||
**/
|
||||
@class editableContainer
|
||||
@uses editableform
|
||||
**/
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
var Popup = function (element, options) {
|
||||
this.init(element, options);
|
||||
};
|
||||
|
||||
|
||||
var Inline = function (element, options) {
|
||||
this.init(element, options);
|
||||
};
|
||||
};
|
||||
|
||||
//methods
|
||||
Popup.prototype = {
|
||||
@@ -25,98 +25,104 @@ Applied as jQuery method.
|
||||
innerCss: null, //tbd in child class
|
||||
containerClass: 'editable-container editable-popup', //css class applied to container element
|
||||
defaults: {}, //container itself defaults
|
||||
|
||||
|
||||
init: function(element, options) {
|
||||
this.$element = $(element);
|
||||
//since 1.4.1 container do not use data-* directly as they already merged into options.
|
||||
this.options = $.extend({}, $.fn.editableContainer.defaults, options);
|
||||
this.options = $.extend({}, $.fn.editableContainer.defaults, options);
|
||||
this.splitOptions();
|
||||
|
||||
|
||||
//set scope of form callbacks to element
|
||||
this.formOptions.scope = this.$element[0];
|
||||
|
||||
this.formOptions.scope = this.$element[0];
|
||||
|
||||
this.initContainer();
|
||||
|
||||
|
||||
//flag to hide container, when saving value will finish
|
||||
this.delayedHide = false;
|
||||
|
||||
//bind 'destroyed' listener to destroy container when element is removed from dom
|
||||
this.$element.on('destroyed', $.proxy(function(){
|
||||
this.destroy();
|
||||
}, this));
|
||||
|
||||
}, this));
|
||||
|
||||
//attach document handler to close containers on click / escape
|
||||
if(!$(document).data('editable-handlers-attached')) {
|
||||
//close all on escape
|
||||
$(document).on('keyup.editable', function (e) {
|
||||
if (e.which === 27) {
|
||||
$('.editable-open').editableContainer('hide');
|
||||
//todo: return focus on element
|
||||
$('.editable-open').editableContainer('hide', 'cancel');
|
||||
//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',
|
||||
'.datepicker', //in inline mode datepicker is rendered into body
|
||||
'.modal-backdrop',
|
||||
'.bootstrap-wysihtml5-insert-image-modal',
|
||||
'.bootstrap-wysihtml5-insert-link-modal'
|
||||
];
|
||||
|
||||
exclude_classes = ['.editable-container',
|
||||
'.ui-datepicker-header',
|
||||
'.datepicker', //in inline mode datepicker is rendered into body
|
||||
'.modal-backdrop',
|
||||
'.bootstrap-wysihtml5-insert-image-modal',
|
||||
'.bootstrap-wysihtml5-insert-link-modal'
|
||||
];
|
||||
|
||||
// select2 has extra body click in IE
|
||||
// see: https://github.com/ivaynberg/select2/issues/1058
|
||||
if ($('.select2-drop-mask').is(':visible')) {
|
||||
return;
|
||||
}
|
||||
|
||||
//check if element is detached. It occurs when clicking in bootstrap datepicker
|
||||
if (!$.contains(document.documentElement, e.target)) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
//for some reason FF 20 generates extra event (click) in select2 widget with e.target = document
|
||||
//we need to filter it via construction below. See https://github.com/vitalets/x-editable/issues/199
|
||||
//Possibly related to http://stackoverflow.com/questions/10119793/why-does-firefox-react-differently-from-webkit-and-ie-to-click-event-on-selec
|
||||
if($target.is(document)) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
//if click inside one of exclude classes --> no nothing
|
||||
for(i=0; i<exclude_classes.length; i++) {
|
||||
if($target.is(exclude_classes[i]) || $target.parents(exclude_classes[i]).length) {
|
||||
return;
|
||||
}
|
||||
if($target.is(exclude_classes[i]) || $target.parents(exclude_classes[i]).length) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//close all open containers (except one - target)
|
||||
Popup.prototype.closeOthers(e.target);
|
||||
});
|
||||
|
||||
|
||||
$(document).data('editable-handlers-attached', true);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
//split options on containerOptions and formOptions
|
||||
splitOptions: function() {
|
||||
this.containerOptions = {};
|
||||
this.formOptions = {};
|
||||
|
||||
|
||||
if(!$.fn[this.containerName]) {
|
||||
throw new Error(this.containerName + ' not found. Have you included corresponding js file?');
|
||||
throw new Error(this.containerName + ' not found. Have you included corresponding js file?');
|
||||
}
|
||||
|
||||
|
||||
//keys defined in container defaults go to container, others go to form
|
||||
for(var k in this.options) {
|
||||
if(k in this.defaults) {
|
||||
this.containerOptions[k] = this.options[k];
|
||||
} else {
|
||||
this.formOptions[k] = this.options[k];
|
||||
}
|
||||
if(k in this.defaults) {
|
||||
this.containerOptions[k] = this.options[k];
|
||||
} else {
|
||||
this.formOptions[k] = this.options[k];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
/*
|
||||
Returns jquery object of container
|
||||
@method tip()
|
||||
*/
|
||||
*/
|
||||
tip: function() {
|
||||
return this.container() ? this.container().$tip : null;
|
||||
},
|
||||
@@ -135,239 +141,239 @@ Applied as jQuery method.
|
||||
return container;
|
||||
},
|
||||
|
||||
/* call native method of underlying container, e.g. this.$element.popover('method') */
|
||||
/* call native method of underlying container, e.g. this.$element.popover('method') */
|
||||
call: function() {
|
||||
this.$element[this.containerName].apply(this.$element, arguments);
|
||||
},
|
||||
|
||||
this.$element[this.containerName].apply(this.$element, arguments);
|
||||
},
|
||||
|
||||
initContainer: function(){
|
||||
this.call(this.containerOptions);
|
||||
},
|
||||
|
||||
renderForm: function() {
|
||||
this.$form
|
||||
.editableform(this.formOptions)
|
||||
.on({
|
||||
save: $.proxy(this.save, this), //click on submit button (value changed)
|
||||
nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)
|
||||
cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on cancel button
|
||||
show: $.proxy(function() {
|
||||
if(this.delayedHide) {
|
||||
this.hide(this.delayedHide.reason);
|
||||
this.delayedHide = false;
|
||||
} else {
|
||||
this.setPosition();
|
||||
}
|
||||
}, this), //re-position container every time form is shown (occurs each time after loading state)
|
||||
rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
|
||||
resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed
|
||||
rendered: $.proxy(function(){
|
||||
/**
|
||||
Fired when container is shown and form is rendered (for select will wait for loading dropdown options).
|
||||
**Note:** Bootstrap popover has own `shown` 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 shown
|
||||
@param {Object} event event object
|
||||
@example
|
||||
$('#username').on('shown', function(e, editable) {
|
||||
editable.input.$input.val('overwriting value of input..');
|
||||
});
|
||||
**/
|
||||
/*
|
||||
TODO: added second param mainly to distinguish from bootstrap's shown event. It's a hotfix that will be solved in future versions via namespaced events.
|
||||
*/
|
||||
this.$element.triggerHandler('shown', $(this.options.scope).data('editable'));
|
||||
}, this)
|
||||
})
|
||||
.editableform('render');
|
||||
},
|
||||
.editableform(this.formOptions)
|
||||
.on({
|
||||
save: $.proxy(this.save, this), //click on submit button (value changed)
|
||||
nochange: $.proxy(function(){ this.hide('nochange'); }, this), //click on submit button (value NOT changed)
|
||||
cancel: $.proxy(function(){ this.hide('cancel'); }, this), //click on cancel button
|
||||
show: $.proxy(function() {
|
||||
if(this.delayedHide) {
|
||||
this.hide(this.delayedHide.reason);
|
||||
this.delayedHide = false;
|
||||
} else {
|
||||
this.setPosition();
|
||||
}
|
||||
}, this), //re-position container every time form is shown (occurs each time after loading state)
|
||||
rendering: $.proxy(this.setPosition, this), //this allows to place container correctly when loading shown
|
||||
resize: $.proxy(this.setPosition, this), //this allows to re-position container when form size is changed
|
||||
rendered: $.proxy(function(){
|
||||
/**
|
||||
Fired when container is shown and form is rendered (for select will wait for loading dropdown options).
|
||||
**Note:** Bootstrap popover has own `shown` 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 shown
|
||||
@param {Object} event event object
|
||||
@example
|
||||
$('#username').on('shown', function(e, editable) {
|
||||
editable.input.$input.val('overwriting value of input..');
|
||||
});
|
||||
**/
|
||||
/*
|
||||
TODO: added second param mainly to distinguish from bootstrap's shown event. It's a hotfix that will be solved in future versions via namespaced events.
|
||||
*/
|
||||
this.$element.triggerHandler('shown', $(this.options.scope).data('editable'));
|
||||
}, this)
|
||||
})
|
||||
.editableform('render');
|
||||
},
|
||||
|
||||
/**
|
||||
Shows container with form
|
||||
@method show()
|
||||
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
||||
**/
|
||||
/* Note: poshytip owerwrites this method totally! */
|
||||
Shows container with form
|
||||
@method show()
|
||||
@param {boolean} closeAll Whether to close all other editable containers when showing this one. Default true.
|
||||
**/
|
||||
/* Note: poshytip owerwrites this method totally! */
|
||||
show: function (closeAll) {
|
||||
this.$element.addClass('editable-open');
|
||||
if(closeAll !== false) {
|
||||
//close all open containers (except this)
|
||||
this.closeOthers(this.$element[0]);
|
||||
this.closeOthers(this.$element[0]);
|
||||
}
|
||||
|
||||
|
||||
//show container itself
|
||||
this.innerShow();
|
||||
this.tip().addClass(this.containerClass);
|
||||
|
||||
/*
|
||||
Currently, form is re-rendered on every show.
|
||||
Currently, form is re-rendered on every show.
|
||||
The main reason is that we dont know, what will container do with content when closed:
|
||||
remove(), detach() or just hide() - it depends on container.
|
||||
|
||||
Detaching form itself before hide and re-insert before show is good solution,
|
||||
but visually it looks ugly --> container changes size before hide.
|
||||
*/
|
||||
|
||||
//if form already exist - delete previous data
|
||||
|
||||
Detaching form itself before hide and re-insert before show is good solution,
|
||||
but visually it looks ugly --> container changes size before hide.
|
||||
*/
|
||||
|
||||
//if form already exist - delete previous data
|
||||
if(this.$form) {
|
||||
//todo: destroy prev data!
|
||||
//this.$form.destroy();
|
||||
this.$form.remove();
|
||||
}
|
||||
|
||||
this.$form = $('<div>');
|
||||
|
||||
|
||||
//insert form into container body
|
||||
if(this.tip().is(this.innerCss)) {
|
||||
//for inline container
|
||||
this.tip().append(this.$form);
|
||||
this.tip().append(this.$form);
|
||||
} else {
|
||||
this.tip().find(this.innerCss).append(this.$form);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//render form
|
||||
this.renderForm();
|
||||
},
|
||||
|
||||
/**
|
||||
Hides container with form
|
||||
@method hide()
|
||||
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
|
||||
**/
|
||||
hide: function(reason) {
|
||||
Hides container with form
|
||||
@method hide()
|
||||
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|undefined (=manual)</code>
|
||||
**/
|
||||
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;
|
||||
return;
|
||||
} else {
|
||||
this.delayedHide = false;
|
||||
}
|
||||
|
||||
this.$element.removeClass('editable-open');
|
||||
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.
|
||||
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 <code>save|cancel|onblur|nochange|manual</code>
|
||||
@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');
|
||||
@event hidden
|
||||
@param {object} event event object
|
||||
@param {string} reason Reason caused hiding. Can be <code>save|cancel|onblur|nochange|manual</code>
|
||||
@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.
|
||||
**/
|
||||
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 <code>$(this).data('editableContainer')</code> 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!');
|
||||
}
|
||||
});
|
||||
**/
|
||||
/**
|
||||
Fired when new value was submitted. You can use <code>$(this).data('editableContainer')</code> 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
|
||||
**/
|
||||
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);
|
||||
this.setContainerOption(key, value);
|
||||
} else {
|
||||
this.formOptions[key] = value;
|
||||
if(this.$form) {
|
||||
this.$form.editableform('option', key, value);
|
||||
this.$form.editableform('option', key, value);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
|
||||
setContainerOption: function(key, value) {
|
||||
this.call('option', key, value);
|
||||
},
|
||||
|
||||
/**
|
||||
Destroys the container instance
|
||||
@method destroy()
|
||||
**/
|
||||
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.
|
||||
Closes other containers except one related to passed element.
|
||||
Other containers can be cancelled or submitted (depends on onblur option)
|
||||
*/
|
||||
closeOthers: function(element) {
|
||||
@@ -377,14 +383,14 @@ Applied as jQuery method.
|
||||
return;
|
||||
}
|
||||
|
||||
//otherwise cancel or submit all open containers
|
||||
//otherwise cancel or submit all open containers
|
||||
var $el = $(el),
|
||||
ec = $el.data('editableContainer');
|
||||
ec = $el.data('editableContainer');
|
||||
|
||||
if(!ec) {
|
||||
return;
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if(ec.options.onblur === 'cancel') {
|
||||
$el.data('editableContainer').hide('onblur');
|
||||
} else if(ec.options.onblur === 'submit') {
|
||||
@@ -393,50 +399,50 @@ Applied as jQuery method.
|
||||
});
|
||||
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
Activates input of visible container (e.g. set focus)
|
||||
@method activate()
|
||||
**/
|
||||
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();
|
||||
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'
|
||||
});
|
||||
**/
|
||||
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;
|
||||
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
|
||||
if (typeof option === 'string') { //call method
|
||||
data[option].apply(data, Array.prototype.slice.call(args, 1));
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
};
|
||||
|
||||
//store constructors
|
||||
$.fn.editableContainer.Popup = Popup;
|
||||
@@ -445,62 +451,62 @@ Applied as jQuery method.
|
||||
//defaults
|
||||
$.fn.editableContainer.defaults = {
|
||||
/**
|
||||
Initial value of form input
|
||||
Initial value of form input
|
||||
|
||||
@property value
|
||||
@type mixed
|
||||
@default null
|
||||
@private
|
||||
**/
|
||||
@property value
|
||||
@type mixed
|
||||
@default null
|
||||
@private
|
||||
**/
|
||||
value: null,
|
||||
/**
|
||||
Placement of container relative to element. Can be <code>top|right|bottom|left</code>. Not used for inline container.
|
||||
Placement of container relative to element. Can be <code>top|right|bottom|left</code>. Not used for inline container.
|
||||
|
||||
@property placement
|
||||
@type string
|
||||
@default 'top'
|
||||
**/
|
||||
@property placement
|
||||
@type string
|
||||
@default 'top'
|
||||
**/
|
||||
placement: 'top',
|
||||
/**
|
||||
Whether to hide container on save/cancel.
|
||||
Whether to hide container on save/cancel.
|
||||
|
||||
@property autohide
|
||||
@type boolean
|
||||
@default true
|
||||
@private
|
||||
**/
|
||||
@property autohide
|
||||
@type boolean
|
||||
@default true
|
||||
@private
|
||||
**/
|
||||
autohide: true,
|
||||
/**
|
||||
Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>.
|
||||
Setting <code>ignore</code> allows to have several containers open.
|
||||
Action when user clicks outside the container. Can be <code>cancel|submit|ignore</code>.
|
||||
Setting <code>ignore</code> allows to have several containers open.
|
||||
|
||||
@property onblur
|
||||
@type string
|
||||
@default 'cancel'
|
||||
@since 1.1.1
|
||||
**/
|
||||
@property onblur
|
||||
@type string
|
||||
@default 'cancel'
|
||||
@since 1.1.1
|
||||
**/
|
||||
onblur: 'cancel',
|
||||
|
||||
|
||||
/**
|
||||
Animation speed (inline mode only)
|
||||
@property anim
|
||||
@type string
|
||||
@default false
|
||||
**/
|
||||
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'
|
||||
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
|
||||
*/
|
||||
@@ -510,6 +516,6 @@ Applied as jQuery method.
|
||||
o.handler();
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
}(window.jQuery));
|
||||
}(window.jQuery));
|
@@ -1,123 +1,79 @@
|
||||
/*
|
||||
* Editable Popover (for Bootstrap 5, No jQuery)
|
||||
/**
|
||||
* Editable Popover for Bootstrap 5 based on Popper.js
|
||||
* ---------------------
|
||||
* requires bootstrap-popover.js
|
||||
*/
|
||||
import { Popover } from "bootstrap";
|
||||
|
||||
class EditablePopover {
|
||||
constructor(element, options = {}) {
|
||||
this.element = element;
|
||||
this.options = Object.assign({
|
||||
placement: "top",
|
||||
trigger: "manual",
|
||||
content: " ",
|
||||
container: "body"
|
||||
}, options);
|
||||
(function ($) {
|
||||
"use strict";
|
||||
|
||||
this.popoverInstance = null;
|
||||
this.initPopover();
|
||||
}
|
||||
//extend methods
|
||||
$.extend($.fn.editableContainer.Popup.prototype, {
|
||||
containerName: 'popover',
|
||||
containerDataName: 'bs.popover',
|
||||
innerCss: '.popover-body',
|
||||
defaults: Popover.Default,
|
||||
|
||||
initPopover() {
|
||||
this.popoverInstance = new Popover(this.element, {
|
||||
placement: this.options.placement,
|
||||
trigger: this.options.trigger,
|
||||
content: this.options.content,
|
||||
html: true
|
||||
});
|
||||
initContainer: function(){
|
||||
$.extend(this.containerOptions, {
|
||||
trigger: 'manual',
|
||||
selector: false,
|
||||
content: ' ',
|
||||
template: this.defaults.template
|
||||
});
|
||||
|
||||
if (this.element.dataset.template) {
|
||||
this.template = this.element.dataset.template;
|
||||
this.element.removeAttribute("data-template");
|
||||
//as template property is used in inputs, hide it from popover
|
||||
var t;
|
||||
if(this.$element.data('template')) {
|
||||
t = this.$element.data('template');
|
||||
this.$element.removeData('template');
|
||||
}
|
||||
|
||||
this.call(this.containerOptions);
|
||||
|
||||
if(t) {
|
||||
//restore data('template')
|
||||
this.$element.data('template', t);
|
||||
}
|
||||
},
|
||||
|
||||
/* show */
|
||||
innerShow: function () {
|
||||
this.call('show');
|
||||
},
|
||||
|
||||
/* hide */
|
||||
innerHide: function () {
|
||||
this.call('hide');
|
||||
},
|
||||
|
||||
/* destroy */
|
||||
innerDestroy: function() {
|
||||
this.call('dispose');
|
||||
},
|
||||
|
||||
setContainerOption: function(key, value) {
|
||||
this.container().options[key] = value;
|
||||
},
|
||||
|
||||
setPosition: function () {
|
||||
(function() {}).call(this.container());
|
||||
},
|
||||
|
||||
call: function() {
|
||||
if ( ! $(this.$element).data(this.containerDataName)) {
|
||||
$(this.$element).data(this.containerDataName,
|
||||
Popover.getOrCreateInstance(this.$element, this.containerOptions)
|
||||
);
|
||||
}
|
||||
|
||||
return this.$element[this.containerName].apply(this.$element, arguments);
|
||||
},
|
||||
|
||||
tip: function() {
|
||||
return this.container() ? $(this.container().tip) : null;
|
||||
}
|
||||
});
|
||||
|
||||
this.show();
|
||||
}
|
||||
|
||||
show() {
|
||||
if (this.popoverInstance) {
|
||||
this.popoverInstance.show();
|
||||
}
|
||||
}
|
||||
|
||||
hide() {
|
||||
if (this.popoverInstance) {
|
||||
this.popoverInstance.hide();
|
||||
}
|
||||
}
|
||||
|
||||
destroy() {
|
||||
if (this.popoverInstance) {
|
||||
this.popoverInstance.dispose();
|
||||
this.popoverInstance = null;
|
||||
}
|
||||
}
|
||||
|
||||
setOption(key, value) {
|
||||
if (this.popoverInstance) {
|
||||
this.popoverInstance._config[key] = value;
|
||||
}
|
||||
}
|
||||
|
||||
setPosition() {
|
||||
if (!this.popoverInstance) {
|
||||
return;
|
||||
}
|
||||
|
||||
const tip = this.element.nextElementSibling; // Popover element
|
||||
if (!tip) {
|
||||
return;
|
||||
}
|
||||
|
||||
let placement = typeof this.options.placement === "function" ? this.options.placement(tip, this.element) : this.options.placement;
|
||||
|
||||
const autoToken = /\s?auto?\s?/i;
|
||||
const autoPlace = autoToken.test(placement);
|
||||
if (autoPlace) {
|
||||
placement = placement.replace(autoToken, "") || "top";
|
||||
}
|
||||
|
||||
const pos = this.element.getBoundingClientRect();
|
||||
const actualWidth = tip.offsetWidth;
|
||||
const actualHeight = tip.offsetHeight;
|
||||
|
||||
if (autoPlace) {
|
||||
const parent = this.element.parentElement || document.body;
|
||||
const docScroll = document.documentElement.scrollTop || document.body.scrollTop;
|
||||
const parentWidth = parent === document.body ? window.innerWidth : parent.offsetWidth;
|
||||
const parentHeight = parent === document.body ? window.innerHeight : parent.offsetHeight;
|
||||
const parentLeft = parent === document.body ? 0 : parent.getBoundingClientRect().left;
|
||||
|
||||
placement = placement === "bottom" && pos.top + pos.height + actualHeight - docScroll > parentHeight ? "top" :
|
||||
placement === "top" && pos.top - docScroll - actualHeight < 0 ? "bottom" :
|
||||
placement === "right" && pos.right + actualWidth > parentWidth ? "left" :
|
||||
placement === "left" && pos.left - actualWidth < parentLeft ? "right" :
|
||||
placement;
|
||||
|
||||
tip.classList.remove(...["top", "bottom", "left", "right"]);
|
||||
tip.classList.add(placement);
|
||||
}
|
||||
|
||||
const calculatedOffset = this.getCalculatedOffset(placement, pos, actualWidth, actualHeight);
|
||||
this.applyPlacement(tip, calculatedOffset, placement);
|
||||
}
|
||||
|
||||
getCalculatedOffset(placement, pos, actualWidth, actualHeight) {
|
||||
switch (placement) {
|
||||
case "bottom":
|
||||
return { top: pos.top + pos.height, left: pos.left + pos.width / 2 - actualWidth / 2 };
|
||||
case "top":
|
||||
return { top: pos.top - actualHeight, left: pos.left + pos.width / 2 - actualWidth / 2 };
|
||||
case "left":
|
||||
return { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left - actualWidth };
|
||||
case "right":
|
||||
return { top: pos.top + pos.height / 2 - actualHeight / 2, left: pos.left + pos.width };
|
||||
}
|
||||
}
|
||||
|
||||
applyPlacement(tip, offset, placement) {
|
||||
tip.style.top = `${offset.top}px`;
|
||||
tip.style.left = `${offset.left}px`;
|
||||
tip.classList.add("show", placement);
|
||||
}
|
||||
}
|
||||
|
||||
}(window.jQuery));
|
Reference in New Issue
Block a user