Marko Widgets extends the Marko templating language to provide a simple and efficient mechanism for binding behavior to UI components rendered on either the server or in the browser. In addition, changing a widgets state will result in the DOM automatically being updated without writing extra code. Marko Widgets has adopted many of the good design principles promoted by the React team, but aims to be much lighter and often faster.
- Features
- Design Philosophy
- Sample Code
- Installation
- Glossary
- Usage
- API
- Widget
- Methods
- $(querySelector)
- addEventListener(eventType, listener)
- appendTo(targetEl)
- destroy()
- detach()
- emit(eventType, arg1, arg2, ...)
- getEl(widgetElId)
- getEls(id)
- getElId(widgetElId)
- getWidget(id[, index])
- getWidgets(id)
- insertAfter(targetEl)
- insertBefore(targetEl)
- isDestroyed()
- on(eventType, listener)
- prependTo(targetEl)
- ready(callback)
- replace(targetEl)
- replaceChildrenOf(targetEl)
- replaceState(newState)
- Properties
- Methods
- Widget
- Frequently Asked Questions (FAQ)
- Changelog
- Discuss
- Contributors
- Contribute
- License
- Simple
- Clean JavaScript syntax for defining widgets
- Utilizes Marko templates (an HTML-based templating language) for the view
- Supports stateful and stateless widgets
- No complex class hierarchy
- Simple, declarative event binding for both native DOM events and custom events
- Lifecycle management for widgets (easily destroy and create widgets)
- Events bubble up and view state changes trickle down
- Only need to understand a few concepts to get started
- High performance
- Lightning fast performance on the server and in the browser (see Marko vs React: Performance Benchmark)
- Supports streaming and asynchronous rendering
- Efficient binding of behavior of UI components rendered on the server and in the browser
- Efficient updating of the DOM via the following tricks:
- Batched updates
- When re-rendering a widget, nested widgets are reused
- Only widgets whose state changed are re-rendered
- Full re-rendering of a widget can be short circuited if state transition handlers are provided
- For container components, nested body DOM nodes are automatically preserved
- Entire DOM subtrees can be preserved between rendering
- Smart template compilers to offload as much work to compile time
- Very efficient event delegation
- Lightweight
- Extremely small JavaScript runtime (~6.3 KB gzipped)
- No dependencies on any other JavaScript library such as jQuery
- Focused exclusively on the UI view (easily mix and match with other libraries/frameworks)
- A UI component should encapsulate view, behavior and styling
- A complex page should be decomposed into modular UI components
- UI components should be used as building blocks
- A component's view should be driven by a pure function that accepts an input state and produces output HTML
- A UI component should be independently testable
- A UI component should not leak its internal implementation
- A UI component should be installable via npm
- A UI component should play nice with other frameworks and libraries
- UI components should be easily composable
- Developers should not need to manually manipulate the DOM
Marko Widgets allows you to declaratively bind behavior to an HTML element inside a Marko template. The widget provides the client-side behavior for your UI component.
src/components/app-hello/template.marko
<div w-bind>
Hello ${data.name}!
</div>
src/components/app-hello/index.js
module.exports = require('marko-widgets').defineComponent({
template: require.resolve('./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);
}
});
Congratulations, you just built a reusable UI component! Your UI component can be embedded in other Marko template files:
<div>
<app-hello name="Frank"/>
</div>
In addition, your UI can be rendered and added to the DOM using the JavaScript API:
var widget = require('./app-hello')
.render({
name: 'John'
})
.appendTo(document.body)
.getWidget();
// Changing the props will trigger the widget to re-render
// with the new props and for the DOM to be updated:
widget.setProps({
name: 'Jane'
});
src/components/app-hello/template.marko
<div w-bind
w-onClick="handleClick">
Hello ${data.name}!
</div>
src/components/app-hello/index.js
module.exports = require('marko-widgets').defineComponent({
template: require.resolve('./template.marko'),
getTemplateData: function(state, input) {
return {
name: input.name
};
},
handleClick: function() {
this.setSelected(true);
},
setSelected: function(selected) {
if (selected) {
this.el.style.backgroundColor = 'yellow';
} else {
this.el.style.backgroundColor = null;
}
}
});
Let's create a stateful widget that changes to yellow when you click on it:
src/components/app-hello/template.marko
<div w-bind
w-onClick="handleClick"
style="background-color: ${data.color}">
Hello ${data.name}!
</div>
src/components/app-hello/index.js
module.exports = require('marko-widgets').defineComponent({
template: require.resolve('./template.marko'),
getInitialState: function(input) {
return {
name: input.name,
selected: input.selected || false;
}
},
getTemplateData: function(state, input) {
var style = ;
return {
name: state.name,
color: state.selected ? 'yellow' : 'transparent'
};
},
handleClick: function() {
this.setState('selected', true);
},
isSelected: function() {
return this.state.selected;
}
});
If you want to avoid re-rendering a widget for a particular state property change then simply provide your own method to handle the state change as shown below:
src/components/app-hello/template.marko
<div w-bind
w-onClick="handleClick"
style="background-color: ${data.color}">
Hello ${data.name}!
</div>
src/components/app-hello/index.js
module.exports = require('marko-widgets').defineComponent({
template: require.resolve('./template.marko'),
getInitialState: function(input) {
return {
name: input.name,
selected: input.selected || false;
}
},
getTemplateData: function(state, input) {
var style = ;
return {
name: state.name,
color: state.selected ? 'yellow' : 'transparent'
};
},
handleClick: function() {
this.setState('selected', true);
},
isSelected: function() {
return this.state.selected;
},
update_selected: function(newSelected) {
// Manually update the DOM to reflect the new "selected"
// state" to avoid re-rendering the entire widget.
if (newSelected) {
this.el.style.backgroundColor = 'yellow';
} else {
this.el.style.backgroundColor = null;
}
}
});
<div w-bind>
<app-overlay title="My Overlay"
w-id="overlay"
w-onBeforeHide="handleOverlayBeforeHide">
Body content for overlay.
</app-overlay>
<button type="button"
w-onClick="handleShowButtonClick">
Show Overlay
</button>
<button type="button"
w-onClick="handleHideButtonClick">
Hide Overlay
</button>
</div>
Below is the content of index.js
where the widget type is defined:
module.exports = require('marko-widgets').defineComponent({
init: function() {
// this.el will be the raw DOM element the widget instance
// is bound to:
var el = this.el;
},
template: require.resolve('./template.marko'),
handleShowButtonClick: function(event) {
console.log('Showing overlay...');
this.getWidget('overlay').show();
},
handleHideButtonClick: function(event) {
console.log('Hiding overlay...');
this.getWidget('overlay').hide();
},
handleOverlayBeforeHide: function(event) {
console.log('The overlay is about to be hidden!');
}
})
A container widget supports nested content. When the container widget is re-rendered, the nested content is automatically preserved.
src/components/app-alert/index.js
<div class="alert alert-${data.type}" w-bind>
<i class="alert-icon"/>
<span w-body></span>
</div>
src/components/app-alert/template.marko
module.exports = require('marko-widgets').defineComponent({
init: function() {
// this.el will be the raw DOM element the widget instance
// is bound to:
var el = this.el;
},
template: require.resolve('./template.marko'),
getInitialState: function(input) {
return {
type: input.type || 'success'
}
},
getTemplateData: function(state, input) {
return {
type: state.type
};
},
getWidgetBody: function(input) {
return input.message || input.renderBody;
},
setType: function(type) {
this.setState('type', type);
}
})
The widget can then be used as shown below:
<app-alert message="This is a success alert"/>
<app-alert>
This is a success alert
</app-alert>
<app-alert message="This is a failure alert" type="failure"/>
<app-alert type="failure">
This is a failure alert
</app-alert>
Sometimes it is important to not re-render a DOM subtree. This may due to either of the following reasons:
- Improved performance
- DOM nodes contains externally provided content
- DOM nodes have internal state that needs to be maintained
Marko Widgets allows DOM nodes to be preserved by putting a special w-preserve
or w-preserve-body
attribute on the HTML tags that should be preserved. Preserved DOM nodes will be reused and re-inserted into a widget's newly rendered DOM automatically.
<div w-bind>
<span w-preserve>
<p>
The root span and all its children will never
be re-rendered.
</p>
<p>
Rendered at ${Date.now()}.
</p>
</span>
<div w-preserve-body>
Only the children of the div will preserved and
the outer HTML div tag will be re-rendered.
</div>
</div>
npm install marko-widgets --save
A few definitions before you get started:
- A "widget" is the "client-side behavior" of a UI component
- A widget instance has the following characteristics
- All widget instances are bound to a DOM element
- All widgets are event emitters
- Client-side behavior includes the following:
- Attaching DOM event listeners (mouse click, keyboard press, etc.)
- Attaching listeners to other widgets
- Manipulating the DOM
- Publishing client-side events
- etc.
Using the bindings for Marko, you can bind a widget to a rendered DOM element using the custom w-bind
attribute as shown in the following sample template:
<div class="my-component" w-bind="./widget">
<h1>Click Me</h1>
</div>
You can also choose to leave the value of the w-bind
attribute empty. If the value of w-bind
is empty then marko-widgets
will search for a widget module by first checking to see if widget.js
exists and then index.js
. Example:
<div class="my-component" w-bind>
<h1>Click Me</h1>
</div>
The widget bound to the <div>
should then be implemented as a CommonJS module that exports a widget type as shown in the following JavaScript code:
src/pages/index/widget.js:
module.exports = require('marko-widgets').defineComponent({
init: function() {
var rootEl = this.el; // this.el returns the root element that the widget is bound to
var self = this;
rootEl.addEventListener('click', function() {
self.addText('You clicked on the root element!');
});
},
addText: function(text) {
this.el.appendChild(document.createTextNode(text));
}
})
When a widget is initially rendered, it is passed in an initial set of properties. For example:
require('fancy-checkbox').render({
checked: true,
label: 'Foo'
});
If a widget is stateful, then the state should be derived from the input properties and the template data should then be derived from the state. If a widget is not stateful, then the template data should be derived directly from the input properties. If you need to normalize the input properties then you can implement the getInitialProps(input)
method as shown below:
module.exports = require('marko-widgets').defineComponent({
template: require.resolve('./template.marko'),
getInitialProps: function(input) {
return {
size: input.size ? input.size.toLowerCase() : 'normal'
};
},
getTemplateData: function(state, input) {
// input will be the value returned by getInitialProps()
// ...
}
// ...
});
Every widget should have an associated Marko template that will be used to render the widget. A widget is associated with a template using the template
property as shown below:
module.exports = require('marko-widgets').defineComponent({
template: require.resolve('./template.marko'),
getTemplateData: function(state, input) {
return {
name: input.name
};
},
...
});
The getTemplateData(state, input)
method is used to build the view model that gets passed to the template based on the state and/or input. If a widget is stateful then the template data should be derived only from the state
. If a widget is stateless then the template data should be derived only from the input
. If a stateful widget is being re-rendered then the input
argument will always be null
. For a stateless widget, the state
argument will be null
.
A stateful widget will maintain state as part of the widget that instance. If the state of the widget changes then the widget will be queued to be updated in the next batch. The initial state should be provided using the getInitialState(input)
method. All state changes should go through the setState(name, value)
or setState(newState)
methods. For example:
module.exports = require('marko-widgets').defineComponent({
template: require.resolve('./template.marko'),
getInitialState: function(input) {
return {
name: input.name,
selected: input.selected || false;
}
},
getTemplateData: function(state, input) {
var style = ;
return {
name: state.name,
color: state.selected ? 'yellow' : 'transparent'
};
},
handleClick: function() {
this.setState('selected', true);
},
isSelected: function() {
return this.state.selected;
}
});
The current state of the widget can always be read using the this.state
property. For example:
var isSelected = this.state.selected === true;
When state is modified using either the setState(name, value)
or setState(newState)
method, only a shallow compare is done to see if the state has changed. Therefore, if a complex object is part of the state then it should be treated as immutable.
Arbitrary widget configuration data determined at render time can be provided to the constructor of a widget by implementing the getWidgetConfig(input)
property as shown below:
module.exports = require('marko-widgets').defineComponent({
template: require.resolve('./template.marko'),
getWidgetConfig: function(input) {
return {
foo: 'bar'
}
},
init: function(widgetConfig) {
var foo = widgetConfig.foo; // foo === 'bar'
},
...
});
The marko-widgets
taglib also provides support for allowing a widget to communicate directly with nested widgets. A nested widget can be assigned a widget ID (only needs to be unique within the scope of the containing widget) and the containing widget can then reference the nested widget by the assigned widget ID using the this.getWidget(id)
method.
The following HTML template fragment contains a widget that has three nested sample-button widgets. Each nested sample-button is assigned an ID (i.e. primaryButton
, successButton
and dangerButton
).
<div class="my-component" w-bind="./widget">
<div class="btn-group">
<sample-button label="Click Me" variant="primary" w-id="primaryButton"/>
<sample-button label="Click Me" variant="success" w-id="successButton"/>
<sample-button label="Click Me" variant="danger" w-id="dangerButton"/>
</div>
...
</div>
The containing widget can then reference a particular nested widget as shown in the following sample JavaScript code:
this.getWidget('dangerButton').on('click', function() {
alert('You clicked on the danger button!');
});
Marko Widgets also supports referencing repeated nested widgets as shown below:
<div class="my-component" w-bind="./widget">
<ul>
<li for="todoItem in data.todoItems">
<app-todo-item w-id="todoItems[]" todo-item="todoItem"/>
</li>
</ul>
</div>
The containing widget can then reference the repeated todo item widgets using the this.getWidgets(id)
method as shown below:
var todoItemWidgets = this.getWidgets('todoItems');
// todoItemWidgets will be an Array of todo item widgets
To try out and experiment with this code please see the documentation and source code for the widgets-communication sample app.
DOM elements nested within a widget can be given unique IDs based on the containing widget's ID. These DOM elements can then be efficiently looked up by the containing widget using methods provided. The w-id
custom attribute can be used to assign DOM element IDs to HTML elements that are prefixed with the widget's ID. For example, given the following HTML template fragment:
<form w-bind="./widget">
...
<button type="submit" w-id="submitButton">Submit</button>
<button type="button" w-id="cancelButton">Cancel</button>
</form>
Assuming the unique ID assigned to the widget is w123
, the following would be the HTML output:
<form id="w123">
...
<button type="submit" id="w123-submitButton">Submit</button>
<button type="button" id="w123-cancelButton">Cancel</button>
</form>
Finally, to reference a widget's nested DOM element's the following code can be used in the containing widget:
var submitButton = this.getEl('submitButton'); // submitButton.id === 'w123-submitButton'
var cancelButton = this.getEl('cancelButton'); // cancelButton.id === 'w123-cancelButton'
submitButton.style.border = '1px solid red';
The object returned by this.getEl(id)
will be a raw HTML element. If you want a jQuery wrapped element you can do either of the following:
Option 1) Use jQuery directly:
var $submitButton = $(this.getEl('submitButton'));
Option 2) Use the this.$()
method:
var $submitButton = this.$('#submitButton');
Marko Widgets also supports referencing repeated nested DOM elements as shown below:
<ul>
<li for="color in ['red', 'green', 'blue']"
w-id="colorListItems[]">
$color
</li>
</ul>
The containing widget can then reference the repeated DOM elements using the this.getEls(id)
method as shown below:
var colorListItems = this.getEls('colorListItems');
// colorListItems will be an Array of raw DOM <li> elements
Marko Widgets supports attaching event listeners to nested DOM elements and nested widgets. Event listeners can either be registered declaratively in the Marko template or in JavaScript code.
A widget can subscribe to events on a nested DOM element.
Listeners can be attached declaratively as shown in the following sample code:
<div w-bind>
<form w-onsubmit="handleFormSubmit">
<input type="text" value="email" w-onchange="handleEmailChange">
<button>Submit</button>
</form>
</div>
And then in the widget:
module.exports = require('marko-widgets').defineComponent({
// ...
handleFormSubmit: function(event, el) {
event.preventDefault();
// ...
},
handleEmailChange: function(event, el) {
var email = el.value;
this.validateEmail(email);
// ...
},
validateEmail: function(email) {
// ...
}
});
NOTE: Event handler methods will be invoked with this
being the widget instance and the following two arguments will be provided to the handler method:
event
- The raw DOM event object (e.g.event.target
,event.clientX
, etc.)el
- The element that the listener was attached to (which can be different fromevent.target
due to bubbling)
For performance reasons, Marko Widgets only adds one event listener to the root document.body
element for each event type that bubbles. When Marko Widgets captures an event on document.body
it will internally delegate the event to the appropriate widgets. For DOM events that do not bubble, Marko Widgets will automatically add DOM event listeners to each of the DOM nodes. If a widget is destroyed, Marko Widgets will automatically do the appropriate cleanup to remove DOM event listeners.
You can also choose to add listeners in JavaScript code by assigning an "element id" to the nested DOM element (only needs to be unique within the scope of the containing widget) so that the nested DOM element can be referenced by the containing widget. The scoped widget element ID should be assigned using the w-id="<id>"
attribute. For example, in the template:
<div w-bind>
<form w-id="form">
<input type="text" value="email" w-id="email">
<button>Submit</button>
</form>
</div>
And then in the widget:
module.exports = require('marko-widgets').defineComponent({
// ...
init: function() {
var self = this;
var formEl = this.getEl('form');
formEl.addEventListener('submit', function(event) {
self.handleFormSubmit(event, formEl)
});
// Or use jQuery if that is loaded on your page:
var emailEl = this.getEl('email');
$(emailEl).on('change', function(event) {
self.handleEmailChange(event, emailEl)
});
},
handleFormSubmit: function(event, el) {
event.preventDefault();
// ...
},
handleEmailChange: function(event, el) {
var email = el.value;
this.validateEmail(email);
// ...
},
validateEmail: function(email) {
// ...
}
});
A widget can subscribe to events on nested widgets. Every widget extends EventEmitter and this allows each widget to emit events.
Listeners can be attached declaratively as shown in the following sample code:
<div w-bind="./widget">
<app-overlay title="My Overlay"
w-onBeforeHide="handleOverlayBeforeHide">
Content for overlay
</app-overlay>
</div>
And then in the widget:
module.exports = require('marko-widgets').defineComponent({
// ...
handleOverlayBeforeHide: function(event) {
console.log('The overlay is about to be hidden!');
}
});
You can also choose to add listeners in JavaScript code by assigning an "id" to the nested widget (only needs to be unique within the scope of the containing widget) so that the nested widget can be referenced by the containing widget. The scoped widget ID should be assigned using the w-id="<id>"
attribute. For example, in the template:
<div w-bind="./widget">
<app-overlay title="My Overlay"
w-id="myOverlay">
Content for overlay
</app-overlay>
</div>
And then in the widget:
module.exports = require('marko-widgets').defineComponent({
// ...
init: function() {
var self = this;
var myOverlay = this.getWidget('myOverlay');
this.subscribeTo(myOverlay)
.on('beforeHide', function(event) {
self.handleOverlayBeforeHide(event);
});
},
handleOverlayBeforeHide: function(event) {
console.log('The overlay is about to be hidden!');
}
});
NOTE: subscribeTo(eventEmitter)
is used to ensure proper cleanup if the subscribing widget is destroyed.
Every widget defined using defineComponent(...)
exports a render(input)
method that can be used to render the widget in the browser as shown below:
var widget = require('fancy-checkbox').render({
checked: true,
label: 'Foo'
})
.appendTo(document.body)
.getWidget();
widget.setChecked(false);
widget.setLabel('Bar');
The appendTo(targetEl)
method is only one of the methods that can be used to insert the widget into the DOM. All of the methods are listed below:
appendTo(targetEl)
insertAfter(targetEl)
insertBefore(targetEl)
prependTo(targetEl)
replace(targetEl)
In order for everything to work on the client-side we need to include the code for the marko-widgets
module and the ./widget.js
module as part of the client bundle and we also need to use the custom <init-widgets>
tag to let the client know which widgets rendered on the server need to be initialized on the client. To include the client-side dependencies will be using the optimizer module and the taglib that it provides. Our final page template is shown below:
src/pages/index/template.marko:
<optimizer-page name="index" package-path="./optimizer.json" />
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Widgets Demo</title>
<optimizer-head/>
</head>
<body>
<!-- Bind a widget to a div element using the "w-bind" attribute -->
<div class="my-component" w-bind="./widget">
<h1>Click Me</h1>
</div>
<optimizer-body/>
<init-widgets/>
</body>
</html>
The optimizer.json
that includes the required client-side code is shown below:
src/pages/index/optimizer.json:
{
"dependencies": [
"require: marko-widgets",
"require: ./widget"
]
}
In the above example, the final HTML will be similar to the following:
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Widgets Demo</title>
</head>
<body>
<div data-rwidget="/src/pages/index/widget" id="w0" class="my-component">
<h1>Click Me</h1>
</div>
<script src="static/index-8947595a.js" type="text/javascript"></script>
<span style="display:none;" data-ids="w0" id="rwidgets"></span>
</body>
</html>
To try out and experiment with this code please see the documentation and source code for the widgets-bind-behavior sample app.
It's also possible to manually initialize rendered widgets as shown in the following code:
var markoWidgets = require('marko-widgets');
var template = require('marko').load(require.resolve('./template.marko'));
module.exports = function(req, res) {
template.render(viewModel, function(err, html, out) {
var widgetIds = markoWidgets.getRenderedWidgetIds(out);
// Serialize the HTML and the widget IDs to the browser
res.json({
html: html,
widgetIds: widgetIds
});
});
}
And then, in the browser, the following code can be used to initialize the widgets:
var result = JSON.parse(response.body);
var html = result.html
var widgetIds = result.widgetIds;
document.body.innerHTML = html; // Add the HTML to the DOM
// Initialize the widgets to bind behavior!
require('marko-widgets').initWidgets(widgetIds);
For UI components that will only be rendered on the server it may be desirable to split the renderer (i.e. rendering logic and template) from the client-side behavior (i.e. widget). This can be done by using defineRenderer(def)
and defineWidget(def)
instead of defineComponent(def)
. An example of a combined and split UI component is shown below.
src/components/app-hello/
├── index.js
└── template.marko
src/components/app-hello/template.marko:
<div w-bind
w-on-click="handleClick">
Hello ${data.name}!
</div>
src/components/app-hello/index.js:
module.exports = require('marko-widgets').defineComponent({
template: require.resolve('./template.marko'),
getTemplateData: function(state, input) {
return {
name: input.name
};
},
handleClick: function() {
this.el.style.backgroundColor = 'yellow';
}
});
src/components/app-hello/
├── index.js
├── renderer.js
├── template.marko
└── widget.js
src/components/app-hello/template.marko:
<div w-bind="./widget"
w-on-click="handleClick">
Hello ${data.name}!
</div>
src/components/app-hello/renderer.js:
module.exports = require('marko-widgets').defineRenderer({
template: require.resolve('./template.marko'),
getTemplateData: function(state, input) {
return {
name: input.name
};
}
});
src/components/app-hello/widget.js:
module.exports = require('marko-widgets').defineWidget({
handleClick: function() {
this.el.style.backgroundColor = 'yellow';
}
});
src/components/app-hello/index.js:
exports.render = require('./renderer').render;
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:
module.exports = require('marko-widgets').defineComponent({
template: require.resolve('./template.marko'),
getTemplateData: function(state, input) {
return {
name: input.name
};
},
handleClick: function() {
this.el.style.backgroundColor = 'yellow';
}
});
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.
See the [Split Ren]
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 defineRenderer(def)
will be a renderer(input, out)
function with a static render(input)
method.
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
:
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:
this.$()
$(this.el)
$("#" + this.id)
$('#<widget-el-id>')
Convenience usage to access a nested widget DOM element wrapped as a jQuery object. All of the following are equivalent:
this.$("#myEl")
$(this.getEl("myEl"))
$("#" + this.getElId("myEl"))
$('<selector>')
Convenience usage to query nested DOM elements scoped to the root widget DOM element. All of the following are equivalent:
this.$("ul > li")
$("ul > li", this.el)
$("#" + this.id + " ul > li")
$('<selector>', '<widget-el-id>')
Convenience usage to query nested DOM elements scoped to a nested widget DOM element. All of the following are equivalent:
this.$("li.color", "colorsUL")
this.$("#colorsUL li.color")
$("li.color", this.getEl("colorsUL"))
$("#" + this.getElId("colorsUL") + " li.color")
$('#<widget-el-id> <selector>')
Convenience usage to query nested DOM elements scoped to a nested widget DOM element. All of the following are equivalent:
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:
this.$(function() { /*...*/ });
$(function() { /*...*/ }.bind(this)); // Using Function.prototype.bind
$($.proxy(function() { /*...*/ }, this));
Moves the widget's root DOM node from the current parent element to a new parent element. For example:
this.appendTo(document.body);
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.
Detaches the widget's root element from the DOM by removing the node from its parent node.
Emits an event. This method is inherited from EventEmitter (see Node.js Events: EventsEmitter
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.
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[]"
)
Similar to getEl
, but only returns the String ID of the nested DOM element instead of the actual DOM element.
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.
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[]"
)
Replaces the state with an entirely new state. If any of the state properties changed then the widget's view will automatically be updated.
Used to change the value of a single state property. For example:
this.setState('disabled', true);
Used to change the value of multiple state properties. For example:
this.setState({
disabled: true,
size: 'large'
});
Force a state property to be changed even if the value is equal to the old value. This helpful in cases where a change occurs to a complex object that would not be detected by a shallow compare.
Example:
// Add a new item to an array without going through `this.setState(...)`
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');
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.
The root HTML element that the widget is bound to.
The String ID of the root HTML element that the widget is bound to.
The current state for the widget. For example:
module.exports = require('marko-widgets').defineComponent({
template: require.resolve('./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
}
});
Please see FAQ.
See CHANGELOG.md
Chat channel: ![Gitter](https://badges.gitter.im/Join Chat.svg)
Questions or comments can also be posted on the RaptorJS Google Groups Discussion Forum.
- Patrick Steele-Idem (Twitter: @psteeleidem)
- Phillip Gates-Idem (Twitter: @philidem)
Pull Requests welcome. Please submit Github issues for any feature enhancements, bugs or documentation problems.
Apache License v2.0