cujojs / wire

A light, fast, flexible Javascript IOC container
Other
861 stars 71 forks source link

Using wirejs with AMD modules that act as factories #67

Open jaxley opened 11 years ago

jaxley commented 11 years ago

We've got javascript AMD modules that are actually factories that create the actual instances. And we use chaining to execute methods on the instances to further configure those instances, such as the below example where we simply say "when you create the instance, append it to the 'body' DOM element" -- when DOM ready, of course.

require(["foo/bar/factory", function(FooBar) {
    FooBar.create({option1:true}).appendTo('body');
});

It is unclear from the documentation that wirejs would work with this kind of setup. I can certainly get instances created easily enough:

require(["wire"], function(Wire) {
       Wire({
               "fooBar1" : {
                   module : "foo/bar/factory",
                   init : {
                       create : { option1: true }
                   }
               }
       });
});

But is there a way to get a handle to the return value of the "create" method within the wirejs framework to continue executing methods on that (which is the real instance)? Or do I need to implement a custom wirejs factory plugin for this? Ideally, I'd still like the actual instances to be able to be passed into other wiring specs by reference as well.

-Jason

unscriptable commented 11 years ago

Hey @jaxley,

Thanks for supplying some code snippets. They help a lot. :)

Something like this might approximate your situation (untested):

   {
        "FooBar": { module: "foo/bar/factory" },
        "fooBar1": {
             create: { 
                 module: { compose: "FooBar.create" },
                 args: { option1: true }
             },
             ready: { appendTo: ["body"] }
        }
    }

One of the big advantages of IOC is the ability to separate application logic from application assembly. Your components don't need to carry dom manipulation methods around just so they can be inserted into the dom. Using wire's dom plugin could help out here:

   {
        "FooBar": { module: "foo/bar/factory" },
        "fooBar1": {
             create: { 
                 module: { compose: "FooBar.create" },
                 args: { option1: true }
             },
             insert: { last: "dom.first!body" }
        },
        plugins: [
            { module: "wire/jquery/dom" } // I'm guessing you use jquery elsewhere in your app
            //{ module: "wire/dom" } // if not needing jquery
        ]
    }

-- John

briancavalier commented 11 years ago

Hey @jaxley,

Good question!

I think the best option, as you noted, is to create your own factory plugin. That'll end up allowing you to define your own nice, compact syntax that you can use in wire specs for creating instances that require this "FooBar.create" factory style.

I'd be more than happy to answer questions, etc. if you want to go for it :) I think it would take relatively few lines of code. In fact, I think it'd make for a great discussion in the cujojs google group or in #cujojs on freenode IRC. We're pretty good about being available for real-time Q&A in #cujojs, and other cujo users might be interested in this, too!

The approach @unscriptable proposed almost works. In fact, I just did a quick experiment, and with a very small change to wire, I got it to work. I'd definitely like to think about this more, because it could be a nice change for the next minor version.

After you have the instance created, you can use init or ready to configure it as necessary, as @unscriptable showed. Also, both init and ready support calling any number of methods, if you need to call more than 1.

Hope that helps, and let us know about writing a factory.

jaxley commented 11 years ago

@unscriptable I realized that my description wasn't sufficiently clear in that the .appendTo() methods (and we have several others), operate on the return value of .create() (the factory method), not the original module.

So I don't think that will work for my situation as the init and ready methods all want to operate on the module instance but what we have is an instance created by the module instance that we'd want to invoke methods or even create a named component for so that we can use it in multiple cases.'

As for DOM element injection, that is a completely valid point and we are planning to use the DOM plugin to avoid the messy selector stuff and allow for injecting the dependencies vs. the service locator pattern of searching the DOM ourselves. In fact, because our factory is not necessarily tied to the DOM, we should be able to have wirejs create the instances and render their contents and only have to wait for DOM ready just to insert the results and have wirejs manage all of that waiting for us. Thus, once we are able to assign named compontents for the return values of our factory methods, we can then invoke methods on those and use DOM injection subsequently when invoking the methods.

I would think this capability would be useful in the core wirejs so if you think it's easy to add and plan to do that in a near-term . release, we'd probably hold off until then. If I get desperate, I can take a quick stab in a future sprint to try my hand at a local implementation until then. But for now, we won't be too hamstrung by it but over the coming weeks and months it might bite us. Would be nice to declaratively wire all of that up.

Thanks for the quick responses!

-Jason