update Select2 integration example and explanation (#1028)

* update Select2 integration example and explanation

* integrate feedback per PR

* oops. integrate the feedback that I missed initially

* make docs great again
This commit is contained in:
Bradley Stafford 2016-09-23 21:53:51 -07:00 committed by Pat Cavit
parent cadb4622f9
commit c82ad36335

View file

@ -7,110 +7,102 @@ It's recommended that you encapsulate integration code in a component or a helpe
The example below shows a simple component that integrates with the [select2 library](http://ivaynberg.github.io/select2/).
```javascript
var Select2 = {
// Returns a select box
view: function(ctrl, attrs) {
var selectedId = attrs.value().id;
//Create a Select2 progrssively enhanced SELECT element
return m("select", {config: Select2.config(attrs)}, [
attrs.data.map(function(item) {
var args = {value: item.id};
// Set selected option
if(item.id == selectedId) {
args.selected = "selected";
}
return m("option", args, item.name);
})
]);
},
/**
Select2 config factory. The params in this doc refer to properties of the `ctrl` argument
@param {Object} data - the data with which to populate the <option> list
@param {prop} value - the prop of the item in `data` that we want to select
@param {function(Object id)} onchange - the event handler to call when the selection changes.
`id` is the the same as `value`
*/
// Note: The config is never run server side.
config: function(ctrl) {
return function(element, isInitialized) {
if(typeof jQuery !== 'undefined' && typeof jQuery.fn.select2 !== 'undefined') {
var el = $(element);
if (!isInitialized) {
el.select2()
.on("change", function(e) {
var id = el.val();
m.startComputation();
//Set the value to the selected option
ctrl.data.map(function(d){
if(d.id == id) {
ctrl.value(d);
}
});
// Component containing a Select that uses Select2 for functionality.
var S2Component = {
if (typeof ctrl.onchange == "function"){
ctrl.onchange(el.val());
}
m.endComputation();
});
}
el.val(ctrl.value().id).trigger("change");
} else {
console.warn('ERROR: You need jquery and Select2 in the page');
}
};
}
};
// Rendered view for S2
view: function(ctrl, attrs) {
var current = attrs.selectedUser
var Dashboard = {
controller: function() {
var ctrl = this,
//list of users to show
data = [
{id: 1, name: "John"},
{id: 2, name: "Mary"},
{id: 3, name: "Seniqua"}
];
ctrl.data = data;
// Has to use a prop for the current user
ctrl.currentUser = m.prop(data[1]);
ctrl.changeUser = function(id) {
console.log(id);
};
},
return m('select', {
class: 'select-field',
config: S2Component._configure
},
attrs.data.map(function(item){
return m('option', {
id: item.id,
value: item.value,
selected: (item.id === current) ? true : false
}, item.name);
})
)
},
view: function(ctrl) {
return m("div", [
m("label", "User:"),
m.component(Select2, {
data: ctrl.data,
value: ctrl.currentUser,
onchange: ctrl.changeUser
})
]);
// Configure function -- called from m('select') in the view
_configure: function(element, initialized) {
/*
Note: This function is being called from the 'config' attribute
in our mithril view.
Integration with third-party party DOM manipulation (jQuery) needs
{config: function(){}} because that's the attribute that exposes the real
DOM element (as opposed to the virtual DOM element) in the corresponding
function so you can access and manipulate it.
*/
// If this hasn't been initialized, we can do our setup
if(!initialized) {
$(element).select2({
// options
});
// Other logic pertaining to this select also goes here.
// e.g. Event handlers, etc.
}
};
}
}
// Primary component.
var MainComponent = {
controller: function() {
var ctrl = this;
// Some arbitrary data
// Aaron Burr is the initially selected user.
ctrl.selectedUser = 2;
ctrl.data = [
{id: 1, name: 'Alexander Hamilton'},
{id: 2, name: 'Aaron Burr'},
{id: 3, name: 'Thomas Jefferson'},
{id: 4, name: 'John Adams'},
{id: 5, name: 'James Madison'},
{id: 6, name: 'Elizabeth Schuyler'},
{id: 7, name: 'King George'},
{id: 8, name: 'Marquis de Lafayette'}
]
},
view: function(ctrl) {
return m('div', {class: 'select-container'}, [
m('label', 'Historical Figure: '),
m(S2Component, {
selectedUser: ctrl.selectedUser,
data: ctrl.data
})
])
}
}
m.mount(document.body, MainComponent)
m.mount(document.body, Dashboard);
```
`select2.config` is a factory that creates a `config` function based on a given controller. We declare this outside of the `select2.view` function to avoid cluttering the template.
[Source code in JSFiddle](https://jsfiddle.net/11pz8afy/9/)
The `config` function created by our factory only runs the initialization code if it hasn't already. This `if` statement is important, because this function may be called multiple times by Mithril's auto-redrawing system and we don't want to re-initialize select2 at every redraw.
The initialization code defines a `change` event handler. Because this handler is not created using Mithril's templating engine (i.e. we're not defining an attribute in a virtual element), we must manually integrate it to the auto-redrawing system.
`_configure` is a helper function that is called via the `config` attribute in the `select` we render in our `SC2Component.view`
This can be done by simply calling `m.startComputation` at the beginning, and `m.endComputation` at the end of the function. You must add a pair of these calls for each asynchronous execution thread, unless the thread is already integrated.
This `_configure` function has a guarded `if` statement: `if(!initialized)`. In the event this component is being instantiated for the first time, we're going to do all of the initial setup on the first render. Subsequent redraws will __not__ run the initialization code again, making sure everything in the `_configure` function is only initialized once.
For example, if you were to call a web service using `m.request`, you would not need to add more calls to `m.startComputation` / `m.endComputation` (you would still need the first pair in the event handler, though).
The initialization code is simply calling `$(element).select2()` on the exposed DOM element in order to initialize it. You can also addevent handlers to these elements. If you modify the DOM or any data that your component relies on inside of this function, you'll need to make sure the component knows to redraw itself by adding `m.redraw` as needed.
On the other hand, if you were to call a web service using jQuery, then you would be responsible for adding a `m.startComputation` call before the jQuery ajax call, and for adding a `m.endComputation` call at the end of the completion callback, in addition to the calls within the `change` event handler. Refer to the [`auto-redrawing`](auto-redrawing.md) guide for an example.
`m.startComputation` and `m.endComputation` are used for asynchronous operations. If you were to call a web service using jQuery, then you would be responsible for adding a `m.startComputation` call before the jQuery ajax call, and for adding a `m.endComputation` call at the end of the completion callback, in addition to the calls within any event handlers. Refer to the [`auto-redrawing`](auto-redrawing.md) guide for an example.
One important note about the `config` method is that you should avoid calling `m.redraw`, `m.startComputation` and `m.endComputation` in the `config` function's execution thread. (An execution thread is basically any amount of code that runs before other asynchronous threads start to run.)
Though possible, you should avoid calling `m.redraw`, `m.startComputation` and `m.endComputation` in the `_configure` function's execution thread. (An execution thread is basically any amount of code that runs before other asynchronous threads start to run.) Relying on multiple redraw passes degrades performance and makes it possible to code yourself into an infinite execution loop situation, which is extremely difficult to debug.
While Mithril technically does support this use case, relying on multiple redraw passes degrades performance and makes it possible to code yourself into an infinite execution loop situation, which is extremely difficult to debug.
The `dashboard` component in the example shows how a developer would consume the select2 component.
The component in the example shows how a developer would consume the `SC2Component`.
You should always document integration components so that others can find out what attribute parameters can be used to initialize the component.
@ -131,4 +123,3 @@ m.mount(document.getElementById("widget2-container"), Widget2)
m.endComputation()
```