Prototype injection, google maps api - javascript

I need to catch the event of getting back suggestions for google maps autocomplete. I know it is undocumented, but doing some research I found that it could be down via some prototype hacking.
<input type='text' id='myInput'>
<script src="http://maps.google.com/maps/api/js?libraries=places&sensor=false&language=en-EN"></script>
<script>
function catcher(key) { console.log(key); }
function MyProto() {}
MyProto.prototype = new google.maps.MVCObject();
MyProto.prototype.changed = catcher;
var gAuto = new google.maps.places.Autocomplete(
document.getElementById('myInput'), ['geocode']);
// one of two should be commented
//gAuto.__proto__.__proto__.changed = catcher; // every key, including 'predictions'
gAuto.__proto__.__proto__ = MyProto.prototype; // only 'types', '0', and 'place' when selected
</script>
JSFiddle link: http://jsfiddle.net/agentcooper/hRyTF/ (check the console)
Check the last two lines. When setting 'changed' function directly on MVCObject prototype (first one, commented), everything works great and I can catch key 'predictions' in the 'catcher' function. The problem is that catcher needs to be different if, for example, I need to have two instances of autocomplete on my page. So when I'm trying to inject custom object in autocomplete's prototype chain (last line) everything fails. Is there any way to solve this?
EDIT: working version, thanks to Sajid :-)
UPDATE: Completed the code, maybe it will be helpful to anyone

In the second line, you are replacing the entire prototype of the MVC object with an instance of the MVC object, and depending on how the thing is initialized, this will likely not work at all. The first like replaces one function, though in the process it completely breaks that function since you don't call its superclass version, so you are not extending, you are really clobbering. To not clobber, you need to do:
(function() {
var oldChanged = gAuto.__proto__.__proto__.changed;
function catcher(key) {
// call old version, and make sure to maintain this reference correctly
oldChanged.call(this, key);
// do your stuff here
}
gAuto.__proto__.__proto__.changed = catcher;
})();
One simple solution is that each object has an idea of this mentioned above. So changed has a reference to this, which will refer to the object being used as the caller's target (except in specific situations that are a bit out of scope here). But basically:
var x = new MVCObject();
x.changed('hi') // this === x
So if your two versions need to do different things, you can check which this the changed method was called from and react appropriately.

Related

How do I keep an array reference through closure?

Quick question that i seem unable to google...
I'm wondering how to keep an array reference through closure. I think.
Im using AngularJS but raw JS has the same behaviour in my trials.
Context: think of a memory game where you match two images. When two selected tiles mismatch, they flip back but only after showing the selected images. So I want to delay the flip.
I have an array that i .filter to a new array.
$scope.selectedTiles = $scope.tiles.filter($scope.filterSelected);
An aside: This new array; does it only contain references or copies of data? MDN says
constructs a new array of all the values for which callback returns a true value.
Not very clear imo. My conclusion is they are references.
Anyways...
This selectedTiles is sent to a closure in order to keep its references. Only it doesnt.
flipSelectedTiles = function (selection){
return function(){
selection.forEach($scope.flipTile);
}
};
function reset(selection){
return function(){
$scope.flipSelectedTiles(selection);
}
}
var resetTiles = reset(filteredTiles);
$timeout($scope.flipSelectedTiles($scope.selectedTiles), $scope.delay);
I need this because I intend to wait a while before executing the flipSelectedTiles.
I also need to clear the selectedTiles immediately in case someone clicks another image.
selectedTiles.splice(0, $scope.selectedTiles.length);
As the timeout fires the array is empty and nothing happens.
How do I keep an array reference through closure?
Edit: here is a fiddle in plain JS http://jsfiddle.net/Tobis/TkjEF/
Edit: added flipSelectedTiles function
You are keeping a reference. The problem is you're emptying the array too early, as you noted yourself. I believe you need a copy instead:
var resetTiles = reset(filteredTiles.slice(0));
$timeout(resetTiles, $scope.delay);
Also note that you weren't passing a function to $timeout, you were invoking flipSelectedTiles immediately (unless flipSelectedTiles returns a function).

DOJO: Explanation of function parameters

So I asked an earlier question (Original Question). I got a great answer that did exactly what I wanted. However, since I am new to Javascript/Dojo, I wasn't able to fully understand it, and neither was the answerer of the question.
My question is: How does the following code work?
dndController: function(arg, params){
return new dijit.tree.dndSource(
arg, // don't mess up with the first parameter
dojo.mixin({}, params, {copyOnly:true}))
//create a copy of the params object, but set copyOnly to true
}
So the part bugging me the most is the "args" and "params" parameters. I don't understand where they come from and what they mean or represent. (If there needs to be more context to the code, I can edit the question later, so just post it in the comments). Also, why couldn't I just use the new dijit.tree.dndSource directly and why did I need to use the function to return it?
Thanks
Take a look at dijit/Tree.js in the dojo source.
in Tree.js, inside the postCreate function (which is used by any widget as a part of the dijit lifecycle):
if(this.dndController){
if(dojo.isString(this.dndController)){
this.dndController = dojo.getObject(this.dndController);
}
var params={};
for(var i=0; i<this.dndParams.length;i++){
if(this[this.dndParams[i]]){
params[this.dndParams[i]] = this[this.dndParams[i]];
}
}
this.dndController = new this.dndController(this, params);
}
You'll see a section which checks what the dndController property is. If it is a string it sets the dndController attribute to the function that creates the class that the string describes (this is what dojo.getObject(string) is doing).
For example if this.dndController was the string "my.special.dnd.controlller", it would return the function that, when called instantiates a new instance of my.special.dnd.controller.
It then copies some parameters into an object, followed by executing the function that was either:
(1) looked up via dojo.getObject
(2) uses the custom function you passed in.
I would assume the maintainer of this widget does it this way as some people only need to specify a specific class to use as the dnd controller, whereas others need to do some something more custom based on what parameters the Tree was passed.

pulling an array of objects

I currently have a validation script that has a selection of <input> elements stored in objects with properties such as "id", "type" "isRequired" and"isValid". I currently have this setup() function that does the following:
function setup(obj) {
obj.getElement().onkeyup = function() {validate(obj)}
}
In order to run this setup() function on all of my input objects I need to execute the following addEvents() function
function setEvents() {
setup(firstName)
setup(lastName)
setup(email)
setup(dateOfBirth)
}
I'm helping create a system that has multiple pages of nothing but forms so I'd prefer if I didn't have to type this for each object. Is there a way I can collect an array of all the objects that are based on a specific object template? This way I could loop through the array and apply a setup to each object in a single function. If not, are there alternatives?
(p.s. I've been asking so many object-oriented(oh, I crack myself up sometimes) questions lately because this is my first time messing with objects)
---Edit---
the object template I'm referring to looks something like this:
function input(id,isRequired,type) {
this.id = id
this.isRequired = isRequired
this.type = type
}
this is then followed by a
firstName = new input('firstName',true,'alpha')
As I said in my comment, you could add the element to an array when you create it:
var inputs = [];
var firstName = new input('firstName',true,'alpha');
inputs.push(firstName);
This is not ver convenient yet. But you could create another object which manages all this:
var InputManager = {
elements: [],
create: function(/* arguments here */) {
var n = new input(/* arguments here */);
this.elements.push(n);
return n;
},
setup: function() {
for(var i = this.elements.length; i--;) {
(function(obj) {
obj.getElement().onkeyup = function() {validate(obj)};
}(this.elements[i]));
}
}
};
with which you can do:
var firstName = InputManager.create('firstName',true,'alpha');
// etc.
InputManager.setup();
Something along these lines. I think this would be a quite object oriented way. If you have a collection of objects, you often have another object which handles the functions that should be performed on all those objects.
As with most javascript questions, the easiest way to do this is with a library such as jQuery. If you have a unique way to differentiate these objects with a css selector (e.g., they all have the class "validate" or they're the only input[type="text"] fields on the page or something), then you can do a simple selection like $('.validate') to get an array of all these objects. You can get this array using javascript of course but it's a tad more complicated. Once you have the array you can loop over the elements or you can do a simple bind like $('.validate').change(validate); which will call the validate() method whenever a dom element with the class 'validate' changes.
Edit: So obviously I don't know the entirety of what you're trying to accomplish, but if you're new to web programming, just note also that no matter what you're doing on the client side (ie in the browser), all validation should also be done on the server side. Javascript validation is generally used to just be user-friendly and not to actually validate your inputs, since I could easily just turn javascript off or redefine validate as function validate() {} and bypass javascript validation for whatever reason.
2nd Edit: So I'm not sure if this answer was 100% what you're looking for but it's good to know regardless.
Judging by your examples you are not using jQuery. And for that reason alone, I'm going to up vote you. On the same note, after you get really comfortable with JS and how you can do things, really consider using a framework or saving your scripts so you don't have to reinvent the wheel for each project.
You can actually use the DOM to your advantage!
All the forms in your page can be referenced with document.forms[index]. Alternatively you can also reference a named form with document.formName.
Look at this jsfiddle for an example using the latter.
UPDATE
Reading your update and the fact that you needed a way of creating the input objects and setup the validation. I updated my fiddle with a different approach.
Used the id to hold the validation info regarding the element then the addValidation function reverts the id to it's basic form so you can still use it normally throughout your application.
The only requirement is that you addValidation the first thing after page load. So the ID get revamped first.
The solution is also JS safe, meaning if the user doesn't have JS, apart from no validation, no other things will happen.
I think your problem is that the obj in the onkeyup scope is undefined.
function setup(obj) {
//obj is desired
obj.getElement().onkeyup = function() {validate(obj) //obj is undefined because onkeyup is the new scope of this function
}
instead you could do this:
function setup(obj) {
obj.getElement().onkeyup = function() {validate(this)
}

jquery: context with closures?

I got a simple question about jQuery but rather javascript approaches in general...
Is it ok, to do this? :
this._checkedTexts.length = 0; // <- array belonging to "me"
var contextCheckedTexts = this._checkedTexts;
$('#valueContainer input:checked').each(function() {
contextCheckedTexts.push($(this).text());
});
Since 'this' points to the element, I am using a closure here to keep additional context for the 'each'-handler around.
Is this the way 'how it's done' or could there be any risk involved using a closure in this way (memoryleak-wise....)?
What other options are there?
I find it very practical - but also I'm always a little worried about introducing reference-count-problems when using closures - which will be hard to find later on.
I don't know what you are trying to achieve but your codes may also be done like this,
this._checkedTexts.length = 0; // <- array belonging to "me"
var arr = $('#valueContainer input:checked').map(function() {
return $(this).text();
}).get();
this._checkedTexts = this._checkedTexts.concat(arr);
It seems to be fine as jQuery methods like each, map, grep does not support context by default (there're bound to the current jQuery object). However you can create you own bind function that will apply context to functions (but in this case you dontn't have to as you use default context to get the elements text).

Can't empty JS array

Yes, I am having issues with this very basic (or so it seems) thing. I am pretty new to JS and still trying to get my head around it, but I am pretty familiar with PHP and have never experienced anything like this. I just can not empty this damn array, and stuff keeps getting added to the end every time i run this.
I have no idea why, and i am starting to think that it is somehow related to the way chekbox id's are named, but i may be mistaking....
id="alias[1321-213]",
id="alias[1128-397]",
id="alias[77-5467]" and so on.
I have tried sticking
checkboxes = []; and
checkboxes.length = 0;
In every place possible. Right after the beginning of the function, at the end of the function, even outside, right before the function, but it does not help, and the only way to empty this array is to reload the page. Please tell me what I am doing wrong, or at least point me to a place where i can RTFM. I am completely out of ideas here.
function() {
var checkboxes = new Array();
checkboxes = $(':input[name="checkbox"]');
$.each(checkboxes,
function(key, value) {
console.log(value.id);
alert(value.id);
}
);
checkboxes.length = 0;
}
I have also read Mastering Javascript Arrays 3 times to make sure I am not doing something wrong, but still can't figure it out....
I think there's a lot of confusion coming out of this because you are clearing the array -- just maybe not for the purpose you want, or at the wrong time, etc.
function () {
var checkboxes = new Array(); // local-scope variable
checkboxes = $(':input[name="checkbox"]'); // new instance
$.each(checkboxes, /* ... */); // (truncated for brevity)
checkboxes.length = 0; // this clears the array
// but, for what...?
// nothing happens now
}
As per your snippet, every call to the function recreates and clears the array. However, all work with the array is done while the array is full (nothing happens after it's cleared).
Also, checkboxes is a private variable to the function. The variable only exists during execution of the function and is forgotten once the function is done.
So, what is the big picture? Why are you trying to clear the array?
To take a guess, it sounds like you're intending on clearing it for the next call of the function.
i.e. (filling in doSomething for function name):
doSomething(); // log all array elements and clears the array
doSomething(); // does nothing, since the array is already empty
To accomplish this, you need to define checkboxes in a single location outside of the function, either as a global variable or using closures (the heavily more recommended, albeit more complex, option):
NOTE: If you haven't dealt with closures before, they may not make much sense after only a single example. But, there are thousands of resources available, including Stack Overflow, to help explain them better than I can here.
// closure (or instantly-called function)
// allows for defining private variables, since they are only known by
// code blocks within this function
(function () {
// private (closure-scoped) variable(s)
var checkboxes = $(':input[name="checkbox"]');
function doSomething() {
$.each(checkboxes, /* ... */);
checkboxes.length = 0;
}
})();
The closure will run once, defining checkboxes as the array of inputs while doSomething will iterate the array before clearing it.
Now, the last step is to expose doSomething -- cause, as with checkboxes, it is also private. You can accomplish exposing it by passing the function reference from the closure to a variable outside the closure:
var doSomething = (function () {
/* ... */
return doSomething; // note that you DO NOT want parenthesis here
})();
Is setting length even possible? ;)
Any way checkboxes is a jquery object not an array.
When You do checkboxes = $(':input[name="checkbox"]'); it's not an array any more and whatever there was before has no influence. It doesn't matter what was in a variable if You assign something new to it in any language I know.
You are making some jquery related error. Please elaborate more so that I can help
Are You sure You put name="checkbox" in all of them? It doesn't seem to have a lot of sense. Maybe You waned $(':input[type="checkbox"]'); ?
Edit: that's funny. the above selector isn't too good as well. It should be:
$('input:checkbox');
as for removing stuff:
delete varname
delete arrname[elem]
is the right way to do it.
assigning null does not change the length but just makes trouble.
What about doing this:
checkboxes = new Array();
You can also delete it.
delete checkboxes;
When you rerun the function it will search for all checkboxes again.
Consider this strategy:
function() {
var checkboxes = $(':input[name=checkbox]:not(.examined)');
checkboxes.addClass('examined');
$.each(checkboxes, function(key, value) {
// ... stuff
});
}
You could also use .data() if you don't want to "pollute" the DOM.

Categories

Resources