Giter Site home page Giter Site logo

marko-js-archive / marko-widgets Goto Github PK

View Code? Open in Web Editor NEW
141.0 141.0 40.0 1.09 MB

[LEGACY] Module to support binding of behavior to rendered UI components rendered on the server or client

Home Page: http://v3.markojs.com/docs/marko-widgets/

License: MIT License

JavaScript 91.89% HTML 8.11%

marko-widgets's People

Contributors

1n50mn14 avatar agyanchand avatar austinkelleher avatar brywatsonnn avatar cestdiego avatar gitter-badger avatar maberer avatar merwan7 avatar mlrawlings avatar patrick-steele-idem avatar philidem avatar sandro-pasquali avatar seangates avatar yomed avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

marko-widgets's Issues

Using w-bind without introducing additional markup

The main use case for w-bind goes something like this:
<div w-bind>
Hello ${data.name}!
</div>

However, there are certain cases where there isn't a parent element that can take the w-bind. For example, consider a widget that manages the pagination links in the head:
<link rel="prev" href="http://www.example.com/article?page=1" />
<link rel="next" href="http://www.example.com/article?page=3" />

You can't introduce any parent markup to bind, because markup like divs aren't allowed in the head. Additionally, placing a w-bind on just one of the links has some strange results. Theoretically you could put w-bind directly on <head> and create a head widget instead, but it seems like that shouldn't be necessary.

Assigning w-id from a non-widget

Is there any way to assign a special w-id to a widget when it is being called from a non-widget, but a parent is a widget? It would look something like this:

container.marko (this is a widget)

<div w-bind>
    <intermediate-template>
</div>

intermediate-template.marko (this is not a widget)

<div>
    <widget-one w-id="widget-one">
    <widget-two w-id="widget-two">
    <widget-three w-id="widget-three">
    <widget-four w-id="maybe-this-has-a-really-special-id">
</div>

widget-*.marko (this is a widget)

<div w-bind>
    ...
</div>

In this sort of setup, the containing widget would have to bubble up through rendering calls. So, if the outer container was given the id w28, then maybe the sub widgets would just be w28-widget-one, w28-widget-two, and so on. Currently, this seems to require intermediate-template.marko to be widgetized, however the only purpose that I can see in this is for ID generation (If this is not the case and this is possible already, please let me know!).

Taglib: provide better support for one widget extending another widget

It is not uncommon where one UI component might need to slightly enhance another component. For example, there might be a app-button and an app-image-button where the only difference is that "image-button" allows an icon to be passed in and for the widget instance to expose a new setIcon(src) method.

Proposal

Introduce a new w-extend attribute that can be used to declare that a target widget will be extended with new functionality by another widget. For simplicity, we will only allow monkey-patching of the existing instance instead of trying to set up inheritance chances. For example:

src/components/app-image-button/template.marko:

<app-button w-extend="./widget">
    <img src="$data.iconSrc"> $data.label
</app-button>

src/components/app-image-button/widget.js:

exports.extend = function(widget, widgetConfig) {
    this.setIcon = function(src) {
        this.$('img').attr('src', src);
    }
}

Externally, we should now be able to do the following:

<app-image-button icon="foo.png" w-id="imageButton"/>
this.widgets.imageButton.setIcon('bar.png');

Add support for attaching DOM event listeners declaratively in the template

Allow DOM events to be mapped to widget methods using a custom w-on*="<target_method>" attribute (e.g., w-onclick="handleClick"). When the DOM event is triggered in the DOM then the specified widget method will be invoked (with this being the widget instance) and two arguments will be provided to the widget method when invoked:

  1. event - The raw DOM event object
  2. el - The raw DOM element that the event listener was attached to (NOTE: This is not necessarily the same as event.target if the event bubbled up)

Example Usage

template.marko:

<div w-bind="./widget"
    w-onclick="handleRootClick">

    <input type="text"
        w-onchange="handleInputChange"
        value="Hello world">
</div>

widget.js

function Widget() {

}

Widget.prototype = {
    handleRootClick: function(event, el) {
        // "this" will be the widget instance!
        this.setColor('red');
    },

    handleInputChange: function(event, el) {
        var value = el.value;
        // Do something with the element
    },

    setColor: function(color) {
        this.el.color = color;
    }
};

module.exports = Widget;

Event Delegation

For performance reasons, whitelisted events that bubble will be handled differently from events that do not bubble. For whitelisted events that bubble, a single DOM listener will be attached to the document.body for each event type. For events that are not whitelisted or do not bubble, a DOM event listener will automatically be attached directly to the DOM element when needed.

When a whitelisted event that bubbles makes it to document.body, Marko Widgets will walk up the DOM starting at event.target to determine if the DOM event is mapped to any widget methods. The walk up will stop once the containing widget is reached (or the root of the DOM is reached). Special data-w-on* attributes (e.g., data-w-onclick="handleClick" will be added to the DOM to record the mapping of DOM events to widget methods.

Error thrown when document.bod not ready when marko-widgets is initializing

document.body is null if marko-widgets is being initialized before document is ready which can happen if require dependency is loaded with "wait": false. The delegate events should only be added if document is ready (use require('raptor-dom').ready) and when testing for attachEvent use document.body || document.createElement('div') as test element (instead of just document.body). Create a temporary element is wasteful but it is an edge case.

marko and CSP

I am creating this issue to raise awareness for the use of CSP (content security policy) on a page that is built with marko.

There are primarily two concerns I currently see:

  1. If marko widgets should be rendered server side (in response to an ajax call) the documentation states the use of eval() to bind the widgets on the client side.
    https://github.com/raptorjs/marko-widgets#rendering-widgets-on-the-server
    When CSP is used, eval() is one of the first things that are prohibited; now if
    eval() is not an option, marko fails to bind the behaviour to the DOM. If one would be able to call the bind code manually (in response to the ajax call), eval() would not be necessary. It is also discussed here: #26
  2. I am not completely sure about this one but I fear, that marko adds quite a few custom script tags (inlined) into the html on the first page load (or later). One of the strategies when using CSP is to refactor all inline script tags into script tags with a trusted resource… now, as there is usually only one client side bundle, why not calling all required code within this package… So if marko utilizes inlined script blocks (or adds them dynamically) please be aware, that it hurts CSP.

Libraries like youtube SPF seem to add inline script tags as well… I am not sure how/if they handle CSP… but I think that SPF really shines when used together with marko and I feel that both should play nice with the security settings of a page.

v4.0: Rendering/Rerendering functions not called when filename is different from "index.js"

To simplify description of this issue, I've created a minimal project: https://github.com/schetnikovich/marko-widgets-question

This project consists of two identical widgets (good-widget and bad-widget) with the only difference in widget's file name:

src/good-widget/index.js
src/bad-widget/widget.js

When using bad-widget, function getTemplateData() seems not called. For good-widget everything is okay. Am I missing something?

Duplicate widget ID when manually initializing server side rendered widgets

Hello,
Was trying out manually initializing server side rendered widgets (https://github.com/raptorjs/marko-widgets#manually-initializing-server-side-rendered-widgets) and have run into an issue.

Say there exists a parent widget.
<div w-bind="./parent_widget"></div>

The above widget would get an ID "w0".

Now, if parent_widget makes an ajax call that renders a child_widget on the server side and we manually try to initialize the widget on the browser with:

require('marko-widgets').initWidgets(widgetIds), the child widget fails to get initialized because the child_widget also comes back with the same ID 'w0'.

I put a break point on the initWidgets function and noticed that the child widget also comes back with widget id "w0" (same as the parent widget). Ideally, the client_widget should have come back with "w1".

Was wondering if i am missing something ?

Regards,
Karthik

Initialize marko widgets while using SPF

SPF requests a page that uses marko widgets. For some cases we can't render templates / widgets on the client so we get a rendered widget through SPF.

In order to get the widgets ready we can call the optimizer and add the css and js into the SPF head and foot. But how do we initialize now the widgets?

A solution would be to render a template with the "init-widgets" taglib separately. I feel like there is a better solution out there. Any recommendations?

Scopes in raptor-widgets-browser need to be cleared after initialization

The "scopes" that are used for quick lookup of widget scopes during widget initialization should be cleaned up after rendering completes.

These scopes should probably not be shared as a singleton in raptor-widgets-browser module. Perhaps you should put them in some context that is associated with the rendering of the widgets.

require('marko-widgets/renderer').render error when only providing single renderer function argument

This works:

require('marko-widgets/renderer').render(function(input, out) {
     out.write('Demo, Global: ' + !!out.global);
}, undefined).appendTo(document.getElementById('demo'));

This DOES NOT work:

require('marko-widgets/renderer').render(function(input, out) {
     out.write('Demo, Global: ' + !!out.global);
}).appendTo(document.getElementById('demo'));

It looks like the require('marko-widgets/renderer').render is incorrectly interpreting the first (and only) argument as the callback and not the renderer.

Invoked widgets can not have dynamic w-ids

<invoke>ing a widget with a w-id (as in #46) only works when the w-id is static. Widgets invoked via taglib can have dynamic IDs, however, so that <widget w-id="${widgetName}" /> works, but <invoke function="..." w-id="${widgetName}" /> does not. Instead, it gives an entirely new w-id.

Provide way to get widget IDs from rendered out

Currently, marko-widgets.js has some internal code that is used to build the list of widget IDs that were rendered to an out. This is the function that does this:

exports.writeInitWidgetsCode = function(widgetsContext, out, options)

We should expose this method:

exports.getWidgetIdsFromOut = function(out)

getWidgetIdsFromOut can get the widgets context from the out via:

widgetsContext = markoWidgets.getWidgetsContext(out);

The widget IDs should then be added to an array in depth-first search order (similar code is already in writeInitWidgetsCode).

accessing nested widgets with gaps

There seem to be some issues in accessing nested widgets when there are gaps between widgets. In this case we have some parentWidget. And then we create children widgets (similar issues can be replicated when using custom tags rather than invoked renderer).

Say that we have some child templates (foo, bar, and baz). Only baz is an actual marko widget, while foo and bar are just template.marko files.

data.renderers = [
    require('./foo'),
    require('./bar'),
    require('./baz')
];

Then we assign nested widgets as follows (although these are not all "true" widgets):

<for each="renderer in data.renderers">
    <invoke function="renderer(myInputObject, out)" w-id="childWidget[]" />
</for>

This will result in markup as follows:

<div class="foo">foo</div>
<div class="bar">bar</div>
<div class="baz" id="parentWidget[0]-childWidget[2]">baz</div>

The childWidget in this case starts at index 2, because foo and bar were correctly skipped. However, when trying to access the nested widgets, this gap seems to cause problems.

parentWidget.getWidgets('childWidget') // comes up empty

Should everything be created as widget to begin with, in order to avoid issues like this? Wouldn't that create unnecessary overhead? Ideally we would only use w-id when we know it's a widget we want to access, but I imagine in some cases that may not be possible when using both widgets and non-widgets similarly.

Stop supporting this.widgets in Widget instance

Dynamic changes to a widgets children makes storage of children widgets in a widgets collections a bad idea. We should use the DOM to lookup children widgets via these methods:

// get single widget
this.getWidget(id);
// get widget within array collection (original "id" would need to end in "[]" so that they widgets are auto-indexed)
this.getWidget(id, index);
// get all widgets that were assigned same ID via "someId[]"
this.getWidgets(id);

issue with specifying widget filename when marko has w-body

I found an issue with specifying a filename for w-bind when there is a w-body in the template.

  1. git clone and npm install
  2. load page above; page loads; check the dom and you'll see that channel__wrapper div
  3. Now, make these changes:
  • change "require: ./index" to "require: ./channel"
  • change w-bind to w-bind="./channel"
  1. load page above; page loads but the content is missing. I see the following channel__wrapper, but there is no content inside:
<div class="channel panel" id="w0" data-w-body="0">
    <div class="channel__wrapper" id="w0-0" data-w-onclick="handleClick|w0">
    </div>
</div>
  1. The widget (channel.js) is loaded and initialized - i.e., alert() runs in init(), but there is no markup created.

Am I missing some step?

We would like to specify names for each module/widget, because we prefer not to have many index.js (which will become hard to find/differentiate).

Binding More then one JavaScript only Marko Widgets to an HTML element

Is there a way to create a JavaScript only widget (Like JQuery effect) which can be bound to any existing HTML element in a way which supports assigning more then one such widget to a single element.

I saw an example of code using

but with this method only one JavaScript widget can be bound.

Following is how JQuery supports multiple JavaScript behavior

$("elementselector").effect1().effect2();

Improved documentation for stateful/complex widgets

Reading through the docs on stateful widget... the following was unclear to me:

    isSelected: function() {
        return this.state.selected;
    }

Where is this used? explicitly or implicitly somehow?
And this:

    getInitialState: function(input) {
        return {
            name: input.name,
            selected: input.selected || false;
        }
    },

I assume for selected to be true, I would have to set the selected attribute on the widget?

<my-widget selected="true"/>

Later...

w-id="overlay" - widget ID ?

this.getWidget('overlay').show();

Would be nice with a little more guidance/explanation :) Thanks!

Conflicting Id's during partial page refresh

Is there a recommended way to generate unique id's for marko widgets?

The problem we are facing is when we have a partial page refresh. On the initial page load the IDs are unique. However if we partially refresh a part of the page, there will be collisions with the IDs. This is with the auto-generated IDs.

We thought of using a server side timestamp to generate the unique identifier with some namespace but even then there were collisions.

Any suggestions?

Allow w-on* to be used subscribing to custom events on other widgets.

Example:

<div w-bind="./widget">
    <app-overlay title="My Overlay"
        w-onBeforeShow="handleOverlayBeforeShow">
    </app-overlay>
</div>

And then in the containing widget:

function Widget() {

}

Widget.prototype = {
    handleOverlayBeforeShow: function(...) {
        // "this" will be the containing widget:
        this.doSomething();
    }
}

exports.Widget = Widget;

Open questions:

  • What should be the args to the event handler method?:
    1. function(event, sourceWidget) (only allows source widget to emit an event with a single arg)
    2. function(sourceWidget, eventArg1, eventArg2, ...)
    3. function(event) with event.source being the the source widget (automatically added)
  • How should the custom event name be derived from the attribute?
    1. w-onBeforeShowbeforeShow
    2. w-onbeforeShowbeforeShow
    3. w-on-beforeShowbefore-show
    4. w-on-before-showbeforeShow

Provide public method to initialize widgets with given IDs

Currently, the method of initializing widgets with given IDs is done via a private method that is put into the window object:

window.$rwidgets = function(ids)

We should expose this method as part of the public interface:

window.$rwidgets = exports.initWidgets = function(ids)

See marko-widgets-browser.js

This addresses the inability to write your own JavaScript code to dynamically initialize the widgets that were rendered to an HTML fragment on the server-side when using eval() is not an option.

expose out object inside define* functions

I would like to be able to access out.global in my component definition, but out is not available in that scope.

Can access it here:

exports = module.exports = function (data, out) {
    data.name = out.global.config.name
    template.render(data, out);
};

But not here:

module.exports = require('marko-widgets').defineComponent({
    template: require('./template.marko'),
    getTemplateData: function(state, input) {
        return {
            name: out.global.config.name // out is undefined
        };
    }
});

Use the event delegation approach for adding DOM event listeners for better performance and improved usability

Currently, widget developers must write their own code to attach DOM event listeners using jQuery or some other library. For example:

function Widget() {
    var self = this;

    $(this.el).click(function(event) {
        self.doSomething();
    });
}

There are three problems with this approach:

  1. Attaching listeners to every DOM element is slower than using event delegation (see http://davidwalsh.name/event-delegate)
  2. The value of this is in the DOM event handler is the DOM element when it would be much more convenient if it was the widget.
  3. It is difficult to remove DOM event listeners unless a reference to the original handle function is kept in a separate variable

To overcome these problems I propose the following solution:

Each Marko widget type should declare its DOM event handlers should code similar to the following:

function Widget() {
    // ...
}

Widget.prototype = {
    doSomething: function() {

    },

    // *** DOM event listeners: ***
    domEvents: {
        'click': function(event) {
            // click listener on the root element
            this.doSomething();
        },

        'click #button1': function(event) {
            // click listener on a nested widget element
        },

        'click .foo': function(event) {
            // Delegate event with a CSS selector. Equivalent to $(this.el).on('click', '.foo', handler)
        },

        'resize window': function(event) {

        }
    }
}

The property names for the domListeners object should match the following format:

<event_type>[ <selector>]

Examples:

  • click
  • click #someEl (e.g. <div w-el-id="someEl">
  • resize window

The marko-widgets module would lazily add DOM event listeners to the root document.body element to capture events that bubble up. For every event that is captured on the root document.body element, look at the event.target to figure out the target widget of the event (if any) using the following approach:

When a DOM event is receive on the root element we could look at the target element of the event and from the target element we could get the associated widget. We could then see if the widget has a method to handle the DOM event and invoke the method (with “this” being the widget instance) and the first argument being the DOM event.

Implementation

var targetWidget; // The target widget instance
var targetWidgetEl; // The ID of the nested widget element (if applicable)

if (event.target.__widget) { // Handle events on the root element
    targetWidget = event.target.__widget;
} else if (event.target.id) { // Handle events on nested widget elements
    var separatorIndex = event.target.id.indexOf('-');
    if (separatorIndex !== -1) {
       var widgetId = event.target.id.substring(0, separatorIndex);
       targetWidget = markoWidgets.getWidgetForEl(widgetId);
       targetWidgetEl = event.target.id.substring(separatorIndex+1);
    }
}

var handler = getEventHandler(eventType, targetWidget, targetWidgetEl);
if (handler) {
    handler.call(widget, event);
}

Thoughts? Concerns?

defineRenderer ends the output stream

When our renderer.js looks like the following:

module.exports = require('marko-widgets').defineRenderer({
    // ...
});

It will correctly render the given component but then end the output stream. However, when we use the following:

exports.render = function render(input, out) {
    // ...
    template.render(data, out);
};

The output stream remains open and further components can be rendered. Is this the intended behavior?

Allow event bubbling from widgets

It would be useful if an event emitted from a widget could bubble up the DOM and be caught by a non-immediate parent. One example of where this would be useful is in a single page app architecture, where a parent widget may want to control navigation between children based on navigation events. In this case there could be many child widgets which emit nav events, and they may not be direct children.

After discussing with Patrick, it seems like a simple implementation could look like this:

var Event = require('marko-widgets/Event');
this.emit('someEvent', new Event(
    {
        bubbles: true,
        data: {
            foo: 'bar'
        }
    }
);

This would make the bubbling optional, and allow the listener to call stop propagation on the event object.

Improve support for referencing nested widgets that are within an <async-fragment>

Example:

<div w-bind="./widget">
    <async-fragment ...>
        <app-hello-world w-id="helloWorld"/>
    </async-fragment>
</div>

The nested "helloWorld" widget will potentially be available only after the ancestor widget is initialized since the descendent widget is nested with an <async-fragment> tag. To solve this problem we will need to introduce a method that can be used to attach a callback to be invoked when the descendent widget is registered. If the descendent widget has already been registered with the ancestor widget then the callback should be invoked immediately. The arguments to the callback will be an error (if any) and the widget. The usage of this new "waitFor" method is shown below:

this.widgets.waitFor('helloWorld', function(err, widget) {
    var helloWorldWidget = widget;
    // ...
});

Add support for providing a selector when using w-on*

NOTE: This proposal is an extension to the previously implemented feature to provide support for w-on* attributes. See: #11

Goal

Allow an optional selector to be provided when binding an event using the following form:

w-on<event_type>="<target_method>; <selector>"

For example:

w-onclick="handleItemClick; li"

Example

template.marko:

<div w-bind="./widget">
    <ul w-onclick="handleItemClick; li">
        <li><span>Red</span></li>
        <li><span>Green</span></li>
        <li><span>Blue</span></li>
    </ul>
</div>

Even if a user clicks on any of the <span> tags nested within the <li> tags, the handleItemClick method should still be invoked.

Pros and Cons

Pros

  • Events on dynamically added DOM elements will correctly be handled

Cons

  • Requires jQuery (or similar CSS selector engine)
  • Extra walk up the tree. Once it is determined that an element has a handler with a selector then the selector must be applied to all of the elements starting from the event target working back up to the current element.
  • Extra code
  • Can be accomplished using jQuery directly (although not quite as efficient)

Reference

  • jQuery code for finding matching handlers: event.js

assigning w-id via invoked renderer

Per marko-js/marko#94

It would be very useful to be able to assign w-id(s) to renderers that are directly invoked, such as:

<for each="renderer in data.renderers">
    <invoke function="renderer(myInputObject, out)" w-id="widget[]" />
</for>

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.