Search code examples
javascriptgoogle-maps-api-3function-prototypes

Prototype injection, google maps api


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&amp;sensor=false&amp;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


Solution

  • 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.