JavaScript events handling - javascript

I've made this wonderful plugin: https://github.com/suprMax/Zepto-onPress
Which works perfectly besides one little detail. When I get callback function I need to store it alongside with my real event handler so I can detach it when someone tries to remove event handler and provide me the original callback. So basically I need to be able to store multiple key-value pairs per element where the key supposed to be a function and value is a function too. And I tried to do just that, but right now the script makes this internally:
(function(){}).toString()
Which is not a best idea since I can remove wrong event handlers because of :
(function(){}).toString() === (function(){}).toString().
I suppose there is a better way to do just that. Any suggestions are very welcome.

The only safe and performant way is to store your functions in an array (indexOf works perfectly as it'll search on the pointer values) and use the array index to push the other method to another one.
var fn1store = [];
var fn2store = [];
function pushFunctions(fn1, fn2) {
var p;
if (p = fn1store.indexOf(fn1) > -1) {
teardown(fn2store[p]);
}
p = fn1store.push(fn1) -1;
fn2store[p] = fn2;
}

Related

Non case-sensitive sorting in dojo dgrid

Is it possible to sort without case-sensitivity?
For instance, sorts by default show up like this:
Awesomeman
adam
beyonce
but, I'd like to sort like:
adam
Awesomeman
beyonce
Is it possible to override the sensitivity easily? From what I can tell the grid inherits from OnDemandGrid and OnDemandList, which both inherit from Grid and List. For my store, I am using Memory wrapped in Observable.
As of now, I'm trying to overwrite _setSort in List.js, however that's not working. Anyone out there familiar with these frameworks?
There are potentially 2 ways of solving this:
On the grid end, by handling and canceling the dgrid-sort event
On the store end, by extending query to coerce sort into doing what you want (preferred)
First, the dgrid-sort version:
grid.on('dgrid-sort', function (event) {
// Cancel the event to prevent dgrid's default behavior which simply
// passes the sort criterion through to the store and updates the UI
event.preventDefault();
// sort is an array as expected by the store API, but dgrid's UI only sorts one field at a time
var sort = event.sort[0];
grid.set('sort', function (a, b) {
var aValue = a[sort.attribute].toLowerCase();
var bValue = b[sort.attribute].toLowerCase();
if (aValue === bValue) {
return 0;
}
var result = aValue > bValue ? 1 : -1;
return result * (sort.descending ? -1 : 1);
});
// Since we're canceling the event, we need to update the UI ourselves;
// the `true` tells it to also update dgrid's internal representation
// of the sort setting, so that toggling between asc/desc will still work
grid.updateSortArrow(event.sort, true);
});
While this works for handling when the user clicks in header cells, it will not take effect for programmatic set('sort') calls, or the initial setting of sort in the object passed to the Grid constructor, which could be problematic.
Since sorting is ultimately a store concern, addressing it on the store end is really the preferable solution. Admittedly dojo/store/Memory and namely dojo/store/util/SimpleQueryEngine doesn't make this...well...simple... but one thing to note about SimpleQueryEngine is that if you pass a function via queryOptions.sort rather than an array, it will be applied verbatim as the sort function to use.
This means we can take the incoming sort array that dgrid will set, write our own version of SimpleQueryEngine's default sort function while also accounting for case-insensitivity, and store that in queryOptions.sort for the inherited call:
var CIMemory = declare(Memory, {
query: function (query, queryOptions) {
var sort = queryOptions && queryOptions.sort;
if (sort) {
// Replace sort array with a function equivalent that performs
// case-insensitive sorting
queryOptions.sort = function (a, b) {
for (var i = 0; i < sort.length; i++) {
var aValue = a[sort[i].attribute].toLowerCase();
var bValue = b[sort[i].attribute].toLowerCase();
if (aValue !== bValue) {
var result = aValue > bValue ? 1 : -1;
return result * (sort[i].descending ? -1 : 1);
}
}
return 0;
}
}
return this.inherited(arguments);
}
});
Using this in place of dojo/store/Memory will cause all sorts to be case-insensitive.
Note that I took a couple of shortcuts over SimpleQueryEngine's sort function (checking for null/undefined and coercing values to primitives). Alter the sort function as necessary if you need to worry about either of those things.
Okay, for lack of a better solution, I found that I had to create a custom version of dojo's SimpleQueryEngine, adding .toLowerCase() on both of the values here. The reason it couldn't be simply changed was because it happens inside of an internal function (inside of another function) so it was easier to make another version entirely.
Then, when creating Memory, passing in the query engine like so:
new Memory({ queryEngine : customEngine });
and it seems to work. If there's a cleaner solution please share bc I hate this one :)

JavaScript cache return value of a function with more than one parameter

I'm going through John Resig's snippets on advanced JavaScript. On #19 he mentions a method to cache the return value of a function. What's the best way to cache the return value of a function that has more than one parameter?
There has to be a much better way than stringify-ing the recieved arguments and using that as the key for the cache object:
function $$(selector, el) {
var cacheKey = JSON.stringify(arguments);
if ($$.cache[cacheKey]) return $$.cache[cacheKey];
return ($$.cache[cacheKey] = NodeListToArray( (el || document).querySelectorAll(s) ));
}
$$.cache = {};
You could use a custom hash function that can operate on objects. But hash functions cause collisions and would require significantly more code than your simple example.
Or you could make the cache n-dimensional, where n is the number of arguments. So essentially this:
function $$(selector, el) {
if ($$.cache[selector] && $$.cache[selector][el])
return $$.cache[cacheKey][el];
// etc.
That assumes that both selector and el are able to be used as object keys. You may need to stringify them in another manner.
Just consider an array element,
JSON (JavaScript Object Notation) works with generic platform, so for easy use you must create a function for your use,
Here, $$.cache[0] is your easy way after reading the cachekey,
If we make thing more easy, we might have security problem later.
I hope this will satisfy your requirement :)

How to detect when a property is added to a JavaScript object?

var obj = {};
obj.a = 1; // fire event, property "a" added
This question is different from this one, where ways to detect when an already declared property is changed, being discussed.
this is possible, technically, but since all current JS implementations that I know of are single threaded it won't be very elegant. The only thing I can think of is a brute force interval:
var checkObj = (function(watchObj)
{
var initialMap = {},allProps = [],prop;
for (prop in watchObj)
{
if (watchObj.hasOwnProperty(prop))
{//make tracer object: basically clone it
initialMap[prop] = watchObj[prop];
allProps.push(prop);//keep an array mapper
}
}
return function()
{
var currentProps = [];
for (prop in watchObj)
{
if (watchObj.hasOwnProperty(prop))
{//iterate the object again, compare
if (watchObj[prop] !== initialMap[prop])
{//type andvalue check!
console.log(initialMap[prop] + ' => ' watchObj[prop]);
//diff found, deal with it whichever way you see fit
}
currentProps.push(prop);
}
}
//we're not done yet!
if (currentProps.length < allProps.length)
{
console.log('some prop was deleted');
//loop through arrays to find out which one
}
};
})(someObjectToTrack);
var watchInterval = setInterval(checkObj,100);//check every .1 seconds?
That allows you to track an object to some extent, but again, it's quite a lot of work to do this 10/sec. Who knows, maybe the object changes several times in between the intervals, too.All in all, I feel as though this is a less-then-ideal approach... perhaps it would be easier to compare the string constants of the JSON.stringify'ed object, but that does mean missing out on functions, and (though I filtered them out in this example) prototype properties.
I have considered doing something similar at one point, but ended up just using my event handlers that changed the object in question to check for any changes.
Alternatively, you could also try creating a DOMElement, and attach an onchange listener to that... sadly, again, functions/methods might prove tricky to track, but at least it won't slow your script down as much as the code above will.
You could count the properties on the object and see if has changed from when you last checked:
How to efficiently count the number of keys/properties of an object in JavaScript?
this is a crude workaround, to use in case you can't find a proper support for the feature in the language.
If performance matters and you are in control of the code that changes the objects, create a control class that modifies your objects for you, e.g.
var myObj = new ObjectController({});
myObj.set('field', {});
myObj.set('field.arr', [{hello: true}]);
myObj.set('field.arr.0.hello', false);
var obj = myObj.get('field'); // obj === {field: {arr: [{hello: false}]}}
In your set() method, you now have the ability to see where every change occurs in a pretty high-performance fashion, compared with setting an interval and doing regular scans to check for changes.
I do something similar but highly optimised in ForerunnerDB. When you do CRUD operations on the database, change events are fired for specific field paths, allowing data-bound views to be updated when their underlying data changes.

javascript: Only return if not false

Scenario: I'm searching for a specific object in a deep object. I'm using a recursive function that goes through the children and asks them if I'm searching for them or if I'm searching for their children or grandchildren and so on. When found, the found obj will be returned, else false. Basically this:
obj.find = function (match_id) {
if (this.id == match_id) return this;
for (var i = 0; i < this.length; i++) {
var result = this[i].find(match_id);
if (result !== false) return result;
};
return false;
}​
i'm wondering, is there something simpler than this?:
var result = this[i].find(match_id);
if (result) return result;
It annoys me to store the result in a variable (on each level!), i just want to check if it's not false and return the result. I also considered the following, but dislike it even more for obvious reasons.
if (this[i].find(match_id)) return this[i].find(match_id);
Btw I'm also wondering, is this approach even "recursive"? it isn't really calling itself that much...
Thank you very much.
[edit]
There is another possibility by using another function check_find (which just returns only true if found) in the if statement. In some really complicated cases (e.g. where you don't just find the object, but also alter it) this might be the best approach. Or am I wrong? D:
Although the solution you have is probably "best" as far as search algorithms go, and I wouldn't necessarily suggest changing it (or I would change it to use a map instead of an algorithm), the question is interesting to me, especially relating to the functional properties of the JavaScript language, and I would like to provide some thoughts.
Method 1
The following should work without having to explicitly declare variables within a function, although they are used as function arguments instead. It's also quite succinct, although a little terse.
var map = Function.prototype.call.bind(Array.prototype.map);
obj.find = function find(match_id) {
return this.id == match_id ? this : map(this, function(u) {
return find.call(u, match_id);
}).filter(function(u) { return u; })[0];
};​
How it works:
We test to see if this.id == match_id, if so, return this.
We use map (via Array.prototype.map) to convert this to an array of "found items", which are found using the recursive call to the find method. (Supposedly, one of these recursive calls will return our answer. The ones which don't result in an answer will return undefined.)
We filter the "found items" array so that any undefined results in the array are removed.
We return the first item in the array, and call it quits.
If there is no first item in the array, undefined will be returned.
Method 2
Another attempt to solve this problem could look like this:
var concat = Function.prototype.call.bind(Array.prototype.concat),
map = Function.prototype.call.bind(Array.prototype.map);
obj.find = function find(match_id) {
return (function buildObjArray(o) {
return concat([ o ], map(o, buildObjArray));
})(this).filter(function(u) { return u.id == match_id })[0];
};
How it works:
buildObjArray builds a single, big, 1-dimensional array containing obj and all of obj's children.
Then we filter based on the criteria that an object in the array must have an id of match_id.
We return the first match.
Both Method 1 and Method 2, while interesting, have the performance disadvantage that they will continue to search even after they've found a matching id. They don't realize they have what they need until the end of the search, and this is not very efficient.
Method 3
It is certainly possible to improve the efficiency, and now I think this one really gets close to what you were interested in.
var forEach = Function.prototype.call.bind(Array.prototype.forEach);
obj.find = function(match_id) {
try {
(function find(obj) {
if(obj.id == match_id) throw this;
forEach(obj, find);
})(obj);
} catch(found) {
return found;
}
};​
How it works:
We wrap the whole find function in a try/catch block so that once an item is found, we can throw and stop execution.
We create an internal find function (IIFE) inside the try which we reference to make recursive calls.
If this.id == match_id, we throw this, stopping our search algorithm.
If it doesn't match, we recursively call find on each child.
If it did match, the throw is caught by our catch block, and the found object is returned.
Since this algorithm is able to stop execution once the object is found, it would be close in performance to yours, although it still has the overhead of the try/catch block (which on old browsers can be expensive) and forEach is slower than a typical for loop. Still these are very small performance losses.
Method 4
Finally, although this method does not fit the confines of your request, it is much, much better performance if possible in your application, and something to think about. We rely on a map of ids which maps to objects. It would look something like this:
// Declare a map object.
var map = { };
// ...
// Whenever you add a child to an object...
obj[0] = new MyObject();
// .. also store it in the map.
map[obj[0].id] = obj[0];
// ...
// Whenever you want to find the object with a specific id, refer to the map:
console.log(map[match_id]); // <- This is the "found" object.
This way, no find method is needed at all!
The performance gains in your application by using this method will be HUGE. Please seriously consider it, if at all possible.
However, be careful to remove the object from the map whenever you will no longer be referencing that object.
delete map[obj.id];
This is necessary to prevent memory leaks.
No there is no other clear way, storing the result in a variable isn't that much trouble, actually this is what variables are used for.
Yes, that approach is recursive:
you have the base case if (this.id==match_id) return this
you have the recursive step which call itself obj.find(match_id) { ... var result = this[i].find(match_id); }
I don't see any reason, why storing the variable would be bad. It's not a copy, but a reference, so it's efficient. Plus the temporary variable is the only way, that I can see right now (I may be wrong, though).
With that in mind, I don't think, that a method check_find would make very much sense (it's most probably basically the same implementation), so if you really need this check_find method, I'd implement it as
return this.find(match_id) !== false;
Whether the method is recursive is hard to say.
Basically, I'd say yes, as the implementations of 'find' are all the same for every object, so it's pretty much the same as
function find(obj, match_id) {
if (obj.id == match_id) return obj;
for (var i = 0; i < obj.length; ++i) {
var result = find(obj[i], match_id);
if (result !== false) return result;
}
}
which is definitely recursive (the function calls itself).
However, if you'd do
onesingleobjectinmydeepobject.find = function(x) { return this; }
I'm not quite sure, if you still would call this recursive.

Is it possible to get the event object for the "current" or "last" event, without receiving it as argument in the handler?

I'm trying to modify the behaviour or a JavaScript library, basically by monkeypatching it (no, there is no better way).
At a certain point in the code, I need to know whether Shift is pressed or not. If the event handler in this library were properly written, it'd receive the "event" as its first parameter, but unfortunately, it isn't (events are wired with onclick inline in the HTML)
So, I'm trying to see if jQuery "stores" the last event object somewhere, or if there is some other way to access it.
Essentially, what I want is "window.event", but ideally I'd like for it to work on Firefox.
Any ideas, besides adding a global onKeyDown handler to the document and keeping track of the state of Shift myself? That feels a bit overkill and a bit too global for my taste.
Can you wrap the function they are using as their event handler? Take this contrived example:
var someObject = {
keyDownListener: function() {
alert('something was pressed!');
}
}
We could replace keyDownListener with our own method that accepts the event object.
var someObject = {
keyDownListener: function() {
alert('something was pressed!');
}
}
var oldKeyDownListener = someObject.keyDownListener;
someObject.keyDownListener = function(event) {
oldKeyDownListener(); // Call the original
// Do anything we need to do here (or before we call the old one!)
}
If you can get inside the function, you can also inspect the arguments object. Your event object should be in there (the first item, I believe).
var f = function() {
console.log(arguments);
}
f(); //= Logs []
f(1); //= Logs [1]
f(1, 'something'); //= Logs [1, 'something']
EDIT (In response to the comment below).
If you can "hijack" the method, here's ONE way you could it. I'm not certain if this is a good approach but if you have all these constraints, it will get you what you want. Basically what this code does is it searches for any elements that have an onclick attribute on them and changes the method signature to include the event object.
We can then wrap the original listener to pull the event object out of arguments and then pass execution back to the original function.
Doing this will get you what you want (I think?). See this code:
HTML:
Click Me​
JavaScript:
window.myFunction = function(one, two, three) {
console.log("one: " + one + ", two: " + two + ", three: " + three);
}
var originalMyFunction = window.myFunction;
window.myFunction = function() {
var event = arguments[arguments.length - 1]; // The last item in the arguments array is our event object.
console.log(event);
originalMyFunction.apply(this, arguments);
}
$('[onclick]').each(function() {
var s = $(this).attr('onclick');
var lastIndex = s.lastIndexOf(')');
var s2 = s.substring(0, lastIndex);
var s3 = s.substring(lastIndex, s.length);
var s4 = s2 + ', event' + s3;
$(this).attr('onclick', s4);
});
You can see it working in this fiddle: http://jsfiddle.net/84KvV/
EDIT 2
If you wanna get really fancy with it, you could even automate the wrapping of the functions. See this fiddle: http://jsfiddle.net/84KvV/2/.
Please note that this is expecting the function strings to be in a certain format so that it can parse it (functionName(...)). If it's not that in that format, this exact code will not work :)
As mentioned in my comment, it IS possible to make window.event exist in browsers that are not IE. It requires you to wrap attachEvent/addEventListener. It would go something like this:
var oldAddEventListener = HTMLElement.prototype.addEventListener;
HTMLElement.prototype.addEventListener = function(type, listener, useCapture) {
var wrapped = function(event) {
window.event = event;
listener(arguments);
}
oldAddEventListener.call(this, type, wrapped , useCapture);
}
As I said, I'm not sure if this will work for inline event listeners, but it might :) If not, at least it's here as a reference for those looking for something similar.
function eventlessHandler(myVal) {
var event = window.event || eventlessHandler.caller.arguments[0];
// some work
}
It may be needed traverse .caller several times depending on actual call chain.
Tested this on IE6, IE11 and latest Chrome. Should work on most other browsers too.
See also How do you find out the caller function in JavaScript?.

Categories