Destroy Bootstrap Popover with selector option - javascript

How do you destroy a Bootstrap popover created with a selector option? e.g.
$e.popover({
selector: 'mark',
trigger: 'hover',
container: "body",
});
If you then call $e.popover('destroy') you get an error.
I note that the Plugin function called by popover('destroy') is as follows:
function Plugin(option) {
return this.each(function () {
var $this = $(this)
var data = $this.data('bs.popover')
var options = typeof option == 'object' && option
var selector = options && options.selector
if (!data && option == 'destroy') return
if (selector) {
if (!data) $this.data('bs.popover', (data = {}))
if (!data[selector]) data[selector] = new Popover(this, options)
} else {
if (!data) $this.data('bs.popover',(data = new Popover(this, options)))
}
if (typeof option == 'string') data[option]() /// <<-- THIS ALWAYS FAILS
})
}
If you call $e.popover('destroy') the above line (clearly marked) always fails because it is calling data['destroy'], however the data will be an object like {mark: Popover}.
It should clearly be calling data['mark']['destroy'] but it is not immediately clear to me how this is supposed to happen.
One option is to create a string s = 'destroy' then add the selector property to the string, but it should be apparent that that is not the intended design.
Alternatively, one could call $e.data('bs.popover').mark.destroy(), but again I am not sure that's the intended design, and it's not documented anywhere I could find.
Here's a sample jsFiddle

As Matt commented, this is a Bootstrap bug in 3.3.1.

Related

How to check textarea vs CKEDITOR?

I need to capture last focused input and paste something in it later.
I've already managed to catch last focused HTML input field (using jQuery on focusin event) or CKEDITOR editor (using CKEDITOR API on focus event). Because I store this last object in one var lastFocusedInput (jQuery object or CKEDITOR editor object), now I need to determine if it is CKEDITOR or jQuery object, due to they have different methods to paste data in it.
Any ideas how to do this in a more sophisticated way than testing it like that:
function isjQueryObject(o)
{
return (o && (o instanceof jQuery || o.constructor.prototype.jquery));
}
function isCKEditorObject(o)
{
return (o && (typeof CKEDITOR !== undefined) && (typeof o.insertHtml !== undefined));
}
EDIT on 2018-03-29
In the meantime I've ended up with type testing as below due to the need of reuse in other areas of the code.
function TypeTester()
{
var result = function (test)
{
return test ? true : false;
};
// jQuery [object Function]
this.jQuery = function (o)
{
return result(o
&& (o instanceof jQuery || o.constructor.prototype.jquery)
);
};
// CKEDITOR [object Object]
this.CKEDITOR =
{
object: function (o)
{
return result(o
&& o.replaceClass === 'ckeditor'
);
},
instance: function (o)
{
return result(o
&& o.insertHtml !== undefined
&& o.insertText !== undefined
);
},
};
};
var isTypeOf = new TypeTester();
var lastFocusedInput = new Object(
{
object: null,
insert: function (content)
{
if (!this.object) return;
switch (true)
{
case isTypeOf.jQuery(this.object) :
this.object.insertAtCaret(content);
break;
case isTypeOf.CKEDITOR.instance(this.object) :
this.object.insertHtml(content);
break;
}
},
});
As you know the typeof object while storing then store it like
var lastFocusedInput= { type:'jQuery', theObject: theObjectToStore};
And access it like so
if(lastFocusedInput.type == 'jQuery'){
//get jquery object -> lastFocusedInput.theObject
}else{
//get CKEDITOR object -> lastFocusedInput.theObject
}
Or use two containers
If object to store is jQuery
var $lastFocusedInput = theObjectToStore;
var CKElastFocusedInput= null;
or vice versa
while accessing
if($lastFocusedInput){// use jquery API on object }
else{ // use CKEDITOR API on object }

Bootstrap Custom Plugin - Getter Method

I am trying to write a Bootstrap 3 Custom plugin for a Wizard. I am doing ok so far, but I am having difficulty creating a method to return the currently active page.
I would hope to do the following:
var activePage = $('#wizard').wizard("getActivePage"); // should return the page number
I have adapter the example found in the standard Bootstrap code:
From:
if (typeof option == 'string') data[option](args);
To:
if (typeof option == 'string') return data[option](args);
And have a function in the code of:
getActivePage() {
return this.currentPage;
}
However, it is returning a JQuery object rather than the page number.
Is there a way to do this or am I going about this all wrong?
Ok, After thinking about this for a bit longer, I realised that the default functionality of the Plugin function is the issue as it is returning the JQuery chain (apologies, the following uses some Typescript):
I originally had:
function Plugin(option, args) {
return this.each(function () {
var $this = $(this);
var data = $this.data('xyz.wizard');
var options = $.extend(option, Wizard.DEFAULTS) as WizardOptions;
if (!data) {
data = new Wizard(this, options);
$this.data('xyz.wizard', data);
}
if (typeof option === 'string') return data[option](args);
});
}
and then added a new part at the top:
function Plugin(option, args) {
// New Part - If option is a string, then call the named method and return the result
if (typeof option === 'string') {
var data = $(this).data('xyz.wizard');
if(data) {
return data[option](args);
}
}
// Otherwise do the default
return this.each(function () {
var $this = $(this);
var data = $this.data('xyz.wizard');
var options = $.extend(option, Wizard.DEFAULTS) as WizardOptions;
if (!data) {
data = new Wizard(this, options);
$this.data('xyz.wizard', data);
}
// if (typeof option === 'string') return data[option](args);
});
}
It does seem to work but I'm not sure if it is the correct approach.

Wrap backbone view events handlers or jQuery event handlers in try catch block

I want to do global event handling for reporting JavaScript errors. We have minified JS files in production so I'm attempting get it done with the help of sourcemap.
Unfortunately, uncaught errors (reported by the browser's top-level
error handler, window.onerror) do not currently include column numbers
in any current browser. The HTML5 Spec has been updated to require
this, so this may change in the near future.
Source : https://rollbar.com/docs/guides_sourcemaps/
So now I need to wrap backbone view events in try catch block. There should be generic way of extending Backbone.View. Probably somewhere at delegateEvents function.
Here is how finally I wrapped all jQuery event handlers in try-catch block.
// maintain a reference to the existing function
var oldOn = $.fn.on;
// ...before overwriting the jQuery extension point
$.fn.on = function(types, selector, data, fn, /*INTERNAL*/ one) {
// parameter correction for backward compatibility copied from `on` function of jQuery JavaScript Library v1.9.0
// Types can be a map of types/handlers
if (typeof types === "object") {
// ( types-Object, selector, data )
if (typeof selector !== "string") {
// ( types-Object, data )
data = data || selector;
selector = undefined;
}
for (type in types) {
this.on(type, selector, data, types[type], one);
}
return this;
}
if (data == null && fn == null) {
// ( types, fn )
fn = selector;
data = selector = undefined;
} else if (fn == null) {
if (typeof selector === "string") {
// ( types, selector, fn )
fn = data;
data = undefined;
} else {
// ( types, data, fn )
fn = data;
data = selector;
selector = undefined;
}
}
if (fn === false) {
fn = returnFalse;
} else if (!fn) {
return this;
}
// ENDS - parameter correction for backward compatibility copied from `on` function of jQuery JavaScript Library v1.9.0
if (fn) {
var origFn = fn;
var wrappedFn = function() {
try {
origFn.apply(this, arguments);
} catch (e) {
//handle the error here.
}
};
fn = wrappedFn;
}
return oldOn.apply(this, [types, selector, data, fn, /*INTERNAL*/ one]);
};
UPDATE:
There was a bug that jQuery-UI droppable divs were sticking to mouse pointer like glue ;) instead of getting dropped and it was traced to this code as culprit.
So please make sure you wrap this extension with condition if(!(arguments.length === 4 && arguments[1] === null)) {}
Like this.
// maintain a reference to the existing function
var oldOn = $.fn.on;
// ...before overwriting the jQuery extension point
$.fn.on = function(types, selector, data, fn, /*INTERNAL*/ one) {
// We can ignore .bind() calls - they are passed from jquery-ui or other outdated components
if(!(arguments.length === 4 && arguments[1] === null)) {
//rest of the above code here.
}
}

Adding custom function to Bootstrap.js

I've recently run across a situation in which I'd like to change Bootstrap's default behavior at a fundamental level. I'd like to add a custom method to the Modal class so that a custom method may be called like any other stock Modal method:
$('#my-modal').modal('myMethod', myParameter);
I have this working by adding a function to the Modal's constructor:
$.fn.modal.Constructor.prototype.myMethod = function (myParameter) {
...
}
However, the myParameter variable is not being passed. How do I access/pass myParameter to a custom Bootstrap method?
There is no way of you doing this as-is. The code Model uses to call a function does not take parameters into consideration;
$.fn.modal = function (option) {
return this.each(function () {
var $this = $(this)
, data = $this.data('modal')
, options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('modal', (data = new Modal(this, options)))
if (typeof option == 'string') data[option]() // <-- here
else if (options.show) data.show()
})
}
Your best bet would be to add a method to $.fn, and then retrieve the Model instance via $(this).data('modal'), as that's where Bootstrap stores the instance;
$.fn.foo = function (param) {
return this.each(function () {
var model = $(this).data('modal');
// blah blah blah
});
}
I found a way to do this, although unfortunately it involves changes to the Bootstrap source. The piece of code that does the actual method calling is this:
$.fn.modal = function (option) {
return this.each(function () {
var $this = $(this)
, data = $this.data('modal')
, options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('modal', (data = new Modal(this, options)))
if (typeof option == 'string') data[option]()
else if (options.show) data.show()
})
}
To change this, the 7th line (line 206 in the source code) should be modified to pass any additional parameters that were originally passed to the enclosing function. In addition, the original arguments must be given to each iteration of jQuery's .each() function. Here's the working code:
$.fn.modal = function (option) {
return this.each(function () {
var $this = $(this)
, data = $this.data('modal')
, options = $.extend({}, $.fn.modal.defaults, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('modal', (data = new Modal(this, options)))
if (typeof option == 'string') data[option].apply($this.data('modal'), Array.prototype.slice.call(arguments, 1)); // pass the parameters on
else if (options.show) data.show()
}, arguments) // execute each iteration with the original parameters
}
I'm still experimenting to make sure this change doesn't create any undesirable side effects, but so far, everything works as expected. Any more elegant solutions would be welcome.

define public method that return data in twitter bootstrap plugin

How can I define public methods in twitter bootstrap plugin that return data?
For example I have this snippet:
var Button = function (element, options) {
this.$element = $(element)
this.options = $.extend({}, $.fn.button.defaults, options)
}
/* BUTTON PLUGIN DEFINITION
* ======================== */
$.fn.button = function (option) {
return this.each(function () {
var $this = $(this)
, data = $this.data('button')
, options = typeof option == 'object' && option
if (!data) $this.data('button', (data = new Button(this, options)))
if (option == 'toggle') data.toggle()
else if (option) data.setState(option)
})
}
I would want something like this:
var text = $('button.general').button().getTextLabel();
It would return an array of text for each button that has .general class and plugin applied
The form that you want to put the query in ($('button.general').button().getTextLabel()) is going to apply the plugin on every element selected, so I don't know how useful that would be. Moreover, it seems unnecessary to hack the plugin source to add this functionality, since you will have to check if the element has the Button data anyway.
Rather I would suggest something along the lines of:
JavaScript
var text = $('button.general')
.filter(function () {
return $(this).data('button');
})
.map(function (i, el) {
return $(el).text();
})
For example, running the above on the Bootstrap Documentation page, using the selector .btn instead of button.general, yields the array
["Left", "Middle", "Right", "Left", "Middle", "Right"]

Categories