I have a question of which someone might find this much simpler than I do, but alas, I don't have much experience with custom jQuery plugins.
The previous developer at my place of work left me with a lot of left-over plugins that don't seem to work very well, most which I've been able to fix but this which has been bugging me for a while.
It is a custom Multiple Suggestion plugin (called multisuggest) written in jQuery, and it has a set of functions that it uses internally (*e.g. setValue to set the value of the box, or lookup to update the search)*
It seems he's tried to call these plugin functions from an external script (this exteranl script specifically imports newly created suggestions into the multisuggest via user input and sets the value) like this:
this.$input.multisuggest('setValue', data.address.id, address);
This seems to call the function as it should, except the second and third parameters don't seem to be passed to the function (setValue receives nothing), and I don't understand how I can get it to pass these. It says it is undefined when I log it in the console. The functions are set out like this (I've only including the one I'm using and an internal function from multisuggest called select that actually works):
MultiSuggest.prototype = $.extend(MultiSuggest, _superproto, {
constructor : MultiSuggest,
select: function () { // When selecting an address from the suggestions
var active, display, val;
active = this.$menu.find('.active');
display = active.attr('data-display');
val = active.attr('data-value');
this.setValue(display, val, false); // This works, however when I do it as shown in the above example from an external script, it doesn't. This is because it doesn't receive the arguments.
},
setValue : function(display, value, newAddress) { // Setting the textbox value
console.log(display); // This returns undefined
console.log(value); // This returns undefined
if (display && display !== "" &&
value && value !== "") {
this.$element.val(this.updater(display)).change();
this.$hiddenInput.val(value);
this.$element.addClass("msuggest-selected");
}
if(newAddress === false){
return this.hide();
}
},
});
Why does it listen to the function, but not the values passed to it? Do I need to include an extra line of code somewhere to define these arguments?
Anyone with jQuery experience would be of great help! This is bottlenecking progress on a current project. Thanks for your time!
EDIT:
I've missed out the code of how the arguments are trying to be passed from the external script to the internal function of the plugin. Here is the plugin definition with how the external call is handled, can anyone see a problem with this?
$.fn.multisuggest = function(option) {
return this.each(function() {
var $this = $(this), data = $this.data('multisuggest'), options = typeof option === 'object' && option;
if (!data) {
$this.data('multisuggest', ( data = new MultiSuggest(this, options)));
} else if (typeof(option) === 'string') {
var method = data[option];
var parameters = Array.prototype.slice.call(arguments, 1);
method.apply(this, parameters);
}
});
};
The "usual" plugin supervisor looks like this :
// *****************************
// ***** Start: Supervisor *****
$.fn.multisuggest = function( method ) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || !method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist in jQuery.' + pluginName );
}
};
// ***** Fin: Supervisor *****
// ***************************
All the looping through this should be inside the method functions, not in the supervisor.
I'm a little worried that new MultiSuggest(...) appears in the current supervisor. That sort of thing is totally unconventional. The original author clearly had something in mind.
You need to extend the jQuery plugin function which is attached to $.fn['multisuggest'], that function is probably only taking and passing one parameter.
Related
I am trying to bind an argument for this to a function named Sequence the binding works, problem is more than one Sequence overwrites each other, so I have to use new here's the issue...
//js code
function init(cmd) {
cmd.exec.call(e,Sequence.bind(cmd));
}
Example
init({
exec:function(seq){
seq("a",function(){
console.log(this);// returns init object itself
});
}
});
Works great but when I do
//init for js above
...,function(seq) {
seq("a",function(){
console.log ("hello");
},document.getElementById("google"));
});
...,function(seq) {
seq("d",function(){
console.log("goodbye");
});
});
The second sequence is ran goodbye. never the first because it is being written over.
Sequence function
function Sequence(key, fn, location) {
if (!location) location = document;
var self = this; //object that is bound to Sequence
location.addEventListener("keydown", function sequenceMode(e) {
if(self.waiting)
{
if (keyCodes.literal[key.toUpperCase()] === e.which) {
fn.call(self,e);
self.waiting = false;
this.removeEventListener("keydown", sequenceMode);
}
} else location.removeEventListener("keydown", sequenceMode);
});
}
So my issue here is how do I A bind the this property to be the object calling Sequence or B how do I create a new instance of Sequence and still allow the user to define inside the function?
cmd.call(e,new Sequence().bind(cmd)); //can not call bind from Constructor
So basically I need to have the user still be able to define the arguments themselves for Sequence and this be bound to the object calling it. Any suggestions?
EDIT
http://jsbin.com/dulesejame
Not getting the same results so I'm overlooking my code now,
So I've edited the bin with my actual JavaScript. It's doing it now.
Open the developer panel to read console. Press ctrl+a then b, then press ctrl+b press a, doesn't show any so press b and it's running ctrl+a seq function.
One main issue I can tell you is using the same default object, by which your commands.cmd[combinator] will point to the same object which came last.
Make a copy of default before assigning
var def = Object.create(defaults);
for(var option in options)
{
if(option !== "executed" && option !== "called")
{
def[option] = options[option];
}
}
After realizing what I did wrong thank's to code-jaff which I'll accept his answer since he did give me the way to it. Except instead of using Object.create I just created a new Object literal.
var def = {};
for(var option in defaults)
{
if(options.hasOwnProperty(option) && option !== "executed" && option !== "called" && option !== "waiting")
{
def[option] = options[option];
} else {
def[option] = defaults[option];
}
}
Update:
Sorry for the lengthy stupid question. It was a problem with my selector. Ignore post!
I have a widget that is having public functions. that I normally am able to call like:
$("#mygrid").myGrid( "MyFunction", args );
Problem:
But in some cases the function is not getting called.
Here are some trivia
My widget is derived from another (base)widget I created.
The same function works when called for the base widget
The function is not overridden in the child widget.
The same function works for another child widget of the same parent
with the same piece of code I mentioned earlier except the name of
the widget and the DOM element.
Now, here are some important information.
A similar function in the problem widget works and when I stepped inside the call, I found a difference in both the calls in the following function in jquery-ui.js:
$.widget.bridge = function( name, object ) {
var fullName = object.prototype.widgetFullName || name;
$.fn[ name ] = function( options ) {
var isMethodCall = typeof options === "string",
args = slice.call( arguments, 1 ),
returnValue = this;
// allow multiple hashes to be passed on init
options = !isMethodCall && args.length ?
$.widget.extend.apply( null, [ options ].concat(args) ) :
options;
if ( isMethodCall ) {
this.each(function() {
var methodValue,
// Rest of the code.
Here, For my function that is not getting called, it won't enter in that this.each. when I checked the this pointer, there was a difference.
For the function that is getting called, the content of this is like the following:
0: div#myGrid
context: document
length: 1
selector: "#mygrid"
__proto__: Object[0]
But for the function that is NOT getting called, the first item and the length attribute was missing.
context: document
selector: "#mygrid"
__proto__: Object[0]
What Could be the problem?
Hello everyone.
I am trying to develop a Jquery plugin following the steps I found in http://docs.jquery.com/Plugins/Authoring and I seem to have problems reaching the caller object (the “this” variable) inside the options passed to the plugin. It is a plugin that I just want to use to make a button have a “blink” effect.
I would like to be able to pass the functions to execute in “show/hide” (or link blink-on, blink-off, if you prefer) as an option for the plugin. Let's say the user wants to achieve the “blinking” effect by hiding/showing the whole button every 1000 milliseconds. Then I would like the options to be something like:
$("#bttnOk").myBlinker ({
blinkHide: function(){$(this).hide();},
blinkShow: function(){ $(this).show();},
interval:1000
});
// … //
// And to make it actually blink:
$("#bttnOk").myBlinker ("blink");
Or let's say that the user wants to move the button up and down applying an inline css sytle every 200ms. Then the options would something like:
$("#bttnOk").myBlinker ({
blinkHide: function(){$(this).css(“margin-top: 10px”);},
blinkShow: function(){ $(this).css(“margin-top: 0px”);},
interval:200
});
The problem is that I seem to lose the reference to “$(this)” when I am inside the options. When the plugin reaches the blinkHide/blinkShow functions, “this” is the whole DOM window, not the button $(“#bttnOk”) my “myBlinker” plugin is attached to.
This is the first Jquery plugin I'm trying to write so I'm not even sure if there's a way to achieve what I'm trying to do.
My plugin code follows the following structure:
(function($){
var defaultOptions = {
interval: 500
}
var methods = {
init : function( options ) {
return this.each(function(){
this.options = {}
$.extend(this.options, defaultOptions, options);
var $this = $(this);
var data = $this.data('myBlinker');
// If the plugin hasn't been initialized yet
if ( ! data ) {
$this.data('myBlinker', {
on : true
});
}
});
},
destroy : function( ) { // Some code here},
blink: function ( ){
console.log("Blinking!. This: " + this);
var current = 0;
var button=this.get(0);
setInterval(function() {
if (current == 0){
button.options["blinkShow"].call(this);
current=1;
} else {
button.options["blinkHide"].call(this);
current=0;
}
}, button.options["interval"]);
}
};
$.fn. myBlinker = function( method ) {
// Method calling logic
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.myBlinker ' );
return null;
}
};
})(jQuery);
Any idea, correction, link or tip will be appreciated.
Thank you.
Within the setInterval function, this is the global object, not the current element DOMElement like in the blink function.
A solution to that is to save a reference of this and use this saved reference in the setInterval:
blink: function ( ){
// save a reference of 'this'
var that = this;
setInterval(function() {
// use the saved reference instead of 'this'
button.options["blinkShow"].call(that);
}, button.options["interval"]);
}
DEMO
I've got a rather large plugin that I am currently writing in jQuery which is using a lot of internal functions that can accept varying arguments depending on the function.
I caught myself constantly writing the following in every function to stop the code from running if an argument hasn't been supplied or isn't valid:
add : function(args) {
if (args===undefined) return;
// function code;
},...
I was hoping that in a DRY type of sense it would be a good idea to write a little internal helper function that would do this for me.
Is this actually a good idea and most importantly what is the best/secure way to check for a varied range of acceptable arguments?
There are a lot of functions with multiple arguments in this plugin, for example:
load : function( filename , path , excludeFromRandom , callback ) {}
where filename is a string,
path is a string,
excludeFromRandom is a boolean and
callback can be a function or a string.
What is a good way to check for the existence and validity of these types of arguments without rewriting the same code over and over?
Any suggestions and ideas would be great.
Thanks for reading.
It depends to what extent you want to do this. In idea would be to create a validation function which takes a argument -> rule mapping. E.g.:
function foo(somestring, somenumber) {
var rules = {
'somestring': Validator.rules.isString,
'somenumber': Validator.rules.inRange(5,10);
};
}
Validator would contain the basic logic and some helper functions (rules):
var Validator = {
valid: function(args, rules) {
for(var name in rules) {
if(!rules[name](args[name])) {
return false;
}
}
return true;
},
rules: {
isString: function(arg) {
return (typeof arg === 'string');
},
inRange: function(x,y) {
return function(arg) {
return !isNaN(+arg) && x <= arg && arg <= y;
}
}
}
}
This is just a sketch, it certainly can be extended (like accepting multiple rules per argument), but it should give you some idea.
That said, you don't have to check every argument. Provide decent documentation. If people use your plugin in a wrong way, i.e. passing wrong argument types, then your code will throw an error anyway.
Update:
If want to do this very often, then a good idea is to write a wrapper function and you just pass the function and the rules to it:
function ensure(func, rules, context) {
context = context || this;
return function() {
if(Validator.valid(arguments, rules)) {
return func.apply(context, arguments);
}
return null; // or throw error, whatever you want
}
}
Then you can define your function normally as:
var foo = function(somestring, somenumber) {
// ...
};
and just add validation to it:
var rules = {...};
foo = ensure(foo, rules);
You could even consider to make ensure accept a callback which gets called on error or success of the function, instead of returning a value. There are a lot of possibilities.
First of all I don't know how to phrase the question "title", sorry if I am confusing everyone with the title here.
Anyway, I saw this code at jQuery http://docs.jquery.com/Plugins/Authoring
(function( $ ){
var methods = {
init : function( options ) {
return this.each(function(){
var $this = $(this),
data = $this.data('tooltip'),
tooltip = $('<div />', {
text : $this.attr('title')
});
// If the plugin hasn't been initialized yet
if ( ! data ) {
/*
Do more setup stuff here
*/
$(this).data('tooltip', {
target : $this,
tooltip : tooltip
});
}
});
},
destroy : function( ) {
return this.each(function(){
var $this = $(this),
data = $this.data('tooltip');
// Namespacing FTW
$(window).unbind('.tooltip');
data.tooltip.remove();
$this.removeData('tooltip');
})
},
reposition : function( ) { // ... },
show : function( ) { // ... },
hide : function( ) { // ... },
update : function( content ) { // ...}
};
$.fn.tooltip = function( method ) {
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.tooltip' );
}
};
})( jQuery );
My question being is that I cannot understand why do we need this if statement?
if ( methods[method] ) {
return methods[method].apply( this, Array.prototype.slice.call( arguments, 1 ));
}
Or in other words, in what scenario that we will pass in argument like "methods[method]" base on the example?
Thanks!
That if statement will check if you are trying to call one of the methods available to the plugin. In the case of you example you have these methods:
init, destroy, reposition, show, hide, and update
So you can do a call like :
$.tooltip('init', { arg1: true, arg2: 'a value' });
Then your code knows where to send the arguments because this if statement will be true:
if(methods['init'])
You see at the beginning that the code defines an object methods.
The function $.fn.tooltip = function( method ) accepts an argument with name method (no s at the end).
The function will execute one of the methods defined in methods, but it can only do it, if this method is also available. Hence the if(methods[method]).
The expression will be true if method is e.g. show, hide, update, etc, i.e. if the methods object has a property with the name contained in method.
Therefore the expression will be false for foo or bar. If the if statement would not be there, the code would try to call method['foo'], which does not exist and you would get an error:
TypeError: object is not a function
Is this what you wanted to know?
Your code snippet isn't complete and it doesn't contain a demo to show how it's called, so it's hard to give a definite answer.
However, here's what I think from what the code looks like:
The if statement is necessary because the tooltip function will be called with arguments such as init, destroy, show, hide, update, which refer to the functions defined in the methods hash. You probably call tooltip with init to initialize the tooltip, hide to hide it, show to show it etc. If you don't pass an argument at all, it defaults to the init method and initializes the tooltip (second branch of the if).
First of all, the piece of code declares an hashmap named methods which contains some functions.
Then, the second part declares a function named tooltip which takes a parameter named method. This parameter is the name of the function we want to call, this name is the index of this function in the methods array.
So, when you do $('#whatever').tooltip('destroy'); it will look in the methods array for the function referenced with the destroy key.