understanding js code in classie.js - javascript

hey guys a i downloaded some simple effect coded in JS . the plugin is called classie.js and the guy has written some custom code that interacts with this plugin classie.js
a similar question got asked a while ago classie.js Question and the guy really perfectly answered how the code inside classie.js is functioning . thats great , so now i understand how the code inside classie.js works , now my problem is , there is a lot of code written that actually interacts with this plugin called classie.js and and i have some difficulty understanding . so let me explain what elaboratly is my problem :
The classie.js code :
( function( window ) {
'use strict';
var hasClass, addClass, removeClass;
if ( 'classList' in document.documentElement ) {
// console.log(document.documentElement);
hasClass = function( elem, c ) {
// cons100%ole.log("elem is : " + elem + " c is " + c);
return elem.classList.contains( c );
};
addClass = function( elem, c ) {
console.log('elem Logged');
console.log(elem);
elem.classList.add( c );
};
removeClass = function( elem, c ) {
console.log('removeClass function got used in if statement')
elem.classList.remove
( c );
};
}
else {
// I have deleted this part as its only a fallback for older browsers. :)
}
function toggleClass( elem, c ) {
var fn = hasClass( elem, c ) ? removeClass : addClass;
fn( elem, c );
}
var classie = {
// full names
hasClass: hasClass,
addClass: addClass,
removeClass: removeClass,
toggleClass: toggleClass,
// short names
has: hasClass,
add: addClass,
remove: removeClass,
toggle: toggleClass
};
// transport
if ( typeof define === 'function' && define.amd ) {
// AMD
define( classie );
} else if ( typeof exports === 'object' ) {
// CommonJS
module.exports = classie;
} else {
// browser global
window.classie = classie;
}
})( window );
The code that Interacts with classie.js :
(function() {
// disable/enable scroll (mousewheel and keys) from https://stackoverflow.com/a/4770179
// left: 37, up: 38, right: 39, down: 40,
// spacebar: 32, pageup: 33, pagedown: 34, end: 35, home: 36
var keys = [32, 37, 38, 39, 40], wheelIter = 0;
function preventDefault(e) {
e = e || window.event;
if (e.preventDefault)
e.preventDefault();
e.returnValue = false;
}
function keydown(e) {
for (var i = keys.length; i--;) {
if (e.keyCode === keys[i]) {
preventDefault(e);
return;
}
}
}
function touchmove(e) {
preventDefault(e);
}
function wheel(e) {
// for IE
//if( ie ) {
//preventDefault(e);
//}
}
function disable_scroll() {
window.onmousewheel = document.onmousewheel = wheel;
document.onkeydown = keydown;
document.body.ontouchmove = touchmove;
}
function enable_scroll() {
window.onmousewheel = document.onmousewheel = document.onkeydown = document.body.ontouchmove = null;
}
var docElem = window.document.documentElement,
scrollVal,
isRevealed,
noscroll,
isAnimating,
container = document.getElementById( 'container' ),
trigger = container.querySelector( 'button.trigger' );
function scrollY() {
return window.pageYOffset || docElem.scrollTop;
}
function scrollPage() {
scrollVal = scrollY();
// console.log(scrollVal);
if( classie.has( container, 'notrans' ) ) {
classie.remove( container, 'notrans' );
return false;
}
if( isAnimating ) {
return false;
}
if( scrollVal <= 0 && isRevealed ) {
toggle(0);
}
else if( scrollVal > 0 && !isRevealed ){
toggle(1);
}
}
function toggle( reveal ) {
isAnimating = true;
if( reveal ) {
classie.add( container, 'modify' );
}
else {
noscroll = true;
disable_scroll();
classie.remove( container, 'modify' );
}
// simulating the end of the transition:
setTimeout( function() {
isRevealed = !isRevealed;
isAnimating = false;
if( reveal ) {
noscroll = false;
enable_scroll();
}
}, 600 );
}
// refreshing the page...
var pageScroll = scrollY();
noscroll = pageScroll === 0;
disable_scroll();
if( pageScroll ) {
isRevealed = true;
classie.add( container, 'notrans' );
classie.add( container, 'modify' );
}
window.addEventListener( 'scroll', scrollPage );
// trigger.addEventListener( 'click', function() { toggle( 'reveal' ); } );
})();
alot of the code that interacts with classie.js is actually derived from a thread directly from stackoverflow : how to disable and enable scroll
now all the above is just for your clear understanding , what my question really is , is i don't quite understand the usage of the add method in the code that interacts with the classie.js API , its somehow does't make any sense to me and MDN doc's says very little about this method . what is that method really really doing ?? .
Edit :: The confusing part :
function toggle( reveal ) {
isAnimating = true;
if( reveal ) {
classie.add( container, 'modify' );
}
else {
noscroll = true;
disable_scroll();
classie.remove( container, 'modify' );
}
// simulating the end of the transition:
setTimeout( function() {
isRevealed = !isRevealed;
isAnimating = false;
if( reveal ) {
noscroll = false;
enable_scroll();
}
}, 600 );
}
The above is the part that confuses me , am i right when i am guessing , that if any function from classie.js gotta be used , it has to be used like follows :
classie.functionname(); ?? and can't be directly assessed ?? eg. functionname();
My Second Big Problem (understanding JS syntax of classie.js) :
also as a supplementary question , you can choose not to answer , but certain parts of the code that interacts with classie.js has a lot of confusing syntax , let me point it out .
in the disable_scroll function there is this :
window.onmousewheel = document.onmousewheel = wheel;
document.onkeydown = keydown;
and in the enable scroll function there is this :
window.onmousewheel = document.onmousewheel = document.onkeydown = null;
now i understand
A = B ;
where ur assigning A the value of B ;
But The above Syntex is more like , A = B = C ; and thats totally over my head .
can somebody clarify Please
if somebody can be elaborate and explain , it would be great .
Thank you.
Alexander.

Don't have enough rep to comment yet.
The add() method is not a 'native' js function. If you look at the classie.js code, towards the end of it the is an object 'classie' which defines public shortcuts to the internal function addClass :
var classie = {
// full names
hasClass: hasClass,
addClass: addClass,
removeClass: removeClass,
toggleClass: toggleClass,
// short names
has: hasClass,
add: addClass,
remove: removeClass,
toggle: toggleClass
};
These shorcuts will let you call the internal functions (which cannot be accessed otherwise from the global scope) by calling classie.publicFunctionName(args) or window.classie.publicFunctionName(args) where "publicFunctionName" is the shorcut defined, which is exactly what the second chunk of code does :
...
classie.remove( container, 'modify' );
...
classie.add( container, 'modify' );
All the addClass() method does is add a class to the html element it is called on.
I believe this is called the 'revealing module' design pattern, but not 100% sure.
Hope that helps at least a bit.
If you want to learn a bit on js design patterns I warmly recommend reading Addy Osmani's very good (and free) book here : http://addyosmani.com/resources/essentialjsdesignpatterns/book/

Related

Javascript error - cannot call method 'appendChild' of null (svgicons)

I get this error in the Chrome Developer Tools, but I can't see any errors visually on the page.
Here is a short extract of the JS:
function svgIcon( el, config, options ) {
this.el = el;
this.options = extend( {}, this.options );
extend( this.options, options );
this.svg = Snap( this.options.size.w, this.options.size.h );
this.svg.attr( 'viewBox', '0 0 64 64' );
this.el.appendChild( this.svg.node );
// state
this.toggled = false;
// click event (if mobile use touchstart)
this.clickevent = mobilecheck() ? 'touchstart' : 'click';
// icons configuration
this.config = config[ this.el.getAttribute( 'data-icon-name' ) ];
// reverse?
if( hasClass( this.el, 'si-icon-reverse' ) ) {
this.reverse = true;
}
if( !this.config ) return;
var self = this;
I believe that this code does not tell much, so the page can be found here: http://goo.gl/j7M5r8
I have tried solving the problem by looking into other similar cases on Stackoverflow, and it points out that there are either something missing or something with the order it is read/written in.
I have not written svgicons.js, so there are not supposed to be any issues with it. However I can see that some has the same issues as me, but there are not provideded an explanation or answer for the problem. Here is basically the same question from another person.
Relevant files:
http://goo.gl/0RisGa
http://goo.gl/UgWHMS
http://goo.gl/X269NO
Check your hamburger_animations.js file - it's at http://www.vekvemedia.no/wp-content/themes/vekvemedia/js/hamburger_animation.js
(function() {
[].slice.call( document.querySelectorAll( '.si-icons-default > .si-icon' ) ).forEach( function( el ) {
var svgicon = new svgIcon( el, svgIconConfig );
} );
new svgIcon( document.querySelector( '.si-icons-easing .si-icon-hamburger' ), svgIconConfig, { easing : mina.backin } );
new svgIcon( document.querySelector( '.si-icons-easing .si-icon-hamburger-cross' ), svgIconConfig, { easing : mina.elastic, speed: 600 } );
})();
document.querySelector( '.si-icons-easing .si-icon-hamburger' ) is resulting in null

jQuery .on and multiple scopes

I'm building a class in js where I use jQuery.on() to do some stuff. I know that I can use bind to make the scope of the class is referred to this, but this way it replaces the scope of the current object inside the .on() function. I'm using the old trick of var self = this, and it works, but I'm wondering if there's a more elegant way to do that.
Here's an example of what I'm doing:
var MyClass = function(settings){
this.mySetting = settings.mySetting;
this.otherSetting = settings.otherSetting;
this._initFunction();
};
MyClass.prototype = {
mySetting : '',
otherSetting : '',
_initFunction: function(){
// keep a referente to the class scope
var self = this;
$('.selector').on( 'click', '.trigger', function(){
if( self.mySetting == 'something' && self.otherSetting = 'some other thing'){
// here, this is referred to '.trigger'
$( this ).slideUp();
}
});
}
}
But, if I do this, the code doesn't work because the scope issue:
var MyClass = function(settings){
this.mySetting = settings.mySetting;
this.otherSetting = settings.otherSetting;
this._initFunction();
};
MyClass.prototype = {
mySetting : '',
otherSetting : '',
_initFunction: function(){
$('.selector').on( 'click', '.trigger', function(){
if( this.mySetting == 'something' && this.otherSetting = 'some other thing'){
// here, this is referred to 'MyClass', so it won't work
$( this ).slideUp();
}
}.bind( this ) );
}
}
Any tips on how to make this more elegant, avoiding the use of var self = this?
One way would be to use bind to make this point to the MyClass instance and use the event object to get to DOM Elements that triggered the event.
MyClass.prototype = {
mySetting : '',
otherSetting : '',
_initFunction: function(){
$('.selector').on( 'click', '.trigger', function (event){
if( this.mySetting == 'something' && this.otherSetting = 'some other thing'){
$( event.target ).slideUp();
}
}.bind( this ) );
}
}
In addition to using bind, you can use jQuery's proxy functionality to do essentially the same thing, but without worrying about old browsers' lack of support for bind.
MyClass.prototype = {
mySetting : '',
otherSetting : '',
_initFunction: function(){
$('.selector').on( 'click', '.trigger', $.proxy(function (event){
if( this.mySetting == 'something' && this.otherSetting = 'some other thing'){
$( event.target ).slideUp();
}
}, this ) );
}
}

How to prevent function to double animation

In my plugin I handle animations by passing methods in external functions.
Firstly I have to check if the sidebars exists:
var left = cfg.left.selector,
right = cft.right.selector;
function Sidebar( e ) {
if ( e == undefined ) {
console.log( "WARNING: A (or more) sidebar(s) has been left behind" );
} else {
var align, sbw, marginA, marginB, $sbRight, $sbLeft,
$sidebar = $( e );
Once I'm sure that the sidebars (or maybe just one ) exists I define the variables I need to animate all elements:
switch ( e == cfg.right.selector ) {
case true:
align = "right";
marginA = "margin-right";
marginB = "margin-left";
$sbRight = $( cfg.right.selector );
break;
case false:
align = "left";
marginA = "margin-left";
marginB = "margin-right";
$sbLeft = $( cfg.left.selector );
break;
}
var def = cfg[align], //new path to options
$opener = $( def.opener ),
sbMaxW = def.width,
gap = def.gap,
winMaxW = sbMaxW + gap,
$elements = //a very long selector,
w = $( window ).width();
//defining sbw variable
if ( w < winMaxW ) {
sbw = w - gap;
} else {
sbw = sbMaxW;
}
//setting $sidebar initial position and style
var initialPosition = {
width: sbw,
zIndex: cfg.zIndex
};
initialPosition[marginA] = -sbw;
$sidebar.css( initialPosition );
And here are the animations. They are handled as external functions. The first animation works really good. It does its job:
//Animate $elements to open the $sidebar
var animateStart = function() {
var ssbw = $sidebar.width();
animation = {};
animation[marginA] = '+=' + ssbw;
animation[marginB] = '-=' + ssbw;
$elements.each(function() {
$( this ).animate( animation, {
duration: duration,
easing: easing,
complete: function() {
$( 'body, html' ).attr( 'data-' + dataName, 'overflow' );
maskDiv.fadeIn();
}
});
});
},
but the second one is doubled when two sidebars exist!! I need to retrieve the .wrapper offset and then move $elements according to its value. So I thought that it works as in the first animation function, and that it would be as simple as there:
animationReset = function() {
var offset = $wrapper.offset().left;
reset = {};
console.log( offset );
Console returns two time the value so the animation in doubled.
reset[marginA] = '-=' + offset;
reset[marginB] = '+=' + offset;
$elements.each(function() {
$( this ).animate( reset, {
duration: duration,
easing: easing,
complete: function() {
maskDiv.fadeOut(function() {
$( 'body, html' ).removeAttr( 'data-' + dataName );
});
}
});
});
};
So now I run the animations on click functions.
$( $opener ).click( animateStart );
maskDiv.click( animationReset );
}
}
And finally I pass the values running the main function two times.
Sidebar( left );
Sidebar( right );
Why it worked on the first animation and then it is doubled in the second animation?
DEMO: http://dcdeiv.github.io/amazing-sidebar/
FULL CODE: https://github.com/dcdeiv/amazing-sidebar/blob/master/development/jquery.amazingsidebar.js
I think the problem is that you are adding two times the click event at
maskDiv.click( animationReset );
since maskDiv is the main div of the website and there is only one, but the looping is doing it twice.
Try to take that out of the loops definitions and do it only once, but maybe it is difficult because it is defined inside the Sidebar function, so you can try to create a global variable at the top and, after the first click event is added, not add any more:
At the top:
var notAdded = true;
In the sidebar function:
$( $opener ).click( animateStart );
if(notAdded){
maskDiv.click( animationReset );
notAdded = false;
}
Hope it helps!
Here is the cause :
var maskDiv = $( 'body' ).children().filter(function(){
return $( this ).data( dataName ) === 'mask' ;
});
and
maskDiv.click( animationReset );
You have two data-named 'mask' elements on the body.
So the click action is setup on both when you call the Sidebar function.
As you call it twice, you got the reset action run twice ...
EDIT : I was wrong : you have only one mask, but as it is used by both sidebars, you still have a double setup on it.

javascript change event fires when page loads - how to avoid this?

I have a form with chained select boxes. The zend controller sets the default values for these select boxes (values come from the db). I am a jquery novice.
$form->setDefaults($data);
a jquery file is loaded :
$(document).ready(function(){
// set up the chained select
$("#region").remoteChained("#country", "/ws/regionstructure");
$("#province").remoteChained("#region", "/ws/regionstructure");
$("#town").remoteChained("#province", "/ws/regionstructure");
});
The problem is that when the page loads it is triggering the change event for country and resetting all of the selects.
Here is the remoteChained jquery code that is being called:
/*
* Remote Chained - jQuery AJAX(J) chained selects plugin
*
* Copyright (c) 2010-2011 Mika Tuupola
*
* Licensed under the MIT license:
* http://www.opensource.org/licenses/mit-license.php
*
*/
(function($) {
$.fn.remoteChained = function(parent_selector, url, options) {
return this.each(function() {
/* Save this to self because this changes when scope changes. */
var self = this;
var backup = $(self).clone();
/* Handles maximum two parents now. */
$(parent_selector).each(function() {
$(this).bind("change", function() {
/* Build data array from parents values. */
var data = {};
$(parent_selector).each(function() {
var id = $(this).attr("id");
var value = $(":selected", this).val();
data[id] = value;
});
$.getJSON(url, data, function(json) {
/* Clear the select. */
$("option", self).remove();
/* Add new options from json. */
for (var key in json) {
if (!json.hasOwnProperty(key)) {
continue;
}
/* This sets the default selected. */
if ("selected" == key) {
continue;
}
var option = $("<option />").val(key).append(json[key]);
$(self).append(option);
}
/* Loop option again to set selected. IE needed this... */
$(self).children().each(function() {
if ($(this).val() == json["selected"]) {
$(this).attr("selected", "selected");
}
});
/* If we have only the default value disable select. */
if (1 == $("option", self).size() && $(self).val() === "") {
$(self).attr("disabled", "disabled");
} else {
$(self).removeAttr("disabled");
}
/* Force updating the children. */
$(self).trigger("change");
});
});
/* Force updating the children. */
$(this).trigger("change");
});
});
};
/* Alias for those who like to use more English like syntax. */
$.fn.remoteChainedTo = $.fn.remoteChained;
})(jQuery);
I don't see any issue with removing the change event, but the way that plugin works seems highly inefficient. I refactored this (untested), so give this a go and see how it works for you.
You can call it like so:
$( "#region" ).remoteChained( { parentSelector: "#country", url: "/ws/regionstructure" } );
$( "#province" ).remoteChained( { parentSelector: "#region", url: "/ws/regionstructure" } );
$( "#town" ).remoteChained( { parentSelector: "#province", url: "/ws/regionstructure" } );
This shouldn't trigger the change event. If you find you need to do this, you can do something like this:
$( '#region' ).trigger( 'change' );
Here is the new plugin code. Let me know if you have any issues with it:
( function( $ ) {
$.fn.remoteChained = function ( options ) {
var defaults = { },
settings = $.extend( { }, defaults, options );
return this.each( function () {
var el = $( this ),
parent = $(settings.parentSelector),
data = { };
parent.each( function () {
var el = $(this);
data[ el.attr( 'id' ) ] = el.find( ':selected' ).val();
});
parentSelector.bind( 'change.remotechanged', function ( e ) {
var request = $.ajax( { url: settings.url, data: data } );
request.done( function ( response ) {
var newOption;
el.find( 'option' ).remove();
for ( var key in response ) {
if ( !response.hasOwnProperty( key ) ) {
continue;
}
newOption = $( '<option />' )
.val( key )
.append( response[ key ] );
key === 'selected' && newOption.attr( 'selected', 'selected' );
newOption.appendTo( el );
}
if ( el.find( 'option' ).length === 1 && el.val() === '' ) {
el.attr( 'disabled', 'disabled' );
} else {
el.removeAttr( 'disabled' );
}
});
});
});
};
})( jQuery );

Why is my javascript preventing the browser from going to a link

I have a script on http://joelglovier.com to add a class of "active" to each navigation element when it's corresponding section in the document. The script is adapted with permission from w3fools.com, so it was written without my scenario in mind.
The difference is that on w3fools.com, the nav links only refer to elements on the page, whereas in my navigation, there is one element at the end that refers to a new page.
The problem is that as I have adapted it, it works fine for the links that refer to sections on the page. However, for some reason unbeknownst to me (sorry - JS/jQuery novice) it is blocking the browser from following the link to the new page (the Blog link).
I tried reading through the code and understanding what the script is doing - however I cannot seem to understand why it is blocking that external link from being clicked, or more specifically how to fix it.
Can anybody suggest the simplest way to remedy my issue without breaking the original functionality of the script for it's purpose?
See it live here: http://joelglovier.com
Or...
Markup:
<div id="site-nav">
<div class="wrap">
<nav id="nav-links">
Top
Background
Projects
Random
Credits
Blog <span class="icon"></span>
</nav>
</div>
Javascript:
(function($) {
$(function() {
var $nav = $('#nav-links'),
$navLinks = $nav.find('a.scroll'),
cache = {};
$docEl = $( document.documentElement ),
$body = $( document.body ),
$window = $( window ),
$scrollable = $body; // default scrollable thingy, which'll be body or docEl (html)
// find out what the hell to scroll ( html or body )
// its like we can already tell - spooky
if ( $docEl.scrollTop() ) {
$scrollable = $docEl;
} else {
var bodyST = $body.scrollTop();
// if scrolling the body doesn't do anything
if ( $body.scrollTop( bodyST + 1 ).scrollTop() == bodyST) {
$scrollable = $docEl;
} else {
// we actually scrolled, so, er, undo it
$body.scrollTop( bodyST - 1 );
}
}
// build cache
$navLinks.each(function(i,v) {
var href = $( this ).attr( 'href' ),
$target = $( href );
if ( $target.length ) {
cache[ this.href ] = { link: $(v), target: $target };
}
});
// handle nav links
$nav.delegate( 'a', 'click', function(e) {
// alert( $scrollable.scrollTop() );
e.preventDefault(); // if you expected return false, *sigh*
if ( cache[ this.href ] && cache[ this.href ].target ) {
$scrollable.animate( { scrollTop: cache[ this.href ].target.position().top }, 600, 'swing' );
}
});
// auto highlight nav links depending on doc position
var deferred = false,
timeout = false, // so gonna clear this later, you have NO idea
last = false, // makes sure the previous link gets un-activated
check = function() {
var scroll = $scrollable.scrollTop(),
height = $body.height(),
tolerance = $window.height() * ( scroll / height );
$.each( cache, function( i, v ) {
// if we're past the link's section, activate it
if ( scroll + tolerance > v.target.position().top ) {
last && last.removeClass('active');
last = v.link.addClass('active');
} else {
v.link.removeClass('active');
return false; // get outta this $.each
}
});
// all done
clearTimeout( timeout );
deferred = false;
};
// work on scroll, but debounced
var $document = $(document).scroll( function() {
// timeout hasn't been created yet
if ( !deferred ) {
timeout = setTimeout( check , 250 ); // defer this stuff
deferred = true;
}
});
// fix any possible failed scroll events and fix the nav automatically
(function() {
$document.scroll();
setTimeout( arguments.callee, 1500 );
})();
});
})(jQuery);
You're trying to tell it to scroll to "http://..." which doesn't exist on the current page, so it fails and does nothing.
It should work if you change your code to this
// handle nav links
$nav.delegate( 'a', 'click', function(e) {
// alert( $scrollable.scrollTop() );
e.preventDefault(); // if you expected return false, *sigh*
if ( cache[ this.href ] && cache[ this.href ].target ) {
// preserve http:// links
if (this.href.substr(0, "http://".length) == 'http://')
location.href = this.href;
else
$scrollable.animate( { scrollTop: cache[ this.href ].target.position().top }, 600, 'swing' );
}
});

Categories