Skip to content
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

Dynamic component assignment through attribute #24

Closed
bkniffler opened this issue Feb 1, 2015 · 3 comments
Closed

Dynamic component assignment through attribute #24

bkniffler opened this issue Feb 1, 2015 · 3 comments

Comments

@bkniffler
Copy link

Hey,
This might be a hard thing to integrate, but I'd like to discuss it, maybe you will find it useful.
What want to accomplish is to load a component dynamically. Instead of using a html tag, I want to load it through a dedicated attributes value, lets say 'taglib'-attribute.

<div for="item in data.components" taglib="$!{item.component}-component" model="$!{item.value}">

or

<div for="item in data.components" taglib="item.component" model="$!{item.value}">

The problem is, if we'd like to be able to use every template available, we would need some sort of index with template names and their relative path. Alternatively, we could scope the available templates to only a certain directory, which would be easier, but not as clean.

This would require us to have a compiled template like this.

templates = { "component-one": "./components/component-one/template.marko" };
return function render(data, out) {
      out.w('xxx');
      forEach(data.components, function(item) {
       __helpers.l(require.resolve(templates[item.component])).render({"value": str(item.value)}, out);
      });
    out.w('xxx');
  };

or

templates = {
            "component-one": __helpers.l(require.resolve("./components/component-one/template.marko"))
        };
return function render(data, out) {
      out.w('xxx');
      forEach(data.components, function(item) {
          templates[item.component].render({"value": str(item.value)}, out);
      });
    out.w('xxx');
  };

Of course we could check, if array contains element etc. This is just a dirty example. I know that this adds a bit of an overhead to the lightweight templates, especially if you got lots of components, but I couldn't think of any different/better way of doing this without sacrificing client-side compatibility. What do you think?

I could integrate it, though it will take me some time to dive into the code. A few hints where I find the responsible bits would surely help me.

@patrick-steele-idem
Copy link
Contributor

Hi @bkniffler, a custom tag is just a mapping to a render(input, out) function that gets resolved at compile time. It's possible to invoke a render function directly instead of going through a custom tag and I think that might help in your case. This is done using the <invoke> tag as shown below:

<!-- Loop over components, each which as a render function and an input model -->
<div for="item in data.components">
   <!-- Invoke the render function for the current item using the current item's input model -->
    <invoke function="item.render(item.value, out)"/>
</div>

NOTE: A loaded template also has a template.render(input, out) function so you could also do the following:

<invoke function="item.template.render(item.value, out)"/>

I'm not sure if you are using the RaptorJS Optimizer or Browserify, but the RaptorJS Optimizer supports dynamic requires while Browserify does not. Therefore, if you are using the RaptorJS Optimizer then you can send down all of the templates that may be needed on the client and dynamically require them instead of relying only on static code analysis. You can use a glob pattern to send down all of the templates in a directory as shown below:

optimizer.json

{
    "dependencies": [
        "*.marko"
    ]
}

Those templates could be dynamically required doing something similar to the following:

var template = require('marko').load(require.resolve('./components/' + componentName + '/template.marko'));

NOTE: We currently don't register the *.marko.html extension for the optimizer-marko plugin so you might need to send a PR for the plugin.

Does this help in your use case? If not, please provide more details.

@philidem
Copy link
Member

philidem commented Feb 1, 2015

@bkniffler I would recommend an approach that uses a single component/tag that simply delegates rendering to multiple other components/renderers.

For example, I often run into the use case of choosing a template based on a type or layout attribute. Consider the case where you might want to do something like this:

<app-message type="info">${data.message}</app-message>
<app-message type="warning">${data.message}</app-message>
<app-message type="error">${data.message}</app-message>

Depending on your use case, this might be simpler than having three separate tags (app-info-message, app-warning-message, and app-error-message, for example).

In this example, app-message may have a template for each type of message. The app-message renderer would simply choose the right template based on the type.

The app-message component would have a renderer.js with something like this:

var marko = require('marko');

var TEMPLATE_BY_TYPE = {
    info: marko.load(require.resolve('./info.marko')),
    warning: marko.load(require.resolve('./warning.marko')),
    error: marko.load(require.resolve('./error.marko'))
};

module.exports = function(input, out) {
    // find the template that maps to the given type
    var template = TEMPLATE_BY_TYPE[input.type];
    if (!template) {
        throw new Error('Invalid type "' + input.type + '".');
    }

    // now render the template
    template.render(input, out);
}

In summary, I think this logic of choosing a template would be easier to read if the developer moved this logic to the renderer. I don't think there would be much benefit of implementing this "choosing" logic in the template.

Please let me know if you still don't think the solution from @patrick-steele-idem or myself would help in your use case (or if you are still looking for a more elegant solution).

@bkniffler
Copy link
Author

@philidem This is a nice approach, unfortunately not what I'm looking for.

Thanks to both of you for taking the time to respond in such detail., this helped me understand marko more. And sorry for apparently not explaining my issue properly, when re-reading the docs I found a great solution already in place: the include-tag. It seems to do all the required stuff like loading and rendering.

<include template="app/components/$!{item.component}/template.marko.html" value="$!{item.value}">

This will help me get started for now!

I will add .marko.html support to optimizer-marko in the next days.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants