MithrilJS / mithril.js

A JavaScript Framework for Building Brilliant Applications
https://mithril.js.org
MIT License
13.98k stars 925 forks source link

Rename "controller" #499

Closed gilbert closed 9 years ago

gilbert commented 9 years ago

My thinking is that "controller" is not sufficiently agnostic; the word comes with MVC baggage, yet it's use is very flexible source

It's not that the view context can't serve as a controller, nor that it shouldn't if you choose to use MVC, but that it shouldn't be inferred to be limited to that concept by it's name. source

Suggestions: scope, context, viewContext, viewctx, state, viewstate

Counter arguments:

While a controller can do many other things I think the MVC "baggage" is useful as it's a shared term with an understood meaning that gets the point across. Just because you can use it in other ways doesn't negate the fact that calling it a controller identifies the basic utility of it in a quick way that doesn't force you to learn a new term source

Why the word "controller" is wrong? "Controllers contain the interface between their associated models and views and the input devices (keyboard, pointing device, time). Controllers also deal with scheduling interactions with other view-controller pairs: they track mouse movement between application views, and implement messages for mouse button activity and input from the input sensor. Although menus can be thought of as view-controller pairs, they are more typically considered input devices, and therefore are in the realm of controllers." This is what the controller of mithril is doing right now. Mithril is one of the few frameworks that i really believe is using the MVC pattern correctly. source

ciscoheat commented 9 years ago

Hey @mindeavor, glad you liked it, and that you broke out of the separation dogma. :) It's really nice to see. And I think you're right on point with the questions. Reenskaug says it himself in the MVC Pattern document on page 11:

In simple cases, the Model, View and Controller roles may be played by the same object. Example: A scroll bar. The View and Controller roles may be played by the same object when they are very tightly coupled. Example: A Menu. In the general case, they can be played by three different objects. Example: An object-oriented design modeller.

About controllers and view-related state, I would like to keep it even more simple: The view-related state should be in the View object. If we can just admit for consideration that the View is a real object, all issues about the place for animations and other view-related state will go away. And the Controller still has its purpose when used correctly, either together with a View (same object even), or coordinating Views and their common input. What do you think?

gilbert commented 9 years ago

@ciscoheat That sounds fine to me. In the context of Mithril though, the view isn't an object, so it has to rely on the controller for state. Which, as you helped discover, is perfectly ok in the general case :)

ciscoheat commented 9 years ago

Hmm, then considering the example on the Mithril front page, what is a Controller and a Model, what is the difference between them and a View, and why can they have state but not the View?

gilbert commented 9 years ago

The view is supposed to be a pure function, considering you don't know how frequently it will run, due to how the vdom algorithm works. It's an implementation detail, but an important one nonetheless.

ciscoheat commented 9 years ago

The view template is supposed to be defined as a pure function (http://lhorie.github.io/mithril-blog/when-css-lets-you-down.html), are you sure that's identical to a Mithril view (and supposedly an MVC view)? I haven't read about any such restrictions.

gilbert commented 9 years ago

My understanding is that the view is the view function. I'm not sure what you're saying, could you explain with some code examples?

ciscoheat commented 9 years ago

Compare it to todo.vm in http://lhorie.github.io/mithril/getting-started.html , I don't see why you cannot create a view in a similar way, which will give it properties like any other object (since a function is an object). A contrived example:

todo.view = (function() {
    var animating = m.prop(false);
    var view = function() {
        return m("div", ...);
    }
    view.isAnimating = function() { return animating(); };
    return view;
}());
gilbert commented 9 years ago

Ok, I see. I wasn't saying you can't use closures to give your function state technically, I was trying to say it's not the way Mithril encourages. Using closures would be the way to go if Mithril only looked for view, and didn't do anything with the controller function. Certainly interesting, but I think we're getting off topic.

ciscoheat commented 9 years ago

I thought we were discussing, not urging, the "encouragements" of Mithril. :) Which hopefully are focused on how to create code that is more understandable in the end. This area of separating interface logic from business logic is quite important, given the MVC confusion that exists.

So unless you consider this off topic, I think we're back at my original point: Why is it not encouraged that the view is an object similar to the example above? Not doing that seems to result in view state being put in the Controller, or abstracted to another object. I don't think either of those makes the code more understandable.

gilbert commented 9 years ago

Discussing Mithril encouragements is good and fun, but we might want to move the conversation to a new issue or the chat room. The topic of this particular issue is whether or not we should rename controller. Which, I take it, you think we should not, correct? :)

ciscoheat commented 9 years ago

Yup, I promised to step back a long time ago so I should do that. And no, I don't think we should rename it if we want to encourage writing good software, true MVC being one of the best examples of improved code quality. This ties back to the MVC discussion straight away so I'll move to the chat room now...!

barneycarroll commented 9 years ago

@ciscoheat

PS: It is still impossible to 'construct' a function as of ES6. A non-native function cannot be an instanceof anything other than a function.

lhorie commented 9 years ago

@ciscoheat the view can hold state (in config's context argument, and in the DOM, which is inherently stateful). The view function represents syntactically and returns for machine consumption a description of what the underlying markup will look like (much like an HTML view file is a high-level description of the state that the browser is to sustain)

Since we're talking about Reenskaug, Gossman, etc, maybe a summary of my interpretation of MVC/MVVM/MVP is in order.

My understanding is that Smalltalk MVC (i.e. Reenskaug) was implemented with observables:

//models do this
on("updateThingy", function(data) {
  save(data)
  trigger("updatedThingy")
})

//controllers do this
on("update", function() {
  trigger("updateThingy", data)
})

//views do this
on("updatedThingy", updateSelf)
<button onclick="trigger("update")">Save</button>

Early web frameworks (e.g. Struts, etc) implemented MVC using procedural style because HTTP doesn't allow us to layer the observable pattern on top of it (WebSockets do, but they are a fairly new technology)

//models do some variation of this
SomeModel.save = function(data) {
  return save(data)
}

//controllers do some variation of this
//[route("/save")]
function save(data) {
  ctx.someData = SomeModel.save(data)
}

//views do some variation of this
<div>{{ctx.someData}}</div>
<form action="/save"><button>Save</button></form>

What's also relevant is that the Model API would also usually be procedural for consistency, familiarity, and lack of maturity/popularity of non-blocking I/O at the time (e.g. Java before NIO)

In a RESTful SPA, that might look like this:

//models do some variation of this
SomeModel.save = function(data) {
  return save(data)
}

//controllers do some variation of this
controller.save = function(data) {
  ctx.someData = SomeModel.save(data)
}

//views do some variation of this
<div>{{ctx.someData}}</div>
<form onsubmit="ctrl.save(data)"><button>Save</button></form>

You've all probably seen those MVC/MVVM/MVP diagrams with silly boxes and arrows. The "devil in the details" are the arrows: in Reenskaug's MVC, the arrows are the names of the observable channels: The V -> C arrow is identifiable by the "update" string in our example. The C -> M arrow is "updateThing" and the M -> V arrow is "updatedThingy".

In "web" MVC, the arrows map roughly like this: V -> C is ctrl.save, C -> M is SomeModel.save(data), and M -> V is = (in view.someData =)

Note that here, the M -> V arrow is in what the framework calls a controller.

So, this style of MVC is structurally identical to MVP, where all communication between view and model goes through the controller. To make things more interesting, MVP actually has a flavor called Supervisor Controller, where communication between model and view do NOT necessarily go through the presenter if it's not needed.

MVVM is not defined by the presence of a view-model or the lack of a controller (Angular, for example, doesn't have angular.viewModel, but it does have angular.controller). Rather, MVVM is defined by the presence of a binder (as in the term "bi-directional data binding"), as opposed to procedural calls to manually update things. So, you could write a helper that turns m("input", {onclick: m.withAttr("value", foo), value: foo()}) into input(foo) and you'd have MVVM even though you changed nothing else in the architecture. In fact, one could even argue that m.withAttr and m.prop are the binder APIs, and that Mithril is MVVM out of the box by virtue of not requiring a render/setState/echo/print/whatever call...

So, generally speaking, I think the MVC/MVVM/MVP patterns (in Mithril) overlap quite a bit and the implementation differences out to be more academic than practical.

With that being said, I agree w/ @ciscoheat that MVC is far more "battle-tested" than the alternatives. For example, In MVC, redirecting (or routing) is a controller role. In MVVM, the role of redirecting doesn't really fit anywhere in the pattern unless you go and use a... controller.

Another example has to do with multi-component architectures. HMVC address a lot of issues in multi-component architectures that I feel MVP and MVVM gloss over by being too concrete. I think MVP/MVVM can fall apart at scale because their entire premises hinge revolve around a tangible tool (i.e. bindings, presenters). "If all you have is a hammer", etc.

In contrast, with MVC, the core flow of data transcends the naming of objects in such a way that you can implement the pattern at an extremely high level (e.g. Flux), in addition to breaking down each component into MVC entities themselves.

barneycarroll commented 9 years ago

@lhorie that's a really good exposition of key differences. You should expand that a bit and blog it!

@ciscoheat there's a notion addressed in your closured context component structure that the end user of a component may want to retrieve instances and do things other than hit the view — ie render the view here and maybe offer a hook for communicating with the component to this other component. We can currently do this by overloading the view function:

component = {
  view : function( state, input ){
    if( input === 'isAnimating' ){
      return state.animating;
    }

    return actualView;
  }
}
l-cornelius-dol commented 9 years ago

It seems clear to me from this thread that:

  1. Developers cannot not agree on exactly what MVC is and how it should be implemented.
  2. Mithril views are not View objects but virtual DOM specification generator functions which receive their state/context for any particular rendering as an argument in the guise of the "controller" object.
  3. Multiple developers in this community have been confused (a) with the role the "controller" should play and (b) by the fact that the thing labeled "controller" is in fact a constructor.
  4. In the final analysis, Mithril is arguably not MVC, per se, but MVx.

I think it would be an improvement if Mithril presented this object as an untyped state container that could serve whatever role the developer desires instead of using a misnomer that suggests it "should be" something quite specific.

ciscoheat commented 9 years ago

@lhorie nice summary, thanks! Happy to hear your positive words about MVC too. Just a few things about the original MVC: Reenskaug was not the implementer of Smalltalk MVC, Jim Althoff was the primary author. Smalltalk has observables built into Object so it was natural to use there. Of course, the whole language is an incremental research product that resulted in a hideous class inheritance structure. In the words of Coplien, "It's a prototype that someone forgot to throw away."

So Reenskaug used it but has never been happy with the observer pattern. Here's one of his concerns: https://groups.google.com/d/msg/object-composition/b3ivMZ91ZBw/uDFE12DjGskJ

And here's a quote from him answering a question of mine actually (source):

A view is an object, so a view can play a role. In othe view role method actually updates the view, This is more direct than using the observer pattern, for example.

So while I agree with you @lawrence-dol that developers cannot agree what MVC is, the reason probably is that they haven't done their homework, which we cannot blame MVC for.

@barneycarroll I'll keep going in the chat now, talk to you there about your post(s). :)

lhorie commented 9 years ago

Reenskaug was not the implementer of Smalltalk MVC, Jim Althoff was the primary author

I see. On wikipedia, Reenskaug is being credited as being the person who introduced MVC into smalltalk-76 (Jim Althoff is mentioned as an author for the smalltalk-80 version), so I assumed Reenskaug was the creator. Thanks for the correction! As the numbers 76 and 80 may suggest, all this stuff is from before I was born, so I apologize if I get my history wrong ;)

l-cornelius-dol commented 9 years ago

@leeoniya made a similar request in #492.

it's a bit late in the game, but it would be nice to rename controller to constructor, or init in the modules too to maintain uniformity with the suggested view constructor naming. i was a bit baffled by the naming, since all it is is a module's constructor. I know it's from MVC, but in Mithril's case there's pretty much no reason to name it controller when it literally is the constructor and nothing more. all the view binding is closer to MVVM where the views reference models through the module/ctrl.

l-cornelius-dol commented 9 years ago

Frankly, whatever the name of the thing ends up being, in the component object it must end up with a lead capital if it remains a constructor. That name is in application space, not Mithril's, so it should follow proper conventions. It would be less confusing as a constructor if it carried a lead capital:

{
    Controller: MyController,
    view      : new MyView().view,
}
barneycarroll commented 9 years ago

@lhorie this API change is small change compared to components branch, which promises never having to manually invoke a controller. I'd be really disappointed if the terminology bikeshedding took higher priority than the practical gains of components branch.

lhorie commented 9 years ago

Ok, let's try to make this less bikesheddy: assume that Mithril is targeting to be an MVC system. Not MVVM, not MVP, not "technically-MVC-but-you-can-contort-it-into-MVx", not MVx-agnostic. Given the formal definition of a controller (or at least your best understanding of it): What should a controller look like?

barneycarroll commented 9 years ago

model view Constructor! Perfect!

l-cornelius-dol commented 9 years ago

After sitting down the other day and hammering on various ideas, I concluded that the heart of the problem is that Mithril isn't given a controller. Rather it's given a controller creator. It then occurred to me it isn't really given a view either, but a view creator. The former can be expressed as a constructor, but needn't be; the latter cannot be expressed as a constructor.

I am now thinking it would be cleanest if a Mithril component expressed, or designated, a pair of controller/view creators, where the latter is given, as an argument, the result of the former.

In the requested MVC terms, a component would be an object which conforms to:

var aComponent={
    ctrlCreator: function() {
        return {
            xxx: ...
        };
    }

    viewCreator: function(ctrl) {
        return m("div", [
            ...
        ]);         
    }
}

Then Mithril doesn't do controller=new component.controller() but rather controller=component.ctrlCreator().

In MVx terms, exactly the same construct, only ctrlCreator becomes ctxtCreator and ctrl becomes ctxt.

But what if m.component was used to remove the naming from application domain? Via something like:

m.component=function(ctrlCreator,viewCreator/*, optional-args...*/);
barneycarroll commented 9 years ago

@lawrence-dol I guess the way to validate these terms is:

l-cornelius-dol commented 9 years ago

@barneycarroll : Your first point is not necessarily applicable if Mithril has coerced an incorrect conceptualization on developers -- this is often the case when something has delivered the wrong abstraction, or the right abstraction with the wrong name. People may not have named it {{thing}}Creator, but that's what the thing called controller actually is, and it's definitely what the view function is.

To your second point, this is what I meant by "hammering on the various ideas". I have monkey-patched Mithril and I am looking at my code reconceptualized in different ways (as my time permits -- I actually have other, higher priorities right now).

barneycarroll commented 9 years ago

has delivered the wrong abstraction, or the right abstraction with the wrong name

Isn't that entirely the point of this thread?

gilbert commented 9 years ago

Personally I think controller is a fine name if the first thing you see in the docs is an explanation of what it is.

l-cornelius-dol commented 9 years ago

@barneycarroll : I've updated my earlier 2 comments to be clearer.

What I meant by "has delivered the wrong abstraction, or the right abstraction with the wrong name" is that there will likely be no examples of {{thing}}Creator in existing Mithril code because the API belies the reality of what these to items are and therefore developers have been coerced into misnaming things by the API. And this kind of naming may not be widespread in JS in general, simply because most developers do not think about the names of things nearly hard enough, in my experience.

My monkey patching and playing with reformulating my code is what brings me to the conclusion that controller and view are not really what these two things are; rather creating a controller (again, in MVC terms) and creating a view is what they do -- so, ideally, the names of their keys should be verbs, not nouns.

However, the controller creator achieves that aim by means of being a constructor -- so its key name should be capitalized to reflect that; if so then it's a constructor, and by convention its name should remain a noun. And if so capitalized then its name is correct, according to the standard JS convention for constructors, which are named according to the initialization they perform on the newly created object which corresponds to this.

Therefore, in summary, Mithril should conceptualize a component as a binding of a controller creator and a view creator and the names should reflect those functions. If the controller creator continues to be a constructor then its name is currently correctly a noun but the noun should be capitalized to reflect the fact of it being a constructor of the named thing. If the controller is reconceptualized to be a function which returns a controller by whatever means appropriate, then its name should change to a verb-form. Finally, I still contend that the controller should be renamed to something which aligns with MVx, so that Mithril is not misleadingly cast as strictly limited to MVC.

nijikokun commented 9 years ago

The Controller is technically supposed to initialize or reference the view to be used (see java reference below to how a controller works).

In many of these examples (here and in the current documentation), the controller is actually the parent object containing the property we currently call the controller.

Java Reference

public class RealLoginController implements LoginController {
    private LoginViewFactory viewFactory;

    /* constructor */
    public LoginController(LoginViewFactory viewFactory) {
        /* controller referencing view */
        this.viewFactory = viewFactory;
    }

    /* example of view usage */
    public ModelAndView handleRequest(HttpServletRequest request, HttpServletResponse response) {
         if (isLoggedIn()) {
             return viewFactory.createLoggedInView();
         } else {
             return viewFactory.createLoggedOutView();
        }
    }

    // ...
}
leeoniya commented 9 years ago

creating a controller (again, in MVC terms) and creating a view is what they do -- so, ideally, the names of their keys should be verbs, not nouns.

+1 from me.

something like getCtrl/getView, mkCtrl/mkView, makeCtrl/makeView, initCtrl/initView. Ctrl in any of these can be renamed to Ctx, State, etc. to keep it MVx-agnostic. I'm personally not a fan of Scope since it carries language terminology baggage.

atis-- commented 9 years ago

There is no divine and absolute meaning to any of the proposed names, as the incessant ramblings seem to suggest... Is there an actual benefit to renaming the thing? Like, if we called it X, then people would immediately know what it is, and how to use it better, or be more likely to try Mithril, or some other reason? If so, how do we know (beyond our own personal feelings)?

The 'view' could have been called a 'template' or 'display' or 'layout' etc. 'View' is a nice, short name. But it wins because it's right there in the popular acronym MVC. Same story for 'controller' (see first paragraph of lhorie's getting-started guide). I would argue that MVC as a letter combo beats the others (MVVM, MVx, MVP, MVWTF) in popularity, so it's easier for newbies to recognize the basic idea.

barneycarroll commented 9 years ago

@atis-- couldn't agree more.

nijikokun commented 9 years ago

@atis-- @barneycarroll

There is no divine and absolute meaning to any of the proposed names, as the incessant ramblings seem to suggest... Is there an actual benefit to renaming the thing? Like, if we called it X, then people would immediately know what it is, and how to use it better, or be more likely to try Mithril, or some other reason? If so, how do we know (beyond our own personal feelings)?

feelings aside

It's not a controller.

It isn't referencing the view (whatever you want to call it).

The object dwarfing the view and controller is the actual controller (the example below is on the homepage, if you have another example that conflicts with this please let me know and I will address that).

// This is not the "namespace" this is the ACTUAL controller
// according to how a controller should act in the namespace of MVC
var app = {};

// this is a model which should be done outside of this instead of inside the
// controller, and then initialized within it, but whatever best pratices.
app.PageList = function() {
    return m.request({method: "GET", url: "pages.json"});
};

// this is the constructor
app.controller = function() {
    var pages = app.PageList();
    return {
        pages: pages,
        rotate: function() {
            pages().push(pages().shift());
        }
    }
};

// this is the view
app.view = function(ctrl) {
    return [
        ctrl.pages().map(function(page) {
            return m("a", {href: page.url}, page.title);
        }),
        m("button", {onclick: ctrl.rotate}, "Rotate links")
    ];
};

// Renderer / Instantiator / Module / whatever.
m.module(document.getElementById("example"), app);

Otherwise it would be something like this

// Namespaced application
app = {}

// Model Class
app.Model = function () {
  return m.request({method: "GET", url: "pages.json"})
}

// Main View
app.mainView = function () {
  var ctrl = this

  return [
    ctrl.pages().map(function (page) {
      return m("a", { href: page.url }, page.title);
    }),
    m("button", { onclick: ctrl.rotate }, "Rotate links")
  ];
}

// Main Controller
// Optionally can accept a view (not shown below), or reference a view itself to be 
// initialized at its own digression or at the cores (core being Mithril.js)
app.mainController = function () {
  var self = this

  // controller handles a new model
  this.pages = new app.Model()

  // allow for later invocation
  this.view = app.mainView.bind(this)

  // controller methods
  this.rotate = function () {
    self.pages().push(self.pages().shift())
  }
}

m.module(document.body, app.mainController)

I agree that MVC is popular and newbies recognize it, that does not mean they understand it, and teaching them incorrect practices isn't going to help, and calling yourself something but not being it will only harm you in the long run, this thread is advocates that point pretty well.

gilbert commented 9 years ago

MVC is a fine name for Mithril. As @lhorie said earlier this thread, the core flow of data is more important than anything else.

lhorie commented 9 years ago

@Nijikokun afaik, there's no requirement for a controller to contain a view. Backbone, Maria and Cycle are examples of js mvc frameworks where that doesn't happen (assuming you use them correctly). The Java thing you mention is more of an unfortunate implementation detail, I think. In Play Framework, for example, a controller doesn't explicitly state what view is attached to it. There, the relationship is established by conventions.

@lawrence-dol re: uppercasing classes, one of the things I'm doing w/ the documentation for the components branch is change the controllers so that they return an object, instead of using this. People will still be able to use constructor semantics for convenience and for backwards compatibility, but for all intents and purposes you can think of controller as a function by default. Whether the code internally uses return new x or var y = {}; return x.call(y) || y ought to be considered an implementation detail that has no bearing on API conventions.

tinchoz49 commented 9 years ago

"An important aspect of the original MVC was that its Controller was responsible for creating and coordinating its subordinate views."

This is what is doing the mithril controller...or am i wrong?

tinchoz49 commented 9 years ago

or maybe we can change "controller" by "editor"

http://heim.ifi.uio.no/~trygver/2007/MVC_Originals.pdf

ciscoheat commented 9 years ago

It's quite hard to make a framework enforce best practices. And as long as we don't start reasoning in terms of objects we're not gonna get far in the discussion of getting closer to the original MVC. MVC is about objects and end-users, because we code for them and they think in objects.

I have only seen Trygve use Editor very sporadically the last years, but I'll ask him what he thinks about the term nowadays. I'll be back with his response.

ciscoheat commented 9 years ago

@tinchoz49 A remark about Editor: I don't think it's generic enough. A Controller should be able to coordinate anything in a system, not just things regarding editing. The word is probably well chosen for that reason.

nmai commented 8 years ago

I'm in favor of renaming controller to something else too. Mithril is the first MVC framework that I've really dedicated myself to learning. But I realize it's not your typical MVC framework. Mithril doesn't attempt to force a certain programming paradigm on developers, thus I feel it should not use traditional naming conventions. It's a different beast entirely. I feel like a great deal of confusion could be avoided if we renamed controller.

Alternatively, we could add a page to the docs discussing the purpose of controller and demonstrating a few of the different ways it can be used. I might actually prefer this. It shouldn't be necessary to rename controller if we make it clear from the very start what the differences are.

avesus commented 8 years ago

It is just the controller view pattern: http://blog.andrewray.me/the-reactjs-controller-view-pattern/ On Sun, Nov 29, 2015 at 9:20 AM Nick notifications@github.com wrote:

I'm in favor of renaming controller to something else too. Mithril is the first MVC framework that I've really dedicated myself to learning. But I realize it's not your typical MVC framework. Mithril doesn't attempt to force a certain programming paradigm on developers, thus I feel it should not use traditional naming conventions. It's a different beast entirely. I feel like a great deal of confusion could be avoided if we renamed controller.

Alternatively, we could add a page to the docs discussing the purpose of controller and demonstrating a few of the different ways it can be used. I might actually prefer this. It shouldn't be necessary to rename controller if we make it clear from the very start what the differences are.

— Reply to this email directly or view it on GitHub https://github.com/lhorie/mithril.js/issues/499#issuecomment-160432994.

pygy commented 8 years ago

@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 {controller, view} and the new {init, view} syntax. It would enable users to migrate their apps peicewise, rahter than warranting a big rewrite.

l-cornelius-dol commented 8 years ago

@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).

dead-claudia commented 8 years ago

Could I get a quick description of this proposed "init"/"view" syntax? I haven't been on Gitter much today.

On Sun, Dec 20, 2015, 22:05 Lawrence Dol notifications@github.com wrote:

@pygy https://github.com/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.

— Reply to this email directly or view it on GitHub https://github.com/lhorie/mithril.js/issues/499#issuecomment-166185005.

l-cornelius-dol commented 8 years ago

@isiahmeadows : https://github.com/lhorie/mithril.js/issues/499#issuecomment-83591478

But it's well worth reviewing the entire thread.

barneycarroll commented 8 years ago

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 controller or vm. My proposal is to have components be functions (classes if desired) whose only requirement is to return a view method. Under this proposal, the front page component example becomes:

// 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 this to a variable named ctrl or simply create an object in the component closure, that's fine. If anything you have more flexibility in how you structure a component.

l-cornelius-dol commented 8 years ago

@barneycarroll :+1: 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 load / view / unload tuple.

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.

barneycarroll commented 8 years ago

@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 class and Object.create do not mandate constructors: in fact these structures make for 'purer' view-only components since the signature no longer mandates a reference to the leading ctrl argument (make that an arrow function and you have effectively enforced purity by preventing any reference to this)...

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 this if they so desire (so you can kiss goodbye to painstakingly imperatively defining, attaching and binding your methods in a function call).

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 this and explicit return statements can even get by with nothing but arrows and free floating variables per instance.

ciscoheat commented 8 years ago

@barneycarroll that looks great, I'm completely with you about doing away with the controller function, since the object itself is the Controller. Same with the vm, the View object can play that role.

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.

michaek commented 8 years ago

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.