If someone can think of a better title for this question, please let me know.
I just figured out the answer to this question after an insane amount of time. I'm sure someone else will make the same mistake, so I'll post the answer below.
The question will make more sense once I provide some background info. I have a parent component which has an array of objects with matching keys (e.g. ctrl.arrayOfObjects
). I sort this array by the value of one of the keys shared by each object (e.g., ctrl.arrayOfObjects[0].keyToSortBy
). Then, I pass each element in the now-sorted array to another component.
This child component has an onclick
method which changes the data shared between the parent and the child, i.e., one of the objects in the parent's ctrl.arrayOfObjects
. It changes the value of the key which was used to sort the parent component's ctrl.arrayOfObjects
(e.g., sharedObject.keyToSortBy
).
My problem is that, after triggering this onclick
event, the parent component does not re-sort the array of objects. The onclick
event does modify the data it's supposed to (sharedObject.keyToSortBy
), which I know because the page does re-render the new value of sharedObject.keyToSortBy
. But it does not re-sort the parent component's ctrl.arrayOfObjects
.
The weird part is that if I do a console.log
, the sort method is being called, but the page does not display any changes.
Here's what the code looks like (ctrl.arrayOfObjects
is ctrl.jokes
):
var JokeWidgetSeries = {
controller: function() {
var ctrl = this;
ctrl.jokes = [{
text: "I'm a joke",
votes: 3
}, {
text: "This is another joke",
votes: 1
}, {
text: "A third joke",
votes: 1
}]
},
view: function(ctrl) {
return m('#jokes-container',
m('#jokes',
ctrl.jokes.sort(function(a, b) {
// Sort by number of votes.
return (a.votes < b.votes) ? 1 : (a.votes > b.votes) ? -1 : 0;
}).map(function(joke) {
// Create a 'JokeWidget' for each object in 'ctrl.jokes'.
// Share the 'joke' object with the child component.
return m.component(JokeWidget, joke);
})
)
);
}
};
var JokeWidget = {
controller: function(inherited) {
var ctrl = this;
ctrl.joke = inherited;
},
view: function(ctrl) {
return m('.joke', [
m('.joke-text', ctrl.joke.text),
m('.joke-vote-count', ctrl.joke.votes),
m('.thumb-up', {
onclick: function(e) {
// Here, the page shows that the vote count has changed,
// but the array of 'JokeWidget' DOM elements do not re-sort.
ctrl.joke.votes += 1;
}
})
]);
}
};
After triggering the onclick
method of the JokeWidget
component, the .joke-vote-count
DOM element which displays ctrl.joke.votes
does increase by 1. But it does not move up in the list/array of widgets on the page like it should.
However, if I combine these two components into one, the array does get re-sorted like I want it. Like this:
var JokeWidgetSeries = {
controller: function() {
var ctrl = this;
ctrl.jokes = [{
text: "I'm a joke",
votes: 3
}, {
text: "This is another joke",
votes: 1
}, {
text: "A third joke",
votes: 1
}]
},
view: function(ctrl) {
return m('#jokes-container',
m('#jokes',
ctrl.jokes.sort(function(a, b) {
// Sort by number of votes.
return (a.votes < b.votes) ? 1 : (a.votes > b.votes) ? -1 : 0;
}).map(function(joke) {
// Create a 'JokeWidget' component instance for each object in 'ctrl.jokes'.
// Share the 'joke' object with the child component.
return m('.joke', [
m('.joke-text', joke.text),
m('.joke-vote-count', joke.votes),
m('.thumb-up', {
onclick: function(e) {
// Here, the array of 'JokeWidget' DOM elements
// DO re-sort. Why?
joke.votes += 1;
}
})
]);
})
)
);
}
};
How can I separate these components and still get this re-sorting method to work?
The solution is simple. When sharing the object between the parent and child components, pass the object to the child component's view
method rather than the controller
method.
When creating components in Mithril, you can pass data to both the controller
and the view
methods of the component. This data will be the first parameter of the controller
method and the second parameter of the view
method.
Instead of setting the shared object to ctrl.joke
inside of the JokeWidget
's controller
method and then referencing it inside of its view
method, just pass the shared object directly to the view
method. Like this:
var JokeWidget = {
// Do not pass the object this way.
// controller: function(joke) {
// var ctrl = this;
// ctrl.joke = joke;
// },
// Pass the shared object as a second parameter to the 'view' method.
view: function(ctrl, joke) {
return m('.joke', [
m('.joke-text', joke.text),
m('.joke-vote-count', joke.votes),
m('.thumb-up', {
onclick: function(e) {
// Now, the array of 'JokeWidget' DOM elements will re-sort.
joke.votes += 1;
}
})
]);
}
};
This also removes an extra layer of abstraction, making it easier to read and understand.