/**
Attaches stand-alone container with editable-form to HTML element. Element is used only for positioning, value is not stored anywhere.
This method applied internally in $().editable()
. You should subscribe on it's events (save / cancel) to get profit of it.
Final realization can be different: bootstrap-popover, jqueryui-tooltip, poshytip, inline-div. It depends on which js file you include.
Applied as jQuery method.
@class editableContainer
@uses editableform
**/
(function ($) {
var Popup = function (element, options) {
this.init(element, options);
};
var Inline = function (element, options) {
this.init(element, options);
};
//methods
Popup.prototype = {
containerName: null, //tbd in child class
innerCss: null, //tbd in child class
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.splitOptions();
//set scope of form callbacks to element
this.formOptions.scope = this.$element[0];
this.initContainer();
//bind 'destroyed' listener to destroy container when element is removed from dom
this.$element.on('destroyed', $.proxy(function(){
this.destroy();
}, 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
}
});
//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',
'.bootstrap-wysihtml5-insert-image-modal',
'.bootstrap-wysihtml5-insert-link-modal'];
//if click inside one of exclude classes --> no nothing
for(i=0; i');
//insert form into container body
if(this.tip().is(this.innerCss)) {
//for inline container
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 save|cancel|onblur|nochange|undefined (=manual)
**/
hide: function(reason) {
if(!this.tip() || !this.tip().is(':visible') || !this.$element.hasClass('editable-open')) {
return;
}
this.$element.removeClass('editable-open');
this.innerHide();
/**
Fired when container was hidden. It occurs on both save or cancel.
@event hidden
@param {object} event event object
@param {string} reason Reason caused hiding. Can be save|cancel|onblur|nochange|undefined (=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);
},
/* 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 od 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)
@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));