-
Notifications
You must be signed in to change notification settings - Fork 28
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
v0.8 #87
Comments
I'm so excited about the progress I've made in branch next In a nut shell: the wiring API's up and running. Not set in stone obviously, but pretty definite already. // v0.7
context.wireView("SomeView", SomeView);
context.wireClass("someClass", SomeClass);
context.wireValue("someValue", "some value");
context.wireSingleton("someSingleton", SomeSingleton);
context.getObject("someSingleton");
context.hasWiring("someSingleton");
//v0.8
context.wire(SomeView).as.constructor("SomeView"); //will resolve to a constructor function
context.wire(SomeClass).as.producer("someClass"); //will resolve to an instance of SomeClass
context.wire("some value").as.value("someValue"); //will resolve to "some value"
context.wire(SomeSingleton).as.singleton("someSingleton"); //will resolve to "some value"
context.get("someSingleton"); //will return the SomeSingleton instance
context.has("someSingleton"); //will output `true`
//however, then things start happening.
//ALL `.as.<provider>` (and some other) methods accept a "string", an object, an array,
//parameters or any mix of those
context.wire(SomeSingleton).as.singleton("SingletonA", "SingletonB", "SingletonC"); //equals:
context.wire(SomeSingleton).as.singleton(["SingletonA", "SingletonB", "SingletonC"]); //equals:
context.wire(SomeSingleton).as.singleton({ a: "SingletonA", b: "SingletonB", c: "SingletonC"} ); //equals:
context.wire(SomeSingleton).as.singleton([["SingletonA"]], "SingletonB", {c: "SingletonC"});
//now what does this mean?
//They all share the same singleton instance
context.get("SingletonA") === context.get("SingletonB") === context.get("SingletonC");
//sometimes it's more beneficial to have separate singletons though, i.e. a "multiton"
context.wire(SomeSingleton).as.multiton("SingletonA", "SingletonB", "SingletonC");
//will create three separate singletons
//both of the above open up a ton of possibilities, where classes use the same dependencies,
//however they're named differently which boosts reuse, flexibility et cetera.
//but there's more: sometimes you want to create an instance (to configure it for example)
//at wiring-time, but you want its dependencies to be resolved lazily, that's possible now too.
var foo = new FooClass(); //it has its dependencies declared as a `wiring` object inside the prototype
context.wire(foo).as.unresolved("foo");
context.get("foo"); //will resolve its dependencies
//oh, and `get` and `has` are just as flexible as the `.as.<provider>` methods:
var result = context.get("SingletonA", ["someClass"], {view:"SomeView"});
console.log(result);
/*
output:
{
SingletonA: <SomeSingleton instance>,
someClass: <SomeClass instance>,
view: <SomeView instance>
}
*/
//but if you `get` a single key, it returns a single value
context.get("SingletonA");
//the same goes for `has`
var result = context.has("SingletonA", ["someClass"], {view:"SomeView"}, "notfound");
console.log(result);
/*
output:
{
SingletonA: true,
someClass: true,
view: true,
notfound: false
}
*/
//And if you simply want to be sure all keys have been wired:
var result = context.has.each("SingletonA", ["someClass"], {view:"SomeView"}, "notfound");
console.log(result);//outputs: false (since "notfound" is not wired)
//the same flexibility applies to `release`
context.release.wires("SingletonA")
context.release.wires("SingletonA", ["someClass"], {view:"SomeView"});
context.release.all();
//Oh, and wirings get merged.
function Foo(){
this.wiring = "a";
}
//either:
context.wire(Foo).as.singleton("foo").using.wiring("b");
var foo = context.get("foo"); //instance with "a" and "b" resolved
//or:
var foo = new Foo();
context.resolve(foo, "b"); //foo has its dependencies "a" and "b" resolved
//ah yes, wiring declarations are just as flexible:
function Foo(){
this.wiring = "a"; //resolves "a" into member "a"
this.wiring = ["a", "b"]; //resolves "a" and "b" into members "a" and "b"
this.wiring = { foo: "a", baz : "b"};//resolves "a" and "b" into members "foo" and "baz"
this.wiring=["a", {baz:"b"}];//resolves "a" and "b" into members "a" and "baz"
} Now on to tackle the rest. |
wow! You're on fire these days! I'm getting excited too!! 😃 I'm anxious to see a working version that I can test! I have several projects that I want to convert to Geppetto, some quite big. Any idea how long you're giving yourself to 'tackle the rest'? |
@mmikeyy hard to say, since I can only work on this very irregularly. Also, there's still a ton to do. The two major jobs are
//we want to be able to let components react to context events
wire(SomeView).as.producer("someView")
.and.on("some:context:event").execute("render")
//or if you want to setup a bunch at once:
wire(SomeView).as.producer("someView")
.and.on({ "some:context:event" : "render" });
//but we also want the reverse, i.e. components should easily be able to communicate to the context
wire(SomeView).as.producer("someView")
.and.relay("some:view:event") //sends "some:view:event" through the context
//but we want event translation as well
wire(SomeView).as.producer("someView")
.and.relay("some:view:event").as("another:context:event"); //when view triggers "some:view:event" the context translates it to "another:context:event"
//again with a map
wire(SomeView).as.producer("someView")
.and.relays({ "some:view:event":"another:context:event" })
//or an array
wire(SomeView).as.producer("someView")
.and.relays([ "some:view:event" ])
//then the context needs to be able to do stuff with context events
//relay them to other contexts
context.relay("some:view:event").to.all(); // this is to every other context
//or to its parent
context.relay("some:view:event").to.parent(); // this is to its parent context
//but we also want to be able to translate events
context.relay("some:view:event").as("another:context:event")
//to other contexts if necessary
context.relay("some:view:event").as("another:context:event").to.all()
context.relay("some:view:event").as("another:context:event").to.parent()
//all components have their `trigger` method which dispatches to direct listeners,
//(or context listeners if configured that way) but they also have
//a `relay` method which functions as described above
//the context also dispatches with `trigger` which is exaclty like in Backbone
context.trigger("event", ...params);
//except it's enhanced, allowing you to dispatch to other contexts
context.trigger("event", ...params).to.all(); //dispatches within 'context' but also to all other contexts
context.trigger("event", ...params).to.parent(); //dispatches within 'context' but also to parent context Just to be clear, I'm totally focusing on how everything works at wiring-time, but everything you can configure outside the components will be available inside the components as well. I.e. it will cater to both styles. |
Then, on to commands and listening directly: //if you want run a default handler:
wire(SomeView).as.producer("someView")
.and.on("some:context:event").execute(); //calls the instance's `execute` method
//which leads us to commands, changed my mind about not having them in the API.
context.wire(SomeCommand).as.command().on("some:context:event"); //which is (almost) equivalent to:
context.wire(SomeCommand).as.producer("someCommand")
.and.on("some:context:event").execute();
//you could provide a name to the "normal" commands as well:
context.wire(SomeCommand).as.command("someCommand").on("some:context:event");
//the above means you can do:
context.wire(SomeCommand).as.singleton("someCommand")
.and.on("some:context:event").execute(); //creates a singleton command, i.e. it's not dropped after execution
//then, listening directly on a context, the BB way
context.on("some:context:event", function(){
console.log("I rock!");
});
//or fluently
context.on("some:context:event").execute( function(){
console.log("And roll!");
}); As you can see I aim for a use-it-as-you-wish API, which allows for being very strict, but also creating exceptions when necessary. |
OK. I can see that the new Geppetto won't be available any time soon. No problem: we already have something very good... I think it's good to keep the Backbone event system. Why reinvent the wheel? But then... one could retort that this comment is not surprising coming from a Backbone user, and not everyone uses Backbone. Anyway, I don't remember seeing any complaints about this... Concerning events, I've been wondering why we always send events to self, to parent and/or to parents. Why not have children too as an option? If I'm not mistaken, the only way to reach children ATM is to dispatch to all. Anxious to test a functional version! |
So, time for an update. I just pushed into the Usage: var dispatcher = new Geppetto.Events();
//equals
var dispatcher = Geppetto.Events();
//equals
var dispatcher = _.extend({}, Geppetto.Events); //As you would do with Backbone.Events
//dummy handler
var handler = function(){
console.log(arguments);
}
//fully Backbone.Events compatible:
dispatcher.on("event", handler, someObject);
dispatcher.off("event");
someObject.listenTo(dispatcher, "event", handler);
//et cetera
//But it's enhanced:
dispatcher.on("event").have(someObject).execute(handler); // in scope of `someObject`
//you can attach functions directly to events as well
dispatcher.on("event").execute(handler);
//if your listener has a method "foo" for instance you can use these too:
dispatcher.on("event").have(someObject).execute("foo");
//when hanging on a geppetto context you can use keys for lazy creation:
context.on("event").have("loginService").execute("signin"); // loginService will only be created when event is first dispatched
//"execute" is the default handler, which leads to the new way of wiring commands (Yes, changed my mind again
context.on("event").have(MyCommand).execute();
//BTW: you can pass multiple functions to `execute`:
//as parameters
context.on("event").execute(f1, f2, f3)
//as an array
context.on("event").execute([f1, f2, f3])
//as an object
context.on("event").execute({ a: f1, b : f2, c: f3 });
//`trigger` works as in Backbone
context.trigger("event", a, b, c); //handling function will receive `a,b,c`
//and you can use `dispatch` to have the old Geppetto style
context.dispatch("event", { a: a, b: b, c: c});
//handling function receives following object
var received = {
eventName : "event",
eventData : {
a: a,
b: b,
c: c
}
}
//`listenTo` works as in Backbone:
context.listenTo(someObj, "event", handler);
//but is also enhanced:
context.listenTo(someObj).on("event").execute(handler);
//and aliased to `allow`
context.allow(someObj).on("event").execute(handler);
//`once` and `listenToOnce` work as in BB AND are enhanced as well
context.once("event").execute(f1, f2, f3)
//And if you prefer a really explicit style, you can do this:
var when = Geppetto.Events;
when(context).dispatches("event").execute(handler);
when(context).dispatches("event").have(someObject).execute(handler);
//completely equal to:
context.on("event").execute(handler);
context.on("event").have(someObject).execute(handler); I'm not entirely satisfied with |
488 tests already! |
So I started on the next minor/major version. I'd been thinking about 0.7.2 and what needed to be done: updating the tests, documentation and a number of patches here and there to get things better, until I suddenly realised I should be focusing on getting the next major version out instead of trying to modify 0.7 towards what I'd really want.
So, here's what's happening: I'm trying to
wireView
orwireCommand
. Of course it will still be possible to wire views, commands et cetera. But just as we have nowireModel
I think it's beneficial to make Geppetto a more flexible library which can be used in a MVC, MVVM, MyOwnCustomWhateverConcept w/o having the feeling of trying to fit a square peg in a round hole. As implied in Bringing Geppetto to life. #86 this could be potentially a powerful approach and selling point. To achieve this it's necessary to make the Geppetto concepts dedicated and not adhere to another paradigm. Obviously the role of the documentation will be to show the way on how to use it in an MVC or similar context.Forgive me the convoluted example, but I tried to cover as much ground as possible. I think most of it is pretty self-explanatory if you're familiar with the current version. Obviously everything that's done using the context API here, can be configured inside your components as well: wiring, handlers, et cetera.
As you can see views are no longer wired as "view", but as factory. And
wireClass
is replaced withas.producer
which is a better fitting term, IMO.There's a new concept as well, "providers":
Now, what's this all about? ATM Geppetto contexts allow for registering objects as singletons, values, ... but I wanted to allow developers to create their own types. E.g. the above example would allow maintaining a list of all
Requester
instances. Theprovide
method receives anItemClass
, which in this case isRequester
. It creates a new instance and stores it in a queue.The
SomeService
class is totally oblivious to whether it's receiving a singleton instance, or anew
instance, or (in this case) anew
instance which is stored in a queue.As you can see the
Context#provide
method accepts a string which is used to expand the context API. I passed 'queue' to it, which adds aqueue
member towire.as
.This allows for extremely powerful and versatile concepts and patterns. E.g. I can imagine use cases for an "object pool" or "cyclic" provider for instance.
initialize
method which allows for a better life cycle as well:constructor
: pre-resolution,initialize
: post-resolution.Well, that's about it FTM. As always, @geekdave please pull the reigns if I'm off into cuckoo-land.
The text was updated successfully, but these errors were encountered: