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>
Related
How can I refer the the object itself in an event callback defined within an object literal in JS/jQuery please?
I have researched various answers and articles, such as this question: How to access the correct `this` inside a callback? but only found myself more confused.
It makes sense that this should refer to the element that was clicked as we need access to it, but how then do I refer the the object containing the binding function itself?
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>This</title>
<script
src="https://code.jquery.com/jquery-3.3.1.min.js"
integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
crossorigin="anonymous"></script>
</head>
<body>
<button id="test">Click Me</button>
<script>
$( document ).ready( function() {
console.log(MyObj.introspect());
MyObj.bindEvents();
} );
MyObj = {
myProperty : 'a property',
bindEvents : function(){
$('#test').on('click', MyObj.introspect)
},
introspect : function(){
console.log(this);
}
}
</script>
</body>
</html>
In your case, you'd use MyObj, just like you did in bindEvents, since it's a singleton:
MyObj = {
myProperty : 'a property',
bindEvents : function(){
$('#test').on('click', MyObj.introspect)
},
introspect : function(){
console.log(MyObj);
// ---------^^^^^
}
}
Side note: Your code is falling prey to what I call The Horror of Implicit Globals. Be sure to declare your variables (with var, or ES2015's let or const). In your case, you can make MyObj entirely private since you only need it in your own code, by moving it into the ready callback and declaring it:
$( document ).ready( function() {
var MyObj = { // Note the `var`
myProperty : 'a property',
bindEvents : function(){
$('#test').on('click', MyObj.introspect)
},
introspect : function(){
console.log(MyObj);
}
}; // Also added missing ; here
console.log(MyObj.introspect());
MyObj.bindEvents();
});
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 )
What am doing wrong. I try to make object but when i try to initialize i get this error in console: I try to put all in document.ready and whitout that but dont work. In both case i have some error. Am new sorry for dumb question
ReferenceError: Circle is not defined
var obj = new Circle;
JS
$(function(){
var Circle = {
init: function() {
console.log("Circle initialized");
}
};
});
HTML
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script>
<script src="javascript/circle.js"></script>
<script>
$(document).ready(function(){
var obj = new Circle;
obj.init();
})
</script>
</head>
<body>
<div id="test" >TODO write content</div>
</body>
</html>
NEW UPDATE
$(function(){
window.Circle = {
init: function() {
console.log("Circle initialized");
}
};
window.Circle.init();
});
....
<head>
<script>
window.Circle().init();
</script>
</head>
You've defined your "Circle" function inside another function — the anonymous function you pass in as a a "ready" handler. Therefore, that symbol ("Circle") is private to that function, and not visible to the other code.
You can make it global like this:
window.Circle = {
// ...
};
You could also add it to the jQuery namespace (may or may not be appropriate; depends on what you're doing), or you could develop your own namespace for your application code. Or, finally, you could consider combining your jQuery "ready" code so that the "Circle" object and the code that uses it all appears in the same handler.
edit — another possibility is to move your "Circle" declaration completely out of the "ready" handler. If all you do is initialize that object, and your property values don't require any work that requires the DOM or other not-yet-available resources, you can just get rid of the $(function() { ... }) wrapper.
1) you are assigning Circle in a function context, not as a global. You can only use it there unless you expose it to global.
2) you are calling Circle as a constructor, but Circle is not a function.
This solves both issues:
var Circle = function () {};
Circle.prototype.init = function () {
console.log('Circle initialized.');
};
var obj = new Circle();
obj.init();
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>
I'm currently trying to use the YouTube API as part of a jQuery plugin and I've run into a bit of a problem.
The way the YT api works is that you load the flash player and, when it's ready it will send a call back to a global function called onYouTubePlayerReady(playerId). You can then use that id combined with getElementById(playerId) to send javascript calls into the flash player (ie, player.playVideo();).
You can attach an event listener to the player with player.addEventListener('onStateChange', 'playerState'); which will send any state changes to another global function (in this case playerState).
The problem is I'm not sure how to associate a state change with a specific player. My jQuery plugin can happily attach more than one video to a selector and attach events to each one, but the moment a state actually changes I lose track of which player it happened in.
I'm hoping some example code may make things a little clearer. The below code should work fine in any html file.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="application/text+html;utf-8"/>
<title>Sandbox</title>
<link type="text/css" href="http://jqueryui.com/latest/themes/base/ui.all.css" rel="stylesheet" />
<script type="text/javascript" src="http://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("jquery", "1.3.2");
google.load("jqueryui", "1.7.0");
</script>
<script type="text/javascript" src="http://swfobject.googlecode.com/svn/tags/rc3/swfobject/src/swfobject.js"></script>
<script type="text/javascript">
(function($) {
$.fn.simplified = function() {
return this.each(function(i) {
var params = { allowScriptAccess: "always" };
var atts = { id: "ytplayer"+i };
$div = $('<div />').attr('id', "containerplayer"+i);
swfobject.embedSWF("http://www.youtube.com/v/QTQfGd3G6dg&enablejsapi=1&playerapiid=ytplayer"+i,
"containerplayer"+i, "425", "356", "8", null, null, params, atts);
$(this).append($div);
});
}
})(jQuery);
function onYouTubePlayerReady(playerId) {
var player = $('#'+playerId)[0];
player.addEventListener('onStateChange', 'playerState');
}
function playerState(state) {
console.log(state);
}
$(document).ready(function() {
$('.secondary').simplified();
});
</script>
</head>
<body>
<div id="container">
<div class="secondary">
</div>
<div class="secondary">
</div>
<div class="secondary">
</div>
<div class="secondary">
</div>
</div>
</body>
</html>
You'll see the console.log() outputtin information on the state changes, but, like I said, I don't know how to tell which player it's associated with.
Anyone have any thoughts on a way around this?
EDIT:
Sorry, I should also mentioned that I have tried wrapping the event call in a closure.
function onYouTubePlayerReady(playerId) {
var player = $('#'+playerId)[0];
player.addEventListener('onStateChange', function(state) {
return playerState(state, playerId, player); } );
}
function playerState(state, playerId, player) {
console.log(state);
console.log(playerId);
}
In this situation playerState never gets called. Which is extra frustrating.
Edit:
Apparently calling addEventListener on the player object causes the script to be used as a string in an XML property that's passed to the flash object - this rules out closures and the like, so it's time for an old-school ugly hack:
function onYouTubePlayerReady(playerId) {
var player = $('#'+playerId)[0];
player.addEventListener('onStateChange', '(function(state) { return playerState(state, "' + playerId + '"); })' );
}
function playerState(state, playerId) {
console.log(state);
console.log(playerId);
}
Tested & working!
Im Using Jquery SWFobject plugin, SWFobject
It is important to add &enablejsapi=1 at the end of video
HTML:
<div id="embedSWF"></div>
Jquery:
$('#embedSWF').flash({
swf: 'http://www.youtube.com/watch/v/siBoLc9vxac',
params: { allowScriptAccess: "always"},
flashvars: {enablejsapi: '1', autoplay: '0', allowScriptAccess: "always", id: 'ytPlayer' },
height: 450, width: 385 });
function onYouTubePlayerReady(playerId) {
$('#embedSWF').flash(function(){this.addEventListener("onStateChange", "onPlayerStateChange")});
}
function onPlayerStateChange(newState) {
alert(newState);
}
onYouTubePlayerReady must be outside of $(document).ready(function() to get fired
I had this same problem and tried the accepted answer. This didn't work for me; the playerState() function was never called. However, it put me on the right path. What I ended up doing was this:
// Within my mediaController "class"
window["dynamicYouTubeEventHandler" + playerID] = function(state) { onYouTubePlayerStateChange(state, playerID); }
embedElement.addEventListener("onStateChange", "dynamicYouTubeEventHandler" + playerID);
// End mediaController class
// Global YouTube event handler
function onYouTubePlayerStateChange(state, playerID) {
var mediaController = GetMediaControllerFromYouTubeEmbedID(playerID);
mediaController.OnYouTubePlayerStateChange(state);
}
It's fairly nasty, but so is the current state of the YouTube JavaScript API.
Here is some other helpful/nasty code if you are using any kind of advanced prototyping patterns. This basically allows you to retrieve a "class" instance from the YouTube player ID:
// Within my mediaController "class"
// The containerJQElement is the jQuery wrapped parent element of the player ID
// Its ID is the same as the player ID minus "YouTubeEmbed".
var _self = this;
containerJQElement.data('mediaController', _self);
// End mediaController class
// Global YouTube specific functions
function GetMediaControllerFromYouTubeEmbedID(embedID) {
var containerID = embedID.replace('YouTubeEmbed', '');
var containerJQObject = $("#" + containerID);
return containerJQObject.data('mediaController');
}
function onYouTubePlayerReady(playerId) {
var mediaController = GetMediaControllerFromYouTubeEmbedID(playerId);
mediaController.OnYouTubeAPIReady();
}
Here's a nice article that goes through creating a class to wrap an individual player, including dispatching events to individual objects using a similar approach to that mentioned in a previous answer.
http://blog.jcoglan.com/2008/05/22/dispatching-youtube-api-events-to-individual-javascript-objects/
How about something like so:
var closureFaker = function (func, scope) {
var functionName = 'closureFake_' + (((1+Math.random())*0x10000000)|0).toString(16);
window[functionName] = function () {
func.apply(scope || window, arguments);
};
console.log('created function:', functionName, window[functionName]);
return functionName;
};
ytplayer.addEventListener("onStateChange", closureFaker(function () {
//place your logic here
console.log('state change', arguments)
}));