Plugin method declarations - can't make it work - javascript

I've been struggling with exactly what the correct syntax is to make methods available on an object with a plugin. Here's the basic framework:
<!DOCTYPE html>
<html>
<head>
<!-- jQuery -->
<script type="text/javascript" src="http://goo.gl/XQPhA"></script>
<script type="text/javascript">
(function($) {
$.test = function(el, options) {
this.whiten = function() {
$(this).css('background-color', options.bg);
}
};
$.test.settings = {
bg: 'white'
};
$.fn.test = function(options) {
options = $.extend(options, $.test.settings);
return this.each(function() {
$.test(this, options);
});
};
})(jQuery);
$(document).ready(function() {
$('#list').test().css('background-color', 'wheat');
$('#go').click(function() {
$('#list').whiten();
});
});
</script>
</head>
<body>
<button id="go">whiten</button>
<ul id="list">
<li>Aloe</li>
<li>Bergamot</li>
<li>Calendula</li>
<li>Damiana</li>
<li>Elderflower</li>
<li>Feverfew</li>
</ul>
</body>
</html>
and I guess what I'm not sure about is how to make the function assignment. this inside of $.test will refer to the jQuery object wrapped around my list so I would have thought that this.myMethod = function() { would have worked but it doesn't. $(this) would be a double wrapper, el is my list (and I don't want to assign the method directly to the object since I wouldn't be able to call it like this: $('#list').whiten()), and $(el) would be the same as $(this)... so how is this done?
-- update --
I've created a [jsfiddle] to play with the problem
-- update --
I also did try placing the method in the $.fn.test function but to no avail

Try this:
$.fn.test = function(options) {
options = $.extend(options, $.test.settings);
var self = this;
return this.each(function() {
$.test(self, options);
});
};

after much wailing and gnashing of teeth, I figured it out. I'm not sure I understand why it works that way but for now I'm just happy it does!
<!DOCTYPE html>
<html>
<head>
<script type="text/javascript" src="http://goo.gl/XQPhA"></script>
<script type="text/javascript">
(function($) {
$.test = {
bg: 'white'
};
$.fn.test = function(options) {
options = $.extend({}, $.test, options);
this.whiten = function() {
$(this).css('background-color', options.bg);
};
return this.each(function() {
$.fn.test(options);
});
};
})(jQuery);
$(document).ready(function() {
$('#list').test().css('background-color', 'wheat');
$('#go').click(function() {
$('#list').whiten();
});
});
</script>
</head>
<body>
<button id="go">whiten</button>
<ul id="list">
<li>Aloe</li>
<li>Bergamot</li>
<li>Calendula</li>
<li>Damiana</li>
<li>Elderflower</li>
<li>Feverfew</li>
</ul>
</body>
</html>

Related

Difference between init events in js file or html template

Is there a difference between lauch js functions from same JS file where they declared after page load, or in html template? When both signed into $(document).ready(function () {...}).
I assume that no, but I ran into a problem when replace my ExampleService.init() function from template to separate JS file.
For example i have that construction:
common.js
var ExampleService= {
catalogSpinner: '',
init: function() {
this.initEvents();
},
initEvents: function() {
var self = this;
$('.example-button').on('click', function() {
//do some logic, append spinner...
self.removeSpinner();
});
},
removeSpinner: function() {
$(this.catalogSpinner).fadeOut('slow', function() {
$(this).remove().css({display: 'block'});
});
}
}
index.html
<script src="js/common.js"></script>
<script>
$(document).ready(function () {
ExampleService.catalogSpinner = '<div class="spinner"></div>'; // css3 animation
ExampleService.init();
});
</script>
That way all works perfect, my catalogSpinner overriden from template, and i can use them like DOM element.
But! if i move ExampleService.init(); to common.js file, like that:
common.js
var ExampleService= {
...
// all the same...
...
};
$(document).ready(function () {
'use strict';
ExampleService.init();
});
index.html
<script src="js/common.js"></script>
<script>
$(document).ready(function () {
ExampleService.catalogSpinner = '<div class="spinner"></div>';
});
</script>
That way it wouldn't work. And throw console error Uncaught TypeError: this.catalogSpinner.fadeOut is not a function
Why it's happens? After all in both cases init functions starts only after full page load, and no matters that i override my variable after starting base functions. What im doing wrong?
About orders in which inits will executed. How i understand its no matter. Cause in any case, second document.ready from template file, always ovverride empty catalogSpinner variable from JS file, before click event happens
It's almost certainly a timing issue. What guarantee do you have that $(document).ready in common.js will fire after the same event handler in your html file (which is what needs to happen according to your implementation)?
Or, you need to make sure that when it occurs in common.js, that code can somehow retrieve the catalogSpinner value.
Also, catalogSpinner needs to be a valid jQuery object, not a string.
It will and it does work in both the cases. To use jQuery methods over DOM elements, you must have valid jQuery selectors which will return objects binded with jQuery methods.
Try this:
case 1:
common.js
var ExampleService= {
catalogSpinner: '',
init: function() {
this.initEvents();
},
initEvents: function() {
var self = this;
$('.example-button').on('click', function() {
//do some logic, append spinner...
self.removeSpinner();
});
},
removeSpinner: function() {
this.catalogSpinner.fadeOut('slow', function() {
$(this).remove().css({display: 'block'});
});
}
};
index.html
<!doctype html>
<html>
<head>
<script src="jquery.min.js"></script>
<script src="common.js"></script>
<style>
</style>
</head>
<body>
<div class="spinner">Spinner</div>
<button type="button" class="example-button">Remove</button>
<script type="text/javascript">
$(document).ready(function () {
ExampleService.catalogSpinner = $('.spinner');
ExampleService.init();
});
</script>
</body>
</html>
case 2:
common.js
var ExampleService = {
catalogSpinner: '',
init: function () {
this.initEvents();
},
initEvents: function () {
var self = this;
$('.example-button').on('click', function () {
//do some logic, append spinner...
self.removeSpinner();
});
},
removeSpinner: function () {
this.catalogSpinner.fadeOut('slow', function () {
$(this).remove().css({display: 'block'});
});
}
};
$(document).ready(function () {
'use strict';
ExampleService.init();
});
index.html
<!doctype html>
<html>
<head>
<script src="jquery.min.js"></script>
<script src="common.js"></script>
<style>
</style>
</head>
<body>
<div class="spinner">Spinner</div>
<button type="button" class="example-button">Remove</button>
<script type="text/javascript">
$(document).ready(function () {
ExampleService.catalogSpinner = $('.spinner');
});
</script>
</body>
</html>

backbone events are not triggered by the button

I am working with Backbone and Jquery. I have a button inside a template and for some reason that button does not trigger any click events. I've tried all the suggested solutions on stackoverflow but none of them worked for me. Below is the code Im working with - I've made it as short as possible.
Any ideas why the YES button does not work?
EditView.html
<section id="EditView">
<button id="button-yes">YES</button>
</section>
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
</head>
<body>
<header id="header"></header>
<div id="content"><div id="content-inner"></div></div>
<script src="http://code.jquery.com/jquery-2.1.1.min.js"></script>
<script src="http://underscorejs.org/underscore-min.js"></script>
<script src="http://backbonejs.org/backbone-min.js"></script>
<script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.2.0/js/bootstrap.min.js"> </script>
<script type="text/javascript">
var myapp = myapp || {};
myapp.EditView = Backbone.View.extend({
events: {
'click #button-yes': 'buttonClickHandler'
},
initialize: function () {
this.render();
},
render: function () {
this.$el.html(this.template());
return this; // support chaining
},
buttonClickHandler : function(event){
alert( $(event.currentTarget).text() );
return false;
}
});
myapp.utils = {
loadTemplates: function(views, callback) {
var deferreds = [];
$.each(views, function(index, view) {
if (myapp[view]) {
deferreds.push($.get(view + '.html', function(data) {
myapp[view].prototype.template = _.template(data);
}));
} else {
console.log(view + " not found");
}
});
$.when.apply(null, deferreds).done(callback);
}
};
myapp.AppRouter = Backbone.Router.extend({
routes: {
"": "add"
},
initialize: function() {
this.add;
},
add: function() {
if (!this.editView) {
this.editView = new myapp.EditView({el: $("#content-inner")});
};
$('#content').html(this.editView.el);
}
});
myapp.utils.loadTemplates([ 'EditView'], function() {
myapp.router = new myapp.AppRouter();
Backbone.history.start();
});
</script>
</body>
</html>
I'm not entirely sure why you're having a problem, seems to be some race condition where the element doesn't exist when backbone attempts to bind the event to it. It's an interesting case. Anyway, you can solve it for now by adding
myapp.router.editView.delegateEvents();
after Backbone.history.start();

How do I add utility methods to jQuery plugin?

I typically use the below design pattern to create jQuery plugins (http://jsbin.com/vegevido/1/).
For this one, I wish to add a utility method which will not be applied to elements like normal jQuery plugins, but could be used by the parent script.
As shown below, I have added the utility method multiply which is accessed by the parent script using $('#elem').makeRed('multiply',5,3).
Is this how I should be implementing this? It seems like it would be better to access this method using something like myPlugin.multiple(5,3) as it has nothing to do with #elem.
Thanks
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
<title>Testing</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.9.1/jquery.js" type="text/javascript"></script>
<style type="text/css">
</style>
<script type="text/javascript">
(function($){
var defaults = {};
var methods = {
init : function (options) {
var settings = $.extend({}, defaults, options);
return this.each(function () {
$(this).css('color', 'red');
});
},
makeBlue : function () {
$(this).css('color', 'blue');
},
multiply : function (x,y) {
return x*y;
},
destroy : function () {
return this.each(function () {});
}
};
$.fn.makeRed = 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.makeRed' );
}
};
}(jQuery));
$(function(){
var myPlugin=$('#elem').makeRed();
$('#makeBlue').click(function() {$('#elem').makeRed('makeBlue');});
$('#multiply').click(function() {
console.log(myPlugin);
alert($('#elem').makeRed('multiply',5,3));
});
});
</script>
</head>
<body>
<div id="elem">Some Text</div>
<button id="makeBlue">makeBlue</button>
<button id="multiply">multiply</button>
</body>
</html>
One common pattern is to add it to the plugin definition like
$.makeRed = {
multiply: function (x, y) {
return x * y;
}
}
Demo: Fiddle
This is how jQuery UI exposes utility methods like $.datepicker.formatDate( format, date, settings )

Overriding a jQuery Core function in javascript - getJSON

I am trying to override the getJSON function in jQuery to provide additional params and then call the original getJSON. I am fairly unfamiliar with JavaScript and have scraped this together from other similar examples. Most of the examples I have seen override the behavior from a function in the jQuery.fn namespace. Could someone help me by telling me what I am doing wrong?
<html>
<head>
<script type="text/javascript" src="./js/jquery-1.6.4.min.js"></script>
<script type="text/javascript">
(function($){
var _old = $.getJSON;
$.getJSON = function(){
alert("Calling overridden getJSON");
return _old.apply(this,arguments);
};
})(jQuery);
$(document).ready(function() {
try {
$(this).getJSON('ajax/test.json', function(data) {
var items = [];
alert('done');
});
}
catch (err)
{
alert(err.message);
}
});
</script>
</head>
<body>
</body>
</html>
You need to call $.getJSON, not $(foo).getJSON;
$.getJSON('ajax/test.json', function(data) {
var items = [];
alert('done');
});

Use jQuery method as callback parameter

I have this little jQuery plugin:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head><title></title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript"><!--
(function($){
$.fn.foo = function(options){
var options = $.extend({
text: "Foo!",
}, options
);
this.prepend(
$("<span></span>").text(options.text)
).css({color: "white", backgroundColor: "black"});
return this;
};
})(jQuery);
$(function(){
$("div").foo().foo({text: "Bar!"}).css({border: "1px solid red"});
});
//--></script>
</head>
<body>
<div>One</div>
<div>Two</div>
<div>Three</div>
</body>
</html>
Now I want to improve it so you are able to control where the text gets inserted by means of providing a callback function:
$(function(){
var optionsFoo = {
text: "Foo!",
insertionCallback: $.append
}
var optionsBar = {
text: "Bar!",
insertionCallback: $.prepend
}
$("div").foo(optionsFoo).foo(optionsBar).css({border: "1px solid red"});
});
(Please remember this is just sample code. I want to learn a technique rather than fix an issue.)
Can I pass a jQuery method as an argument and use it inside the plugin in the middle of a chain? If so, what's the syntax? If not, what's the recommended alternative?
Update: myprogress so far
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head><title></title>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js"></script>
<script type="text/javascript"><!--
(function($){
$.fn.foo = function(options){
var options = $.extend({
text: "Foo!",
insertionCallback: $.append
}, options
);
options.insertionCallback.call(this, // options.insertionCallback is undefined
$("<span></span>").text(options.text)
).css({color: "white", backgroundColor: "black"});
return this;
};
})(jQuery);
$(function(){
var optionsFoo = {
text: "Foo!",
insertionCallback: $.append
}
var optionsBar = {
text: "Bar!",
insertionCallback: $.prepend
}
$("div").foo(optionsFoo).foo(optionsBar).css({border: "1px solid red"});
});
//--></script>
</head>
<body>
<div>One</div>
<div>Two</div>
<div>Three</div>
</body>
</html>
Sure you can, JavaScript is really dynamic:
this.prepend(
$("<span></span>").text(options.text)
).css({color: "white", backgroundColor: "black"});
You can replace the native call to this.prepend() with the jQuery function you pass in the options object as a parameter.
Since most jQuery methods behave on a current set of elements (the this object in your plugin) you need to use apply or call to make it work. This way you can call the options.insertionCallback function with the current this object as its context.
Something like:
options.insertionCallback.call(this, // pass the current context to the jQuery function
$("<span></span>").text(options.text)
).css({color: "white", backgroundColor: "black"});
Edit
However you can't access jQuery methods directly from the $ namespace, you need a constructed jQuery object to access its methods, or simply use $.fn.functionName as Nick Craver pointed out.
alert($.prepend); // won't alert the function
alert($.fn.prepend); // will alert the function
extend it like so:
(function($){
$.fn.foo = function(options){
var options = $.extend({
text: "Foo!",
cb: null
}, options
);
if($.isFunction(options.cb)){
// optimal also pass parameters for that callback
options.cb.apply(this, []);
}
this.prepend(
$("<span></span>").text(options.text)
).css({color: "white", backgroundColor: "black"});
return this;
};
})(jQuery);
I don't really understand what you mean with
Can I pass a jQuery method as an argument and use it inside the plugin in the middle of a chain?
Why would you want to pass a jQuery method ? it's already in there so you can just call it on any jQuery object.
You can also do this string based to keep it simple, like this:
(function($){
$.fn.foo = function(options){
var options = $.extend({
text: "Foo!",
insertionCallback: "append"
}, options
);
return this[options.insertionCallback]($("<span></span>").text(options.text))
.css({color: "white", backgroundColor: "black"});
};
})(jQuery);
Then your call looks like this:
$(function(){
var optionsFoo = {
text: "Foo!",
insertionCallback: "append"
}
var optionsBar = {
text: "Bar!",
insertionCallback: "prepend"
}
$("div").foo(optionsFoo).foo(optionsBar).css({border: "1px solid red"});
});​
You can test it here, this would allow you to use append, prepend, html and text, giving you a few options to set the content. In this case we're just taking advantage of JavaScript being a dynamic language, where this.prepend(something) is equal to this["prepend"](something).
Not an answer to your question, but the recommended style is:
<script type="text/javascript">
//this
jQuery(function($) {
//... stuff using the jQuery lib, using the $
});
//not this
$(function() {
//...
});
//or this
(function($) {
//...
})(jQuery);
</script>

Categories