Relative paths in specs

79 views
Skip to first unread message

Gehan Gonsalkorale

unread,
Aug 5, 2013, 6:23:43 AM8/5/13
to cuj...@googlegroups.com
Nice to see you've added initial support for these!

I notice they don't work with plugins yet though (e.g. text!, css!), so can't really take advantage of this yet - but looking forward to when you do!

Gehan

Brian Cavalier

unread,
Aug 5, 2013, 9:09:48 AM8/5/13
to cuj...@googlegroups.com
Thanks, Gehan.  Yeah, that's the logical next step.

Because module IDs are the domain of whatever loader the environment provides, we have to do a minimal id transformation outside of the loader (be it curl, requirejs, node, etc etc), so we have to be a bit careful.  One additional problem here is that the portion of the module ID to the right of the "!" in an AMD plugin resource ID like "text!./id/of/some/text.html" isn't actually specified as being a module ID.  It can be anything--i.e. plugin specific.  In the most common cases, it happens to be a module ID (or module ID-like), and we could relativize it correctly before sending it through to the loader.  In other cases, we could potentially mangle a non-ID and cause an error that is hard to track down.

I think we can do it, but I know we'll have to be a bit more careful in these cases :)

Gehan Gonsalkorale

unread,
Aug 6, 2013, 7:06:01 AM8/6/13
to cuj...@googlegroups.com
Ok awesome!

On another note, I went to a meet up last week where we were talking about front-end tech. I brought up wire.js as a great way to provide an awesome base/structure but noone had heard about it.

Well almost noone. There was a guy called Jeff from Pivotal was there (their office is near ours) and he mentioned you were in the office the other week and were trying to get him to use it ;)

He was saying you and John are used to enterprise-level programming, and that front-end stuff really isn't that mature yet so perhaps you might want really simple example apps.

I was also saying that for rendering views, I think the way you guys are doing it could be improved. At the moment you create a static html file, and attach events in the spec to html files and route them somewhere. To alter the view then you have to do some DOM manipulation directly inside a controller. We used to do it like that and it gets quite nasty to be honest.

Have you ever tried the model-viewmodel-view way of doing things? It's amazing and solves so many problems, most notably  it creates a 2-way binding between the dom and your controller (or in mvvm speak your view and viewmodel). 

Anyway, we use http://knockoutjs.com/ for this and it's great. In conjunction with backbone and a little library called knockback (which provides a binding between the others). 

I'm going to knock up a little sample app for work as we're doing a code retreat on friday. I share it with you also if you like.

Scott Andrews

unread,
Aug 7, 2013, 4:42:26 PM8/7/13
to cuj...@googlegroups.com
Hi Gehan,

I was in London for a bit back in June to kick off an internal project, not directly related to cujoJS.

It's true that many of the cujo projects have yet to hit 1.0, but that doesn't means you can't get started using them. Each project has a robust set of unit tests, so we feel confident about the functionality that is released, the version number is more a reflection of how much functionality, documentation and samples are left before 1.0, as well as the risk for potential API changes.

Brian and John can chime in on the MVVM-like work we have in flight.

I don't have any plans to head back to London, but will let you know if that changes.

-Scott


--
You received this message because you are subscribed to the Google Groups "cujojs" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cujojs+un...@googlegroups.com.
To post to this group, send email to cuj...@googlegroups.com.
Visit this group at http://groups.google.com/group/cujojs.
To view this discussion on the web visit https://groups.google.com/d/msgid/cujojs/5fb74272-a174-4a11-8cc5-8df0acd3460a%40googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.
 
 

unscriptable

unread,
Aug 7, 2013, 6:40:11 PM8/7/13
to cuj...@googlegroups.com
Hey Gehan,

Apologies for the lack of documentation in cola.js, but it does not require DOM manipulation.  It uses css rules to configure data binding into those static html templates.  The next version will likely allow other ways to define the binding points, too.  (mustache-like syntax, perhaps?)

Of course, we understand that things like knockout exist and work well, so we're very interested to learn how you integrate it! 

Regards,

-- John

Gehan Gonsalkorale

unread,
Aug 8, 2013, 3:18:02 PM8/8/13
to cuj...@googlegroups.com
Hi Scott

Sorry I must have phrased myself badly. I didn't mean that the cujojs libraries are not mature - I meant that the world of front-end engineering is not that mature. At least in comparison to the world of enterprise java programming. As such there aren't many frameworks, most of the ones that do exist are written badly and there isn't a clear winner. Further more a lot, if not most, of the apps that exist are quite simplistic. Therefore I meant that the example apps should be quite simple :)

Gehan Gonsalkorale

unread,
Aug 8, 2013, 3:18:42 PM8/8/13
to cuj...@googlegroups.com
Yep, I'm making a seed app for work which you can see below. Well I say seed but really it's going to be a technology demo. It doesn't do a lot yet, but I'll update and let you know later!


For cola then, you attach behaviours to the templates by specifying selectors in the cola bindings and the specific behaviour? 

{
    "input[name=first_name]": {
        "click": "doSomething"

Brian Cavalier

unread,
Aug 8, 2013, 4:35:47 PM8/8/13
to cuj...@googlegroups.com
Hey Gehan,

Using the current version of cola, you can setup several things declaratively in a wire spec: 2-way data binding, and/or custom transformations if simple data binding isn't enough, a comparator for sorting a list view, etc.  What you've shown in your example is more like what you do with wire/on--setting up event handlers.

Here's a small example from the cujoJS TodoMVC implementation: https://github.com/cujojs/todomvc/blob/cujo/labs/architecture-examples/cujo/app/main.js#L34-L44

That sets up binding between the data field "text" (i.e. todo.text)  and 2 dom elements (actually, all elements that match the selector 'label, .edit', that is, a label or anything with the class "edit").  It also sets up binding between the todo.complete field and dom elements with the "toggle" class.  Additionally, it adds a custom transform which adds or removes a css class when the todo.complete value changes (adding/removing the class is a requirement of the TodoMVC app).

Then, you can see a bit farther down (https://github.com/cujojs/todomvc/blob/cujo/labs/architecture-examples/cujo/app/main.js#L117-L131) where the controller is hooked up to various dom events.

The next version of cola is in development right now, and will automate more things.  It's something of a combination of MVVM meets functional reactive.

Gehan Gonsalkorale

unread,
Aug 8, 2013, 7:21:31 PM8/8/13
to cuj...@googlegroups.com
In principle I quite like how the interactions between the view and controller are defined completely within the spec - it keeps them completely decoupled (as seems to be the goal with all this). 

I'll play around with cola, although there are no docs right? Be good to get a proper feel for it. Also todo list apps are quite contrived so maybe I  can only appreciate this way when the app is on a larger scale.

When using something like knockout then the view and viewmodel are coupled, or at least the view is coupled to the viewmodel as the bindings are declared within it. I guess in your example the bindings are declared within the wire spec and nowhere else.

Initially I didn't like knockout because of this, however it does make it quite clear what the view actually does when you look at it. Also without the viewmodel the view is quite useless, so it's not really a self-contained component like other things can be.

Using knockout you could do this as below, using your example. I guess the viewmodel then has the todos collection mixed in as well as the methods on the controller, and the view has all the bindings declared on it but it seems it a bit slicker and less terse to me. 

<ul data-bind="foreach: todos">
    <li>
        <div class="view">
            <input class="toggle" type="checkbox" data-bind="click: updateTodo">
            <label data-bind="text: text></label>
            <button class="destroy" data-bind="click: removeTodo"></button>
        </div>
        <input class="edit" value="">
    </li>
</ul>


What do you think of mvvm approaches like this?

Brian Cavalier

unread,
Aug 11, 2013, 8:26:33 PM8/11/13
to cuj...@googlegroups.com
Yeah, apologies for the lack of docs.  However, we are quite far along on the next iteration of cola, and it's coming together nicely.  We're actually approaching it in a way that allows it to work with and/or alongside other domain modeling systems (i.e. models and collections), such as Backbone, etc.  It'll be interesting to see if it could work with Knockout as well.  Cola will do more than just view-model data binding, so there will still be plenty of benefit to using it.

As for TodoMVC, there's always tension when creating demo apps: trying to create something that illustrates the concepts while still being small enough that you can actually finish it without completely neglecting your other responsibilities :)

I think the "bindings in html" is a reasonable way to do it.  In fact, cola.next will support a form of this, although you will also be able to plugin other binding mechanisms if you want (e.g. cola's current css-selector based binding approach will be an option).  We actually get quite a bit of positive feedback from people who like that cola lets them NOT put binding information in their templates.  We like that approach because it allows non-engineers to edit templates freely without as much worry about breaking the system.

Gehan Gonsalkorale

unread,
Aug 12, 2013, 8:21:31 AM8/12/13
to cuj...@googlegroups.com
Ok nice - look forward to seeing it!

Not having or having binding in the templates would be a nice option to have. Sometimes the bindings in html can get unwieldy..!

Brian Cavalier

unread,
Aug 12, 2013, 12:38:30 PM8/12/13
to cuj...@googlegroups.com
Cool, we'll probably have some simple demos ready to show by early September.  We'll post more info about it as we get closer.  It'll be great to get your feedback!

Gehan Gonsalkorale

unread,
Oct 1, 2013, 5:56:40 PM10/1/13
to cuj...@googlegroups.com
Hey guys,

Any progress on having more widespread relative specs? I'm writing a few components at the moment and the massive paths are hurting my brain a bit :D

Gehan


On Monday, 5 August 2013 14:09:48 UTC+1, Brian Cavalier wrote:

Brian Cavalier

unread,
Oct 2, 2013, 12:58:52 PM10/2/13
to cuj...@googlegroups.com
Hey Gehan, thanks for poking us on this :)  v0.10.2 has support (https://github.com/cujojs/wire#0102), but we still haven't addressed the issue of AMD plugins.  We discussed it a bit more today, and it seems like there are only a handful of workable solutions:

1. While there's no real specification for how this works, AMD loaders tend to use a heuristic in deciding if a plugin resource id is a module id or not.  This requires loading the plugin itself, which obviously the AMD loader needs to do. Wire could mimic this, but it just seems like a lot of work, and duplicating an unofficial algorithm that might change.

2. Always assume that the resource id portion (the bit after the "!") is module-id-like.  We could split the whole id, transform both the plugin id (the bit before the "!") and the resource id, reconstruct a new whole id, and pass that through to the underlying loader.  That's all pretty simple.

#1 scares me a little.  #2 sounds like the more reasonable path.  However, we'd need to provide a way to "opt out" of the resource id transformation on a per module id basis, since you might be using plugins where transforming the resource id would break things.

The only thought I have is to invent some syntax, like adding a prefix (which wire would notice and remove) to a module id when you want wire NOT to transform the resource portion.  For example, if you had: 'my/foo/plugin!this/is/not/an/id', you'd add some prefix like '*': '*my/foo/plugin!this/is/not/an/id', or perhaps using '!!' instead of '!': 'my/foo/plugin!!this/is/not/an/id' (too subtle??), or wrap it in parens or brackets: '(my/foo/plugin!this/is/not/an/id)'.  Honestly, I'm not crazy about any of those :(  Seems like they will just confuse people. Anyone have any better ideas?

unscriptable

unread,
Oct 2, 2013, 1:16:03 PM10/2/13
to cuj...@googlegroups.com
My current thinking / opinion is that we need to make some serious progress on our "load by file extension" strategy that we've kicked around a few times.  Rather than use AMD "plugin!resource" notation, the dev would just map file extensions to curl.js loaders.  The most common mappings would be available by default: .html->text!, .css->css!, etc.

Thoughts?

Brian Cavalier

unread,
Oct 2, 2013, 2:38:37 PM10/2/13
to cuj...@googlegroups.com
I love that idea.  Text and CSS seem like no-brainers here.  One possible snag: i18n.  Since it's just javascript, how would the "by file extension" machinery differentiate between plain JS and an i18n resource which needs special treatment?  If we can figure that out, I think I'm all for this.

unscriptable

unread,
Oct 3, 2013, 11:04:12 AM10/3/13
to cuj...@googlegroups.com
Yah, good question.  The hackish way to fix the problem is to assign a different file extension to i18n bundles:  "en-gb/strings.locale".  This could probably work for i18n files.  I'm not sure how loudly people would complain if we made this concession.

However, what about if devs are mixing AMD, CJS, and ES6 modules in a single package?  They all end in ".js".

My answer to this is Don't Do This.  I can't see why anybody would want to mix these in a single package.  In curl.js, each package can specify its own loader with its own config.  So, as long as each package can specify which type of module is associated with ".js", this should work.

Still, I wonder if there are other cases where sniffing the file extension isn't good enough.  Anybody have any other examples?  Any possible solutions to this problem?

Thanks!

-- John

Brian Cavalier

unread,
Oct 3, 2013, 11:19:06 AM10/3/13
to cuj...@googlegroups.com
I wonder if mime types help at all ... or does that just move the problem from curl to the web server?  i.e. how does the web server know that strings.js is a locale file, in order to give it a specialized mime type??  Hmmm, so maybe that doesn't help :)

A ".locale" file extension seems just as good as a mime type, or possibly ".locale.js".

Seems like the only way to deal with mixed module formats in a single package would be to inspect the text of the module ... blech.  I'm with you: just don't do it.

unscriptable

unread,
Oct 3, 2013, 11:26:49 AM10/3/13
to cuj...@googlegroups.com
".locale.js" is very interesting. :)

Gehan Gonsalkorale

unread,
Oct 9, 2013, 3:19:26 PM10/9/13
to cuj...@googlegroups.com
I quite like the extension based loaders! It would make things a lot neater, and means that you could use relative paths off the bat? Having either .locale or .locale.js is quite a good idea too. Dooo it!

For using relative paths in wire would make them a lot more readable, and without plugin support it means I can't html/css files so seems inconsistent to have some of the spec relative and some of it absolute.

On another note, we had a senior dev (Guy) join this week and he was wondering who uses wirejs in production, what are the plans for cujojs and how big is the community? He likes it but has reservations about scale (ie new dev joining) if there are no basic tutorials and you can't search the internet to find answers to questions.

I remember I asked about 7 weeks ago and John emailed about some vague future plans but it's be good to know more solid plans, e.g. what are you trying to achieve with each project and when that'll happen. I guess at the end of the day you want lots of people using cujojs and so this might be important! 

I mentioned this last time, but I read making good open-source code is about:

   1. Empower the community, both the developers and the users (see http://communityovercode.com/).
   2. Make it predictable; roadmaps and clear upgrade paths are mandatory 
   3. Make it easy to learn and use 
   4. Make the code good 

Which is kinda obvious in a way, but a bit sad that it admits the reality that you can have a popular library with terrible code. I'm sure you can think of a few like that!

With making good code I don't see a problem. To make it easy to learn use depends on a good api and plugin architecture (which you have) and also... docs and tutorials! The docs are good if you know the library already but I found them hard to understand initially. Guy felt the same and I had to explain a lot - which has spurred me on to actually write some docs :)

Anyway, be interesting to hear what you think. For a tutorial for wire I'm going to make a basic version of gmail, which I think will explain things well! Although will have to involve using backbone.

Mudi Ugbowanko

unread,
Oct 10, 2013, 9:27:25 AM10/10/13
to cuj...@googlegroups.com
This is a very interesting discussion, I had to comment on because I have been dealing with the same issues:

RE: ![plugin resource id]
It is currently impossible to rely on a correct interpretation of what the resource id could be. My use case was where I built a plugin that would load a specific spec depending on some externally set variable.

The plugin worked as expected. However after handing it over to another dev to use within a spec, he came up with something like this:

childContext: {
wire: {
spec: 'myPlugin!contexts/welcome-pane'
}
}

I expected it to still work ... which it did, but only when unbuilt. When we attempt to build it we ran into several problems. One of the problems I found was that the deps of all the child specs that the plugin could load was not included in the build. Simply down to two things (from my investigation):
  1. built plugin resources usually manifest in an optimized  'middle man' versions (not a json spec or something that the builder for wire is expecting or can currently understand)
  2. Then I thought should wire be responsible for this (it already does quite a lot)? It sounds like the AMD spec needs to be upgraded to allow more flexibility, consistency and transparency in how plugins work in built and unbuilt environments.
Bottom line my two cents to add to this discussion is to bare in mind the build step in whatever is decided (anti climax before deployment when it fails!).

RE: Relative Paths
I have no solid opinion on this. I do feel though there must be a way to use the underlying relative resolving functionality that exists on the amd loader level (e.g. curl, requirejs) to handle this.

Furthermore I encourage users to use the wire!... or the wire factory to wire up specs in oppose to doing it deep in their code by hand e.g:

wire( {...} ).then( ... ); // bad ... you loose context/location

With this approach I believe there should be a way for wire to use the underlining AMD loader or figure out a concrete way of resolving (simple) relative paths within a spec.

By simple I mean ... none plugin references e.g myPlugin!....

Its easier to get the job done with what you have than with what you don't have.

M

Brian Cavalier

unread,
Oct 10, 2013, 12:01:07 PM10/10/13
to cuj...@googlegroups.com
Gehan and unscriptable,

I think the extension-based loader approach is the way to go.  It solves the 3 main use cases where we know the resource id can be treated as a path: html templates, css files, and i18n files (if we use ".locale.js" or "-locale.js" or some other variant).

unscriptable: should we open a github issue for this?

Brian Cavalier

unread,
Oct 10, 2013, 12:23:36 PM10/10/13
to cuj...@googlegroups.com
Mudi,

Cool, thanks for adding your thoughts!

On your first question: Your myPlugin will need to implement a build-time plugin API as well.  Are you using cram.js or r.js to build?  I agree more documentation is needed around build-time AMD plugin APIs.  One problem is that the build-time API has not really been standardized, afaik.  For example, it differs between cram.js and r.js.

And the second question, re:

> wire( {...} ).then( ... ); // bad ... you loose context/location
>
> With this approach I believe there should be a way for wire to use the underlining AMD loader or figure out a concrete way of resolving (simple) relative paths within a spec.

There are two issues here.  First, using the top-level wire module is typically not the right thing to use programmatically unless you are actually at the top level of your application.  Wire contexts are intended to be used in a hierarchy, begotten one from another by calling the wire() *method* of an existing context, e.g.: parentContext.wire(...).then(...), or by using an injected wire function, e.g. injecting { $ref: 'wire!' }.  Both of those preserve the parent-child relationships between contexts (and all of the advantages that brings, like component visibility, chained lifecycle management, etc.).  In contrast, the top-level wire *function* always creates a context that inherits from the (implicit) root context.

Second is, as you mentioned, the interaction with the underlying loader.  Unfortunately, the way AMD and CommonJS are designed means you must use the local `require()` function to correctly resolve relative paths.  This local require() is injected as a free variable that can only be seen within the module itself.  That means that it is, by design, impossible for wire's core library code to get its hands on the local require function of a wire spec that it is asked to wire (since the spec's local require function exists only within the spec module).  For that to work, the spec itself would need to hand the local require() function to wire.  That's a possibility, but starts to make things more verbose.  For example, you must use the expanded AMD form:

// Wire spec that uses local require
define(['require'], function(require) {
// Wire DOES NOT support this currently
// But it might be possible to add support
// Provide the local require function to wire so that
// wire can use it to load modules for this spec
$require: require,

component1: {
create: ...
},

...
});

FYI, it is possible to do the above when using wire programmatically, although it's not documented: wire(spec, { require: require }), or parentContext.wire(spec, { require: require })

It's also possible to *directly use* the local require function inside of wire specs, but again, it gets a little cumbersome, and there are probably some edge cases we haven't tested:

// Wire spec that uses local require
define(['require'], function(require) {
// This works today
component1: {
create: {
module: require('../relative/path/to/my/module')
}
},

// And will even work with plugins
text: {
module: require('text!../a/template.html')
}

...
});

I am not sure, though, whether that last example would work in a build.  It would be interesting to try it, and maybe unscriptable knows the answer.

Brian Cavalier

unread,
Oct 10, 2013, 3:15:48 PM10/10/13
to cuj...@googlegroups.com
Argh, sorry.  My examples are not syntactically correct, here are corrected versions:

// Wire spec that uses local require
define(['require'], function(require) {
return {
// Wire DOES NOT support this currently
// But it might be possible to add support
// Provide the local require function to wire so that
// wire can use it to load modules for this spec
$require: require,

component1: {
create: ...
},

...
};
});

// Wire spec that uses local require
define(['require'], function(require) {
return {
// This works today
component1: {
create: {
module: require('../relative/path/to/my/module')
}
},

// And will even work with plugins
text: {
module: require('text!../a/template.html')
}

...
};
});

Gehan Gonsalkorale

unread,
Oct 10, 2013, 5:24:39 PM10/10/13
to cuj...@googlegroups.com
Sounds like an awesome plan! I guess the extension will lie in a config somewhere for each plugin so it doesn't really matter what it is?

Brian Cavalier

unread,
Oct 14, 2013, 11:57:26 AM10/14/13
to cuj...@googlegroups.com
Gehan,

John and I talked about this a bit more today, and I just created a github issue for it: https://github.com/cujojs/curl/issues/234

So, we can move specific discussions/ideas there.

unscriptable

unread,
Oct 16, 2013, 9:14:51 AM10/16/13
to cuj...@googlegroups.com
Brian:

Your examples should be built correctly by both cram.js and r.js.  You can simplify the boilerplate a wee bit like this:

// Wire spec that uses local require

define
(function(require) { // no deps array

 
return {
   
// This works today
    component1
: {
      create
: {
       
module: require('../relative/path/to/my/module')
     
}
   
},


   
// And will even work with plugins
    text
: {
       
module: require('text!../a/template.html')
   
}

   
...
 
};
});
Reply all
Reply to author
Forward
0 new messages