Skip to content

Instantly share code, notes, and snippets.

@DmitryBaranovskiy
Last active August 29, 2015 14:03
Show Gist options
  • Select an option

  • Save DmitryBaranovskiy/9a72314d5c801085fa56 to your computer and use it in GitHub Desktop.

Select an option

Save DmitryBaranovskiy/9a72314d5c801085fa56 to your computer and use it in GitHub Desktop.
Events…
// This gist is based on eve (https://github.com/adobe-webplatform/eve), but really any event based API will do
// Idea is to recreate backbone-like model, but on events only
// Model here is a hash-map, but it shouldn’t be. You could implement any structure this way.
function model(id) {
var modelid = ["model", id],
name = function () {
return modelid.concat.apply(modelid, arguments);
};
(function init() {
var model = {};
eve.on(name("set"), function (key, value) {
var old = {};
if (typeof key != "object") {
var attr = {};
attr[key] = value;
key = attr;
}
for (var k in key) {
old[k] = model[k];
model[k] = key[k];
eve(name("change", k), null, k, key[k], old[k]);
}
eve(name("update"), null, key, old);
});
eve.on(name("unset"), function (key) {
var old = model[key];
delete model[key];
eve(name("deleted"), null, key);
eve(name("update"));
});
eve.on(name("json"), function () {
this(JSON.stringify(model));
});
eve.on(name("get"), function (key) {
this(model[key]);
});
eve.on(name("has"), function (key) {
this(key in model);
});
eve.on(name("death"), function () {
eve.off(modelid);
});
}());
// the following part is optional.
// If id is known we could communicate with the model via events only
return {
id: function () {
return id;
},
"set": function (key, value) {
eve(name("set"), null, key, value);
},
"get": function (key) {
var res;
eve(name("get", key), function (value) {
res = value;
}, key);
return res;
},
has: function (key) {
var res;
eve(name("has"), function (value) {
res = value;
}, key);
return res;
},
unset: function (key) {
eve(name("unset"), null, key);
},
toJSON: function () {
var res;
eve(name("json"), function (value) {
res = value;
});
return res;
},
die: function () {
eve(name("death"));
},
// as I mentioned, I don’t like these methods, but for the sake of it…
listenTo: function (other, event, callback) {
var id = other.id(),
eventName = ["model", id].concat(event);
eve.on(eventName, callback);
eve.once(name("stopListening", id), function () {
eve.off(eventName, callback);
});
},
stopListening: function (other) {
eve(["stopListening", other ? other.id() : "*"]);
}
};
}
eve.on("*", function () {
console.debug(eve.nt());
});
var m1 = model(1),
m2 = model(2);
m1.set({a: 1, b: 2});
m1.die();
m2.set({a: m1.get("a"), b: m1.get("b")});
m2.unset("b");
console.log(m1.toJSON());
console.log(m2.toJSON());
@atirip

atirip commented Jul 7, 2014

Copy link
Copy Markdown

It is ok. As a coincidence, im in the process of shimming eve to be full blown replacement for Backbone Event. The reason is mainly wildcard support. Theres one roadblock, i guess you will find it too soon. Attaching listeners is anonymous, so when a listens some event and you implement listenTo and then listen to from b to a ( b.listenTo(a, ...), it works. Basically same way as yours, by prepending the event with object id. But then when you need to universally stop listening of all events, then you have no sane way in eve to differentiate which listeners belongs to who. So b.stopListening() does not know which of the 2 listeners attaced to a is his...

@DmitryBaranovskiy

Copy link
Copy Markdown
Author

I don’t really appreciate events attached to or from particular objects. I am big proponent of global event space. I am sure there is a pretty name for this model in CS somewhere. That being said, you can implement listenTo like this:

listenTo: function (other, event, callback) {
    var id = other.id(),
        eventName = ["model", id].concat(event);
    eve.on(eventName, callback);
    eve.once(name("stopListening", id), function () {
        eve.off(eventName, callback);
    });
},
stopListening: function (other) {
    eve(["stopListening", other ? other.id() : "*"]);
}

(there is more logic in full implementation of object.stopListening([other], [event], [callback]), but you got the idea)

Update: added it to the gist.

@WebReflection

Copy link
Copy Markdown

I am big proponent of global event space

so why are you attaching everything to a single eve object ? isn't that exactly the same as having all global events ?

return (function api() { this closure is pointless, not sure why is there, the returned object is fine as it is.

About the rest, first time I see this(invoked) which is IMO unexpected inside a listener … either is the object where the listener has been attached or the object used as listener through handleEvent so all this makes almost no sense to me after a quick read: it isn't DOM, it isn't node like EventEmitter … "what the hack is this and what is this trying to solve" would be my first counter question

@DmitryBaranovskiy

Copy link
Copy Markdown
Author
  1. This is what proponent means, I guess.
  2. You’re right, this is leftover. I’ll remove it.
  3. I just passed function as a context, could be a parameter, not a point. (I think this(invoked) is cute ;))
  4. I am trying to show off my thoughts on purely event based API on example of recreating backbone model. Was trying to make it small. I think it simpler this way, but may be it’s just simpler for my corrupted mind. I reckon that decoupling is good, but may be I’ve gone too far?

@abhip

abhip commented Jul 8, 2014

Copy link
Copy Markdown

Looks great but it may be hard for someone new to follow.

But it is freaking awesome

@hitsthings

Copy link
Copy Markdown

I disagree with making every instance method call cancellable by any code running in the page. In team development, this encourages spaghetti code where someone junior will cancel your events from unrelated code to achieve their goal. And so every method call you make on the model must be done with the understanding that it might be cancelled. This means you can't have any certainty that a few method calls in serial will do what you expect. has() after set() might return false. Your code becomes non-deterministic (when you look at just the local code).

@DmitryBaranovskiy

Copy link
Copy Markdown
Author

@hitsthings: Isn’t it the same as being afraid that somebody will overwrite Model.prototype.has?

@apsavin

apsavin commented Jul 8, 2014

Copy link
Copy Markdown

@DmitryBaranovskiy what do you want to do this? I don't get it, why do you need to recreate Backbone.Model api using events?

@apsavin

apsavin commented Jul 8, 2014

Copy link
Copy Markdown

The main prop is possibility to communicate with the model using events only (if id is known)?

@DmitryBaranovskiy

Copy link
Copy Markdown
Author

My original question was, “What do you think of API like this?”. Like 100% event based. To show the API I created purely hypothetical example. “Why?” is irrelevant. What pros and cons do you see?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment