Background:
I have a function that I call like this:
hide_modules('string1','string2');
The function is something like:
function hide_modules(param1,param2) {
MM.getModules()
.withClass(param1)
.exceptWithClass(param2)
.enumerate(function(module) {
module.hide(
// some other code
);
});
}
Most of the time I call the function with values as shown above.
Sometimes I do not want 'string1' to have a value and I'd like the my function to not use that first selector, effectively like this:
MM.getModules()
// .withClass(param1)
.exceptWithClass(param2)
.enumerate(function(module) {
module.hide(
// some other code
);
});
I've tried just calling it with an empty string, 0, false as param1 but the end result class selection is not what I want.
Sometimes I also call it with param2 empty and not wanting to have the param2 related selector used either.
So the question is:
Without writing a big if-then-else statement, is there some fancy way I can make those selectors non-functional (the equivalent of commenting it out like above) when the param1 and/or param2 values are not specified?
The supporting code that my function calls is provided for me in a 3rd party library that I can't change. I include some of the relevant parts here as it may help with the answer:
var withClass = function (className) {
return modulesByClass(className, true);
};
var modulesByClass = function (className, include) {
var searchClasses = className;
if (typeof className === "string") {
searchClasses = className.split(" ");
}
var newModules = modules.filter(function (module) {
var classes = module.data.classes.toLowerCase().split(" ");
for (var c in searchClasses) {
var searchClass = searchClasses[c];
if (classes.indexOf(searchClass.toLowerCase()) !== -1) {
return include;
}
}
return !include;
});
Since js doesn't supports function overloading, the only way is to validate your parameters inside your method. Check for truthy and ternary operator will do the trick
var modules = MM.getModules();
modules = param1 ? modules.withClass(param1) : modules;
modules = param2 ? modules.exceptWithClass(param2) : modules;
modules.enumerate(function(module) {
module.hide(
// some other code
);
});
to skip first parameter
hide_modules(null,'string2');
to skip second parameter
hide_modules('string1');
I'm trying to reinvent the wheel, sort of.. Just messing around trying to remake some jquery functions.. I've come this far
var ye = function (ele) {
if (ele[0] == "#")
{
return document.getElementById(ele.slice(1));
}
else if (ele[0] == ".")
{
// returns an array, use index
return document.getElementsByClassName(ele.slice(1));
}
else
{
// also returns an array
return document.getElementsByTagName(ele);
}
}
but how can I use this element as a parameter in a function in the 'ye' prototype. For example, if I wanted to make fontsize how could I get the dom element like here:
ye.prototype.fontSize = function (ele)
{
ele.style.fontSize = "30px";
}
Just to add a bit to make the title relevant.. forEach inserts three arguments into the callback function, just like I want ye to insert ele into the fontSize function.
Just messing around trying to remake some jquery functions...
...but how can I use this element as a parameter in a function in the 'ye' prototype..
Here is a very crude and simple way to start...
Create a function with a property called elems which is an array and will store the selected DOM elements.
Like this:
var oye = function() { this.elems = []; };
On its prototype, you can create your custom functions which you want to expose. e.g. the function fontSize (as in your question), iterate over the elems array property that we created earlier changing the font size of each DOM element stored in. this points to the instance which is calling this function which we will ensure to be of type oye later on. To enable chaining, we simply return itself via this.
Like this:
oye.prototype.fontSize = function(size) {
this.elems.forEach(function(elem) {
elem.style.fontSize = size;
});
return this;
};
Now create the selector function called ye. This serves the purpose of selecting the DOM elements, storing them in the elems array property of a new instance of oye class, and return the instance. We call the slice of the array prototype to convert the nodeList to an array.
Like this:
var ye = function(elem) {
var newOye = new oye;
newOye.elems = [].slice.call(document.querySelectorAll(elem));
return newOye;
};
Now start using it in your code. Just like jQuery, you can use ye to select and then call your custom functions.
Like this:
ye("#elem1").fontSize('30px');
Just like jQuery, you can also chain multiple custom functions as shown in the complete working example below:
ye("P").fontSize('24px').dim(0.4);
Next step: Remember this is just a very crude example. You can now proceed to club the step 1 and 2 into a single call using the init pattern returning the new object from the selector function itseld. Learn more about Javascript and best practices.
Here is a sample working demo:
var oye = function() { this.elems = []; };
oye.prototype.fontSize = function(size) {
this.elems.forEach(function(elem) {
elem.style.fontSize = size;
});
return this;
};
oye.prototype.dim = function(value) {
return this.elems.forEach(function(elem) {
elem.style.opacity = value;
});
return this;
};
var ye = function(elem) {
var newOye = new oye;
newOye.elems = [].slice.call(document.querySelectorAll(elem));
return newOye;
};
ye("#elem1").fontSize('30px');
ye(".elem2").fontSize('20px');
ye("P").fontSize('24px').dim(0.4);
<div>This is normal text.</div>
<div id="elem1">size changed via id.</div>
<div class="elem2">size changed via class.</div>
<div class="elem2">size changed via class.</div>
<p>size changed and dimmed via tag name</p>
<p>size changed and dimmed via tag name</p>
Regarding your question, I may think you're new to JavaScript, or not familiar with its basic concepts. I'm not sure reinventing the wheel is a good thing in such conditions.
Since you've cited jQuery, you can have a look at its source code to understand how it works under the hood:
https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/core.js#L17-L23
https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/core.js#L38-L81
https://github.com/jquery/jquery/blob/99e8ff1baa7ae341e94bb89c3e84570c7c3ad9ea/src/core/init.js#L19-L114
Having that said, I would have done something like this:
var ye = function ( ele ) {
return new ye.prototype.init(ele);
};
ye.prototype.init = function( ele ) {
this._elements = [].slice.call(document.querySelectorAll(ele));
return this;
};
ye.prototype.forEach = function( fn ) {
this._elements.forEach(fn);
return this;
};
ye.prototype.fontSize = function( fontSizeValue ) {
this.forEach(function (ele) {
ele.style.fontSize = fontSizeValue;
});
return this;
};
The associated usage is as follow:
var myCollection = ye('.someClassName');
myCollection.forEach(function ( item, index ) {
console.log(item.style.fontSize);
});
myCollection.fontSize('45px');
myCollection.forEach(function ( item, index ) {
console.log(item.style.fontSize);
});
Use ye function calling before setting style, something like:
ye.prototype.fontSize = function(ele) {
ye(ele).style.fontSize = '30px';
}
returned object should be richer, like that:
var baseObject = {
// Will be used for the element:
element: null,
width: function(){ return this.element.getwidth(); /* or anything similar*/ }
// ... Further methods
}
and then in your ye function:
var ye = function (ele) {
var yeElem = clone(baseObject); // See comment below!!
if (ele[0] == "#") { yeElem.element = document.getElementById(ele.slice(1)); }
else if (ele[0] == "."){ /*...*/ }
else { /*...*/ }
return yeElem;
}
This way the new element has built in methods.
As for the clone() method used, it doesn't exist but you have to use some clone method.
I recommend Loadsh's _.cloneDeep() (here).
Say I have three <div> elements with a selector of .js-hello on, for example:
<div class="js-hello">...</div>
<div class="js-hello">...</div>
<div class="js-hello">...</div>
I want this to look like:
<div class="js-hello">Hello Foo!</div>
<div class="js-hello">Hello Foo!</div>
<div class="js-hello">Hello Foo!</div>
I now I have this vanilla JS (please, no jQuery)
;(function(root, factory) {
if(typeof define === 'function' && define.amd) {
define(['Hello'], function(Hello) {
return (root.Hello = factory(Hello));
});
} else if(typeof exports === 'object') {
module.exports = factory(require('carousel-js'));
} else {
root.Hello = factory(root.Hello);
}
}(this, function() {
'use strict';
var Hello = function(el, name) {
if (document.querySelector(el)) root.el = document.querySelector(el);
else return;
this.name = name;
this.sayHello();
};
Hello.prototype.sayHello = function() {
return this.el.innerHTML = 'Hello ' + this.name + '!';
};
return Hello;
}));
Finally, I want to invoke this prototypal 'plugin' each time per selector using the same selector, for example:
var hello = new Hello('.js-hello', 'Foo');
Now, I know document.querySelector(el) will only find the first element, but what I'm more interested in is being able to use it where ever .js-hello is found within the page. document.querySelectorAll(...) isn't quite the answer.
I'm trying to figure out how to do something similar to how MooTools would do an each loop on the element, for example ($$ being all elements matched):
$$('.js-hello').each(function(el) {
return new Hello(el, 'Foo');
});
I can't quite wrap my head around it! Any help would be greatly appreciated or if someone could point me to a post that answers it, I've found zilch!
invoke the 'plugin' each time per selector using the same selector
That doesn't make sense, or at least is very bad design. Invoking a function with the same arguments should get you the same results, instead of using some internal, global counter that determines which of the many elements with that selector you're going to get.
Instead, you should follow that MooTools example more closely. Don't pass selectors to Hello, but elements, and do the selection outside of your constructor.
function Hello(el, name) {
this.el = el;
this.name = name;
this.sayHello();
}
…
var els = document.querySelectorAll(".js-hello");
for (var i=0; i<els.length; i++)
new Hello(els[i], "Foo");
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( ... )
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.