Skip to content

Instantly share code, notes, and snippets.

@trodrigues
Created February 9, 2012 17:48
Show Gist options
  • Save trodrigues/1781533 to your computer and use it in GitHub Desktop.
Save trodrigues/1781533 to your computer and use it in GitHub Desktop.
references inside references
// should this.modules.module1 work?
define({
appStructure: {
literal: {
module1: { $ref: 'module1' },
module2: { $ref: 'module2' }
}
},
module1: {
create: {
module: 'module1'
},
properties: { modules: {$ref: 'appStructure'} }
},
module2: {
create: {
module: 'module2'
},
properties: { modules: {$ref: 'appStructure'} }
}
});
@trodrigues
Copy link
Author

Also, if the literal on appStructure is removed, the whole thing won't work.

@unscriptable
Copy link

In general, components referenced inside of components is supported. I'll defer to @briancavalier about how wire handles circular references. I'm not sure if the literal factory breaks the cycle somehow.

I assume the following creates your modules?

define({
    module1: {
        create: {
        module: 'module1'
    }
},
module2: {
    create: {
        module: 'module2'
    }
}

});

@briancavalier
Copy link

There are a couple of things going on here. First, the literal factory tells wire not to process the object at all. So, wire's not going to resolve any refs inside the object passed to literal:

appStructure: {
    literal: {
      // wire stays out of here entirely, so refs won't be resolved, etc.
    }
}

Second, it sounds like you tried:

appStructure: {
      module1: { $ref: 'module1' },
      module2: { $ref: 'module2' }
}

Normally, that'd be just fine--wire understands plain object literals like that, and would resolve the refs. But in this particular case, there are circular references between appStructure and module1/module2. Currently, wire can't resolve circular references, and it effectively creates a promises deadlock situation.

Can you describe what you're trying to accomplish? Typically, in an IOC environment, you don't need to collect all the components into something like appStructure, since the spec/context itself is the app structure. But again, maybe your specific situation needs something like this, so let me know what the goals are and we can try to find a solution.

@trodrigues
Copy link
Author

Ok, so that explains it.

I actually thought more about the problem after posting this question and thought that I really don't need that whole list of modules referenced across the whole app. Basically this is an app that uses backbone, and when one module decides to use backbone's navigate to go to some other module, it doesn't directly call backbone's navigate method, because it needs to do some extra things. I can use one module as the main "router" and just have that one referenced across the others, and it will take care of that action for all the other modules.

I guess in this particular case, given that I'm trying to adapt an existing app (which had a few architectural issues) to use AMD and wire.js, without trying to completely rewrite and restructure the app, some of the things that I end up trying to do don't always make much sense and then I realize the best way to actually do it.

Thanks for the help! I'm thinking I might end up turning this into a blog post at some point as a sort of case study for these tools, as I haven't yet seen much use of wire.js in the wild, and there's a few things to think about when adapting an existing app instead of writing something from scratch.

@briancavalier
Copy link

No problem, glad that cleared things up a bit. There's been some use of wire.js + Backbone. In fact, @pieter-vanderwerff created a version of @addyosmani's todomvc that uses wire + backbone. You may want to have a look there to get some ideas, too.

Refactoring an existing app to use wire + AMD (are you using curl?) is certainly an interesting case! I don't know of anyone else doing it, in fact. I think a blog post on it would be awesome, and like you said, the challenges can be different than when building from scratch.

Good luck, and feel free to ping again if you get stuck.

@trodrigues
Copy link
Author

Yup. It's curl + wire + r.js, and I'm trying to achieve a build where I can have an initial stage with the main libs and then load app modules as necessary.

@unscriptable
Copy link

unscriptable commented Feb 9, 2012 via email

@trodrigues
Copy link
Author

@unscriptable yes, I do know that, and it's a bit annoying that require.js doesn't have either a css or link plugin (at least it's not listed on their wiki, there might be one around). It's not that bad given that there's only one css file in my use case.

Anyway, I just realized how I was looking at this whole problem in the wrong light. The thing is, for some stupid reason, I assumed you could only require your spec once, and my problem was getting access to instances created by the spec across different modules. Of course that I can just require the spec again. Which is silly for me, because even though I had never really used DI, I saw it working on Symfony 2 and understood perfectly that it worked exactly like that.

So all is fine now, my spec is much simpler and smaller, and I'm actually hoping to have the rest of this app entirely AMD'ed and wired very quickly!

@unscriptable
Copy link

I lied a wee bit. There are some plugins that will work across AMD build tools. curl.js's domReady! and link! plugins should work just fine since they have no build-time functionality.

If you'd like to post your code in a private gist when you've got it working, we'd gladly provide feedback. We're very interested to see how other people are using our tools. :)

@briancavalier
Copy link

A quick note on requiring a spec more than once. There is a small "gotcha" there when using wire as an AMD plugin, due to the way AMD loaders will cache the module definition, even when dealing with plugins. For example, if you do the following multiple times, it will only actually invoke wire the first time. Subsequent times, the loader will simply return the same wire context that was created the first time:

curl(['wire!my/wire/spec'], function(context) {
   // The first time, wire will be invoked and will create a new context from
   // my/wire/spec.
   // After that, wire will not be invoked, and the cached context will be returned
});

To wire the same spec multiple times, you can use wire as a module instead of a plugin. You can programmatically wire the spec as many times as you need, and each time, a new context will be created:

curl(['wire', 'my/wire/spec'], function(wire, spec) {
    // This will always invoke wire and create a new context.
    wire(spec).then(function(context) { ... });
});

Or, you can simply pass a module id to wire directly:

curl(['wire'], function(wire) {
    // This will always invoke wire and create a new context.
    wire('my/wire/spec').then(function(context) { ... });
});

However, the preferred way to get a handle on the wire function is by injecting it into any component using the wire! reference resolver. Here's a quick example:

define({
  module1: {
    create: {
      module: 'module1'
    },
    properties: {
        // Set the _wireMoreStuff to a function that will wire any spec passed to it
        // So, module1 can call this._wireMoreStuff('another/wire/spec') to wire more stuff
        // More info below
        _wireMoreStuff: { $ref: 'wire!' }
    }
  }
// ...
});

When using wire that way, it is actually a contextual version of wire, that will wire a child context of the current context. Creating hierarchies of contexts like this can be very useful, because child contexts can see components in their parent context, and thus can $ref them. You can read more about contexts and context hierarchies here. They are a very powerful part of wire, and we've come to use the hierarchy a lot when building complex systems.

You may also want to have a look at the wire factory, which is yet another way to "use" wire from within other components. The defer option can be especially powerful.

I'll second John's request for a private gist, if that's doable, when you get to a point where you can share. We'd certainly love to see how you're using cujo :)

@trodrigues
Copy link
Author

Oh, I see. The wire! reference resolver injected into the module is something I hadn't noticed before (maybe it could be better highlighted in the docs?) and it actually makes a lot more sense than always having to require the spec. And, having the spec cached is something I don't mind, and I actually expected it to work like that.

I was actually looking at contexts right now, as I have a possible use case for them. This app was organized under a global namespace with sub namespaces under it, and there's wasn't much thought of having a very organized interaction between modules, so there's things like submodules setting values on higher level namespaces. At times I struggle a bit figuring out the best way to do some things with this new workflow.

I'll try to show you guys some stuff when I get it working properly, but it still won't be pretty. Basically, turning the existing modules into AMD modules and using wire.js where it makes sense is the first step to the restructuring of this app. There's a lot of logic in views and other issues that will require some reorganization and splitting up of things and I thought it would be better to first get these tools in place before starting to do that sort of work.

Also, there's a few quirks regarding the way the app is bootstrapped and deployed (this is a Spotify App by the way!) that make it a bit different than most browser based single page apps.

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