35

Is there a way to know when a user has pushed (via push()) an item onto an array?

Basically I have an asynchronous script that allows the user to push commands onto an array. Once my script loads, it execute the commands. The problems is, the user may push additional commands onto the array after my script has already run and I need to be notified when this happens. Keep in mind this is just a regular array that the user creates themselves. Google Analytics does something similar to this.

I also found this which is where I think Google does it, but I don't quite understand the code:

    Aa = function (k) {
        return Object.prototype[ha].call(Object(k)) == "[object Array]"

I also found a great example which seems to cover the bases, but I can't get my added push method to work correctly: http://jsbin.com/ixovi4/4/edit

3
  • Does the user create this array through a UI that you are providing? Commented Mar 15, 2011 at 2:34
  • No, it has to be a regular array because my script hasn't loaded yet, so I don't have any special wrapper objects they can create, etc. Commented Mar 15, 2011 at 16:51
  • look at Array.observe() method Commented Jun 24, 2020 at 13:52

12 Answers 12

39

You could use an 'eventify' function that overrides push in the passed array.

var eventify = function(arr, callback) {
    arr.push = function(e) {
        Array.prototype.push.call(arr, e);
        callback(arr);
    };
};

In the following example, 3 alerts should be raised as that is what the event handler (callback) does after eventify has been called.

var testArr = [1, 2];

testArr.push(3);

eventify(testArr, function(updatedArr) {
  alert(updatedArr.length);
});

testArr.push(4);
testArr.push(5);
testArr.push(6);
Sign up to request clarification or add additional context in comments.

2 Comments

This one is the perfect answer in the context given question.
could even make this more generic: eventify = function(arr,what,callback) { arr[what] = function(e) { Array.prototype[what].call(arr, e); callback(arr); }; }; let test = []; eventify(test,'push',(arr)=>{ console.log(`Push1: Array has length: ${arr.length}`); }) test.push(1); test.push(2); test.push(3);
22

The only sensible way to do this is to write a class that wraps around an array:

function EventedArray(handler) {
   this.stack = [];
   this.mutationHandler = handler || function() {};
   this.setHandler = function(f) {
      this.mutationHandler = f;
   };
   this.callHandler = function() { 
      if(typeof this.mutationHandler === 'function') {
         this.mutationHandler();
      }
   };
   this.push = function(obj) {
      this.stack.push(obj);
      this.callHandler();
   };
   this.pop = function() {
      this.callHandler();
      return this.stack.pop();
   };
   this.getArray = function() {
      return this.stack;
   }
}

var handler = function() {
   console.log('something changed');
};

var arr = new EventedArray(handler);

//or 

var arr = new EventedArray();
arr.setHandler(handler);


arr.push('something interesting'); //logs 'something changed'

7 Comments

The problem is my script hasn't loaded yet, so it has to be a regular array. Any special objects I could create, they won't have access to when they create the array.
I also found this which is where I think Google does it, but I don't quite understand the code: Aa = function (k) { return Object.prototype[ha].call(Object(k)) == "[object Array]"
Or if I had a way to convert the regular array to my special object type once my script is loaded and then attach the special event handler.
I created an example using your code, but once I push an item onto the array, the array still has no items: jsbin.com/ilehu5/edit >> I also did an example where I try to swap the objects, but the first issue is keeping me from testing it fully: jsbin.com/isiti3/2/edit >> Thanks so much for your help.
@KingOfHypocrites Updated my answer to define a getArray() method. Use this to access the underlying array. Here's your example, modified to work with this method: jsbin.com/ilehu5/3/edit
|
15

try this:

var MyArray = function() { };
MyArray.prototype = Array.prototype;
MyArray.prototype.push = function() {
    console.log('push now!');
    for(var i = 0; i < arguments.length; i++ ) {
        Array.prototype.push.call(this, arguments[i]);
    }
};

var arr = new MyArray();
arr.push(2,3,'test',1);

you can add functions at after pushing or before pushing

Comments

4

Why not just do something like this?

Array.prototype.eventPush = function(item, callback) {
  this.push(item);
  callback(this);
}

Then define a handler.

handler = function(array) {
    console.log(array.length)
}

Then use the eventPush in the place that you want a specific event to happen passing in the handler like so:

a = []
a.eventPush(1, handler);
a.eventPush(2, handler);

Comments

4

I'd wrap the original array around a simple observer interface like so.

function EventedList(list){
    this.listbase = list;
    this.events = [];
}

EventedList.prototype.on = function(name, callback){
    this.events.push({
        name:name,
        callback:callback
    });
}

//push to listbase and emit added event
EventedList.prototype.push = function(item){
    this.listbase.push(item);
    this._emit("added", item)
}

EventedList.prototype._emit = function(evtName, data){
    this.events.forEach(function(event){
        if(evtName === event.name){
            event.callback.call(null, data, this.listbase);
        }
    }.bind(this));
}

Then i'd instantiate it with a base array

    //returns an object interface that lets you observe the array
    var evtList = new EventedList([]);

    //attach a listener to the added event
    evtList.on('added', function(item, list){
         console.log("added event called: item = "+ item +", baseArray = "+ list);
    })

    evtList.push(1) //added event called: item = 1, baseArray = 1
    evtList.push(2) //added event called: item = 2, baseArray = 1,2
    evtList.push(3) //added event called: item = 3, baseArray = 1,2,3

you can also extend the observer to observe other things like prePush or postPush or whatever events you'd like to emit as you interact with the internal base array.

Comments

1

This will add a function called onPush to all arrays, by default it shouldn't do anything so it doesn't interfere with normal functioning arrays.

just override onPush on an individual array.

Array.prototype.oldPush = Array.prototype.push;
Array.prototype.push = function(obj){
    this.onPush(obj);
    this.oldPush(obj);
};
//Override this method, be default this shouldnt do anything. As an example it will.
Array.prototype.onPush = function(obj){
    alert(obj + 'got pushed');
};

//Example
var someArray = [];

//Overriding
someArray.onPush = function(obj){
    alert('somearray now has a ' + obj + ' in it');
};

//Testing
someArray.push('swag');

This alerts 'somearray now has a swag in it'

Comments

1

If you want to do it on a single array :

var a = [];

a.push = function(item) {
    Array.prototype.push.call(this, item);
    this.onPush(item);
};

a.onPush = function(obj) {
    // Do your stuff here (ex: alert(this.length);)
};

Comments

1

Sometimes you need to queue things up before a callback is available. This solves that issue. Push any item(s) to an array. Once you want to start consuming these items, pass the array and a callback to QueuedCallback(). QueuedCallback will overload array.push as your callback and then cycle through any queued up items. Continue to push items to that array and they will be forwarded directly to your callback. The array will remain empty.

Compatible with all browsers and IE 5.5+.

var QueuedCallback = function(arr, callback) {
  arr.push = callback;
  while (arr.length) callback(arr.shift());
};

Sample usage here.

Comments

0

Untested, but I am assuming something like this could work:

Array.prototype.push = function(e) {
    this.push(e);
    callbackFunction(e);
}

4 Comments

actually maybe not, i was trying to extend the built in push
This is close to what i need, but I wonder if there is a way to only do this on one specific array or if I could somehow know the id of the array? I got this to somewhat work, but not sure how to make sure it's the one array I need to actually look at: jsbin.com/axate5/edit
I also found this which is where I think Google does it, but I don't quite understand the code: Aa = function (k) { return Object.prototype[ha].call(Object(k)) == "[object Array]"
This works, i have suggested something like this in my answer below [stackoverflow.com/a/74764472/3057246]
0

A lot better way is to use the fact that those methods modify array length. The way to take advantage of that is quite simple (CoffeeScript):

class app.ArrayModelWrapper extends Array
  constructor: (arr,chName,path)->
    vl  = arr.length
    @.push.apply(@,arr)
    Object.defineProperty(@,"length",{
      get: ->vl
      set: (newValue)=>
        console.log("Hello there ;)")
        vl = newValue
        vl
      enumerable: false
    })

Comments

0

for debugging purpose you can try. And track the calling function from the call stack.

yourArray.push = function(){debugger;}

Comments

0

We can prototype Array to add a MyPush function that does push the rec to the array and then dispatches the event.

Array.prototype.MyPush = (rec) => 
{
    var onArrayPush = new Event("onArrayPush",{bubbles:true,cancelable:true});
    this.push(rec);
    window.dispatchEvent(onArrayPush);
};

and then we need an eventhandler to capture the event, here I am capturing the event to log the event and then indexing the record for example:

addEventListener("onArrayPush",(e)=> {
    this.#Log(e);
    this.#IndexRecords(); 
});

But in 2022 you may also go with callback as:

Array.prototype.MyPush = function(rec,cb){
    this.push(rec);
    cb(rec);
};

here cb is the callback that is invoked after rec is pushed to the Array. This works at least in the console.

enter image description here

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.