-
-
Notifications
You must be signed in to change notification settings - Fork 926
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Rename "controller" #499
Comments
This proposal (as modified below by @pygy) has my strong, unequivocal vote. My contention is that the thing called "controller" is broader than an MVC controller, and the name is too conceptually restrictive. From my comment on the original thread:
Supported in part by Leo's comment in the docs:
My point being that if "Mithril controllers can be stripped down to [view-models]", then they are not specifically controllers in the MVC sense, nor specifically view-models in the MVVM sense, but both and neither. And I want to stress my earlier comment that my experience writing code "has ingrained in me to use things the way they are designed to be used, or risk all hell breaking loose down the road". If Mithril's design intent is not to limit the use of the thing called controller, as the documentation currently states, then it should not be named "controller". So, my answer to "What's wrong with controller", is that it's simply the wrong name for the concept it embodies in Mithril. |
You linked my argument already, so just stopping by to reiterate my 👎 on changing this. |
There's also the question of the name, for which My vote would be for Personally, I'd prefer the latter, because I vastly prefer closure-based objects, not this-based objects. However, I don't feel strongly about that since if it's redefined as a view-context a plain JS object is quite tolerable. |
@tivac: Of course, renaming it to That would seem to make you ambivalent to the proposal rather than opposed to it; not to put words in your mouth. (Apart from then needing to do a global find/replace of "controller" to "ViewModel", perhaps?) |
I think that we need to use different names for the initializer function (currently called {
init: function(foo) {
foo.data = m.prop()
},
view: function(foo) {
return m(".bar", etc...)
}
} ...where |
Nice! that's a neat option I haven't seen before. I think this will allow Mithril to get rid of the One affected aspect that I can of is @lhorie's blog post on using Meteor with Mithril (a lovely combination, by the way). The // I renamed `instance` to `foo` to correspond to your example
var reactive = function(controller) {
return function(foo) {
var computation = Deps.autorun(function() {
m.startComputation()
controller(foo)
m.endComputation()
})
foo.onunload = function() {
computation.stop()
}
}
}
Leaderboard.controller = reactive(function(foo) {
foo.players = Leaderboard.players()
}) Astounding. To me this just feels right. If I'm not mistaken, it also makes controller/init functions more composable. This also sidesteps the problem of what to name it our misunderstood little controller. If we call this new function something like |
@pygy : That's strikes me as a really promising idea. I very much like the symmetry of invocation in initializing the view-context and generating the view. And it removes Mithril from having an opinion about what role the context fulfills. I agree with @mindeavor, that it has a "feels right" appeal to it. And notice his example uses it as a controller with absolutely zero cognitive impedance. I presume the Mithril call then becomes, using current
Where the name "context" is entirely up to Leo and is never seen externally; heck, it could remain controller, for all we care, though leaving it as that internally might be too dissonant to the actual use and documentation. As well, logically then there's never a "dummy" context like we now have with the anonymous controller, only an empty one. This also feels cleaner, even though only really a semantic difference. I would stick with In terms of 0.x compatibility, it would be simple for Mithril to have a
I believe I give this idea a solid thumbs-up. It's the best proposal so far, I think. |
I like this idea of having the unique instance as a passed in argument is great. Several distinct advantages:
This really helps you think about the lifecycle properly and massively demystifies the hitherto 'magical' aura of controllers. When we accept that there's a persistent object you can access from redraw to redraw, and that there's an optional function that only executes on the first draw, 'controllers' such as they are sound a lot like the thing being asked for in #492. Taking the thinking in reverse, and looking at how we currently use myComponent = {
view : function( ctxt, user, data ){
if( !ctxt.init ){
ctxt.stuff = coolThingsWith( data );
ctxt.name = user.firstName + user.secondName;
if( ctxt.stuff.pending ){
ctxt.then( m.redraw );
}
else if( ctxt.stuff.wrong ){
m.route( '/away' );
}
}
return m( 'h1', 'Power overwhelming' );
}
}; |
BTW, in tangential support of the 'get rid of function partialComponent( component, arg1, arg2, etc ){
var args = [].slice.call( arguments, 1 );
var noop = function(){};
return {
controller : component.controller ? function partialCtrl(){
// Javascript is horrible. This is how you apply arguments to a variadic constructor:
return new ( noop.bind.apply( noop, [ component.controller ].concat( args ) ) )();
} : noop,
view : component.view ? function partialView( ctrl ){
return component.view.apply( ctrl, args );
} : noop
};
}; |
@pygy is the lack of |
@lhorie : I believe the lack of |
to clarify, I'm asking because DI'ing the context object means you no longer have control over its creation, so, for example, you can't use the singleton factory pattern. |
@lhorie Mithril could treat it similar to the way JavaScript treats the keyword |
Provided Mithril explicitly documents this behavior. My theoretical context creation function would become:
simplified, inlined and removed in version 1.x to:
though, I believe that would require
|
I hadn't thought it through with respect to singleton factories, but @mindeavour adresses it. That being said, that would be re-implementing a core JS functionality for the sake of ditching Another point, as raised by @barneycarroll on gitter is the ability to use
I don't understand what he means by "what kind of incoming navigation", though. |
That sounds like a description of what it currently does (with the exception that you create the object instead of Mithril) function MyController() {
return {foo: "bar"}
} That doesn't warrant a breaking change, imho. We could talk about the merits of dropping I'd argue that from the controller caller side, it's easier to |
At the risk of putting too much into this thread, I might take this opportunity to point out that I've recently become quite a fan of the |
Let's not get side-tracked from the actual request, which is to rename The change from constructor to initializer is, in my mind, hugely beneficial, in (a) the way it conceptually defines this as a container object, and (b) the symmetry it introduces in the two properties of the module (soon to be component, hopefully), the context initializer and the view generator, and (c) in the conceptual simplicity of having a simple view context that parallels But the main point of the request is to widen the narrow and somewhat contentious concept of |
Angular didn't invent DI, and just cause they go overboard with it doesn't mean it's bad :) But yes, we are getting off topic. If we want to stay backwards-compatible and JavaScript-intuitive, an alternative name for |
I know Angular didn't invent DI and I'm not even opposed to DI in principle, I'm just saying that usage pattern seemed very similar to Btw, console.log({}.constructor === Object) // true
function Foo() {}
var foo = new Foo()
console.log(foo.constructor === Foo) // true |
@nijikokun : Actually, I think far more correct is that the name "controller" sometimes perfectly suits what it does. Other times it "doesn't exist", that is, it's anonymous (in a pure view module), other times it's a view-model (MVVM; see my quote, earlier, from the Mithril docs, where Leo advocates it contains only a generic map named In actuality, the concept that embodies all these disparate things, is a "context for the view" which may be utilized in a variety of ways. Even more importantly, the thing named "controller" isn't even the object, it's (currently) the "whatever-it-is" constructor, and (as very much a secondary idea in this thread) could be changed to be the "whatever-it-is" initializer by conforming to the same DI model as the view generator. |
I have to disagree; as an explicit parameter of an The conceptual notion of a container for holding things for this instance of a module/component is a simple concept, and avoiding The only question before us, on this _secondary_ consideration is whether the loss of being a constructor vs. being a simple object, that is the loss of a specific type is material to the way this object is intended to be used. On that, I think not. But in the rare occasion that it is necessary, maybe allowing the |
@lawrence-dol I apologise if this is deviating even further, but do you think it's right to keep mechanics considerations —and potential changes to them — in this thread, or would it be more helpful if we moved that out to a separate issue? |
On gitter, @lhorie said
The trouble is that the function currently named "controller" is a controller (vm, presenter...) constructor/initializer rather than a controller-level entity as defined in the original MVC pattern. I found the terminology confusing, at first. I actually wrote a post about it on the Google group, then removed it when I grasped its usage. I had refrained from bikeshedding on the name, but since we're doing it now ;-) |
Exactly! So did I. Furthermore, an MVC controller is not an MVVM view-model, nor an MVP presenter, etc. Worse, there's very little agreement on what constitutes a controller, even if you are using MVC. Whatever the thing initialized by what Mithril calls a "controller" is, it is not (always) the "C" of MVC. Even Leo advocates so in the documentation and in "Put your controllers on a diet". So the problem is two-fold; not only is the thing referenced a constructor for the object, the purpose of said object does not always (even often) conform to an MVC controller. Also, allow me to point out that quite apart from the "it's not a controller it's a constructor" argument... there are those, like me, who don't use it as a controller and don't want it called "controller", and those who do use it as a controller for whom a more accurate universal name would do no harm. |
My reservation with the word The confusion between "presenter", "view-model" and "controller" still falls back to the idea that they are things with nuanced differences but with similar roles, and for better or for worse, the word "controller" has become a bit of a catch-all term for the controller-like entity in systems with models and views. I don't have any objections about |
As much as I like Correctness does not always bring popularity, unfortunately. On Thursday, March 19, 2015, Leo Horie notifications@github.com wrote:
|
the traditional MVC as a pattern for frontend-only is very murky with regard to benefits. declaring something as MVC for marketability when in fact the edit: i actually think that it's precisely the familiarity with the prescriptionist naming/expectations of |
I'm in favor of renaming Alternatively, we could add a page to the docs discussing the purpose of |
It is just the controller view pattern:
|
@barneycarroll raised this again recently on gitter, and, if it came again on the table: I just thought of this to provide an easier transition route: For a few versions, support both the old |
@pygy : I've also previously raised the point of when to eliminate the "controller" and suggested that it be done at version 1.0 when all the 0.x "wood-shedding" is complete (or as complete as it's ever going to be). |
Could I get a quick description of this proposed "init"/"view" syntax? I On Sun, Dec 20, 2015, 22:05 Lawrence Dol notifications@github.com wrote:
|
@isiahmeadows : #499 (comment) But it's well worth reviewing the entire thread. |
So I'd been working with Ember for a month when I happened upon this article by Dan Abramov on distinguishing between elements, components and component instances in React. These terms are necessary when discussing the structure and operating logic of any modern JS MVC library. It struck me that despite components being functionally much simpler and having a much much smaller API surface in Mithril compared to either React or Ember, the type of conversation Dan was trying to elucidate is far easier to have when dealing with either of those frameworks than with Mithril, because Mithril mandates the extra overhead of grasping what a controller is, because a practical conversation that refers to the Mithril API requires reference to controller functions and controller instances. My contention is that the initial cognitive load and learning curve of Mithril can be much reduced by simply doing away with // ES6
class Demo {
constructor() {
this.pages = Page.list();
},
rotate() {
this.pages().push(this.pages().shift());
},
view() {
return m("div", [
this.pages().map(page =>
m("a", {href: page.url}, page.title);
}),
m("button", {onclick: this.rotate}, "Rotate links")
]);
}
};
// ES5
var Demo = Object.create( {
rotate: function() {
this.pages().push(this.pages().shift());
},
view : function() {
return m("div", [
this.pages().map(function(page) {
return m("a", {href: page.url}, page.title);
}),
m("button", {onclick: this.rotate}, "Rotate links")
]);
}
}, {
pages : Page.list()
} );
// ES3
function Demo {
this.pages = Page.list();
}
Demo.prototype = {
rotate : function() {
this.pages().push(this.pages().shift());
},
view : function() {
return m("div", [
this.pages().map(function(page) {
return m("a", {href: page.url}, page.title);
}),
m("button", {onclick: this.rotate}, "Rotate links")
]);
}
};
// This-less Factory with closured private references
function Demo(){
const pages = Page.list()
function rotate() {
pages().push(pages().shift());
}
return {
view : () =>
m("div", [
pages().map(page =>
m("a", {href: page.url}, page.title);
}),
m("button", {onclick: rotate}, "Rotate links")
]);
}
};
}; I'm hoping @ciscoheat will back this proposal as truer to the original definition of MVC, inasmuch as we are no longer misusing the term 'controller'. To be clear: this doesn't change the fundamental lifecycle at all, and doesn't prevent users creating whatever objects they deem fit: if you want to bind |
@barneycarroll 👍 This is more or less where my intuitions have been leading me as well. Since I build closure-based objects and eschew the psuedo-class prototypical objects entirely, I would be on-board with this. In the end, the so-called controller is simply a view context, created by Mithril and initialized by the application. I have many things which are view-only "components", where I inject the needed state in when I call it. I would also be totally happy with a component being an But I think @lhorie sees components more microscopically, where a component is, for example, one row in a table. I, for my part, typically consider a row in a table to be part of a table component where a view-only row-renderer is employed in a loop. And I think @lhorie is also fairly strongly wedded to the idea that components are created and destroyed on demand by Mithril -- I am not sure that he doesn't have a point. |
@lawrence-dol to be honest, any conceivable practical example can be written in either style. If you're concerned that this proposal invites complexity by providing a closure for otherwise pure (ie stateless, view-only) components, note that Having said that, extending the component with statefulness is easy: as you say, we can write a closured component that enables truly private state without the need to attach stateful references to a host object; alternatively (or in addition), all component methods get a stateful reference to Seeing as current Mithril is duty-bound to provide an empty controller constructor if you don't and pipe in the reference to your view even if you don't want it, this implementation is not any more laden with unnecessary stateful functions by default — arguably the opposite. But when the requirement for statefulness crops up, it's both less convoluted and far more flexible. People with an ardent hatred of |
@barneycarroll that looks great, I'm completely with you about doing away with the In the 0.2.1 release I saw some regression for the Mithril/Haxe-library, and the issue has always been about instantiating the controller. So moving to a user-defined constructor (in all the variations you present) instead of a convention-based controller function, would only do good things for me in that sense. |
I think this proposal would make it easier for newcomers to introspect what's going on in the instantiation of a component and how they can work with the component as a container for whatever pattern works best. Sounds great to me. |
what about: function someView() {
return [
'random string', // "random string"
m('div', foo), // "<div>foo</div>"
() => 'view function', // "view function"
{
init: () => {foo: 'with init function'},
view: (scope) => m('.component', scope.foo)
} // <div class='component'>with init function</div>
function() {
var bar = 'with revealing view'
return () => m('.component', bar)
} // <div class='component'>with revealing view</div>
]
} Last one seems tricky. Can't imagine how to preserve identity. |
@StephanHoyer trying to work out the execution logic. Are all functions encountered in a view executed (and their outputs, if they are functions, executed, recursively) up until they deliver valid virtual DOM, and the last function thus encountered re-executed (with any higher order function closured the first time it executes) on subsequent draws? |
@barneycarroll Yes. Something like this. Haven't thought it to the very end. Maybe the 3rd way should'n be possible. Can't find a situation, where this would be practical. In this case, functions in the VDOM are always considered as components that have to be called and should return a view function that returns a VDOM-element. |
I think the 4th version is also pretty useless without parameters. Currently the components can get args with Maybe better (I think @lhorie tried this once when introducing components with ver 0.2.0): function initComponent(arg0, arg1) {
return {
init: () => {foo: 'with init function' + arg0},
view: (scope) => m('.component', scope.foo + arg1)}
}
}
initComponent('000', '111') // <div class='component'>with init function000111</div> This would also allow to use with ES6 classes. Having that said the closure version might look like this: function closureComponent(arg0, arg1) {
var bar = 'with revealing view' + arg0
return () => m('.component', scope.bar + arg1)
}
closureComponent('000', '111') // <div class='component'>with revealing view000111</div> It's pretty much syntactic sugar for the first on it can easily be achieved with a wrapper function: function wrapClosure(fn) {
return function() {
return { init: fn, view: (closureView) => closureView.apply(null, arguments) }
}
} To shim this behaviour we can also provide a wrapper function (I think @lhorie showed this somewhere) function wrapComponent(fn) {
return m.component({
controller: function() {
var component = fn.apply(null, arguments)
var scope = component.init()
scope.__view = component.view
return scope
},
view: function(scope) {
return scope.__view(scope);
}
})
} |
@StephanHoyer the interop wrapper function highlights the fact that we don't really need to petition for core Mithril changes to validate our ideas about better component structure. Mithril is currently overburdened with non-trivial upgrade work, and last this conversation gained traction there wasn't sufficient energy to provide proofs of concept for these breaking change proposals. Even at v0.1, there was a massive sense of foreboding in deprecating any of the API. I'm going to try my luck at implementing a framework-agnostic component library - spiritual successor to Modulator - using my proposal above. My plan is first of all to provide a totally generic component structure and consumer function that simply provides a scope for a view and can be plugged in to Mithril/Cito/DOMVM/React/whatever, and then to see about whether it's worth pursuing extensions to hook into lifecycles. I've tried the latter a few times, and it inevitably ends up with a lot of overhead replicating Mithril's internal logic, but it's easier than patching a branch of Mithril and valuable enough as an experiment (maybe it's worth the extra overhead just to have a nicer API, maybe the PoC can provide insights for Mithril). I'd previously been toying with the idea of writing a component-conscious layer on top of Cito called Citoplasm, but the more generic Plasm will suit all contexts. |
@barneycarroll Even though I'm in favor of this proposal, I have to agree that this is a rather trivial issue and probably not worth the effort right now. I was about to un-watch this thread, but the component library you mentioned caught my interest. Have you started on it yet? I'd love to see where this goes. Do you think you could make it work with Angular 2 as well? edit: Just discovered your Plasm repo, looks good so far- will be checking up on it every once in a while! |
@nmai I'm tentatively calling the lib Plasm. Repo is here — it's just a manifesto and an untested proof of concept ES6 file (with a dubious ES7 dependency) ATM. AFAICT Angular 2, like Ember, still insists on writing views in its own weird string-based template language, locking the author out of direct access to the interpolation logic, so you wouldn't be able to plug this in directly. As with Ember, what's actually going on at any point in time is so far removed from application code that you'd probably need a custom helper / directive to integrate this. We'll see if that becomes worthwhile. In any case, I digress. Please file issues there if you're interested. @StephanHoyer I like the simple function returning a function system — removes the need to debate whether |
@barneycarroll great manifesto!! i like it! that's the pure idea of components. |
@barneycarroll @lawrence-dol While I like much about Mithril, the way it handles components seems awkward to me and creates surprises (like when top level components work differently than nested components or trouble thinking through what happens when components get made and updated). Mithtil controllers were also awkward to integrate with TypeScript classes (although doable via static methods as I explained here). Components are one area where Mithril tries to do too much and so creates unneeded complexity in an attempt to support one specific use case for managing the component lifecycle. So, I can see the benefit of the Plasm approach or other ideas sketched above. You may be interested in this example I made to go with the Maquette vdom: Maquettte does not have built-in component support, but IMHO is better in that specific way for not trying to do it. See also the Maquette documentation on arrays from some ideas about a common issue which Mithril solves well in its own way: I'm not saying Maquette is better than Mithril in all ways. In pursuit of rendering speed, makes different tradeoffs many people would not like which make its API harder to use in some ways. But by doing less with components it actually does more in a way, since you just manage components yourself and call view/render methods yourself or with a helper function. With a small tweak to call a callable passed in or a view/render method on an object passed in to an "h" function (equivalent to "m"), I feel Maquette would be even more component friendly (similar to what the second helper function linked below does). Anyway, Maquette's simpler approach is something worth studying as an alternative approach for a future Mithril version in a future rewrite. With that said, you can just ignore the Mithril component approach in nested components and DIY. Just make an instance of a class which has a view method and call the view method yourself in the middle of various "m" calls in some other view method. Here are two versions of a "r" (for "render") helper function for DIY components done for Maquette but which could be used in a similar way for Mithril: |
I am trialing Leon Sorokin's DOMVM in my current project, precisely because it's completely paradigm agnostic (i.e. it's not MVx but can be if that's your thing) and (so far) seems completely amenable to a component-first architecture (something which is my thing). Leon also brings a solid commitment to minimalism, a clean and consistent API and to never introducing "tooling" for producing DOMVM, something I very much support since I do want to contribute. Lastly, DOMVM is well structured and module, but requires only simple concatenation of the bits you want -- toolkit free and modular, something I think is where Mithril's community failed (I mean that in the nicest way possible). I too feel that Mithril doesn't get components right, though I still have several key projects that use Mithril. What's worried me a bit more is that Mithril has suddenly gone "dark" before reaching anything that could be considered a 1.0 release. I do like Mithril, component implementation aside, and I hope to see a re-imagined Mithril from Leo... but so far there's not much indication of that and my development must go on. I am also fairly heavily invested in Mithril, but not so much it can't be undone if warranted. However, with all that said, I am inexpressibly grateful to Leo for introducing me to the concept of vDOM and pure JavaScript front-end development, before which I was seriously considering quitting my job over the prospect of having to play in the JS+HTML+CSS pool for the majority of my work. The Mithril approach of pure JS plus targeted CSS was a revolution for me. @barneycarroll : Hmmm. Plasm looks nearly identical to DOMVM. |
Actually, I got curious myself if it was possible to mock Mithril's MVC-isms, args, contexts and global auto-redraw semantics in domvm. I focused on the feature set needed to be able to copy-paste the link rotator demo from Mithril's home page and have it "just work". Turns out that it was pretty trivial [1] [2]. The mithril adapter itself is tiny [3]. [1] http://leeoniya.github.io/domvm/demos/mithril-adapter/ |
@lawrence-dol how are you liking DomVM so far? |
I am not nearly as far along yet as with Mithril in other projects, so it's a bit soon to tell, but so far it's making a lot of sense and most of the concepts translate well. Components seem more intuitive, to me anyway, since each view-model is a component encompassing a DOM subtree and losing the MVC inferences makes me feel like I am not swimming against upstream anymore. It probably helps that I am in the camp that thinks MVC is irrelevant in a JavaScript app. Conceptually I like the idea that the view-model pieces tie the data-model and the application logic together in any way that makes sense, which is more or less what I am doing with Mithril anyway. I have a few component pieces that use Mithril which turned out really well and will be easy to convert to DOMVM should I elect in the end to stick with it. The use of I think it also helps that it seems Leon and I are far more on the same page for the most part with our approaches to coding, tooling, modularization, and other things somewhat tangential to actually delivering a black-box library. He seems to care about names and the concepts the communicate as much as I do. So the jury's still out, but it's looking likely that DOMVM is where I'll end up. The answer will only come with more time, doing actual coms and wiring together parts of the app as UI interactions happen. |
Suggestions: scope, context, viewContext, viewctx, state, viewstate
Counter arguments:
The text was updated successfully, but these errors were encountered: