I have a jQuery plugin that uses namespacing (methods) and also has options, with defaults, that can be overridden on initialization.
I'm wondering what the best way to define and use options is with this plugin in the namespaces.
I was originally using a $.fn.dropIt.settings within the wrapper function to define the settings, but then switched to defining them inside of the init method. This is very limiting in terms of scope however..
Here is the relevant code in my plugin
(function($, window, document, undefined){
var methods = {
init: function(options)
{
var settings = $.extend({
trigger: "hover",
animation: 'slide', /* none, slide, fade, grow */
easing: 'swing', /* swing, linear, bounce */
speedIn: 400,
speedOut: 400,
delayIn: 0,
delayOut: 0,
initCallback: function(){},
showCallback: function(){},
hideCallback: function(){}
}, options);
$(this).each(function(index, ele){
$ele = $(ele);
$ele.addClass('dropit');
//Attach event handlers to each list-item
$('li', $ele).dropIt('attach', settings);
//If list is displayed veritcally, add extra left padding to all sub-menus
if($(ele).hasClass('vertical'))
{
$('li', $ele).find('ul').addClass('nested sub-menu');
} else {
$('li ul', $ele).addClass('nested').find('ul').addClass('sub-menu');
}
});
//Call custom callback
settings.initCallback.call();
//Return jQuery collection of lists
return $(this);
},
attach: ...
_trigger: ...
_hide: ...
}
};
$.fn.dropIt = function(method){
//Variables and Options
var $this = $(this);
// 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.dropIt' );
}
};
})(jQuery, window, document);
After reading the jQuery Plugins/Authoring page, I structured by plugin basically like this:
(function ($) {
var defaults = {
// set default options
}
// internal functions
var methods = {
// plugin methods
}
$.fn.pluginName = function (method) {
}
})(jQuery);
And, like you have, $.extend the defaults within the init method, but I like to keep the defaults declared separately, personally, for clarity. It's been working well for me.
I always set the settings object as a global var to the plugin scope, like this:
(function($, window, document, undefined){
var settings; //NOTE THIS LINE
var methods = {
init: function(options)
{
settings = $.extend({ //AND THIS LINE
trigger: "hover",
animation: 'slide', /* none, slide, fade, grow */
easing: 'swing', /* swing, linear, bounce */
speedIn: 400,
speedOut: 400,
delayIn: 0,
delayOut: 0,
initCallback: function(){},
showCallback: function(){},
hideCallback: function(){}
}, options);
$(this).each(function(index, ele){
$ele = $(ele);
$ele.addClass('dropit');
//Attach event handlers to each list-item
$('li', $ele).dropIt('attach', settings);
//If list is displayed veritcally, add extra left padding to all sub-menus
if($(ele).hasClass('vertical'))
{
$('li', $ele).find('ul').addClass('nested sub-menu');
} else {
$('li ul', $ele).addClass('nested').find('ul').addClass('sub-menu');
}
});
//Call custom callback
settings.initCallback.call();
//Return jQuery collection of lists
return $(this);
},
attach: ...
_trigger: ...
_hide: ...
}
};
$.fn.dropIt = function(method){
//Variables and Options
var $this = $(this);
// 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.dropIt' );
}
};
})(jQuery, window, document);
EDIT:
You can also do $(this).data('youPlugInName', settings), then if you want to change it latter you can retrieve this from data('yourPlugInName'), and update whatever property you want.
Related
I'm trying to implement smoothState.js into a custom WordPress theme. I'm flummoxed. I feel like my code is correct according to the smoothState documentation, but it's still producing an error. But I'm still a beginner at JS/JQuery and scripting in general so I'm probably missing something.
Using JQMigrate version 1.4.1 and JQuery 1.12.4 in noConflict mode. WordPress is 4.6.1.
jQuery(document).ready(function($){ // WordPress doesn't release $ so we need this line
$(function() {
var $body = $('html, body'),
content = $('#wrapper').smoothState({
prefetch: true,
pageCacheSize: 4,
debug: true,
// onStart is for when a link is clicked
onStart: {
duration: 2500, // animation duration in ms
render: function (url, $container) {
$container.addClass('is-exiting');
$body.animate({
scrollTop: 0
});
smoothState.restartCSSAnimations();
}
},
onEnd : {
duration: 1500, \
render: function (url, $container, $content) {
$container.removeClass('is-exiting');
$body.css('cursor', 'auto');
$body.find('a').css('cursor', 'auto');
$container.html($content);
// Trigger document.ready and window.load
$(document).ready();
$(window).trigger('load');
}
},
onAfter : function(url, $container, $content) {
}
}).data('smoothState');
});
(function($, undefined) {
var isFired = false;
var oldReady = jQuery.fn.ready;
$(function() {
isFired = true;
$(document).ready();
});
jQuery.fn.ready = function(fn) {
if(fn === undefined) {
$(document).trigger('_is_ready');
return;
}
if(isFired) {
window.setTimeout(fn, 1);
}
$(document).bind('_is_ready', fn);
};
})(jQuery);
});
Clicking any hyperlink inside #wrapper throws the following console error...
Uncaught TypeError: Cannot read property 'addClass' of undefined
w # jquery.smoothState.min.js:9
b # jquery.smoothState.min.js:9
dispatch # jquery.js?ver=1.12.4:3
r.handle # jquery.js?ver=1.12.4:3
I've checked for any potential plugin or theme conflicts and found none. I've even tried explicitly declaring the container like var $container = $("#wrapper"); but still get the same results. Any ideas?
After looking over the smoothState onStart documentation it seems the render function only accepts a single argument like so:
$('#main').smoothState({
onStart: {
// How long this animation takes
duration: 0,
// A function that dictates the animations that take place
render: function ($container) {}
}
});
Your code has two:
render: function (url, $container) {
This would likely cause your render function url argument to be set to $container as it is the first argument passed, and the $container argument to be undefined, as the argument is never passed/set. Thus, when you call:
$container.addClass('is-exiting');
You receive the error:
Cannot read property 'addClass' of undefined
Because $container is not an object.
According to this jQuery UI documentation, there is a toggleClass method which takes an options object. However, looking in the source, I don't see this version of the method being supported. When I try to the following, no animation occurs:
$("#element").toggleClass("fixed", showOrHide, {
duration: animationDuration,
easing: "swing",
queue: false,
children: true
});
I'm using jQuery 1.10.2 and jQuery UI 1.11.0. Am I missing something here?
Please view this jsfiddle example and here is a version without Bootstrap to show that it is not a conflict issue.
Note: .addClass( className [, options ] ) does not work either!
jQuery UI Snippet
toggleClass: (function( orig ) {
return function( classNames, force, speed, easing, callback ) {
if ( typeof force === "boolean" || force === undefined ) {
if ( !speed ) {
// without speed parameter
return orig.apply( this, arguments );
} else {
return $.effects.animateClass.call( this,
(force ? { add: classNames } : { remove: classNames }),
speed, easing, callback );
}
} else {
// without force parameter
return $.effects.animateClass.call( this,
{ toggle: classNames }, force, speed, easing );
}
};
})( $.fn.toggleClass )
Try like this
var animationDuration=1000;
var t=true;
function showOrHide()
{
if(t)
t=false
else
t=true
return t;
}
$("#element").toggleClass( "fixed",showOrHide(),{duration:animationDuration, easing:"swing",queue: true});
you can also use like this
$("#element").toggleClass( "fixed",{switch:showOrHide,duration:animationDuration, easing:"swing",queue: true});
I'm trying to toggle between divs so that when an tag is clicked a div will show.
When another a tag is clicked, the div will replace the div shown.
This is what I did.
HTML:
a<br>
b<br>
c<br>
d<br>
<div id="slidingDiv">a</div>
<div id="slidingDiv_2">a</div>
<div id="slidingDiv_3">a</div>
<div id="slidingDiv_4">a</div>
JQUERY:
function ($) {
$.fn.showHide = function (options) {
//default vars for the plugin
var defaults = {
speed: 1000,
easing: '',
changeText: 0,
showText: 'Show',
hideText: 'Hide'
};
var options = $.extend(defaults, options);
$(this).click(function () {
var toggleDiv;
var $divA = $('#slidingDiv'),
$divB = $('#slidingDiv_2'),
$divC = $('#slidingDiv_3'),
$divD = $('#slidingDiv_4'),
$divE = $('#slidingDiv_5'),
$divF = $('#slidingDiv_6'),
$divG = $('#slidingDiv_7'),
$divH = $('#slidingDiv_8'),
$divI = $('#slidingDiv_9');
if( $divA.is( ':visible' ) ){
$divA.hide();
}
if( $divB.is( ':visible' ) ){
$divB.hide();
}
if( $divC.is( ':visible' ) ){
$divC.hide();
}
if( $divD.is( ':visible' ) ){
$divD.hide();
}
if( $divE.is( ':visible' ) ){
$divE.hide();
}
if( $divF.is( ':visible' ) ){
$divF.hide();
}
if( $divG.is( ':visible' ) ){
$divG.hide();
}
if( $divH.is( ':visible' ) ){
$divH.hide();
}
if( $divI.is( ':visible' ) ){
$divI.hide();
}
// this reads the rel attribute of the button to determine which div id to toggle
toggleDiv = $(this).attr('rel');
$('.toggleDiv').slideUp(options.speed, options.easing);
// this var stores which button you've clicked
var toggleClick = $(this);
var toggleDiv = $(this).attr('rel');
// here we toggle show/hide the correct div at the right speed and using which easing effect
$(toggleDiv).slideToggle(options.speed, options.easing, function() {
// this only fires once the animation is completed
// if(options.changeText==0){
//$(toggleDiv).is(":visible") ? toggleClick.text(options.hideText) : toggleClick.text(options.showText);
//}
});
return false;
});
};
})(jQuery);
This currently works, but I know that this can be done better instead of using the if statement.
Thanks
Here we go: http://jsfiddle.net/fqK36/5/
Your whole function becomes:
$.fn.showHide = function (options) {
//default vars for the plugin
var defaults = {
speed: 1000,
easing: '',
changeText: 0,
showText: 'Show',
hideText: 'Hide',
slideDiv: '.slide-div'
};
var options = $.extend(defaults, options);
return this.each(function () {
$(this).click(function () {
$(options.slideDiv).hide();
// this var stores which button you've clicked
var toggleClick = $(this),
toggleDiv = $(this).data('slide-id');
// here we toggle show/hide the correct div at the right speed and using which easing effect
$(toggleDiv).slideToggle(options.speed, options.easing, function () {
// this only fires once the animation is completed
// if(options.changeText==0){
//$(toggleDiv).is(":visible") ? toggleClick.text(options.hideText) : toggleClick.text(options.showText);
//}
});
});
});
};
Then you can use it like this:
$('a').showHide({'slideDiv' : '.slide-div'});
The slideDiv option can be a custom selector you're using can the divs you wish to slide.
All slides are assigned a class which means you can hide them all at once. Then you can show the targeted div by getting the clicked link's data-slide-id attribute.
Generally, I do this by creating a hidden class. When you go to switch to a new div, you can do something like this:
$(this).click(function(){
$(this).removeClass('hidden')
$('div:not(#' + $(this).attr('id') + ')').addClass('hidden')
});
This uses the not selector to find everything but your current item. Sans animation, this is the simple way to do it. You can also grab whatever is not hidden by using $('div:not(.hidden)') and then run your toggle on everything in that selector.
$(this).click(function(){
if($(this).hasClass('hidden'){
$(this).show()
$(this).removeClass('hidden')
}
$('div:not(#' + $(this).attr('id') + ') :not(.hidden.)')
.hide().addClass('hidden')
});
Might help clean things up a bit.
I'm trying to augment the (very nice) jQuery Spotlight plugin so that I can programmatically invoke the "hide" behavior.
I've moved the related code into a hide() function, and it works fine when invoked from within spotlight itself. But when I try to invoke it from outside of spotlight nothing happens. I've checked that spotlight.hide is in fact defined to type function, but invoking it seemingly does nothing.
(function($) {
$.fn.spotlight = function(options) {
var hide = function() {
alert('hiding...'); /* never gets invoked when called from outside spotlight */
if(settings.animate){
spotlight.animate({opacity: 0}, settings.speed, settings.easing, function(){
if(currentPos == 'static') element.css('position', 'static');
element.css('z-index', '1');
$(this).remove();
// Trigger the onHide callback
settings.onHide.call(this);
});
} else {
spotlight.css('opacity', '0');
if(currentPos == 'static') element.css('position', 'static');
element.css('z-index', '1');
$(this).remove();
// Trigger the onHide callback
settings.onHide.call(this);
}
};
// Default settings
settings = $.extend({}, {
opacity: .5,
speed: 400,
color: '#333',
animate: true,
easing: '',
exitEvent: 'click',
onShow: function(){},
onHide: function(){}
}, options);
// Do a compatibility check
if(!jQuery.support.opacity) return false;
if($('#spotlight').size() == 0){
// Add the overlay div
$('body').append('<div id="spotlight"></div>');
// Get our elements
var element = $(this);
var spotlight = $('#spotlight');
// Set the CSS styles
spotlight.css({
'position':'fixed',
'background':settings.color,
'opacity':'0',
'top':'0px',
'left':'0px',
'height':'100%',
'width':'100%',
'z-index':'9998'
});
// Set element CSS
var currentPos = element.css('position');
if(currentPos == 'static'){
element.css({'position':'relative', 'z-index':'9999'});
} else {
element.css('z-index', '9999');
}
// Fade in the spotlight
if(settings.animate){
spotlight.animate({opacity: settings.opacity}, settings.speed, settings.easing, function(){
// Trigger the onShow callback
settings.onShow.call(this);
});
} else {
spotlight.css('opacity', settings.opacity);
// Trigger the onShow callback
settings.onShow.call(this);
}
// Set up click to close
spotlight.live(settings.exitEvent, hide);
}
// Returns the jQuery object to allow for chainability.
return this;
};
})(jQuery);
I install it with:
var spotlight = $('#media-fragment').spotlight({
opacity: .5,
speed: 400,
color: '#333',
animate: false,
easing: '',
exitEvent: 'click',
onShow: function(){},
onHide: function(){}
});
And then to hide it I do:
spotlight.hide();
I'm pretty sure that there is a scope or this issue involved.
Update: full solution at https://gist.github.com/2910643.
Try changing:
var hide = function() {
to:
this.hide = function() {
var defines the scope of the function or variable within the parent scope, i.e. it's essentially protected. this on the otherhand will explicitly set it on the parent object, prototype, and make it publicly accessible.
So here is my view:
$(function() {
var ImageManipulation = Backbone.View.extend({
el: $('body'),
tagName: "img",
events: {
'mouseover img': 'fullsize',
'click img#current': 'shrink'
},
initialize: function() {
_.bindAll(this, 'render', 'fullsize', 'shrink');
//var message = this.fullsize;
//message.bind("test", this.fullsize);
},
render: function() {
},
fullsize: function() {
console.log("in fullsize function");
console.log(this.el);
$('.drop-shadow').click(function() {
console.log(this.id);
if (this.id != 'current') {
$('.individual').fadeIn();
$(this).css('position', 'absolute');
$(this).css('z-index', '999');
$(this).animate({
top: '10px',
height: '432px',
}, 500, function() {
this.id = "current";
console.log("animation complete");
return true;
});
};
});
},
shrink: function() {
$('.individual').fadeOut();
$('#current').animate({
height: '150px',
}, 500, function() {
this.id = "";
$(this).css('position', 'relative');
$(this).css('z-index', '1');
console.log("animation complete");
return true;
});
}
});
var startImages = new ImageManipulation();
});
What I don't understand is how to change the el to make 'this' take over the click function I have in full-size. I would much rather have the click jQuery function removed and have the mouseover function be another click, but I cant seem to figure out how to assign 'this' to the particular image that is being clicked. I hope my question makes sense.
Backbone's event handler assumes that you want to know about the object (both its code, and its DOM representation, the View.el object) for every event, and that the event is intended to change some aspect of the view and/or model. The actual target of the click is something you're assumed to know, or assumed to be able to derive.
Derivation is rather simple:
fullsize: function(ev) {
target = $(ev.currentTarget);
And replace all your this. references within your call to target.. this. will continue to refer to the View instance. In your inner function, the anonymous one assigned to .drop-shadow, this. will refer to the object that was just clicked on. If you want access to the surrounding context, use the closure forwarding idiom:
fullsize: function(ev) {
var target = ev.currentTarget;
var self = this;
$('.drop-shadow').click(function(inner_ev) {
console.log(this.id); // the same as inner_ev.currentTarget
console.log(self.cid); // the containing view's CID