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.
Related
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 }
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.
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.
I am going to attempt develop a jQuery plugin and just wanted to get expert opinion on whether this is a good way to develop jQuery plugins.
I've done a lot of searching and have found many ways on doing jQuery plugins. I am surprised that these isn't a standard approach as most plugins seem to all be used in the same way.
I tried to follow a similar approach to the jQueryUI plugins whereby methods are called on elements by invoking $('selector').plugin('method1');
Any comments/critisms on my solution would be useful.
(function(plugin, $, undefined) {
var instances = [];
var defaultOptions = {
exampleOpt1: 1,
exampleOpt2: 2
};
var pluginClass = function($element,options) {
this.options = options;
}
pluginClass.prototype.method1 = function() {
console.log(this.options.exampleOpt1);
}
pluginClass.prototype.method2 = function(param2) {
console.log('Hello there '+param2);
}
$.fn.plugin = function() {
if ((arguments.length==0) || $.isPlainObject(arguments[0])) {
var opts = $.extend({}, defaultOptions, arguments[0]||{});
this.each(function(index, Element){
if (typeof $(Element).data('instanceIndex') == 'undefined') {
instances.push(new pluginClass($(Element).data('instanceIndex',instances.length),opts));
} else {
$.error('Attempting to initialise the element again.')
}
});
} else {
if (typeof pluginClass.prototype[arguments[0]] == 'undefined') {
$.error('Method '+arguments[0]+'() not defined.');
} else {
var iArguments = arguments; //arguments disappears in each() callback
this.each(function(index,Element){
if (typeof $(Element).data('instanceIndex') != 'undefined') {
instances[$(Element).data('instanceIndex')][iArguments[0]].apply(instances[$(Element).data('instanceIndex')],Array.prototype.slice.call(iArguments,1));
} else {
$.error('Cannot call method as element has not been initialised yet.');
}
});
}
}
return this;
}
}(window.plugin = window.plugin || {}, jQuery ));
I am trying to set a custom error handler for 3rd party plugins/modules in my core library, but somehow, myHandler does not alert the e.message.
Can somebody help me please? thank you
Function.prototype.setErrorHandler = function(f) {
if (!f) {
throw new Error('No function provided.');
}
var that = this;
var g = function() {
try {
var a = [];
for(var i=0; i<arguments.length; i++) {
a.push(arguments[i]);
}
that.apply(null,a);
}
catch(e) {
return f(e);
}
};
g.old = this;
return g;
};
function myHandler(e) {
alert(e.message)
};
// my Core library object
(function(){
if (typeof window.Core === 'undefined') {
var Core = window.Core = function() {
this.addPlugin = function(namespace, obj){
if (typeof this[namespace] === 'undefined') {
if (typeof obj === 'function') {
obj.setErrorHandler(myHandler);
} else if (!!obj && typeof obj === 'object') {
for (var o in obj) {
if (obj.hasOwnProperty(o) && typeof obj[o] === 'function') {
obj[o].setErrorHandler(myHandler);
}
}
}
this[namespace] = obj;
return true;
} else {
alert("The namespace '" + namespace + "' is already taken...");
//return false;
}
};
};
window.Core = new Core();
}
})();
// test plugin
(function(){
var myPlugin = {
init: function() {},
conf: function() {
return this.foo.x; // error here
}
};
Core.addPlugin("myPlugin", myPlugin);
})();
// test
Core.myPlugin.conf(); // supposed to alert(e.message) from myHandler()
setErrorHandler in the above code doesn't set an error handler on a Function, as such. JavaScript does not give you the ability to change the called code inside a Function object.
Instead it makes a wrapped version of the function it's called on, and returns it.
obj.setErrorHandler(myHandler);
Can't work as the returned wrapper function is thrown away, not assigned to anything.
You could say:
obj[o]= obj[o].setErrorHandler(myHandler);
though I'm a bit worried about the consequences of swapping out functions with different, wrapped versions. That won't necessarily work for all cases and could certainly confuse third-party code. At the least, you'd want to ensure you don't wrap functions twice, and also retain the call-time this value in the wrapper:
that.apply(this, a);
(Note: you don't need the manual conversion of arguments to an Array. It's valid to pass the arguments object directly to apply.)