JavaScript API ============== # marko-widgets exports ## defineComponent(def) Used to define a UI component that includes both the renderer *and* the widget (i.e., the client-side behavior). If a UI component is to only be rendered on the server then you might benefit from defining the renderer independently of the widget using the `defineRenderer(def)` and `defineWidget(def)` functions, respectively. The return value of `defineComponent(def)` will be a `Widget` constructor function with static `renderer(input, out)` and `render(input)` methods. Example usage for defining a stateless UI component: ```javascript module.exports = require('marko-widgets').defineComponent({ template: require('./template.marko'), getTemplateData: function(state, input) { return { name: input.name }; }, handleClick: function() { this.el.style.backgroundColor = 'yellow'; } }); ``` ## defineRenderer(def) The `defineRenderer(def)` function can be used to define a UI component renderer independently from an associated widget. This can be beneficial when a UI component needs to only be rendered on the server and it is desirable to avoid sending down the template and rendering logic to the browser. For UI components that are only rendered on the server, only the client-side behavior really needs to be be sent to the browser. The return value of `defineRenderer(def)` will be a `renderer(input, out)` function with a static `render(input)` method. ## defineWidget(def) The `defineWidget(def)` function can be used to define a UI component's client-side behavior independent of the code to render the UI component. This can be beneficial when a UI component needs to only be rendered on the server and it is desirable to avoid sending down the template and rendering logic to the browser. For UI components that are only rendered on the server, only the client-side behavior really needs to be be sent to the browser. The return value of `defineWidget(def)` will be a widget constructor function that is used to instantiate new widget instances. ## getRenderedWidgets(out) _NOTE: Available server-side only_ Used to support initializing widgets bound to to UI components rendered on the server and sent down to the browser via an AJAX request: ```javascript var markoWidgets = require('marko-widgets'); var template = require('./template.marko'); module.exports = function(req, res) { template.render(viewModel, function(err, html, out) { var renderedWidgets = markoWidgets.getRenderedWidgets(out); // Serialize the HTML and the widget IDs to the browser res.json({ html: html, renderedWidgets: renderedWidgets }); }); } ``` And then, in the browser, the following code can be used to initialize the widgets: ```javascript var result = JSON.parse(response.body); var html = result.html var renderedWidgets = result.renderedWidgets; document.body.innerHTML = html; // Add the HTML to the DOM // Initialize the widgets to bind behavior! require('marko-widgets').initWidgets(renderedWidgets); ``` ## getWidgetForEl(el) The `getWidgetForEl(el)` function can be used to retrieve a widget object outside of its nested context. ```javascript var myToggle = require('marko-widgets').getWidgetForEl('w0-myToggle'); myToggle.setSelected(true); ``` It is also possible to get a widget handle using the widget el: ```javascript var el = document.getElementById('w0-myToggle'); var myToggle = require('marko-widgets').getWidgetForEl(el); myToggle.setSelected(true); ``` # Widget ## Methods ### $(querySelector) This is a convenience method for accessing a widget's DOM elements when jQuery is available. This mixin method serves as a proxy to jQuery to ease building queries based on widget element IDs. Internally, this jQuery proxy method will resolve widget element IDs to their actual DOM element ID by prefixing widget element IDs with the widget ID. For example, where this is a widget with an ID of `w123`: ```javascript this.$() ➡ $("#w123") this.$("#myEl") ➡ $("#w123-myEl") ``` The usage of this mixin method is described below: __`$()`__ Convenience usage to access the root widget DOM element wrapped as a jQuery object. All of the following are equivalent: ```javascript this.$() $(this.el) $("#" + this.id) ``` __`$('#')`__ Convenience usage to access a nested widget DOM element wrapped as a jQuery object. All of the following are equivalent: ```javascript this.$("#myEl") $(this.getEl("myEl")) $("#" + this.getElId("myEl")) ``` __`$('')`__ Convenience usage to query nested DOM elements scoped to the root widget DOM element. All of the following are equivalent: ```javascript this.$("ul > li") $("ul > li", this.el) $("#" + this.id + " ul > li") ``` __`$('', '')`__ Convenience usage to query nested DOM elements scoped to a nested widget DOM element. All of the following are equivalent: ```javascript this.$("li.color", "colorsUL") this.$("#colorsUL li.color") $("li.color", this.getEl("colorsUL")) $("#" + this.getElId("colorsUL") + " li.color") ``` __`$('# ')`__ Convenience usage to query nested DOM elements scoped to a nested widget DOM element. All of the following are equivalent: ```javascript this.$("#colorsUL li.color") this.$("li.color", "colorsUL") $("li.color", this.getEl("colorsUL")) $("#" + this.getElId("colorsUL") + " li.color") ``` __`$(callbackFunction)`__ Convenience usage to add a listener for the "on DOM ready" event and have the this object for the provided callback function be the current widget instance. All of the following are equivalent: ```javascript this.$(function() { /*...*/ }); $(function() { /*...*/ }.bind(this)); // Using Function.prototype.bind $($.proxy(function() { /*...*/ }, this)); ``` ### addEventListener(eventType, listener) ### appendTo(targetEl) Moves the widget's root DOM node from the current parent element to a new parent element. For example: ```javascript this.appendTo(document.body); ``` ### destroy() Destroys the widget by unsubscribing from all listeners made using the `subscribeTo` method and then detaching the widget's root element from the DOM. All nested widgets (discovered by querying the DOM) are also destroyed. Destroy takes 2 optional parameters: ```javascript widget.destroy({ removeNode: true, //true by default recursive: true //true by default }) ``` Setting `removeNode` parameter to `false` will keep the widget on the DOM while still unsubscribing all events from it. Setting `recursive` to `false` will prevent children widgets from being destroyed. ### detach() Detaches the widget's root element from the DOM by removing the node from its parent node. ### doUpdate(stateChanges, oldState) ### emit(eventType, arg1, arg2, ...) Emits an event. This method is inherited from EventEmitter (see [Node.js Events: EventsEmitter](http://nodejs.org/api/events.html#events_class_events_eventemitter) ### getBodyEl() ### getEl(widgetElId) Returns a nested DOM element by prefixing the provided `widgetElId` with the widget's ID. For Marko, nested DOM elements should be assigned an ID using the `w-id` custom attribute. Returns `this.el` if no `widgetElId` is provided. ### getEls(id) Returns an Array of _repeated_ `DOM` elements for the given ID. Repeated DOM elements must have a value for the `w-id` attribute that ends with `[]` (e.g., `w-id="myDivs[]"`) ### getElId(widgetElId) Similar to `getEl`, but only returns the String ID of the nested DOM element instead of the actual DOM element. ### getWidget(id[, index]) Returns a reference to a nested `Widget` for the given ID. If an `index` is provided and the target widget is a repeated widget (e.g. `w-id="myWidget[]"`) then the widget at the given index will be returned. ### getWidgets(id) Returns an Array of _repeated_ `Widget` instances for the given ID. Repeated widgets must have a value for the `w-id` attribute that ends with `[]` (e.g., `w-id="myWidget[]"`) ### insertAfter(targetEl) ### insertBefore(targetEl) ### isDestroyed() ### isDirty() ### on(eventType, listener) ### prependTo(targetEl) ### ready(callback) ### replace(targetEl) ### replaceChildrenOf(targetEl) ### replaceState(newState) Replaces the state with an entirely new state. If any of the state properties changed, the widget's view will automatically be updated. Important to know: While `setState()` is additive and will not remove properties that are in the old state but not in the new state, `replaceState()` will add the new state and remove the old state properties that are not found in the new state. State or template data values that are derived from state properties that are not part of the new state, are `undefined`. Thus, if `replaceState()` is used, one must consider possible side effects if the new state contains less or other properties than the replaced state. ### rerender(data) Rerenders the widget using its `renderer` and either supplied `data` or internal `state`. ### setState(name, value) Used to change the value of a single state property. For example: ```javascript this.setState('disabled', true); ``` Be aware, that `setState()` only nominates the component for a possible rerender. Thus, the component is only rerendered, if at least one of the component state properties changed (`oldValue !== newValue`). If none of the properties changed (because identical or not detected by a shallow comparision), invoking `setState()` is a no operation. (great for performance). Nice to know: Compared to `setState()`, `setStateDirty()` does not nominate a component for rerendering but instead always rerenderes the component independently from its state property values (even if they did not change). ### setState(newState) Used to change the value of multiple state properties. For example: ```javascript this.setState({ disabled: true, size: 'large' }); ``` ### setStateDirty(name, value) Force a state property to be changed even if the value is equal to the old value. This is helpful in cases where a change occurs to a complex object that would not be detected by a shallow compare. Invoking this function completely circumvents all property equality checks (shallow compares) and always rerenders the component. Additional information: The first parameter `name` is used to allow update handlers (e.g. `update_foo(newValue)`) to handle the state transition for the specific state property that was marked as dirty. The second parameter `value` is used as the new value that is given to update handlers. Because `setStateDirty()` always bypasses all property equality checks, this parameter is optional. If not given or equal to the old value, the old value will be used for the update handler. It is important to know, that the given parameters do not affect how or if `setStateDirty()` rerenderes a component; they are only considered as additional information to update handlers. Example: ```javascript // Add a new item to an array without going through `this.setState(...)` - because this // does not create a new array, the change would not be detected by a shallow property comparison this.state.colors.push('red'); // Force that particular state property to be considered dirty so // that it will trigger the widget's view to be updated this.setStateDirty('colors'); ``` ### setProps(newProps) For stateless widgets, setting a widgets properties will result in the widget being re-rendered using the new input. For stateful widgets, setting a widgets properties will result in `getInitialState(newProps)` being called again to determine the new state and the widget state will be updated to use the new state. ### subscribeTo(targetEventEmitter) ### update() Force the DOM to update immediately, rather than following the normal queued update mechanism for rendering. ```js this.setState('foo', 'bar'); this.update(); // Force the DOM to update this.setState('hello', 'world'); this.update(); // Force the DOM to update ``` ## Properties ### this.el The root [HTML element](https://developer.mozilla.org/en-US/docs/Web/API/element) that the widget is bound to. ### this.id The String ID of the root [HTML element](https://developer.mozilla.org/en-US/docs/Web/API/element) that the widget is bound to. ### this.state The current state for the widget. For example: ```javascript module.exports = require('marko-widgets').defineComponent({ template: require('./template.marko'), getInitialState: function(input) { return { disabled: false } }, init: function() { console.log(this.state.disabled); // Output: false this.setState('disabled', true); console.log(this.state.disabled); // Output: true } }); ```