HOW TO: inject context data on async partial using Marko

279 views
Skip to first unread message

Steve Stacha

unread,
Jan 5, 2015, 4:44:18 PM1/5/15
to rapt...@googlegroups.com
I really like the syntax of Marko and decided to post to see if there is something I am missing before going back to my underscore engine.  I have a need to look for parameters on partials and call a web service for the data if the right parameters exist.  I saw your async-fragment tag and was really hoping I could use it.  Essentially I was wanting to have components and then users can drop those components on a page.   I would write a block when dropped something like this :
<async-fragment data-provider='{"ws-data-url":"http://localhost:3000/data/users.json"}' var="users">  (or whatever 
parameters are available for this component).

Then, hopefully string replace or add the actual data when the partial is called if I find a ws-data-url property.
Dust has an onLoad method you can over-ride that gets called on every load of a page or partial but doesn't allow me to change
the context. Many renders allow me to call my own method like compilePage before I call the normal page render where
I can do the string replacement before hand and then compile into the function cache. The idea being I can cache the webservice data for
each partial into the page and then execute render on the compiled function. My problem with marko is that I don't
see a way to do either. Marko seems to always want a file path; I can't compile the source (after I have modified it).
It also doesn't seem to have any method I can override when loaded that would allow me to modify the context and add
the web service data to each async partial.

Am I missing something in Marko that would allow me to accomplish this? Perhaps taking a different approach?

Thanks,

Steve

Phil Gates-Idem

unread,
Jan 5, 2015, 5:00:29 PM1/5/15
to rapt...@googlegroups.com
Steve,
The async-fragment tag does allow for arguments to be passed to the data provider. The data-provider argument should always be a function with one of these signatures:
  1. function(args, callback)
  2. function(callback)
You should use the first method signature if your data provider expects arguments (as in your scenario).

So for example, in the code that renders the template:
var out = fs.createWriteStream('somefile.html', {encoding: 'utf8'});
require('marko').load(require.resolve('./sometemplate.marko')).render({
    webServiceDataProvider: function(args, callback) {
        var dataUrl = args.dataUrl;
        // send request ot get data
        callbac(null, {
            hello: 'World'
        })
    }
}, out);


In the template:
<async-fragment data-provider="data.webServiceDataProvider"
    var="users">
    <!-- Do something with "users" -->
    <for each="user in users">
        ${user.name}
    </for>
</async-fragment>

NOTE: I didn't run this code but it should be close (if it doesn't work please let me know).

Let me clarify that Marko does not care how the data provider fetches the data (it could read a file, send an HTTP request, fetch from cache, etc.).

Please let me know if that answers your question or if you would like more clarification.

Thanks,
Phil

Phil Gates-Idem

unread,
Jan 5, 2015, 7:17:09 PM1/5/15
to rapt...@googlegroups.com
Also, Steve, feel free to ask questions in the Gitter chatroom for Marko:
https://gitter.im/raptorjs/marko?utm_source=share-link&utm_medium=link&utm_campaign=share-link

It's a little more realtime and I can use markdown to format the messages.


On Monday, January 5, 2015 4:44:18 PM UTC-5, Steve Stacha wrote:

Steve Stacha

unread,
Jan 6, 2015, 5:28:49 PM1/6/15
to rapt...@googlegroups.com
Phil,

Hey, thanks a lot for your help.  The data provider path is actually a much better solution than string replacements.  I think this can work.  I can have the webservice provider always loaded and then when I get to components I can use the component provider by default and a developer can change it to webservice if needed.

New Question:
When I load a marko template it creates a js file on the filesystem right next to it.  Is there some performance reason for this?  Does it have anything to do with the async-fragment pull on the client?

Patrick Steele-Idem

unread,
Jan 7, 2015, 12:59:14 AM1/7/15
to rapt...@googlegroups.com
Hi Steve,

Every compiled Marko template results in a new *.marko.js file next to original file. The resulting *.marko.js file can then easily be loaded using the standard Node.js module loader. We found it very helpful to have the compiled template be written to disk so that if there are any JavaScript errors when rendering the template then those errors can easily be debugged since the stack traces will have valid file names and line numbers. Also, placing the *.marko.js files next to the original template files allows for all templates to be pre-compiled at build time to avoid a slight hit at runtime. You'll want to make sure you exclude the generated *.marko.js files from your source control system. I hope that answers your question.

Let us know if you have any other questions.

--Patrick

Phil Gates-Idem

unread,
Jan 7, 2015, 1:59:55 AM1/7/15
to rapt...@googlegroups.com
Glad data-provider with args will work for you.

Regarding, the *.marko.js files:
These files are created as part of the template load/compilation process and it's not related to the async fragments. The generated template code is designed to be loaded by the NodeJS module loader in the same way as all of your other CommonJS modules (to prevent having to hack with the NodeJS module loader to make this work). By putting the compiled template on disk next to the source template (*.marko file), the relative paths inside the template will be resolved correctly when using require() or require.resolve() inside your template. There is also a performance gain by writing the compiled template to disk because the marko template loader will only compile the source template if the compiled template on disk is out of date.

Be sure to add *.marko.js to your .gitignore (or similar file for your source control repo).

Thanks,
Phil
--
You received this message because you are subscribed to the Google Groups "RaptorJS" group.
To unsubscribe from this group and stop receiving emails from it, send an email to raptorjs+u...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Steve Stacha

unread,
Jan 7, 2015, 2:29:21 PM1/7/15
to rapt...@googlegroups.com
I was having problems with the require.resolve() method of creating the path.  Instead I was always passing templatePath as the fully qualified path to the page template.  ie. 
var templatePath = "/Users/sstacha/dev/projects/nodejs/myproj1/views/hello.marko";

I verified that the .js file was generated only once.  I then touched the .marko file and verified that the .js was re-rendered.  Is there any reason this will be a problem?  In either case I fail to see how the .js is ever loaded instead of the .marko source file.

var templatePath = require.resolve('./hello.marko');
var template = require('marko').load(templatePath);

Phil Gates-Idem

unread,
Jan 7, 2015, 2:38:45 PM1/7/15
to rapt...@googlegroups.com
Steve,

The load function is implemented in this file:
https://github.com/raptorjs/marko/blob/master/runtime/loader.js

You might not care about the internals of compilation but this is the general flow:
  • Read file (templatePath as in your example) -- typically you would use require.resolve(...) to get the absolute path but you could also provide your own absolute path.
  • Compile source code and write file to temporary location
  • Move temporary file to templatePath + ".js"
  • Use require to load templatePath + ".js"
In particular, this is the line inside loader.js that uses NodeJS require function:
Line 43: return require(targetFile);

NOTE: There is also a browser version of the loader (loader_browser.js) that simple uses a client-side implementation of the require function.

What problems specifically are you seeing when trying to use require.resolve() ?

Thanks,
Phil
Reply all
Reply to author
Forward
0 new messages