The code I currently have gets the whole CSS, even the default one. What I want is to get only the CSS changed from default.
function baba() {
addEventListener("mouseover", function() {
var elem = document.getElementById("ex");
cssObj = window.getComputedStyle(elem, null)
var txt = "";
for (i = 0; i < cssObj.length; i++) {
cssObjProp = cssObj.item(i)
txt += cssObjProp + " = " + cssObj.getPropertyValue(cssObjProp) + "<br>";
document.getElementById("empty").innerHTML = txt;
}
})
}
<p id="ex" onclick="baba()">Hello World</p>
<h1>Hello World</h1>
<p id="empty"></p>
Okay, so here's how I'd tackle it. Note: I just slammed this out in the console in 5 minutes; I'm sure there are more efficient ways to handle it, but for PoC this should get you going.
Requirements Analysis
Really (barring a more specific edge-case application, anyway), what you're asking for is "How does Element <XXX>'s current computed style differ from a vanilla object of the same type in the same context?" It's nonsensical to ask how it differs from "default" because "default", perforce, is going to be influenced by said context (don't agree? Wait for it; I'll 'splain).
Because of this, really what we need to be examining is a <XXX> that lacks the effects applied to your target object (consequences of its DOM position, class, id, attributes, predecessors, etc.). The good news is: we can totally fake it! Check it out:
The Setup
First thing's first, let's get hold of our object. This would be better executed as a function, I know, but for illustrative purposes, work with me here. Let's pick an object you can see the results on right away. Let's see... how about the Search bar at the top of this very page? Hit f12 to pop your console, and you'll see it's name is 'q'. That'll work.
// Get the StackOverflow Search field from the top of this page.
var targetDOMElement = document.querySelector('[name="q"]');
// Now, let's get its current style snapshot.
var targetObjsStyles = window.getComputedStyle(targetDOMElement);
// ... and vomit it out to our console, just so we know what we're dealing with.
console.log('ORIGINAL SET (' + Object.keys(targetObjsStyles).length + ' rules):',targetObjsStyles);
Capital! Now we have our source object (our "<XXX>", if you will).
The Control
Next, we need something to compare it against. Now, being the obedient little boy who was raised Orthodox Scientist that I am, in my mind that's a control. Fortunately, we know plenty about our source object, so let's manufacture one:
// Create a new element of the same type (e.g. tagName) as our target
var tempCopyOfTarget = document.createElement(targetDOMElement.tagName);
// Insert it into the context AT THE BEGINNING of the page. Both bits here are important:
// if we create it within a documentFragment (try it) literally every property will
// be flagged as unique. I suspect this has to do with the client's default
// renderer, vs. the purity of a abstracted prototype, but I won't lie: I'm guessing.
// It MUST be at the start of the body to avoid silliness like
// body > .first-element ~ xxx { display:none; }
// CSS still won't let us target predecessors/ancestors, alas.
document.body.insertAdjacentElement('afterBegin', tempCopyOfTarget);
// Now our new object shares our target's context, get ITS snapshot.
var basicElementsCSS = window.getComputedStyle(tempCopyOfTarget);
console.log('BASELINE (DUMMY OBJECT) SET (' + Object.keys(basicElementsCSS).length + ' rules):',basicElementsCSS);
The Grunt Work
While I'm certain most folks see where I'm going at this point, let's finish her off. Given a testable quantity, and a control, check for deltas.
// Create an empty object to store any changes in.
var cleanSetOfStyles = {};
// Objectify our target's style snapshot, and iterate.
Object.entries(targetObjsStyles).forEach(p=>{
// If a key-value pair exists that matches our control, ignore it. Otherwise,
// tack it onto our clean object for later perusal.
if(basicElementsCSS[p[0]] !== p[1]){
cleanSetOfStyles[p[0]] = p[1];
}
});
Awesome! Nice work!
Conclusion
Now, assuming my hypothesis is correct, we should see within our clean object a set of properties and their corresponding values. The length of this list should be both non-zero, and different than the count contained within the raw sets above (which, the more observant of you will have noticed, WERE the same, in that the browser assigns ALL possible styles' values to an object when a getComputedStyles collection is requested.
// Display our deltas
console.log('CLEAN SET (' + Object.keys(cleanSetOfStyles).length + ' rules):',cleanSetOfStyles);
// Oh, and always remember to clean up after you make a mess in the lab.
tempCopyOfTarget.remove()
What's this!? VICTORY! At least in my environment (which has to factor my browser make, version, active plug-ins, supported features, operating system, etc., etc.; your mileage may vary), I count 116 rules that remain and are acting on our target object. These are the rules that differ from our vanilla, first-line-of-code object we summoned into being for the picoseconds it took the browser to take a gander at it.
CAVEATS
There's always a catch, isn't there?
This is NOT a foolproof system. I can think of a half dozen ways this will fail off the top of my head (:empty modifiers, depending on the scope you're in... [name="q"] ~ [name="q"] rules, the insertion of our dummy object now making apply to our target... :first-of-type no longer being applicable... all kinds of 'whoopsies'). BUT, I'm prepared to assert all the ones I can think of are both edge cases, and themselves manageable, given proper forethought.
TLDR
Here's the whole code, copy+pasteable directly into console, if you're so inclined, and sans comments:
var targetDOMElement = document.querySelector('[name="q"]');
var targetObjsStyles = window.getComputedStyle(targetDOMElement);
console.log('ORIGINAL SET (' + Object.keys(targetObjsStyles).length + ' rules):',targetObjsStyles)
var tempCopyOfTarget = document.createElement(targetDOMElement.tagName);
document.body.insertAdjacentElement('afterBegin', tempCopyOfTarget);
var basicElementsCSS = window.getComputedStyle(tempCopyOfTarget);
console.log('BASELINE (DUMMY OBJECT) SET (' + Object.keys(basicElementsCSS).length + ' rules):',basicElementsCSS)
var cleanSetOfStyles = {};
Object.entries(targetObjsStyles).forEach(p=>{
if(basicElementsCSS[p[0]] !== p[1]){
cleanSetOfStyles[p[0]] = p[1];
}
});
console.log('CLEAN SET (' + Object.keys(cleanSetOfStyles).length + ' rules):',cleanSetOfStyles);
tempCopyOfTarget.remove()
Final note: I know this question is a couple months old, but nobody really answered it outside of "Nope! You're screwed!"
On the off chance #angels7 still needs the fix, here ya go. Otherwise, "Hi, all you far-out future folk!"
Related
This question stems from a conundrum I am facing in Javascript, though a more general scientific response would be extremely helpful.
If an object or array is being iterated over for another purpose—and it is known that only one element of interest has changed which can be acted upon for manipulation—is it best to:
Simply replace every element with new data to reflect the change
Rigorously check each element and replace only that which has changed
(In this example, heights of all bars of a graph are being adjusted—as they are relative—though only one textual piece of information is targeted for change.)
Array.from(result['data']).forEach(row => {
const bar = document.getElementById('bar-' + row['date']);
bar.style.height = 'calc(1.6rem + ' + row['percentage'] + '%)';
bar.firstChild.textContent = row['distance'];
});
Or:
Array.from(result['data']).forEach(row => {
const bar = document.getElementById('bar-' + row['date']);
bar.style.height = 'calc(1.6rem + ' + row['percentage'] + '%)';
if (bar.firstChild.textContent !== row['distance']) bar.firstChild.textContent = row['distance'];
});
I suppose this is a question that exposes my ignorance and it has made it difficult for me to research a conclusion: Is it more computationally exhausting to replace all elements when a difference is known to exist somewhere in the set, or is it cheaper to seek out the offending individual and change only that value?
(Setting timers, i.e. console.timeEnd(), has proved inconclusive.)
Any education would be throughly appreciated. I can't get my head around it.
It depends on the browser.
On Chrome and Opera, at least, plain assignment without checking looks to be more performant than looking up the existing text, even without possible assignment on top of looking up the existing text, by an order of around 3x:
(warning: running the following code will block your browser for some time, only press "Run" if you're sure)
const fn1 = () => {
const bar = document.querySelector('#bar');
for (let i = 0; i < 9999999; i++) bar.textContent = 'bar1';
};
const fn2 = () => {
const bar = document.querySelector('#bar');
for (let i = 0; i < 9999999; i++) {
// The following condition will never be fulfilled:
if (bar.textContent !== 'bar2') bar.textContent = 'bar2';
}
};
const now0 = performance.now();
fn1();
const now1 = performance.now();
fn2();
const now2 = performance.now();
console.log(now1 - now0);
console.log(now2 - now1);
<div id="bar"></div>
On the other hand, on Firefox 56, the lookup seems to take next to no time at all (whereas assignment is computationally expensive)
But this is only really something to worry about if you have tons and tons of elements. Unless you're dealing with thousands or tens of thousands of elements, it's not something worth optimizing for.
It's not necessary to check if the property already has the value you're assigning to it. The browser will determine if the value actually changed and handle it accordingly.
Since in your example you have already called DOM method getElementById which is the slowest part checking the property is way faster than performing a change to it. So it's always better to keep DOM-manipulations as little as possible.
UPD #CertainPerformance's test shows that performance varies amongst browsers =)
I'm using Animate CC (the erstwhile Flash CC) to do some ads that I'm exporting in HTML5 format (<canvas> and CreateJS stuff). They're working quite nicely overall.
I have a formatted number, in a Static Text box, like so: 5,000,000 and I want to tween it to, say, 20,000, over the course of 30 frames. I want to tween the same text to 5,000 and 1,000,000 and so on throughout the course of my scene.
In my limited Animate CC experience, I've managed to avoid using any Javascript, but I imagine that I will need to now. So, my question: how do I do this?
My thoughts on ways of doing this:
Since I'm using CreateJS, which has the TweenJS library as part of it, maybe I can just use that for tweening? Make little Actions at different points of my timeline? Not sure how all that works, and a lot of the references online are for ActionScript 3 or even AS2. Sample code would be appreciated.
If I do get into Javascript, there's the question of how I would do the number formatting. I could tween the number as 5000000 -> 20000 and on each frame update insert commas, that's one way of doing it. But to make matters more complex, these ads are going to be translated, and different locales come into the mix. So in English you get 5,000,000 and in German would you have 5.000.000, of course.
Since we're talking Javascript in the browser, I'm aware of the method Number.prototype.toLocaleString() which does the following:
The toLocaleString() method returns a string with a language sensitive
representation of this number.
That seems like it would do the trick, but then I have to worry about browser compatibility and what happens if I don't specify a locale. Ideally, since the German ads would only be displayed to people who had a German locale on their browser/OS, I could just call the method without any locale specified, and it would read it off the user's computer. I suppose it's possible to have the scenario where a German person is seeing an English ad, but I'm not that worried about it.
However, on the MDN page for toLocaleString() it has this big warning about earlier versions of FF defaulting to Western Arabic digits, so it makes me doubt the use of the method entirely.
Finally, I have the interesting fact that the translators will almost certainly take 5,000,000 and convert it into 5.000.000 for German. So it may be possible to avoid the use of toLocaleString() since I'll already have localized text. So if it were possible to write a simple Javascript function that could tween arbitrarily formatted numbers, I think that would do the trick. Perhaps:
Take the starting number and rip formatting out of it, save it
Tween the number
On each frame update, inject the formatting back into it
Probably not that hard from a JS perspective, but where I get stumped is how the heck I would do this in Animate/Flash and/or with CreateJS/TweenJS?
As far as tweening a formatted number using TweenJS, you can just tween a non-formatted number, and on "change", create a formatted version to do what you need:
createjs.Tween.get(obj, {loop:true})
.to({val:10000}, 4000)
.to({val:0}, 4000)
.on("change", formatNumber);
function formatNumber(event) {
// Round and format
var formattedNumber = (obj.val|0).toLocaleString();
}
Here is a simple fiddle: http://jsfiddle.net/m3req5g5/
Although Lanny gave some good data, I wanted to lay out exactly what I ended up doing to get this working.
First, you need to get a reference to the object you're going to be tweening in some way. When you make an Action in Flash and write Javascript, this is bound to the Stage, or to the MovieClip or Graphic that you're editing:
http://createjs.com/html5ads/#Scope
You can access objects using their instance names which are defined in Flash on the Properties of the object, once you've placed it on the Stage. Some sources online said that it was based on the symbol name or some such, but I haven't found that to be the case.
// Get a reference to the object you want to tween
var obj = this.anInstanceName;
Note that, if you want to access something that's inside a MovieClip, you will need to give your MovieClip on the stage an instance name, and then go inside the MovieClip and give an instance name to your target object. Then you can just walk down the hierarchy:
// Get a reference to the nested object you want to tween.
var obj = this.movieClipInstanceName.nestedInstanceName;
Now you can tween any numeric property of the object in question. For me, because I wanted to tween the text, I set an additional property on the object and tweened that, then formatted and copied it over into the text property as I went along.
It was useful to be able to specify how many frames the tween lasted, rather than the milliseconds, so I passed the useTicks flag.
obj.counter = 0;
createjs.Tween.get(obj, {useTicks: true})
.to({counter: 100}, 30) // <- 30 frames, this number is ms without useTicks
.on("change", formatNumber);
function formatNumber(event) {
obj.text = obj.counter.toLocaleString();
}
The above is generally applicable. Otherwise, here's the working code that I ended up using. It should be able to be dropped into a Flash Action in an HTML5 Canvas project and just work.
// Figures for tweening
var textStart = "2,000";
var textEnd = "6,000,000";
// Locate our target text box
var target = this.myTextBox; // replace "myTextBox" with your instance name
// Get our formatting data and so on
var data = this.getFormatData(textStart);
// Set up the text box
target.number = data.number;
target.text = textStart;
// Get the raw number we're tweening to
var endNumber = this.getFormatData(textEnd).number;
// Create the tween
createjs.Tween.get(target, {useTicks: true})
.to({number:endNumber}, 30)
.on("change", format);
//Formatting function, gets called repeatedly for each frame
function format(event) {
var rounded = Math.round(target.number);
var formatted = formatNumber(rounded, data.format);
target.text = formatted;
}
// UTILITY FUNCTIONS:
// Takes "###,###,###" or somesuch
// Returns a raw number and a formatting object
function getFormatData(formattedNumber) {
var toString = "" + formattedNumber; // in case it's not a string
var reversed = toString.split('').reverse(); // get a reversed array
// now walk (backwards) through the array and remove formatting
var formatChars = {};
for (var i = reversed.length-1; i >= 0; i--) {
var c = reversed[i];
var isnum = /^\d$/.test(c);
if (!isnum) {
formatChars[i] = c;
reversed.splice(i, 1);
}
}
// get the actual number
var number = parseInt(reversed.reverse().join(''));
// return the data
var result = {number: number, format: formatChars};
return result;
}
// Takes a raw number and a formatting object and produces a formatted number
formatNumber(number, format) {
var toString = '' + number;
var reversed = toString.split('').reverse();
var index = 0;
while (index < reversed.length) {
if (format[index]) {
reversed.splice(index, 0, format[index]);
}
index++;
}
var finished = reversed.reverse().join('');
return finished;
}
This fiddle demos the formatting and has a bit more of an explanation in the comments.
There are other ways of doing this, for sure, such as toLocaleString(), but this fit my exact requirements. Hopefully it'll help someone else.
I'm building a node server that needs to execute code that might be unsafe. In order to achieve this I'm using a Sandbox API that blocks attacks and returns the result and output from a script. It uses a modified global object to keep access hidden from the Node global object (and the use of require... etc).
My specific need right now is to take an object that is defined by a user (this is all trusted, nothing from random users on the internet so security isn't the biggest concern at the moment, right now it's to get it working) and create a dynamic bit of code that will "transfer" the object along with their code to a child Node process for safe execution (the security here is so that any errors don't crash the main process).
My current goal is to take an object, like the following:
obj = {
defaultName: "Unnamed",
hello: function(name) {
if (typeof name === "undefined" || name === null)
name = this.defaultName;
echo("Hello, " + name + "!");
}
}
(This is very simplistic, it's for testing)
I'm using FJSON to serialize the functions for transfer as well. My attempt at serializing this for transfer with the code is as follows:
// "code" is the users code
// "obj" is the object above
// "Extend" is a function defined by the Child process
var str = FJSON.funkify(obj);
code = "var temp = FJSON.unfunkify(\"" + str + "\"); Extend(this, temp); temp = undefined; " + code;
After doing this, and attempting to write it to the child I get weird (and cryptic errors) like: "Unexpected token {" or (rarely and more cryptic) "Unexpected token ILLEGAL '" (which, this is confusing because I've verified that nowhere in the code am I inserting a ' and there are none in the test code).
The funkified string is {"defaultName": "Unnamed","hello":{"FUNCTION":true,"params":["name"],"body":"\n\r if (typeof name === \"undefined\" || name === null)\n\r name = this.defaultName;\n\r echo(\"Hello, \" + name + \"!\");\n\r "}}
And finally, for the sake of testing, I've tried serializing a simple object (without functions using JSON, and with functions using FJSON) and then attempting to run eval on the string in the Node REPL but I keep getting ... when I try eval(JSON.stringify(objWithoutFunctions)); and the same with the FJSON.
I've struggled with this problem for several hours now and can't think of any other things to try/check. Any suggestions are appreciated.
UPDATE
I still have been unable to determine the most efficient way to do this, as stringifying the object and transferring it along with code was not working and I was unable to get it to work nicely I've reverted to converting the object into code, essentially looping through the properties and assigning the variables manually. To provide example:
The object:
obj = {
prop: "ItsValue",
otherProp: true
};
Would become:
this.prop = "ItsValue"; this.otherProp = true;
I found a workaround as listed in the Update, I just converted the object into code. It could have been issues with the FJSON library which I've fixed since then. This is no longer an issue but I still welcome any answers that may be able to address the original problem.
I'm writing a little cached function in a plugin / library. It takes a HTMLElement and returns a Decorator.
return function _cache(elem) {
if (elem.id === "") {
elem.id = PLUGIN_NAME + "_" + uid++;
}
if (cache[elem.id] === void 0) {
cache[elem.id] = _factory(elem);
}
return cache[elem.id];
}
Here I'm storing some expensive operation in a cache by the id of the HTMLElement. This is a O(1) lookup but it uses the "bad practice" of setting elem.id and having a side effect.
The alternative would be O(N) lookup on the cache
return function _cache(elem) {
for (var i = 0, ii = cache.length; i++) {
var o = cache[i];
if (o.elem == elem) return o.data;
}
var ret = _factory(elem);
cache.push({ elem: elem, data: ret });
return ret;
}
But this means that my cached expensive method doesn't have any side effects on the HTMLElement.
Question:
Is this "side effect" innocent and is it worth doing for the optimization on my decorator?
Real Code:
Gist of plugin template where I use this snippet
Edit:
I'm clearly too tired and forgot data-foo exists. Here's how it should be implemented
var attr = "data-" + PLUGIN_NAME + "-cache";
return function _cache(elem) {
var val = elem.getAttribute(attr);
if (val === null || val === "") {
val = PLUGIN_NAME + "_" + uid++;
elem.setAttribute(attr, val);
}
if (cache[val] === undefined) {
cache[val] = _factory(elem);
}
return cache[val];
}
Instead of using the id, use data-x - that's what it was created for.
id has a specific meaning, is confusing to see it automagically generated (even if properly documented, which is nearly never.) You're also risking a slight chance of override.
Is this "side effect" innocent
No, clearly. Is it going to interact well with other scripts on the page? Don't know... that depends what it's for and what other kinds of scripts you expect it to be combined with. You can never make a ‘plugin’ that won't ever fail when interacting with other plugins and scripts, but by keeping the side-effects to a minimum you can at least try to minimise it.
Note that id is not a unique identifier. Although there should be only one element with a given ID in a document at one particular time, (a) multiple elements might be created with the same ID and inserted into the document sequentially (perhaps one element replacing another with the same ID), and (b) people still do use duplicate IDs even though it's wrong. Either would cause your cache to collect old, no-longer-used elements and return them inappropriately.
It is unfortunate that there is no JavaScript function to get a scalar/hashable unique identifier for an arbitrary object; the only way to obtain object identity is to ===-compare against other objects.
Another common way forward is to add an arbitrary new property to the node (‘expando’ in IE terms), with a randomised really-unique ID. Expandos aren't guaranteed by standard to work, but it has worked in all browsers back to day one and is commonly used.
This is how for example jQuery identifies elements uniquely, and if you are writing a plugin for jQuery you might try taking advantage of that—jQuery.expando holds the name of the arbitrary expando property being used for this purpose... or, sticking within the documented featureset, data() could be used to add your own metadata to the element including another unique ID of your own.
Expandos do have some unpleasant side-effects including accidentally treating them as attributes in IE<9 (which can't tell the difference between properties and attribute), but if you're using jQuery anyway you probably don't have anything to lose.
is it worth doing for the optimization on my decorator?
Depends how many you're expecting to have on a page. Comparing each item to each other item is an O(n²) operation; tolerable (and probably preferable given the side-effects) if n is low, but quickly getting unmanageable as n grows.
I have a connected, directed, cyclic graph. The task is to discover every single node in the graph without falling into an infinite loop, as a regular tree traversal algorithm will do.
You can assume that I already know what node to start at so as to reach all points in the directed graph, and that for each node I have a function that will return the nodes it directs to. Is there a known algorithm for finding all nodes?
The main issue is really avoiding cycles, and I would love it if there was a way to do that without keeping track of every single node and comparing it with a list of nodes that has already been traversed.
If you need more details, the actual task is to get a list of every named function in JavaScript, including functions that are properties of other objects. So I tried something like the following, as I thought the JS objects' references to each other made a tree (but of course it doesn't):
function __findFunctions(obj){
for (var f in obj){
// for special case of edge with self
if (obj === obj[f]){
continue
}
if (typeof obj[f] === 'function' &&
obj.hasOwnProperty(f) &&
// exclude native functions, we only want user-defined ones
!(/\[(native\scode|object\sfunction)\]/i).test(obj[f].toString()) &&
// exclude functions with __ prefix
!(/^\s*function\s*__/).test(obj[f].toString())
){
document.write(f + "<br/>" + obj[f].toString() + "<hr/>");
}
//alert(typeof obj[f] + "\n" + obj + "\n" + obj[f] + "\n" + f)
__findFunctions(obj[f]);
}
}
__findFunctions(window);
The problem with this code is that it gets stuck in cycles.
I would love it if there was a way to do that without keeping track of every single node and comparing it with a list of nodes that has already been traversed.
It may not be as bad as checking a list of already-traversed nodes. You could, instead, give each node a unique ID of some sort:
// psuedo
id=0;
for each node
node.id = id++;
etc.
Then you can add each node's ID to a hash while you traverse:
var alreadyTraversed = {};
// Traversing a node:
alreadyTraversed[node.id] = true;
And later on, when you need to check whether or not its already been traversed:
if (node.id in alreadyTraversed) // It's already been traversed...
Or, for a really rudimentary solution, simply set some property on each object that you traverse:
node._traversed = true;
// Later:
if (someNode._traversed) // already traversed.
You would need to maintain a list of already visited nodes if you want to avoid cycles.
E.g.
__findFunctions(obj, visited) as your signature
at start do an "in array" test for current obj and return if so.
at start add obj to the visited for subsequent traversals.