Flash/Animate CC Tween Formatted Number - javascript

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.

Related

getcomputedstyle only the changes from default

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!"

Shorthand code for multiple "or" statements inside the if statement? [duplicate]

I have a group of strings in Javascript and I need to write a function that detects if another specific string belongs to this group or not.
What is the fastest way to achieve this? Is it alright to put the group of values into an array, and then write a function that searches through the array?
I think if I keep the values sorted and do a binary search, it should work fast enough. Or is there some other smart way of doing this, which can work faster?
Use a hash table, and do this:
// Initialise the set
mySet = {};
// Add to the set
mySet["some string value"] = true;
...
// Test if a value is in the set:
if (testValue in mySet) {
alert(testValue + " is in the set");
} else {
alert(testValue + " is not in the set");
}
You can use an object like so:
// prepare a mock-up object
setOfValues = {};
for (var i = 0; i < 100; i++)
setOfValues["example value " + i] = true;
// check for existence
if (setOfValues["example value 99"]); // true
if (setOfValues["example value 101"]); // undefined, essentially: false
This takes advantage of the fact that objects are implemented as associative arrays. How fast that is depends on your data and the JavaScript engine implementation, but you can do some performance testing easily to compare against other variants of doing it.
If a value can occur more than once in your set and the "how often" is important to you, you can also use an incrementing number in place of the boolean I used for my example.
A comment to the above mentioned hash solutions.
Actually the {} creates an object (also mentioned above) which can lead to some side-effects.
One of them is that your "hash" is already pre-populated with the default object methods.
So "toString" in setOfValues will be true (at least in Firefox).
You can prepend another character e.g. "." to your strings to work around this problem or use the Hash object provided by the "prototype" library.
Stumbled across this and realized the answers are out of date. In this day and age, you should not be implementing sets using hashtables except in corner cases. You should use sets.
For example:
> let set = new Set();
> set.add('red')
> set.has('red')
true
> set.delete('red')
true
> set.has('red')
false
Refer to this SO post for more examples and discussion: Ways to create a Set in JavaScript?
A possible way, particularly efficient if the set is immutable, but is still usable with a variable set:
var haystack = "monday tuesday wednesday thursday friday saturday sunday";
var needle = "Friday";
if (haystack.indexOf(needle.toLowerCase()) >= 0) alert("Found!");
Of course, you might need to change the separator depending on the strings you have to put there...
A more robust variant can include bounds to ensure neither "day wed" nor "day" can match positively:
var haystack = "!monday!tuesday!wednesday!thursday!friday!saturday!sunday!";
var needle = "Friday";
if (haystack.indexOf('!' + needle.toLowerCase() + '!') >= 0) alert("Found!");
Might be not needed if the input is sure (eg. out of database, etc.).
I used that in a Greasemonkey script, with the advantage of using the haystack directly out of GM's storage.
Using a hash table might be a quicker option.
Whatever option you go for its definitely worth testing out its performance against the alternatives you consider.
Depends on how much values there are.
If there are a few values (less than 10 to 50), searching through the array may be ok. A hash table might be overkill.
If you have lots of values, a hash table is the best option. It requires less work than sorting the values and doing a binary search.
I know it is an old post. But to detect if a value is in a set of values we can manipulate through array indexOf() which searches and detects the present of the value
var myString="this is my large string set";
var myStr=myString.split(' ');
console.log('myStr contains "my" = '+ (myStr.indexOf('my')>=0));
console.log('myStr contains "your" = '+ (myStr.indexOf('your')>=0));
console.log('integer example : [1, 2, 5, 3] contains 5 = '+ ([1, 2, 5, 3].indexOf(5)>=0));
You can use ES6 includes.
var string = "The quick brown fox jumps over the lazy dog.",
substring = "lazy dog";
console.log(string.includes(substring));

How do I use a typed array with offset in node?

I am writing a mat file parser using jBinary, which is built on top of jDataView. I have a working parser with lots of tests, but it runs very slowly for moderately sized data sets of around 10 MB. I profiled with look and found that a lot of time is spent in tagData. In the linked tagData code, ints/uints/single/doubles/whatever are read one by one from the file and pushed to an array. Obviously, this isn't super-efficient. I want to replace this code with a typed array view of the underlying bytes to remove all the reading and pushing.
I have started to migrate the code to use typed arrays as shown below. The new code preserves the old functionality for all types except 'miINT8'. The new functionality tries to view the buffer b starting at offset s and with length l, consistent with the docs. I have confirmed that the s being passed to the Int8Array constructor is non-zero, even going to far as to hard code it to 5. In all cases, the output of console.log(elems.byteOffset) is 0. In my tests, I can see that the Int8Array is indeed starting from the beginning of the buffer and not at offset s as I intend.
What am I doing wrong? How do I get the typed array to start at position s instead of position 0? I have tested this on node.js version 10.25 as well as 12.0 with the same results in each case. Any guidance appreciated as I'm totally baffled by this one!
tagData: jBinary.Template({
baseType: ['array', 'type'],
read: function (ctx) {
var view = this.binary.view
var b = view.buffer
var s = view.tell()
var l = ctx.tag.numBytes
var e = s + l
var elems
switch (ctx.tag.type) {
case 'miINT8':
elems = new Int8Array(b,s,l); view.skip(l); console.log(elems.byteOffset); break;
default:
elems = []
while (view.tell() < e && view.tell() < view.byteLength) {
elems.push(this.binary.read(ctx.tag.type))
}
}
return elems
}
}),

Javascript prototype function: decimal time value to a time string

On a project I'm currently working on in JavaScript, I'm using decimal formats so it's easier to calculate with rather than using an hour/minute format in strings (calendar related project). To display the time on the user's screen though, the timecode has to be shown as hh:mm.
I thought it would be great to use a String prototype function for this as it would allow me to use code like:
var time = 8.75;
document.write("Meeting at "+time.toTime()); // writes: Meeting at 8:45
So far, I've got that almost working, using:
String.prototype.toTime = function(){
var hrs = this.toString().slice(0,this.indexOf("."));
var min = Math.round(this.toString().slice(this.indexOf("."))/100*60);
min = min<10 ? "0"+min : min.toString();
return hrs+":"+min;
}
The problem, though, is that this will only work if the variable time is a string. Otherwise it will give an undefined error.
Would there be any way of applying the prototype to a different object in JavaScript, so that I don't have to use time.toString().toTime()?
Thanks!
Firstly, you can add to the Number prototype. Many people will warn against modifying prototypes, which in many cases is justified. If there is a chance 3rd party scripts will be running alongside yours, it is a danger to modify prototypes.
Secondly, I simplified your code a little, using modulus, and floor to calculate the hrs and mins...
Number.prototype.toTime = function(){
var hrs = Math.floor(this)
var min = Math.round(this%1*60)
min = min<10 ? "0"+min : min.toString();
return hrs+":"+min;
}
var time = 8.25;
console.log("Meeting at "+time.toTime());
You can use Object.prototype.toTime.

Google maps javascript issue

I am seeing something I don't understand when using the Google map API. I have the following code to pull the viewport...
var bounds = map.getBounds();
var viewport = {
top: bounds.getNorthEast().lat(),
right: bounds.getNorthEast().lng(),
bottom: bounds.getSouthWest().lat(),
left: bounds.getSouthWest().lng()
};
Turns out that viewport.top returns...
57.220445088498764 {
toJSON : function(key) { return this.valueOf(); } }
Can someone explain to me what this is? I don't completely understand js prototypes, but based on my limited understanding a js prototype is a function attached to an instance of an object. Since a number is an object, has Google placed a prototype function called 'toJSON' on all objects it returns?
How can I get rid of that so I end up with simply the number 57.220445088498764?
I am attempting to use the json2.js JSON.stringify, and it isn't giving proper JSON back because of this weird function.
In Javascript everything is an Object** - even Numbers. This means that we can add methods to anything - even numbers. Try this in Firebug or the Chrome inspector:
Number.prototype.hello = function() { alert("Hello, world!"); }
var x = 2;
x.hello();
Even though x is a number, it can still have methods. By adding a method to the prototype for Number, we ensure that all numbers have that method. Of course, normal numbery things will still work too:
var y = x + 2;
window.console.log(y); // Will output 4.
This seems to be used by the Maps API to add a toJSON method to numbers for convinience. It isn't a great design strategy in my opinion, but it's also harmless. Your number will continue to act just like a number should, so you can safely ignore the toJSON method.
** Except the things that aren't, like number or string literals.

Categories

Resources