plugin methods - what's the magic here? - javascript

I struggled and finally got [this] to work. now, I wanted to break it up as shown below but it doesn't work... is there some voodoo here I don't understand?
<!DOCTYPE html>
<html>
<head>
<!-- jQuery -->
<script type="text/javascript" src="http://goo.gl/XQPhA"></script>
<script type="text/javascript">
(function($) {
$.test = function(options) {
options = $.extend({}, $.test.settings, options);
this.whiten = function() {
$(this).css('background-color', options.bg);
};
};
$.test.settings = { bg: 'white' };
$.fn.test = function(options) {
return this.each(function(index, el) {
$.test(options);
});
};
})(jQuery);
$(document).ready(function() {
$('ul').test().css('background-color', 'wheat');
$('#go').click(function() {
$('ul').whiten();
});
});
</script>
</head>
<body>
<button id="go">whiten</button>
<ul id="list1">
<li>Aloe</li>
<li>Bergamot</li>
<li>Calendula</li>
<li>Damiana</li>
<li>Elderflower</li>
<li>Feverfew</li>
</ul>
<ul id="list2">
<li>Ginger</li>
<li>Hops</li>
<li>Iris</li>
<li>Juniper</li>
<li>Kavakava</li>
<li>Lavender</li>
<li>Marjoram</li>
<li>Nutmeg</li>
<li>Oregano</li>
<li>Pennroyal</li>
</ul>
</body>
</html>
as compared with the previous code, inside of the each() loop I call now $.test(options) instead of $.fn.test(options) - so why does one work and not the other (actually, why/how does the first one work to begin with)?

I would restructure your plugin to follow the guidelines outlined in the plugin authoring guide, most notably storing the data for the settings for your widget with .data() and making method calls to your plugin with .test("method"):
(function($) {
/* Default plugin settings: */
var settings = {
bg: 'white'
};
/* Method definitions: */
var methods = {
init: function(options) {
options = $.extend({}, options, settings);
return this.each(function () {
$(this).data("test", options);
});
},
whiten: function() {
var options = this.data("test");
this.css('background-color', options.bg);
}
};
/* Plugin definition and method calling logic: */
$.fn.test = 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');
}
}
})(jQuery);
Usage: $("elem").test(), $("elem").test("whiten")
Here's a working example: http://jsfiddle.net/z4R3X/
An additional resource for plugin authoring guidance is the jQueryUI source code (take the autocomplete widget for example). These widgets are pretty good examples of how to create reusable, readable jQuery plugins.

Related

TypeError: $(...).find(...).on is not a function

I have to admin a quite small web presentation with only some javascript. It was written some years ago and now I try to implement a mobile compatible menu.
This are the versions of jQuery which were installed before I started with the new menu:
jQuery.min.js : jQuery JavaScript Library v1.6.1
jQuery.tools.overlay.min.js : jQuery Tools v1.2.7 - The missing UI library for the Web
jQuery-ui.min.js : jQuery UI 1.8.13
Now I try several newer jQuery code, and at the moment I use this versions:
<script src="scripts/jquery-1-12-3.min.js" type="text/javascript"></script>
<script src="http://cdn.jquerytools.org/1.2.6/full/jquery.tools.min.js"></script>
The plug in I want to use for the menu is "doubletaptogo.js":
(function($) {
/*
Responsive Flat Menu
http://cssmenumaker.com/menu/responsive-flat-menu
*/
$.fn.menumaker = function(options) {
var cssmenu = $(this),
settings = $.extend({
title: "Menu",
format: "dropdown",
sticky: false
}, options);
return this.each(function() {
cssmenu.prepend('<div id="menu-button">' + settings.title + '</div>');
$(this).find("#menu-button").on('click', function() {
$(this).toggleClass('menu-opened');
var mainmenu = $(this).next('ul');
if (mainmenu.hasClass('open')) {
mainmenu.hide().removeClass('open');
} else {
mainmenu.show().addClass('open');
if (settings.format === "dropdown") {
mainmenu.find('ul').show();
}
}
});
cssmenu.find('li ul').parent().addClass('has-sub');
multiTg = function() {
cssmenu.find(".has-sub").prepend('<span class="submenu-button"></span>');
cssmenu.find('.submenu-button').on('click', function() {
$(this).toggleClass('submenu-opened');
if ($(this).siblings('ul').hasClass('open')) {
$(this).siblings('ul').removeClass('open').hide();
} else {
$(this).siblings('ul').addClass('open').show();
}
});
};
if (settings.format === 'multitoggle') multiTg();
else cssmenu.addClass('dropdown');
if (settings.sticky === true) cssmenu.css('position', 'fixed');
resizeFix = function() {
if ($(window).width() > 768) {
cssmenu.find('ul').show();
}
if ($(window).width() <= 768) {
cssmenu.find('ul').hide().removeClass('open');
}
};
resizeFix();
return $(window).on('resize', resizeFix);
});
};
})(jQuery);
/*
By Osvaldas Valutis, www.osvaldas.info
Available for use under the MIT License
*/
;
(function($, window, document, undefined) {
$.fn.doubleTapToGo = function(params) {
if (!('ontouchstart' in window) &&
!navigator.msMaxTouchPoints &&
!navigator.userAgent.toLowerCase().match(/windows phone os 7/i)) return false;
this.each(function() {
var curItem = false;
$(this).on('click', function(e) {
var item = $(this);
if (item[0] != curItem[0]) {
e.preventDefault();
curItem = item;
}
});
$(document).on('click touchstart MSPointerDown', function(e) {
var resetItem = true,
parents = $(e.target).parents();
for (var i = 0; i < parents.length; i++)
if (parents[i] == curItem[0])
resetItem = false;
if (resetItem)
curItem = false;
});
});
return this;
};
})(jQuery, window, document);
/**
* doubleTapToGoDecorator
* Adds the ability to remove the need for a second tap
* when in the mobile view
*
* #param {function} f - doubleTapToGo
*/
function doubleTapToGoDecorator(f) {
return function() {
this.each(function() {
$(this).on('click', function(e) {
// If mobile menu view
if ($('#menu-button').css('display') == 'block') {
// If this is not a submenu button
if (!$(e.target).hasClass('submenu-button')) {
// Remove the need for a second tap
window.location.href = $(e.target).attr('href');
}
}
});
});
return f.apply(this);
}
}
// Add decorator to the doubleTapToGo plugin
jQuery.fn.doubleTapToGo = doubleTapToGoDecorator(jQuery.fn.doubleTapToGo);
/**
* jQuery
*/
(function($) {
$(document).ready(function() {
$("#cssmenu").menumaker({
title: "Menu",
format: "multitoggle"
});
$('#cssmenu li:has(ul)').doubleTapToGo();
});
})(jQuery);
The menu looks like:
<nav id='cssmenu' role='navigation'>
Show navigation
Hide navigation
<ul>...
...and when I debug with Firebug I always see the error mentioned in subject of this post!
Are there still problems with the versions of jQuery? I'm very sorry, but I'm not used to programming very much in javascript.
Thanks a lot for your help in advance!
I added now
<script type="text/javascript" src="scripts/jquery-ui.min.js" ></script> // v1.11.4
I bind the doubletaptogo with that code to the body of my html:
<script type="text/javascript">
$(function () {
$('.cssmenu').doubleTapToGo();
});
</script>
So far, it unfortunately does not work.
With this doubletaptogo.min.js I have a fine menu on my Laptop, without an error in Firebug, but no menu on my mobile Units:
;(function(e,t,n,r){e.fn.doubleTapToGo=function(r){if(!("ontouchstart"in t)&&!navigator.msMaxTouchPoints&&!navigator.userAgent.toLowerCase().match(/windows phone os 7/i))return false;this.each(function(){var t=false;e(this).on("click",function(n){var r=e(this);if(r[0]!=t[0]){n.preventDefault();t=r}});e(n).on("click touchstart MSPointerDown",function(n){var r=true,i=e(n.target).parents();for(var s=0;s<i.length;s++)if(i[s]==t[0])r=false;if(r)t=false})});return this}})(jQuery,window,document);
With that doubletaptogo.js there is an error in Firebug (like mentioned in the subject), but a fine menu on my Laptop, and a "Menu" on my mobile Units which does not react on anything. I tried also to Change the find() against a filter(), but then the filter() was "not a function".
What else can be the Problem?
I also tried to use only the code of that site as a menu, just to Show it on my site:
http://codepen.io/dmitrykiselyov/pen/XJwqZM
...and this did not work also. There was only the "Menu" on my mobile Units which did not react.
So, may be Problem with the jQuery? I use now the jquery-1-12-3.min.js and the ui is Version v1.11.4.
May be a bad combination?
I got same error, solution is please use the latest jQuery version from 1.x.y serie (you can pick here: https://code.jquery.com/jquery/ 1.12.4 is working well for me).

Calling member function inside of Jquery Plugin

I have created a basic plugin template based on the code generated here:
http://starter.pixelgraphics.us/
Here is a link to the very basic skeleton:
https://jsbin.com/yatofefade/edit?html,js,console,output
$.curationPanel = function( el, options ) {
var base = this;
base.$el = $(el);
base.el = el;
base.$el.data( "curationPanel", base );
base.init = function( ) {
base.options =
$.extend( {}, $.curationPanel.defaultOptions, options );
};
base.runMe = function( ) {
alert( "I've Been Run" );
};
base.init( );
};
$.curationPanel.defaultOptions = { };
$.fn.curationPanel = function( options ) {
return this.each( function( ) {
(new $.curationPanel( this, options ));
});
};
$(".curationPanel").each( function( i, val ) {
var cp = $(this).curationPanel({});
cp.runMe( );
});
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
<div class="curationPanel">INSIDE THE PANEL<div class="curationErrors"></div></div>
</body>
</html>
My question is, why do I get an error when trying to call runMe( ) on a created instance of curationPanel? What is the correct way to create callable public functions within the plugin?
In your case cp is a jQuery object, not an instance of the curationPanel since you are returning this from the plugin method, that is why the error.
There are multiple ways to do this.
One way is to break the chaining nature of the jQuery and return an instance of the plugin object as shown below. Other than breaking the chaining nature of jQuery method, another drawback of this design is, at any call you can use this to initialize the plugin for only one element, ie if you have a selector which selects more than 1 element and then call the plugin, the plugin will be initialized for only the first element.
$.curationPanel = function(el, options) {
var base = this;
base.$el = $(el);
base.el = el;
base.$el.data("curationPanel", base);
base.init = function() {
base.options = $.extend({}, $.curationPanel.defaultOptions, options);
};
base.runMe = function() {
snippet.log("I've Been Run");
};
base.init();
};
$.curationPanel.defaultOptions = {};
$.fn.curationPanel = function(options) {
return new $.curationPanel(this[0], options);
};
$(".curationPanel").each(function(i, val) {
var cp = $(this).curationPanel({});
cp.runMe();
});
<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="curationPanel">INSIDE THE PANEL
<div class="curationErrors"></div>
</div>
Another way is to obtain the plugin instance using the data api like
$.curationPanel = function(el, options) {
var base = this;
base.$el = $(el);
base.el = el;
base.$el.data("curationPanel", base);
base.init = function() {
base.options = $.extend({}, $.curationPanel.defaultOptions, options);
};
base.runMe = function() {
snippet.log("I've Been Run");
};
base.init();
};
$.curationPanel.defaultOptions = {};
$.fn.curationPanel = function(options) {
return this.each(function() {
(new $.curationPanel(this, options));
});
};
$(".curationPanel").each(function(i, val) {
var cp = $(this).curationPanel({});
$(this).data('curationPanel').runMe();
});
<!-- Provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<div class="curationPanel">INSIDE THE PANEL
<div class="curationErrors"></div>
</div>

Is this the right way to "override" a jQuery plugin (to provide default settings)?

I'd like to override (wrap) the $.fn.ckeditor function, that is the jQuery adapter for CKEditor. What value should I return? It this the correct way:
;(function ($) {
var defaults = {}
ckeditor = $.fn.ckeditor;
$.fn.ckeditor = function (cb, options) {
ckeditor.apply(this, [cb || $.noop, $.extend(defaults, options || {})]);
};
}(jQuery));
in order to override a plugin?
Note Not tried ckeditor (plugin).
If interpret post accurately,
Try this (pattern)
html
<article>123</article>
<div>123</div>
<span>123</span>
js
$(function () {;
(function ($) {
$.fn.ckeditor = function (options) {
var defaults = {
"n": 2
};
var options = $.extend({}, defaults, options);
return $(this).html(function (index, o) {
return String(Number(o) * options.n)
})
};
}(jQuery));
$("article").ckeditor();
$("div").ckeditor({
"n": 4
});
$("span").ckeditor({
"n": (function () {
return Number($("article").html())
})()
});
})
jsfiddle http://jsfiddle.net/guest271314/2qrq3/
See jQuery fn.extend ({bla: function(){}} vs. jQuery.fn.bla

How can each instance of a jQuery plugin have its own properties and methods?

I am learning how to create jQuery plugins and have built one using module pattern. It works if I apply it only once, however, if I apply multiple times, all of them get initialized using the last one's settings.
For instance, if I first do $('#div1').myPlugin();, and then later $('#div2').myPlugin({myProperty :'mydiv2Property'});, $('#div1') myProperty is changed from myDefaultProperty to mydiv2Property. The same thing happens when initializing with a different method.
I have a working (well, almost working!) example located at http://jsbin.com/eWePoro/1/, and my full script is listed below.
How do I change this script so each time the plugin is applied, it uses just its own properties and methods? Thank you
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="content-type" content="text/html; charset=ISO-8859-1" />
<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 = {
myProperty :'myDefaultProperty',
myMethod1 :function () {
console.log('myMethod1',this,this.myProperty);
},
myMethod2 :function () {
console.log('myMethod2',this,this.myProperty);
}
};
var methods = {
init : function (options) {
var settings = $.extend(defaults, options || {});
settings.myMethod1();
return this.each(function () {
$(this).click(function(e) {
settings.myMethod2();
});
});
},
destroy : function () {
//Anything else I should do here?
delete settings;
return this.each(function () {});
}
};
$.fn.myPlugin = 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.myPlugin');
}
};
}(jQuery)
);
$(function(){
$('#div1').myPlugin();
$('#div2').myPlugin({
myProperty :'mydiv2Property'
});
$('#div3').myPlugin({
myMethod1 :function () {console.log('myMethod1_new',this,this.myProperty);}
});
$('#div4').myPlugin({
myMethod2 :function () {console.log('myMethod2_new',this,this.myProperty);}
});
});
</script>
</head>
<body>
<div id='div1'>div1</div>
<div id='div2'>div2</div>
<div id='div3'>div3</div>
<div id='div4'>div4</div>
</body>
</html>
The problem is here:
var settings = $.extend(defaults, options || {});
You are actually modifying defaults here with new properties. So next time you run the same code, defaults will be mutated. You should probably do:
var settings = $.extend({}, defaults, options);
This will create a new settings object every time by cloning defaults before extending it.
DEMO: http://jsbin.com/eWePoro/2

Call the same instance of jQuery plugin

I have written a jQuery plugin below and would like to be able to call it again for the same instance on an element.
The plugin goes...
(function($) {
$.fn.myPlugin = function(options){
var settings = {
color: null
};
if (options) {
$.extend(settings, options);
}
return this.each(function(){
var self = this;
var pics = $('li', self);
function refresh() {
pics = $('li', self);
};
$('a', self).click(function(){
pics.filter(':last').remove();
alert(settings.color);
refresh();
return false;
});
});
}
})(jQuery);
In the page this is called...
$('#test').myPlugin({ color: 'blue' });
Now I want to call the same plugin for the same instance but pass the string refresh as the option whilst all the other variables are the same (so color would still be blue) e.g...
$('#test').myPlugin('refresh');
This would then execute the refresh() function.
How could I achieve that with the above?
Edit: To make it clearer I am thinking of how jQuery UI does their plugins. In the sortable plugin you can do $("#sortable").sortable(); and then $("#sortable").sortable('refresh'); on the same element. This is what I am trying to achieve.
You can store your instance with .data() and check for it when creating an instance.
Something like:
$.fn.doStuff = function () {
var ob = $(this);
var data = ob.data();
if (data.doStuff !== undefined) {
return data.doStuff;
}
doStuff;
});
(function($) {
$.fn.myPlugin = function(options){
var init = function($self, ops){
$self.find("a").click(function(){
pics.filter(':last').remove();
alert(settings.color);
refresh();
return false;
});
};
this.refresh = function(){
//your code here
};
return this.each(function(){
var self = this;
var pics = $('li', self);
var settings = {
color: null
};
var ops = $.extend(true, settings, options);
init($(this), ops);
});
}
})(jQuery);
try something like this. and you can call refresh() like $().myPlugin().refresh();

Categories

Resources