jQuery Plugin public methods not working when applied to multiple elements - javascript

I posted a similar issue earlier, but it was flagged as a duplicate. However, this referenced article did not answer my question, so I'll try this again, this time using the solution of said article in my example.
The solution provided in this article creates the same issue I had before: when there is more than one element, I cannot call any of the public methods of the plugin.
Since no working example was provided, let's start with the code the article gave:
(function($){
$.fn.myPlugin = function(options) {
// support multiple elements
if (this.length > 1){
this.each(function() { $(this).myPlugin(options) });
return this;
}
// private variables
var pOne = '';
var pTwo = '';
// ...
// private methods
var foo = function() {
// do something ...
}
// ...
// public methods
this.initialize = function() {
// do something ...
return this;
};
this.bar = function() {
// do something ...
};
return this.initialize();
}
})(jQuery);
I LOVE the internal loop so that it's applied to each instance of the element, but I feel the repeated "return this" is redundant. I think if we removed every single one of them, this plugin would work exactly the same. But, for the sake of argument, I'm going to leave them in my working example.
As you can see in this jsfiddle example, it works fine when there is only one element. The public method runs fine.
However, if I were to comment the other 4 elements back in like here, it throws an error in the console: "undefined is not a function". This, of course, makes sense since I'm attempting to run the public method on a reference to all elements on not an individual element.
Well, then I use .eq(0) to run the method only on the first instance of the element here, but I get the exact same error in the console.
So, why isn't calling the public method on the individual element working? Is this a scoping issue?
Please advise. Thanks!

Ok, so I think I've answered my own question. The issue is that I'm not applying a jQuery plugin to a DOM element. I'm applying it to a jQuery element. So, if I were to apply the jQuery plugin to a jQuery element, referenced like $element or $('.element'), I can then run any public methods because the scope is the same. But, if I were to reference it in a different way, like say $parentelement.eq(0), I'm using a difference reference, one that did not get the plugin applied to it, so naturally, it would not have the defined method. I think I'm getting the concept right. Still a little shaky on this. Maybe someone else can explain it better.
Nevertheless, while the above code does technically work, public methods are not practical on a jQuery plugin. I suggest instead using a Custom Event to make the jQuery plugin do something. Like this:
(function($) {
$.fn.extend({
myTestPlugin: function() {
if (this.length > 1) {
this.each(function() { $(this).myTestPlugin(); });
}
this.done = function() {
$(this).html('Done!');
};
var alsoDone = function() {
$(this).html('Also done!');
};
this.html('Replace me!');
this.on('alsoDone', alsoDone);
}
});
})(jQuery);
Here is an example where I am using trigger to make the plugin do something on an individual element, which works, but the method still fails as expected.
I hope this helps other people with similar issues.

Related

Passing correct jQuery selector to an object

I am writing a jQuery plugin which, ideally I would like in it's own namespace.
So far, this seems to work (in terms of namespace nesting)
(function($) {
$.fn.nspace = {
foo: function() {
// Does not work becuase $(this) is not the correct selector.
$(this).show();
}
}
})(jQuery);
So given then example above, I might call my function like so:
$("html, body").nspace.foo();
but $(this) is not [html, body]...How can I solve this?
EDIT: To clarify (based on user comments)...
$("html, body").nspace.foo(); should call foo for [html, body] but, $(this) inside nspace resolves to nspace...so it's trying to call nspace.foo();
You shouldn't do this, but just because I dislike when someone says "You can't" in programming (often untrue, especially in Javascript) - here's how you could do this:
The jQuery object is constructed each time using its prototype.init function, which is aliased to fn.init, so you could overwrite it with a wrapped function that adds your namespace object in a way that doesn't harm any existing usage or libraries, like so:
(function($) {
var baseInit = $.fn.init;
$.fn.init = function(selector, context, rootjQuery) {
// Instantiate jQuery the way it expects
var j = new baseInit(selector, context, rootjQuery);
// Add our extra object/namespace
// Use j inside to refer to the current jQuery object
j.nspace = {
foo: function() {
j.show();
}
};
// Return it all and other libraries are none the wiser
return j;
}
})(jQuery);
http://jsfiddle.net/b9chris/7TPZY/
You should consider using the classic pattern for a jQuery plugin: define only one method: in your case, nspace. Inside this method, you'll take every case into account. Sounds hard, but it's pretty easy once you've looked into that.
(By the way you definitely have to look at that when writing a jQuery plugin)
You can't add an object as a plugin and still get the jQuery object that was used to get the object. You simply have no reference to that jQuery object when you call a method in your object.
Put the function directly as the plugin:
(function($) {
$.fn.nspace = function() {
this.show();
};
})(jQuery);
Usage:
$("html, body").nspace();
(Note that the object is the jQuery instance, not a selector or an element, so you don't need to use $(this)).

Prototype "classes" vs Jquery

I currently work with prototype, so for instance if i want to create a class or something i would do something like (using it with asp.net)
function MyTestClass()
{
this.MyHtmlDiv = $("myHtmlDiv");
this.MyButton = $("myButton");
this.init();
}
MyTestClass.prototype.init = function()
{
Event.Observe(...some code here ..);
Event.Observe(...more code...);
}
So you get the idea. I like the way it is organized but I have read here in other posts that jquery is better. I would like to start using it but.. is it possible to create "classes" like this, neat?. I usually have a separate .js file for each aspx page. How can I create a class like this with JQuery?
Any pointers, links, ideas or suggestions would be appreciated. When I google this, I see some jquery functions but havent really found a nice template that i can follow to maintain it like above if i was to move to jquery.
jQuery has no confliction with the style you use now, you just need to change your mind of how jQuery selector work and how to bind event handler etc.
function MyTestClass() {
// note jQuery's selector is different from Prototype
this.MyHtmlDiv = $("#myHtmlDiv");
this.MyButton = $("#myButton");
this.init();
}
MyTestClass.prototype.init = function() {
this.MyHtmlDiv.on('event_type', function() {
// some code
});
this.MyButton.on('event_type', function() {
// some code
});
}

In JavaScript is it possible to add to a callback function instead of overwriting it?

I don't know if I'm saying this right, so I'll just ask by explaining with an example.
Let's say I've written a jQuery plugin with an onShowEdit callback.
I later use my plugin and add a bunch of other default functions/methods to the event:
$('.editable_module:not(.custom)').editable({
onShowEdit: function(el){
initRequired();
$(':radio, :checkbox', el).prettyCheckboxes();
initDatePickers();
initChosen();
initMaskedInputs();
$('.dynamic_box.tabs').dynamicBoxTabs();
$('.trigger_dynamic_box').triggerDynamicBox('true');
}
});
So now I have a basic/default element (.editable_module) that calls the plugin and has some methods/functions that are going to be used in all instances.
My question comes when I have a need to add something to this for a 'one time' kind of deal (I need to add some behavior to this callback/event but not something that is used normally). Is it possible to extend or add to this callback/event without overwriting it? I mean, I know I can go in and do this:
$('#new_selector').editable({
onShowEdit: function(el){
initRequired();
$(':radio, :checkbox', el).prettyCheckboxes();
initDatePickers();
initChosen();
initMaskedInputs();
$('.dynamic_box.tabs').dynamicBoxTabs();
$('.trigger_dynamic_box').triggerDynamicBox('true');
//ADD SOME NEW STUFF HERE
}
});
But is that really my only option?
Thanks in advance for any input/suggestions.
You could consider jQuery's own event system as follows: http://jsfiddle.net/VQqXM/1/. You can integrate this in your $.fn function pretty easily - just pass the appropriate function as property of the object instead of a function literal.
$("input").on("foo", function() {
alert(1);
});
// later
$("input").on("foo", function() {
alert(2);
});
// later
$("input").trigger("foo"); // alerts 1 and 2
You can simply use .on/.off to bind and unbind events, and trigger them all with .trigger. jQuery also supports namespacing of the event names to make sure you're not using an already used event.
You could use the new $.Callbacks() method
var $onShowEditCBObj = $.Callbacks();
function onShowEditHandler() {
$onShowEditCBObj.fire();
}
$('#new_selector').editable({
onShowEdit: onShowEditHandler
});
// add default event to callbacks obj
$onShowEditCBObj.add(function(){
initRequired();
$(':radio, :checkbox', el).prettyCheckboxes();
initDatePickers();
initChosen();
initMaskedInputs();
$('.dynamic_box.tabs').dynamicBoxTabs();
$('.trigger_dynamic_box').triggerDynamicBox('true');
});
// add a one time method to the callbacks obj
function oneTimeEvent () {
alert("worky");
$onShowEditCBObj.remove(oneTimeEvent);
}
$onShowEditCBObj.add(oneTimeEvent)
With this setup, you can change what callbacks will be fired without having to do anything extra to the editable plugin.
Edit: I didn't realize that you wrote the plugin. With that in mind, pimvdb's answer is more robust than requiring the developer to code a certain way.
If I understand the question correctly, the key word here is "factory".
jQuery is itself a factory but to get what you describe, you need your plugin also to be a factory within the factory. That requires the plugin to be written in a certain way.
Probably the easiest approach is to use jQuery's UI widget factory. Read about it here.
Defining a separate function for onShowEdit should work.
var myOnShowEdit = function(el, extra_fn) {
//standard functionality goes here
if (typeof extra_fn==='function') extra_fn(); //support for extra stuff
}
$('.editable_module:not(.custom)').editable({
onShowEdit: function(el) {
myOnShowEdit(el);
}
});
$('#new_selector').editable({
onShowEdit: function(el) {
myOnShowEdit(el, function(){console.log('hi');});
}
});
This will give you fair flexibility to add whatever functionality you need in addition to the standard stuff. Just be aware of how this may shift contexts.

how to turn a function from function(element) syntax to $(element).function() syntax

I have a function that accepts the element it needs to operate on as a parameter element
function changeColor(element){
$(element).find('.middleBox').each(function(){
$(this).//do some stuff that does not matter now;
});
}
and I call it this way
changeColor($(document)); //this applies it to the whole document
changeColor($('#sectionOne')); //this applies it to only part of the document
I want to change it from the format where it accepts its object as a param to this format. How do I do it
$('#sectionOne').changeColor();
$(document).changeColor();
As Nikita said, you need to write a jQuery plugin. Here's a basic example, which should be enough for what you are trying to do:
(function($) {
$.fn.changeColor = function() {
return this.each(function() {
$(this).find('.middleBox').each(function() {
$(this).//do some stuff that does not matter now;
});
});
};
})(jQuery);
You want to create a jQuery plugin. jQuery folks provide a tutorial for that.
If you don't like reading much, you can practically copy-paste maxHeight example and replace logic inside.

Is it possible to write a second non-static, selecting, priviledged function in a JQuery Plugin?

Almost all of the examples in the jQuery tutorials that I've read, usually use one major public function for their selecting plugin. When I say 'selecting' plugin, I mean one that is not simply a static function extended onto jQuery.
For example:
(function($) {
jQuery.fn.actionList = function(options) {
var opts = $.extend({}, $.fn.actionList.defaults, options);
return this.each(function(){
alert(this);
});
};
$.fn.actionList.defaults = {
listHtml: '<div>Set the list html</div>'
};
})(jQuery);
but not:
jQuery.log = function(message) {
if(window.console) {
console.debug(message);
} else {
alert(message);
}
};
This works fine for most things, but what I would like to do is be able to call a second function on the object returned from the first call.
var actionBox = $('actionBox').actionList(options);
//Many light-cycles later
actionBox.refreshData(data);
or maybe even:
$('actionBox').actionList(options);
// laaateerr
$('actionBox').actionList.refreshData(data);
I'm guessing one or both of these is not possible or, at least not advisable, but I'm only now getting into the deepest aspects of jQuery and javascript.
Could someone explain how to do this, or if it's not possible or advisable, why? and what they would do instead?
Thanks for reading!
I'm not quite sure what you're getting at, but you can call a second function on the object returned from the first function - in fact, it is very much encouraged to return a jQuery object from your plugins, and the reason why you can chain commands in jQuery.
Using your examples
var actionBox = $('actionBox').actionList(options);
//Many light-cycles later
actionBox.refreshData(data);
would work fine, so long as both .actionList() and .refreshData(data) commands both return a jQuery object.
And
$('actionBox').actionList.refreshData(data);
would need to be
$('actionBox').actionList().refreshData(data);
EDIT:
Looking at the jQuery source code,
jQuery.fn = jQuery.prototype = {
/*
Load of 'property' functions of jQuery object...
*/
}
so, adding properties (a.k.a plugins) to jQuery.fn extends the prototype of the jQuery object. When you call
$(selector, context);
a new jQuery object is returned, using the init property function of the jQuery object
jQuery = window.jQuery = window.$ = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context );
},
I think I've got a plugin that might be very useful for you. It allows you to apply any constructor/object to jQuery as it's own namespace AND you can use 'this' as you would normally with jQuery to refer to the object set. Using this[methodName] will call a method on your object, etc.
http://code.google.com/p/jquery-plugin-dev/source/browse/trunk/jquery.plugin.js
Some code samples are here:
http://groups.google.com/group/jquery-dev/browse_thread/thread/664cb89b43ccb92c/34f74665423f73c9?lnk=gst&q=structure+plugin+authoring#34f74665423f73c9
It's about halfway down the page.
I hope you find it useful!

Categories