animating css transformations with javascript - javascript

I have a specific problem and general question.
I have to animate dragging with javascript (no frameworks), and I think it's better to use translate() instead of changing top and left, at least for better perfomance.
But it is part of user-driven interface, so I can't predefine any animation. So I have to build a string with updated values and assign it to element style property.
It's my problem.
So if I have to animate a few user-driven transformations I have to do relatively heavy string operations to update necessary values and keep those which I don't animate? Is there any kind of simpler API to do it with javascript?
I want to understand it at first, so no frameworks, please.
Thanks.

I saw Ilya's comment about matrices, which is probably a good thing to check out. Given I am struggled at matrix math, a string based solution is right up my alley.
I threw this together in about half an hour but it seems to work well. I assume you'll just capture mouse move and update the translate value. I created the toObject method in case you wanted to do some more advanced animations.
I think if you're going to go this route, you should reconsider not wanting to use frameworks.
(function() {
var setTransFunc, getTransFunc;
// make sure cross browser...
// I don't know if this is nescessary. Every browser I tried worked on the first options.
(function () {
var testElem = document.createElement('DIV');
if (testElem.style.transform !== undefined) {
setTransFunc = function (elem, str) {
elem.style.transform = str;
}
getTransFunc = function (elem) {
return elem.style.transform;
}
}
else if (testElem.style.webkitTransform !== undefined) {
setTransFunc = function (elem, str) {
elem.style.webkitTransform = str;
}
getTransFunc = function (elem) {
return elem.style.webkitTransform;
}
}
else if (testElem.style.msTransform !== undefined) {
setTransFunc = function (elem, str) {
elem.style.msTransform = str;
}
getTransFunc = function (elem) {
return elem.style.msTransform;
}
}
else {
throw ('unable to detect set/get methods for transform style.');
}
}).call();
// constructor
var _tranZ = function (elem) {
this.elem = (typeof elem == 'string') ? document.getElementById(elem) : elem;
};
(function () {
this.elem = null;
// sets transform style
this.set = function (str) {
setTransFunc(this.elem, str);
return this;
}
// gets string of transform style
this.get = function () {
return getTransFunc(this.elem);
}
// adds a trasnform
this.add = function (str) {
this.set(this.get() + ' ' + str);
return this;
}
// removes a transform
this.remove = function (name) {
var re = new RegExp(name + "\\([^)]*\\)", "gi");
console.log(re);
this.set(this.get().replace(re, ""));
return this;
}
// represent transforms as object. Might be easier later on to animate with this.
this.getTransformObject = function () {
var str = this.get(),
re = /(\w+)\s*\(([^)]*)\)/g,
match, obj = {};
while (match = re.exec(str)) {
obj[match[1]] = {
parameters: match[2].split(/\s*,\s*/)
}
}
return obj;
}
}).call(_tranZ.prototype);
// add a window module
window.tranZ = function (elem) {
return new _tranZ(elem);
};
})();
jsFiddle

Related

Change a native function's body while keeping the same "identity"

I'm looking into a way to change a native JS function body, while making it not possible to see that it has been changed. Let's take an example with document.hasFocus():
document.hasFocus = ()=>true;
This method works well to spoof focus, but it can be easily detected that it was modified:
document.hasFocus.toString() // -> "()=>true"
Is there any way, in which I can modify such a function while making it impossible to see it has been tampered with?
You can overwrite toString method in Function prototype, and do something like that:
// https://stackoverflow.com/questions/1833588/javascript-clone-a-function
Function.prototype.clone = function() {
var that = this;
var temp = function temporary() {
return that.apply(this, arguments);
};
for (var key in this) {
if (this.hasOwnProperty(key)) {
temp[key] = this[key];
}
}
return temp;
};
Function.prototype.__oldToString = Function.prototype.toString.clone();
function __toStringHooked() {
if ((this.name == "")||(this.name == "hasFocus")) // on Firefox, hasFocus doesn't have any name
{
return eval+"" // this matches regexp
} else {
return this.__oldToString(); // we're returning default value
}
}
Function.prototype.toString = __toStringHooked
document.hasFocus = () => true
The code above is from Th3B0r3dD3v3l0p3r's GitHub repo, you can check it if you want: https://github.com/Th3B0r3dD3v3l0p3r/focus-spoofer/

Javascript: effective array-based replacement in strings

I am looking for a way to effectively (as in: with as little ressources as possible) replace orrurances of strings by other strings in Javascript.
The focus is on computing time, more than memory consumption.
Search terms and replacements are given as a object used as dictionary
var replacements = {
search : 'replace',
another : 'replacement',
'and one' : 'more'
}
Currently I'm iterating over the keys and building a regexp (with set g flag) out of them, then look up every match in the dictionary and replace it:
String.prototype.mapReplace = function (map, replaceFullOnly = false) {
var regexp = [];
for (var key in map) {
regexp.push(RegExp.escape(key));
}
regexp = regexp.join('|');
if (replaceFullOnly) {
regexp = '\\b(?:' + regexp + ')\\b';
}
regexp = new RegExp(regexp, 'gi');
return this.replace(regexp, function (match) {
return map[match.toLowerCase()];
});
}
This works, however I need to compile a new regular expresison every time. My question is: can somebody come up with an effective way to cache the regular expresisons and, if the same map (same as in "same keys", neither "same object" nor "same values" nor "same order of keys") is given again, the regular expresiosn is re-used?
One obvious way would be sorting, serializing and hashing keys, use it as a key to store the regular expresison and re-use stored regular expressions if existant on future calls. However, I tihnk this will most likely require more time than compiling a new regular expresiosn every time...
Ideas/input?
Edit: RegExp.escape() is a function that escapes special characters in strings for use in regular expressions:
RegExp.escape= function(s) {
return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
};
Usage information:
replacements are done a lot, as this i used in a chat system
changes to the replacement map are infrequent, however it depends on how chat operators use the feature. Automated scripts adding and removing replacement rules automatically and frequently are possible. However, changes to the replacement map will always be less frequent than applying the replacement map to strings.
one or multiple replacement maps might be in use simultaniously and independent of each other.
This is what I came up with:
var ReplacementMap = function (map, replaceFullOnly, ignoreCase) {
var regexp = null;
var update = function (search, replacement) {
if (!isDefined(replacement)) {
if (!(search in map)) return;
delete map[search];
} else {
if (map[search] == replacement) return;
map[search] = replacement;
}
invalidateRegexp();
}
var buildRegexp = function () {
if (regexp != null) return;
regexp = [];
for (var key in map) {
regexp.push(RegExp.escape(key));
}
regexp = regexp.join('|');
if (replaceFullOnly) {
regexp = '\\b(?:' + regexp + ')\\b';
}
regexp = new RegExp(regexp,'g' + (ignoreCase ? 'i' : ''));
}
var invalidateRegexp = function () {
regexp = null;
}
Object.defineProperties(this, {
fullOnly : {
set : value => {
if (replaceFullOnly == value) return;
replaceFullOnly = !!value;
invalidateRegexp();
},
get : () => replaceFullOnly
},
ignoreCase : {
set : value => {
if (ignoreCase == value) return;
ignoreCase = !!value;
invalidateRegexp();
},
get : () => ignoreCase
}
});
this.set = function set (search, replacement) {
if (Array.isArray(search)) {
if (Array.isArray(search[0])) {
search.forEach(function (search) {
set(search);
});
} else {
update(search[0], search.length > 1 ? search[1] : undefined);
}
} else if (search instanceof Object
&& search !== null
&& !String.isString(search)) {
for (key in search) {
update(key, search[key]);
}
} else update(search, replacement);
}
this.get = function (search) {
return search in map ? map[search] : undefined;
}
this.remove = function(search) {
update(search);
}
this.apply = function (string) {
buildRegexp();
return string.replace(regexp, function (match) {
return map[match.toLowerCase()];
});
}
this[Symbol.iterator] = function* () {
for (let key of Object.keys(map)) {
yield {[key] : map[key]};
}
return;
}
if (isDefined(replaceFullOnly)) {
replaceFullOnly = !!replaceFullOnly;
} else {
replaceFullOnly = true;
}
if (isDefined(ignoreCase)) {
ignoreCase = !!ignoreCase;
} else {
ignoreCase = true;
}
if (isDefined(map)) {
let entries = map;
map = Object.create(null);
this.set(entries);
} else {
map = Object.create(null);
}
}
Usage:
// ---- CREATE MAP ----
// Empty Map
var m0 = new ReplacementMap();
// Map initialized with one replacement: foo => bar
var m1_1 = new ReplacementMap('foo','bar');
var m1_2 = new ReplacementMap(['foo','bar']);
var m1_3 = new ReplacementMap({foo : 'bar'});
// Map initialized with two replacements: foo => bar, fooz => baz
var m2_1 = new ReplacementMap([['foo','bar'], ['fooz', 'baz']]);
var m2_2 = new ReplacementMap({foo : 'bar', fooz : 'baz'});
var m2_3 = new ReplacementMap([{foo : 'bar'}, {fooz : 'baz'}]);
// ---- ADD/MODIFY ENTRIES ----
var m0.set(...) // ... parameters work the same as in the constructor
// ---- REMOVE ENTRIES ----
var m2_1.delete('foo') // removes replacement rule for foo => bar
var m2_1.delete('test') // fails silently
// ---- READ ENTRIES ----
var m2_1.get('foo') // returns "bar"
var m2_1.get('test') // returns undefined;
for (rule of m2_1) {
alert(JSON.stringify(rule));
}
// alerts "{'foo':'bar'}" and "{'fooz':'baz'}"
// ---- APPLY ON STRING ----
alert(m2_1.apply("foo bar")) // bar bar
// change behaviour:
m2_1.fullOnly = true; // replace foo with bar, but not foobar with barbar
// default: true;
m2_1.ignoreCase = true; // ignore case. default: true

My object isn't updating by reference, what's wrong with the logic?

I've been working with this bug for a few days now, and I think I've pinpointed the problem area, but I'm not sure why it doesn't work. I think it may have to do with a problem with passing an object by reference, but if that's the case I'm not sure how to apply that solution to my situation.
Basically, I'm working on (as a learning experience) my own implementation of dependency injection (although I've been told my structure is actually called AMD, I'll keep using "DI" until I understand more about the difference). So I'll briefly explain my code, then highlight the problematic part.
The syntax:
This is what my code should do, it's just very very simple DI.
I created scope with a string path, using "/scopeName/subScopeName:componentName" to select a scope, so that code users can select the scope while defining the component in a simple way, using a ":" to select a component from the scope.
There are no interfaces since it's so simple to type check in JS. There are no special component types such as factories, values, etc, every component is treated equally.
var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');
/* ... snip ... */
JHTML.addComponent('/generate:process', function(nodes) {
/* ... snip - the code inside isn't important here - snip ..*/
}).inject(['/generate:jsonInput']);
The inject function just takes an array of component paths in the order the component's arguments are expected.
Hooks are components stored in the hooks property, and then there's a function returnUserHandle which will return an object consisting of just the hooks, so all of the functions are hidden in closures, and you can feed the code user just the usable methods.
JHTML.addHook('generate', function(jsonInput, process) {
var html = process(jsonInput);
return html;
}).inject(['/generate:jsonInput', '/generate:process']);
var handle = JHTML.returnUserHandle();
/* HTML Generator Syntax - Client */
console.log(handle.generate());
The problem:
To point inject to the correct object intuitively, there's a focus property on the main object, and I thought I could use that.focus ( which is a reference to this.focus) within my different methods such as addComponent and inject to link new functions to the correct location in my scope model and have them still referenced in focus after being created with addComponent or after being called by the focusComponent method, and then inject could find the dependencies, and "wire" them by doing this:
that.focus = function() {
that.focus.apply(null, dependencies);
};
And I thought that would package the dependencies (an array) as a closure and when the code user calls the function, the correct dependencies get applied and that's the ball game. But nope. The functions dont seem to be passing by reference from that.focus into the scope model. that.focus updates, but the scope model does not.
What's wrong with my reference logic?
The code:
Here's a simplified version of the code. I think I've done my best to explain how it works and where exactly the reference problem I'm trying to solve is located.
/* Dependency Injection Framework - viziion.js */
function Viziion() {
var that = this;
//here's the focus property I mentioned
this.focus = null;
this.scope = {
'/': {
'subScopes': {},
'components': {}
}
};
this.hooks = {};
this.addScope = function(scopeName) {
/* the way this works inst relevant to the problem */
};
this.addComponent = function(componentName, func) {
var scopeArray = // snip
// snip - just code to read the component path
for (var i = 0; i <= scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else if (i == scopeArray.length) {
// And here's where I add the component to the scope model
// and reference that component in the focus property
scope.components[scopeName] = func;
that.focus = scope.components[scopeName];
} else {
throw 'Scope path is invalid.';
}
}
}
} else {
throw 'Path does not include a component.';
}
return that;
};
this.returnComponent = function(componentName, callback) {
/* ... snip ... */
};
this.addHook = function(hookName, func) {
/* ... snip ... */
};
this.inject = function(dependencyArray) {
if (dependencyArray) {
var dependencies = [];
for (var i = 0; i < dependencyArray.length; i++) {
that.returnComponent(dependencyArray[i], function(dependency) {
dependencies.push(dependency);
});
}
that.focus = function() {
that.focus.apply(null, dependencies);
};
return that;
}
};
/* ... snip - focusComponent - snip ... */
/* ... snip - returnUserHandle - snip ... */
This should, when applied as shown up above under the "Syntax" header, produce a console log with a string of HTML.
Instead, I get TypeError: undefined is not a function, corresponding to the line var html = process(jsonInput);.
If you want to test the full code, all together, here it is:
/* Dependency Injection Framework - viziion.js */
function Viziion(appName) {
if (typeof appName == 'string') {
var that = this;
this.name = appName;
this.focus = null;
this.scope = {
'/': {
'subScopes': {},
'components': {}
}
};
this.hooks = {};
this.addScope = function(scopeName) {
if (typeof scopeName == 'string') {
var scopeArray = scopeName.split('/');
var scope = that.scope['/'];
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
scope.subScopes[scopeArray[i]] = {
'subScopes': {},
'components': {}
};
}
}
}
} else {
throw 'Scope path must be a string.';
}
return that;
};
this.addComponent = function(componentName, func) {
if (typeof componentName == 'string') {
var scopeArray = componentName.split(':');
if (scopeArray.length == 2) {
var scope = that.scope['/'];
var scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
for (var i = 0; i <= scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else if (i == scopeArray.length) {
scope.components[scopeName] = func;
that.focus = scope.components[scopeName];
} else {
throw 'Scope path is invalid.';
}
}
}
} else {
throw 'Path does not include a component.';
}
} else {
throw 'Component path must be a string.';
}
return that;
};
this.returnComponent = function(componentName, callback) {
if (typeof componentName == 'string') {
var scopeArray = componentName.split(':');
if (scopeArray.length == 2) {
var scope = that.scope['/'];
var scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
for (var i = 0; i <= scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if (i == scopeArray.length) {
callback(scope.components[scopeName]);
} else if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
throw 'Scope path is invalid.';
}
}
}
} else {
throw 'Path does not include a component.';
}
} else {
throw 'Component path must be a string.';
}
};
this.addHook = function(hookName, func) {
if (typeof hookName == 'string') {
that.hooks[hookName] = func;
that.focus = that.hooks[hookName];
} else {
throw 'Hook name must be a string.';
}
return that;
};
this.inject = function(dependencyArray) {
if (dependencyArray) {
var dependencies = [];
for (var i = 0; i < dependencyArray.length; i++) {
that.returnComponent(dependencyArray[i], function(dependency) {
dependencies.push(dependency);
});
}
console.log(that.focus);
that.focus = function() {
that.focus.apply(null, dependencies);
};
console.log(that.focus);
console.log(that.scope);
return that;
}
};
this.focusComponent = function(componentPath) {
that.focus = that.returnUserHandle(componentPath);
};
this.returnUserHandle = function() {
return that.hooks;
};
} else {
throw 'Viziion name must be a string.';
}
}
/* JSON HTML Generator - A Simple Library Using Viziion */
var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');
JHTML.addComponent('/generate:jsonInput', [{
tag: '!DOCTYPEHTML'
}, {
tag: 'html',
children: [{
tag: 'head',
children: []
}, {
tag: 'body',
children: []
}]
}]);
JHTML.addComponent('/generate:process', function(nodes) {
var html = [];
var loop = function() {
for (var i = 0; i < nodes.length; i++) {
if (nodes[i].tag) {
html.push('<' + tag + '>');
if (nodes[i].children) {
loop();
}
html.push('</' + tag + '>');
return html;
} else {
throw '[JHTML] Bad syntax: Tag type is not defined on node.';
}
}
};
}).inject(['/generate:jsonInput']);
JHTML.addHook('generate', function(jsonInput, process) {
console.log('Process func arg:');
console.log(process);
var html = process(jsonInput);
return html;
}).inject(['/generate:jsonInput', '/generate:process']);
var handle = JHTML.returnUserHandle();
/* HTML Generator Syntax - Client */
console.log(handle.generate());
Big question, bigger answer. Let's get started.
Heavy OOP, Proper Scope
First and foremost, from your code, it looks like you maybe don't fully grasp the concept of this.
Unless you change the execution context of an object's methods beforehand, said object's methods always have their contextual this bound to the object instance.
That is:
function A () {
var that = this;
this.prop = 1;
this.method = function () {
console.log(that.prop);
};
}
new A().method();
is generally equivalent to:
function A () {
this.prop = 1;
this.method = function () {
console.log(this.prop);
};
}
new A().method();
unless method is adjusted before execution with .bind, .call, or .apply.
Why does this matter? Well, if we use our this context properly we can utilize object prototypes. Prototypes serve as a far more elegant solution to defining every method of an object on a per-instance basis.
Here we create two instances, but only ever one method.
function A () {
this.prop = 1;
}
A.prototype.method = function () {
console.log(this.prop);
};
new A().method();
new A().method();
This is important for clarity, and later on is important when you are binding contexts and arguments to functions (!).
Code Hygiene
You can skip this topic if you like (head down to The Problems(s)), since it might be considered out of place, but keep in mind it does relate to part of the problem with the code.
Your code is hard to read.
Here are some thoughts on that.
Prototypes
Use them. You shouldn't need to worry about users changing execution contexts on you, as that's probably a misuse of your program. Security shouldn't be a concern considering they have the source code.
Not much else to say here.
Exit early
If you're doing sanity checks, try to opt out as early in your code as you can. If you need to throw because of a type mismatch, throw right then and there - not 27 lines later.
// Not great
if (typeof input === 'string') {
...
} else throw 'it away';
// Better
if (typeof input !== 'string') throw 'it away';
...
This goes for loops as well - making appropriate use of the continue keyword. Both of these things improve code clarity, and reduce nesting and code bloat.
Loop caching
When you're looping over a data structure, and you plan to use the current element several times within the block, you should save that element in a variable. Accessing elements and properties isn't necessarily a free-OP.
// Not great
for (var i = 0; i < myArray.length; i++) {
if (myArray[i] > 5) callback(myArray[i]);
internalArray.push(myArray[i]);
}
// Better
var len = myArray.length, element;
for (var i = 0; i < len; i++) {
element = myArray[i];
if (element > 5) callback(element);
internalArray.push(element);
}
When used correctly this improves both clarity and performance.
The Problem(s)
First off, what are we really doing here? The whole problem boils down to an overly complicated application of function binds. That is, simply changing the execution contexts of functions.
I'll also state outright that this program has no bug - it's just flawed.
The major crux of the problem would be these three lines
that.focus = function() {
that.focus.apply(null, dependencies);
};
found in the inject method. They don't make any sense. This would cause an infinite recursion, plain and simple. When you define that function, it doesn't care at all what the focus property of that is right then and there. That matters solely at execution time.
Lucky for us, we never actually get that far, since the process component doesn't get bound correctly.
A huge part of the problem is the focus property. In your program, you're using this as a sort of most recent action. A singular history as to what has just occurred. The problem is, you've tried to hot-swap this value in strange ways.
The focus property (and as you'll see later, other properties) is needed however, because of the reverse application of inject. The way you've structured your component/hook registers into inject model requires state to be held between method invocations.
As an end note for this section, the process component function definition would never have returned anything. Even if your model was correct, your input was flawed. handle.generate() would have returned undefined always.
The Answer(s)
So how can we fix this? Well, the first idea would be to scrap it, honestly. The reverse injection model is ugly, in my opinion. The level of indirection involved with the inject method is very confusing from the surface.
But then we wouldn't learn anything, would we?
So really, how do we fix this? Well, much to the dismay of the functional programmers reading, we need to hold more state.
On its own, our focus property can't provide enough information to properly change the execution contexts of our functions.
On top of our focus, which will simply hold a reference to our most recent component value, we need the field (component/hook name), and the fragment (component object, nothing if hook).
Using these two or three values inside inject, we can take our depedancies array, bind it to our focus, and set the resulting function back into our field.
The great thing about the next part is we can actually drop our closure by making the contextual this of our component/hook the unbound function.
The whole operation looks like this:
var focus = this.focus,
fragment = this.fragment,
field = this.field,
hook = function hook () {
return this.apply(null, arguments);
}, func;
dependencies.unshift(focus);
func = Function.prototype.bind.apply(hook, dependencies);
if (fragment) fragment[field] = func;
else this.hooks[field] = func;
Most of this should be pretty straight forward, but there is one piece that may give people some issues. The important thing to remember is we are essentially creating two functions in sequence here, 'discarding' the first in a sense. (It should be noted that this can be done another way with hook.bind.apply, but it creates even more confusing code. This is about as elegant as you can get.)
dependencies.unshift(focus);
func = Function.prototype.bind.apply(hook, dependencies);
First, we add our focus (our original function) to the front of our list of dependencies. This is important in a moment.
Then we invoke Function.prototype.bind using Function.prototype.apply (remembering that function prototype methods also share the function prototype methods. Pretty much turtles all the way down).
Now we pass our bind context, hook, and our prefixed dependencies to apply.
hook is used as the host for bind, whose contextual this is altered by the first element of the array of arguments passed to apply. The remaining elements are unrolled to shape the subsequent arguments of bind, thus creating the bound arguments of the resulting function.
This isn't a very simple concept, so take your time.
The other thing to note is I've dropped focusComponent completely. Its implementation didn't make sense in context. Your model relies on a last input injection, so you'll need to re-implement focusComponent as a method that simply adjusts the focus, field, and fragment states.
A small sub-fix is the process component function. Not going to go into detail here. You can compare and contrast with your original code, the differences are pretty obvious.
JHTML.addComponent('/generate:process', function (nodes) {
return (function build (struct, nodes) {
var length = nodes.length, node, tag;
for (var i = 0; i < length; i++) {
node = nodes[i];
tag = node.tag;
if (!tag) throw '[JHTML] Bad syntax: Tag type is not defined on node.';
struct.push('<' + tag + '>');
if (node.children) {
build(struct, node.children)
struct.push('</' + tag + '>');
}
}
return struct;
}([], nodes));
}).inject(['/generate:jsonInput']);
The Code
Below is what I would consider a fixed version of your code. It's written in a style that I find useful for both clarity and performance.
/* Dependency Injection Framework - viziion.js */
function Scope () {
this.subScopes = {};
this.components = {};
}
function Viziion (appName) {
if (typeof appName !== 'string') throw 'Viziion name must be a string.';
this.name = appName;
this.working = this.field = this.focus = null
this.scope = { '/': new Scope() };
this.hooks = {};
}
Viziion.prototype.addScope = function (scopeName) {
if (typeof scopeName !== 'string') throw 'Scope path must be a string.';
var scopeArray = scopeName.split('/'),
scope = this.scope['/'],
len = scopeArray.length,
element, sub;
for (var i = 0; i < len; i++) {
element = scopeArray[i];
if (element === '') continue;
sub = scope.subScopes[element]
if (sub) scope = sub;
else scope.subScopes[element] = new Scope();
}
return this;
};
Viziion.prototype.addComponent = function (componentName, func) {
if (typeof componentName !== 'string') throw 'Component path must be a string.';
var scopeArray = componentName.split(':'),
len, element, sub;
if (scopeArray.length != 2) throw 'Path does not include a component.';
var scope = this.scope['/'],
scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
len = scopeArray.length;
for (var i = 0; i <= len; i++) {
element = scopeArray[i];
if (element === '') continue;
sub = scope.subScopes[element];
if (sub) scope = sub;
else if (i === len) {
this.fragment = scope.components;
this.field = scopeName;
this.focus = scope.components[scopeName] = func;
}
else throw 'Scope path is invalid';
};
return this;
};
Viziion.prototype.returnComponent = function (componentName, callback) {
if (typeof componentName !== 'string') throw 'Component path must be a string.';
var scopeArray = componentName.split(':'),
len, element, sub;
if (scopeArray.length != 2) throw 'Path does not include a component.';
var scope = this.scope['/'],
scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
len = scopeArray.length;
for (var i = 0; i <= len; i++) {
element = scopeArray[i];
if (element === '') continue;
sub = scope.subScopes[element]
if (i === len) callback(scope.components[scopeName]);
else if (sub) scope = sub;
else throw 'Scope path is invalid';
}
};
Viziion.prototype.addHook = function (hook, func) {
if (typeof hook !== 'string') throw 'Hook name must be a string.';
this.fragment = null;
this.field = hook;
this.focus = this.hooks[hook] = func;
return this;
};
Viziion.prototype.inject = function (dependancyArray) {
if (!dependancyArray) return;
var dependencies = [],
len = dependancyArray.length,
element;
function push (dep) { dependencies.push(dep); }
for (var i = 0; i < len; i++) {
element = dependancyArray[i];
this.returnComponent(element, push);
}
var focus = this.focus,
fragment = this.fragment,
field = this.field,
hook = function hook () {
return this.apply(null, arguments);
}, func;
dependencies.unshift(focus);
func = Function.prototype.bind.apply(hook, dependencies);
if (fragment) fragment[field] = func;
else this.hooks[field] = func;
return this;
};
Viziion.prototype.returnUserHandle = function () { return this.hooks; };
/* JSON HTML Generator - A Simple Library Using Viziion */
var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');
JHTML.addComponent('/generate:jsonInput', [{
tag: '!DOCTYPE html'
}, {
tag: 'html',
children: [{
tag: 'head',
children: []
}, {
tag: 'body',
children: []
}]
}]);
JHTML.addComponent('/generate:process', function (nodes) {
return (function build (struct, nodes) {
var length = nodes.length, node, tag;
for (var i = 0; i < length; i++) {
node = nodes[i];
tag = node.tag;
if (!tag) throw '[JHTML] Bad syntax: Tag type is not defined on node.';
struct.push('<' + tag + '>');
if (node.children) {
build(struct, node.children)
struct.push('</' + tag + '>');
}
}
return struct;
}([], nodes));
}).inject(['/generate:jsonInput']);
JHTML.addHook('generate', function (jsonInput, process) {
return process(jsonInput);
}).inject(['/generate:jsonInput', '/generate:process']);
var handle = JHTML.returnUserHandle();
console.log(JHTML);
/* HTML Generator Syntax - Client */
console.log(handle.generate());

new Object with prototype function vs regular function which returns an object

The basic idea is to check if it starts with an underscore and if there is split the string and return whatever comes after the underscore. This function will be run many times, but for different strings, it is unlikely i will need to retrieve the information more than once for each stirng.
A simple function which will return an object with the data I need:
var parseElementName = function(i) {
var sliced = [i.slice(0, 1), i.slice(1, i.length)];
var obj = {
isClass: null,
name: ''
}
if(sliced[0] === '_') {
obj.name = sliced[1];
obj.isClass = true;
} else {
obj.name = i;
obj.isClass = false;
}
return obj
}
Called with parseElementName(i);
Object with prototyped function
var parsedElement = function(i) {
this.className =
this.isClass = null;
if(this.setElementName(i))
return true
}
parsedElement.prototype.setElementName = function(i) {
var sliced = [i.slice(0, 1), i.slice(1, i.length)];
if(sliced[0] === '_') {
this.className = sliced[1];
this.isClass = true
} else {
this.className = i;
this.isClass = false
}
}
Called with var parsed_element = new parsedElement();
then parsed_element.className or parsedElement.isClass
Which approach is recommended?
I like the object prototype approach best, but I have a few notes about your code:
Use semicolons at the end of each line
Class names should be capitalized. So it should be ParsedElement
I wouldn't call it className, because it is confusing when it is not a class, I would rename it name
The two ways have different outcomes - that constructor+prototype approach will yield an instance which has a setElementName method. Will you ever need this to change the fields of an existing object? It's a simple parser function, so I would assume no. In that case, you should go with returning the object literal:
function parseElementName(i) {
var isClass = i.charAt(0) == '_';
return {
isClass: isClass,
name = isClass ? i.slice(1) : i
};
}
If you really need that method later, consider #MaxMeier's and #HMR's points.

More efficient Javascript

Looking for another eye on making the following Javascript more efficient.
The following JSON is produced from a Resteasy service:
var testing = {
"com:klistret:cmdb:ci:pojo:successful":true,
"com:klistret:cmdb:ci:pojo:count":1,
"com:klistret:cmdb:ci:pojo:elements":{
"com:klistret:cmdb:ci:pojo:id":123,
"com:klistret:cmdb:ci:pojo:name":"Mars",
"com:klistret:cmdb:ci:pojo:type":{
"com:klistret:cmdb:ci:pojo:id":1,
"com:klistret:cmdb:ci:pojo:name":"Environment"
},
"com:klistret:cmdb:ci:pojo:configuration":{
"#www:w3:org:2001:XMLSchemainstance:type":"Environment",
"#Watermark":"past",
"com:klistret:cmdb:ci:commons:Name":"Mars"
}
}
};
Extended the Extjs JSONReader to handle key depths higher than 2 in the createAccessor method. Wondering if there is a way to make the code more efficient? The function below will be called like function(testing, "com:klistret:cmdb:ci:pojo:configuration.#Watermark") where the com:klistret:cmdb:ci:pojo:elements property is the root.
createAccessor : function(){
var re = /[\[\.]/;
return function(expr) {
if(Ext.isEmpty(expr)){
return Ext.emptyFn;
}
if(Ext.isFunction(expr)){
return expr;
}
# THIS FUNCTION I WANT TO BE EFFICIENT
return function(obj){
while (String(expr).search(re) !== -1) {
var i = String(expr).search(re);
var key = expr.substring(0, i);
if (obj.hasOwnProperty(key)) {
obj = obj[key];
}
expr = expr.substring(i+1, expr.length);
}
return obj[expr];
};
};
}()
This is what I use. I only allow dot annotation, mind:
Ext.override(Ext.data.JsonReader, {
createAccessor: function() {
return function(expr) {
if (Ext.isEmpty(expr)) {
return Ext.emptyFn;
} else if (Ext.isFunction(expr)) {
return expr;
} else {
return function(obj) {
var parts = (expr || '').split('.'),
result = obj,
part,
match;
while (parts.length > 0 && result) {
part = parts.shift();
match = part.match(/^(.+?)(\[(\d+)\])?$/);
result = result[match[1]];
if (result && match[3]) {
result = result[match[3]];
}
}
return result;
}
}
};
}()
});
A basic optimization would be to avoid scanning the string twice with search, which is pretty slow.
The best you could do is replace all the string scanning and substring extraction with a single call to expr.split('.'), which would support accessors of the form aaa.bbb.ccc.ddd and turn them into an array like ['aaa','bbb','ccc','ddd']. The other two characters you seem to support ([ and ]) wouldn't work.
Alternately, you could do an initial match for /[^\].[]+/g over your entire string and keep the matches to obtain a similar array, but this would possibly be slower than the previous solution.

Categories

Resources