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

Define Widgets and Tags via Classes as an option #91

Open
kristianmandrup opened this issue Sep 13, 2015 · 7 comments
Open

Define Widgets and Tags via Classes as an option #91

kristianmandrup opened this issue Sep 13, 2015 · 7 comments

Comments

@kristianmandrup
Copy link

Now that Node.js 4.0 is out and most other modern frameworks leverage the concept of Classes in Javascript, I think it would be very appropriate if Marko Widgets (and Marko Tags in general) supported class definitions as a (modern) alternative. You could even leverage decorators, for those using Babel.

module.exports = require('marko-widgets').defineComponent({
    template: require('./template.marko'),

    getTemplateData: function(state, input) {
        return {
            name: input.name
        };
    },

    init: function() {
        var el = this.el; // The root DOM element that the widget is bound to
        console.log('Initializing widget: ' + el.id);
    }
});

Could be defined as a class, like this.

import {Widget} from 'marko-widgets';

// if no argument, assume this pattern as default
@template('./template.marko')
export default class extends Widget {
  constructor () {
        var el = this.el; // The root DOM element that the widget is bound to
        console.log('Initializing widget: ' + el.id);
  }
  getTemplateData(state, input) {
        return {
            name: input.name
        };
  } 
}

Using class properties

// @widget - not sure if a widget decorator would make sense instead of subclassing?
export default class {
  template = './template.marko'
  ...

or pure ES2015 getter

// @widget - not sure if a widget decorator would make sense instead of subclassing?
export default class {
  get template() { return './template.marko' }
  ...

Looks pretty cool and concise to me :) The nice thing is that it would be easy to do widget inheritance as well. Decorators can be used to provide mixin like behavior if needed (like in React components).

@patrick-steele-idem
Copy link
Contributor

Hi @kristianmandrup, that is kind of supported, but the problem is that the defineComponent(def) and the defineRenderer(def) functions add some additional _static_ methods that are important to rendering:

  • render(input) : RenderResult ⟵ This function can be used to render the UI component
  • renderer(input, out) ⟵ This function is used as the Marko tag renderer for the UI component's custom tag

Those static methods won't get added if you just export a class. Instead, you would need to do the following:

export default class Widget {
    constructor(widgetConfig) {
    }
}

var renderer = require('marko-widgets').defineRenderer({
    template: require('./template.marko'),
    getTemplateData: function(state, input) {
        // ...
    }    
});

Widget.renderer = Widget.prototype.renderer = renderer;
Widget.render = renderer.render;

While it would be nice to just use the class syntax, I don't think there is a good way to avoid the boilerplate if you want to attach the static rendering functions to the Widget class.

@kristianmandrup
Copy link
Author

The elegant solutions: ES7 decorators ;)

@Widget({template: '../cool/template.marko'})
export default class MyCoolWidget extends CoolWidget {
    constructor(widgetConfig) {
      super(widgetConfig);
    }
}

https://github.com/wycats/javascript-decorators

It is also possible to decorate the class itself. In this case, the decorator takes the target constructor.
Since decorators are expressions, decorators can take additional arguments and act like a factory.

http://www.2ality.com/2015/01/es6-destructuring.html
http://www.2ality.com/2011/11/keyword-parameters.html

Destructuring can be used to effectively "simulate" optional function arguments

// Widget decorator: adds static renderer function to class
function Widget({template = './template.marko', templateData = undefined} = {}) {

  return function decorator(constructor)
   var templateData = templateData || constructor.prototype.getTemplateData;

    var renderer = require('marko-widgets').defineRenderer({
      template: require(template), // "configuration then convention " ;)

      getTemplateData: function(state, input) {
        templateData(state, input);
      }    
    });
    constructor.renderer = constructor.prototype.renderer = renderer;
    constructor.render = renderer.render;
  }
}

Pretty cool I should say :)

@kristianmandrup
Copy link
Author

Hey, I'm trying a small experimental project using ES7 with marko templates.

https://github.com/kristianmandrup/marko-es7

However when I try your proposed pattern using

template: require('./template.marko')

I get an error:

SyntaxError: /Users/kristianmandrup/repos/test123/marko-es7/dist/template.marko: Unexpected token (1:6)
> 1 | Hello $data.name!
    |       ^
    at Parser.pp.raise (/Users/kristianmandrup/repos/test123/marko-es7/node_modules/babel-core/node_modules/babylon/lib/parser/location.js:24:13)

Looks like the babel parser is trying to parse it. Should just be "required" by the regular node require and transformed into valid javascript. I think you've added a special require hook for loading/transforming .marko files? How do I ensure that hook is being used without babel interference?

Thanks ;)

@kristianmandrup
Copy link
Author

I kinda have it working now, except I'm a bit lost on how to test this infrastructure properly :P
Any assistance would be greatly appreciated!! :) Please see my project.

Test
https://github.com/kristianmandrup/marko-es7/blob/master/test/widget_test.js#L49

template config
https://github.com/kristianmandrup/marko-es7/blob/master/lib/widget.js#L19

use of template
https://github.com/kristianmandrup/marko-es7/blob/master/lib/index.js#L19

How do I access and use the template correctly here?

@patrick-steele-idem
Copy link
Contributor

Unless I am missing something, the user should not be implementing the render(input, out) method for a UI component. Nor should the user be accessing the template directly. The user should just be implementing the getTemplateData(state, input) method.

Might be premature to use ES7, but interesting none the less. Personally, I am wary of decorators since I think they were abused in languages like Java (i.e. Java annotations).

@patrick-steele-idem
Copy link
Contributor

Hey @kristianmandrup, were you able to come up with a good solution based on ES7 decorators?

@kristianmandrup
Copy link
Author

Since I don't know the internals of defineWidget it was rather hard to debug given the limited time I was willing to throw at it. Mainly I just setup the babel infrastructure. I had problems requiring the .marko template correctly and not sure about my test infrastructure either. Would require a joint effort for ~30 mins I would think...

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

2 participants