0

Consider this code:

app.mediaLibrary = new function MediaLibrary() {
    var self = this;

    self.media = new Proxy([], {
        set(target, key, value) {
            console.log(`Setting value ${key} as ${value}`)
            if (!(value instanceof self.Media))
                throw new Error(`${value} is not instance of Media.`)
            target[key] = value;
            return true
        },
        get (target, key) {
            console.log(`Getting value ${key} as ${target[key]}`)
            return target[key]
        }
    });

    self.Media = function Media(file, fileReader) {
        this.src = fileReader.result;
        this.file = file;
    }

    return self;
}

Whenever I call app.mediaLibrary.media.push(new app.mediaLibrary.Media("", "")) In console I see this:

Getting value push as function push() { [native code] }
Getting value length as 0
Setting value 0 as [object Object]
Setting value length as 1
Uncaught Error: 1 is not instance of Media.(…)

I understand why I see this but how can I code around it? It seems my traps are triggered by internal(push,length) as well as external calls([0]=...) and I don't know how to differentiate between them. Any ideas?

Dread Boy
  • 772
  • 6
  • 28
  • I don't understand your differentiation scheme. If a call can be regarded as "external", then it's definitely `push`. It in turn calls "`get length`" and "`set 0`". So what exactly qualifies the access of a property as internal? – a better oliver Apr 22 '16 at 07:26
  • I didn't call get length in my code so it's internal/implicit call, not external/explicit. – Dread Boy Apr 22 '16 at 08:15
  • According to that definition every library in the world would consist of only internal calls. Even if someone calls your code in a callback. That doesn't make much sense. – a better oliver Apr 22 '16 at 08:20
  • Yes, you got it. In my case that library is vanilla implementation of array. Anyway, do you know how could I solve my problem? – Dread Boy Apr 22 '16 at 08:42
  • _"how could I solve my problem"_ I have a vague idea of what you want to achieve - It's not possible for obvious reasons - but I honestly don't see a problem that needs to be solved. – a better oliver Apr 22 '16 at 08:49
  • Does that mean that you want to disable *external* access? Does it matter to you, if `[0]` is accessed via `push` or via `[0]=`? – nils Apr 22 '16 at 11:36

1 Answers1

1

I think you are asking the wrong question. This isn't about internal vs external calls, this is about the specific object that you are proxying: An array.

You could go with three conditions:

  1. Length can be set to anything (only the array will handle this anyway)
  2. Numeric properties can only be set to Media instances.
  3. All other properties are off limits.

You could write that like this:

app.mediaLibrary = new function MediaLibrary() {
    var self = this;

    self.media = new Proxy([], {
        set(target, key, value) {
            console.log(`Setting value ${key} as ${value}`)

            // Check if this is a valid array index
            // See http://www.ecma-international.org/ecma-262/6.0/#sec-array-exotic-objects
            if (String(key >>> 0) === key && (key >>> 0) != Math.pow(2, 32) - 1) {
                if (!(value instanceof self.Media))
                    throw new Error(`${value} is not instance of Media.`);
            } else if(key !== 'length') {
                throw new Error(`${key} may not be written to.`);
            }
            target[key] = value;
            return true
        },
        get (target, key) {
            console.log(`Getting value ${key} as ${target[key]}`)
            return target[key]
        }
    });

    self.Media = function Media(file, fileReader) {
        this.src = fileReader.result;
        this.file = file;
    }

    return self;
}

If you really need to differentiate between .push and [0]=..., then no, it can't be done.

nils
  • 25,734
  • 5
  • 70
  • 79
  • Well, you could differentiate `.push()` by decorating the returned function in the getter, but I agree that it's not necessary when checking the property names like you do. – Bergi Apr 22 '16 at 13:07
  • Instead of using `Number(key) == key`, you might want to explicitly check for `String(key >>> 0) === key && (key >>> 0) != 4294967295` – Bergi Apr 22 '16 at 13:16
  • @Bergi good idea with the decorators, thanks. I'm a bit spotty on the bitwise operators, would you mind explaining your second part (the `>>>` seems to do the type conversion and rounding trick)? Is `4294967295` the highest possible index? – nils Apr 22 '16 at 13:41
  • It's quite literally the [*isArrayIndex* algorithm](http://www.ecma-international.org/ecma-262/6.0/#sec-array-exotic-objects) from the spec :-) [`>>> 0`](http://stackoverflow.com/q/1822350/1048572) is the common `ToUint32` replication. – Bergi Apr 22 '16 at 13:44
  • Thanks a lot, that makes sense. I integrated it into the answer. – nils Apr 22 '16 at 13:51