MithrilJS / mithril.js

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

Reusable components. #413

Closed vivekimsit closed 9 years ago

vivekimsit commented 9 years ago

Hi,

What about creating a repo which contains a set of reusable UI components? something like Angular-UI or Khan Acacdemy

I am ready to contribute.

Cheers!

StephanHoyer commented 9 years ago

We all thought about this, finally one is speaking it out ;)

I think there are a few problems to solve. Everyone has opinions about how thinks has to be solved so these components should be flexible enough.

This is normally done with configuration. But if you do this, the components tend to get quite complex and large.

On the other hand, with mithril things got quite simple to implement. So in most cases it is easier to reimplement your component specific to your usecase.

Other questions that arise:

This shouldn't keep us away from doing it. Maybe open-source-evolution will figure this out.

vivekimsit commented 9 years ago

Yes,

From this project we will learn:

  1. How awesome mithril is.
  2. How to creates components which are easy to compose.
  3. Mithril best practices which others can follow.

Would like to listen from others too :)

Thanks!

StephanHoyer commented 9 years ago

just found https://github.com/mithril-ui

Seems monolithic and outdated. Also it's bound bootstrap what I personally don't like.

There is also https://github.com/ng-vu/mithril-bootstrap from the same person (@ng-vu)

eddyystop commented 9 years ago

Satyam and I wrote one of the earliest component repos https://github.com/eddyystop/mithril-components. We thought then a standard set of Mithril components was important to acceptance, and I still think so now.

The problem has always been the lack of a canonical component design that turtles all the way down, and allows some components to expose some of their state to others. You'd want components from different authors to play nice with one another.

Like other people, I've rolled my own components, including some sophisticated ones, yet they can't be shared.

iOliverNguyen commented 9 years ago

Hi guys,

I created mithril-bootstrap just for experience how to structure projects and components in Mithril. For now, I agree that it's easier to reimplement components for our specific usecases or wrap other libraries in Mithril-style than force everyone to follow a single style.

JavaScript is evolving rapidly. Yesterday I used RequireJs, today Browserify and tomorrow maybe ES6 Modules. It's up to people to organize their projects and requirements.

vivekimsit commented 9 years ago

Ya, it would not be that trivial but I think its a good project to start with. After all it can show the real power of mithril.

WDYT?

StephanHoyer commented 9 years ago

@ng-vu Totally agree.

Thats why I also think the web-components movement is a dead-end. Easy-to-implement custom tailored components are the way to go. That's why mithril rocks ;).

So conclusion of this issue would maybe to create a list of possible patterns for mithril components where anyone can get inspired and inspire others.

Don't quite know how to achieve this. Maybe a wiki, but this tends to outdate.

Any ideas?

tracim commented 9 years ago

Hello,

I believe a component store would be very nice but I also understand that a reusable component is very complexe to implement because of the required flexibility.

What about a component snippet store ? You would be able to share your components without to search for flexibility. Others can reuse and adapt them to their own needs and maybe can share their new version with different features.

For Boostrap related stuff, there is http://bootsnipp.com/ which offers exactly this kind of stuff. You find code snippets which are ready to use ; but most of the time you adapt them to your own needs.

Damien

Le 16/01/2015 11:50, Stephan Hoyer a écrit :

@ng-vu https://github.com/ng-vu Totally agree.

Thats why I also think the web-components movement is a dead-end. Easy-to-implement custom tailored components are the way to go. That's why mithril rocks ;).

So conclusion of this issue would maybe to create a list of possible patterns for mithril components where anyone can get inspired and inspire others.

Don't quite know how to achieve this. Maybe a wiki, but this tends to outdate.

Any ideas?

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

eddyystop commented 9 years ago

@tracim, I find the idea of a snippet store interesting. In addition to what you mention, it sidesteps the issue of what CSS to decorate the tags with e.g. custom classes (m-dropdown-component) vs Bootstrap classes.

barneycarroll commented 9 years ago

:+1:

I think this is a great idea. Too many people want stuff that just plug and plays, but the truth is you can't write terse and elegant plugins that accounts for everybody's intended usage and caters for all their desired configuration with totally plug-and-play code. People have tried to do this in the past and ended up with the huge bloated nightmares that are jQuery UI and Angular.

If anything, Mithril's philosophy is that functional code should be kept short and sweet and obvious, that way users can learn a little by looking into it and customise it to fit their situation.

Plus, this encourages development and configuration over dumb copy+pasting, which has got to be good for the community.

vivekimsit commented 9 years ago

:+1:

Snippet store seems interesting idea. That will be a kind of cookbook.

jdnier commented 9 years ago

A Mithril Cookbook would be a great resource, given the way people seem to recommend developing with Mithril (solving your own problems effectively and not writing a mini-framework about it). Having several recipes for solving a focused task can be useful when you're learning. Depending on your coding approach (functional, prototypal, class-based), there might be several idiomatic implementations for the same recipe. One concrete example of how this could work is ActiveState's language-specific cookbook recipes; several of the subsites have even been turned into O'Reilly books. Collecting recipes for solving common tasks would be a good start.

lhorie commented 9 years ago

If anyone wants to take a stab at organizing the wiki, feel free.

zserge commented 9 years ago

I agree that Cookbook or a collection of snippets would be a perfect way to learn Mithril and to help people make their own custom components. But Mithril needs to gain popularity and I think if a newbie will see just a list of recipes, and he won't be able to npm-install then to quickly test - he will be disappointed and will go back to React with their react-components repo. I personally find Mithril much more solid and useful than React. But I still have to use React because of its Material Design components, and because I can't assure my colleagues to use Mithril since nobody ever heard of it. So recipes are important to learn Mithril. But pluggable components are important to start quickly use Mithril and to help it become popular.

eddyystop commented 9 years ago

@zserge are you referring to https://github.com/callemall/material-ui ?

zserge commented 9 years ago

@eddyystop Yes, subjectively it seems to be the most usable set of Material components to write mobile HTML5 apps (I've also checked Bootstrap Material, Angular Material and Polymer Project).

vivekimsit commented 9 years ago

Totally agree with @zserge

In fact to develop a strong community we need a strong POCs

cheers!

eddyystop commented 9 years ago

I would think callemall/material-ui custom less CSS would concern some people, as opposed to, say, Bootstrap 3. Any information on this?

I guess one idea is to keep someone's material UI less and redo the JS in Mithril.

zserge commented 9 years ago

@eddyystop You mean they're using different element classes than Bootstrap? I don't see a problem here, since these two are very different:

And I totally agree that "borrowing" LESS styles from the material-ui would simplify things a lot. Or maybe from the Polymer, which also looks nice on mobile.

eddyystop commented 9 years ago

@zserge "Polymer is based on a set of future technologies, including Shadow DOM, Custom Elements and Model Driven Views." So its further removed from the Mithril philosophy than Material-UI or Bootstrap.

The Material-UI and Bootstrap CSS can force the HTML to be structured differently. So you'd need a separate library for each, though they would be similar.

zserge commented 9 years ago

@eddyystop Yes, I agree that Polymer is very different from Mithril and not suitable for porting. I copy-pasted some CSS blocks from there in the past to mimic material UI, so it's possible, but not convenient at all.

React's approach is much nicer, of course.

Also I don't think Bootstrap for Mithril and Material UI for Mithril would have any similar code at all.

Anyway, it looks like we agreed that Bootstrap and Material should be implemented for Mithril. Other ideas? If we make a list of components - we can decide how flexible they should be, probably some requirements and implementation details etc and actually we could start working on them.

eddyystop commented 9 years ago

@zserge, I replied in the Mithril google group ( https://groups.google.com/forum/#!topic/mithriljs/4C4xwYtanjY ) as that may be a more appropriate venue.

eddyystop commented 9 years ago

I'm comfortable with the info I've picked up in the Mithril google group. Is anyone at least semi-serious in developing a UI library?

vivekimsit commented 9 years ago

:+1: of course I am the one.

corymickelson commented 9 years ago

Yes

eddyystop commented 9 years ago

My thoughts:

My suggestion is that we contribute to https://github.com/philtoms/mithril-starter-kit. Its the starter library for mithril.elements and its based on Bootstrap. Its current components illustrate how to use mithril.elements.

One approach to contributions would be to take the Bootstrap docs

I'll be rewriting components I have for dropdowns and for nav bars.

pelonpelon commented 9 years ago

Although I'm not a fan of the bootstrap look, I do think it's the appropriate choice if we want to popularize mithril.

Bootstrap makes extensive use of jQuery. I wonder if we might start out by formalizing 'standard' javascript snippets to replace jQuery equivalents, specifically in regards to functional programming and object manipulation. I'm not sure how to treat DOM manipulation vs. virtual DOM manipulation. That might require evaluation on a case-by-case basis. (I'm assuming we want mithril components to function without dependencies)

We might also want to start a 'best practices' guide.

I don't want us to get too bogged down, we should just start hacking now. But having a single source of truth early on might save a lot of refactoring.

eddyystop commented 9 years ago

I've converted various Bootstrap "javascript components" that use jQuery into mithril plus pure javascript. Its usually straightforward and the resulting mithril code is much easier to understand.

You just have to know what an action would cause and implement the effect in Mithril. Consider a dropdown. Bootstrap manipulates the DOM with jQuery to show or not show the dropdown items. With Mithril you check view-model state to see if the dropdown is open and render the items or not. Much simpler.

I implemented the complex Affix component ( http://getbootstrap.com/javascript/#affix ). Its the sidebar menu floating on the right of the page. You'll see it "land" when you scroll to the top of the page. The key was figuring out what Bootstrap's jQuery addon was doing to the DOM.

The take-aways are that its not hard to implement Bootstrap "javascript components" and that you shouldn't focus on the jQuery.

barneycarroll commented 9 years ago

Most of the time plugin behaviour consists of querying values and changing 'nearby' DOM attributes, which is trivial with Mithril. It's been mentioned before, but things get a bit confusing if you've been using modules where DOM hierarchy matches model hierarchy (why wouldn't it?) and suddenly you discover a situation where that isn't the case (tooltips – crap!). I recently came up with this pattern for defining a view in one place and outputting it in another, which might come in handy for the many components that require some form of overlay. http://jsfiddle.net/barney/Laxn4jem/

Regards, Barney Carroll

barney.carroll@gmail.com +44 7429 177278

barneycarroll.com

On 23 January 2015 at 17:19, Eddyystop notifications@github.com wrote:

I've converted various Bootstrap components that use jQuery into mithril plus pure javascript. Its usually straightforward and the resulting mithril code is much easier to understand.

You just have to know what an action would cause and implement the effect in Mithril. Consider a dropdown. Bootstrap manipulates the DOM with jQuery to show or not show the dropdown items. With Mithril you check view-model state to see if the dropdown is open and render the items or not. Much simpler.

I implemented the complex Affix component ( http://getbootstrap.com/javascript/#affix ). Its the sidebar menu floating on the right of the page. You'll see it "land" when you scroll to the top of the page. The work came down to figuring out what Bootstrap's jQuery addon was doing to the DOM.

The take-away is that its not hard to implement Bootstrap "javascript components".

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

pelonpelon commented 9 years ago

It might be useful for anyone attempting to convert bootstrap-jQuery to bootstrap-mithril to take a look at https://github.com/react-bootstrap/react-bootstrap, especially for clues when to redraw and in rare cases when you need direct DOM manipulation.

I seem to remember issues with click responses on iOS and the need to get this directly from the DOM.

vivekimsit commented 9 years ago

I am also not a big fan of bootstrap, I like polymer more and after that the material design implementation by react is awesome.

  1. https://www.polymer-project.org/docs/elements/
  2. http://material-ui.com/#/components/buttons

Also, one thing about polymer is that it doesn't depend on any other third party library

barneycarroll commented 9 years ago

I think Bootstrap fulfils a different role (as far as we're concerned) in that it provides standard functionality and appearance for various standard UI components that native HTML doesn't do it off the box. Implementation is kind of besides the point (I'm looking at using a Stylus fork, for instance, for the sake of isomorphic CSS logic).

When I'm discussing how to represent a piece of functionality with a UX specialist, it's trivial to be able to suggest a composition of Bootstrap components that provide the necessary low-level functionality. Whether I'm going to use jQuery or SASS or the baked-in Bootstrap theme is besides the point — what matters is that we can easily communicate in shorthand about standard user interaction mechanisms in terms of end-user functionality without bike-shedding about implementation details.

Polymer on the other hand says everything is possible, but has reduced value in that the specifics get lost.

To put it another way: people who want to reinvent the wheel with a standardised low-level API for defining the roundness of the wheel will be interested in Polymer. People who assume a generic roundness out of the box and just want a variety of different wheels (with some tweaking down the line) will want a Bootstrap. You could implement Bootstrap components via Polymer, and implement the Polymer runtime in Mithril for all I care, but Bootstrap has (really) useful specifics.

Which do we want to work on? I think the problem here is one of PR — we want to solve other people's problems but we're not sure who those people are.

On Saturday, 24 January 2015, vivek poddar notifications@github.com wrote:

I am also not a big fan of bootstrap, I like polymer more and after that the material design implementation by react is awesome.

  1. https://www.polymer-project.org/docs/elements/
  2. http://material-ui.com/#/components/buttons

Also, one thing about polymer is that it doesn't depend on any other third party library

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

Regards, Barney Carroll

barney.carroll@gmail.com +44 7429 177278

barneycarroll.com

pelonpelon commented 9 years ago

If you're a programmer with in-depth experience in another framework (angular, ember, backbone), there is likely a bootstrap->yourFavoriteFramework conversion on github that should speed you along in creating mithril components. I'm sure that's true of polymer and maybe of material design, but I suspect the bootstrap libraries are better tested across browsers.

I vote for bootstrap.

zserge commented 9 years ago

@eddyystop I see a lot of people tend to agree with mithril.elements approach. I feel really dumb, but what is the benefit of using mithril.elements? I always seen mithril components as just a view function. It may have a predefined private controller, it may export some attributes, getters, setters, it may receive some arguments to configure its behavior. Something like this (I'm far from being a good JS developer, sorry): http://jsfiddle.net/96356ngx/

But with mithril.elements approach I see a few disadvantages:

Could you please clarify what is the reason against using pure JavaScript + Mithril for components?

philtoms commented 9 years ago

@zserge, the intended benefit of mithril.elements is to extend the DOM through custom elements. It does this by aligning standard mithril components with the prevailing DOM life-cycle. This might not seem like a big deal (and you can view the code to see that it isn't from an implementation perspective) but it allows you to compose complicated element types and use them intrinsically. Also, the resulting 'elements' play very well with standard mithil views and allows 3rd party library elements to be composed idiomatically. Thats the point of mithril.elements really.

The another library issue depends on whether you are a one-core supporter or whether you prefer small component based solutions I guess. I can't really see it as a technical disadvantage though, especially in the light of tooling like Browserify and Webpack (Mithril Starter Kit is geared to deliver all modules through Webpack).

By not type safe I assume you mean tag names are strings rather than object instances? There is a discussion going on about this here but briefly, I don't see why string based element tag registration is any less typesafe / autocomplete friendly than mithril's current string based tag identification scheme.

I wont try to argue your last point, it seems subjective, and anyway, I think most of us already enthralled by mithril will welcome Leo's latest development ideas towards element / tag support. I'd like to think that mithril.elements as played its part in helping to expand the scope of this fantastic framework.

eddyystop commented 9 years ago

@zserge, @lhorie is looking into moving something similar to mithril.elements into mithril core.

Let me look at this from a code-on-the-page perspective. mithril.elements showed that sub-component controllers can be automatically instantiated in the component view for many of the sub-components we are likely to use. We no longer need the boilerplate in component controllers to do this. This saves a fair amount of LOC with associated issues and, let's face it, it looks good.

Beyond that, there are advantages to any standard way of doing things. (And disadvantages.) I think one of the main reasons the different initial Mithril UI repos went dark is because each had its own 'standard'. mithril.elements is a potential standard that does several things rather well.

Your items 1&3 are handled assuming the new m.tags[] lands. I agree with @philtoms on # 2, mithril is already string based for tags.

StephanHoyer commented 9 years ago

I use a pattern I call reveal-view-pattern for some of my components. The component is pretty much a controller that returns a view function instead of a scope. The view function then can be customised elements-wise.

Here is my inline edit component which uses this pattern.

zserge commented 9 years ago

@StephanHoyer This totally matches the way I've seen the components for mithril! This looks more pure to me, it requires no changes to Mithril and it clearly separates DOM nodes (quoted strings) from components (functions). You can also easily deal with namespaces here, if two libraries provide, say, modals, you can assign them to two different variables and use both, while with strings you can only hope that another developer didn't take a cool component name "modal".

StephanHoyer commented 9 years ago

Yes, that's the point. Me personally, I find the mithril syntax pretty ugly but it's better than adding another template language or creating functions for every span, div, table and so one. It's a pretty solid solution but you don't want to piggy-back on this, if you don't have to. That's why I use functions for this. They are more flexible and packaging and integration are far simpler.

vivekimsit commented 9 years ago

@StephanHoyer :+1:

lawrence-dol commented 9 years ago

For those here who are not participating in the Google Group for Mithril, just to make you aware of my proposal for creating components in Mithril, which as of version 0.1.29 is nearly all the way there:

https://groups.google.com/forum/#!topic/mithriljs/wZgudHBk210

and the example Todo mini-app:

http://jsfiddle.net/LawrenceDol/q4yk17r3/

zserge commented 9 years ago

@lawrence-dol This seems to follow the current vision of components (http://lhorie.github.io/mithril/components.html), and matches my vision of them. So I still wonder what's wrong with this approach and why something new should be added to Mithril? How is m('filter'') better than filter.view() or just filter() in some cases?

StephanHoyer commented 9 years ago

@lawrence-dol looks interesting, but your variable names are awful to read... took me a time to get the whole thing.

Your attempt pretty much aligned with my attempt. The only difference is that I return the view function directly and avoid the use of new and constructor-stuff but use only functions.

But I still don't get, in which way mithril has to be improved to make it component ready?

zserge commented 9 years ago

@StephanHoyer @lawrence-dol So far I've seen three ways to implement components. I've combined them all in one place in this jsfiddle: http://jsfiddle.net/hnp4vdxv/

Please, have a look.

The first way is to make a view-function. That's my favourite one. It's very easy to use (code is very short and readable), but it's not Mithril-way, since there is no separate controller, it's hidden inside a constructor function. Still, it resembles me the "partially applied modules" that Leo mentioned in his blog: http://lhorie.github.io/mithril-blog/module-partial-application.html Resulted components can accept options during their creation, can expose public API, and can access child views or attributes if needed (like c.modal({className: '.my-modal'}, m('.content', m('p', 'Some Text'))), where c.modal is a component view function).

The second way is a typical Mithril module, this was described by Leo here: http://lhorie.github.io/mithril-blog/organizing-components.html It's also nice, it needs a bit more code to use components, but it's pure Mithril way (controller constructors and view functions with controller arguments). As in the first case, resulted components can accept options in their controller constructores, controllers may have some public API, views can take custom attributes etc.

The third way is new Mithril core via m.tags. Components are created much in the same manner as in the second approach. Using components seems to be easy (as much as in the first approach), but I don't see a way to use components API. Am I wrong here?

So far I pick the first way, since it's important to let people use components easily, and I don't see much disadvantages of the first way. The second way looks a bit bloated, but still is viable. The third way is a big surprise to me, since it seems to be the least flexible. So why is there so much discussion around it and what's wrong with the first and the second ways?

StephanHoyer commented 9 years ago

I always saw controllers as optional. Only if you want to put a route on it they become mandatory. So I don't see why the first attempt goes against any mithril rules. I find it by far the most pretty one.

eddyystop commented 9 years ago

@zserge and @StephanHoyer your opinions would be worthwhile in a rather big discussion happening about components in the Mithril Google Group.

For example: https://groups.google.com/forum/#!topic/mithriljs/wZgudHBk210 https://groups.google.com/forum/#!topic/mithriljs/f-asji8qoFM

StephanHoyer commented 9 years ago

Ok, then maybe let's close this issue here.

lhorie commented 9 years ago

The m.tags approach solves one important problem that the other two do not: it lets the framework manage component lifecycle. Say you have something like this:

function myView(ctrl) {
  return ctrl.someData.map(function(item) {
    return m("MyComponent", item.name)
  })
}

Say MyComponent has to clean up some web socket subscriptions when it unmounts. With the first two approaches, if you delete an item, you have to manually call onunload on the respective component, (plus sync up the list of components), which is a pain.

The trade-off that comes w/ m.tags, as @zserge mentioned, is that w/ m.tags you're no longer able to consume a component's controller API from a parent controller.

Now, the question of whether a controller prototypal API should be consumable from another controller in the first place is a much bigger conversation.

In Lawrence's example, you'll see that TodoList depends on SimpleTextFilter (i.e. the SimpleTextFilter instance is injected into TodoList as an argument and is expected to have a .matches public method). But if instead you needed a AllFieldsSearchFilter, you'd be duplicating the template for SimpleTextFilter. This may be fine for a single input tag, but starts looking like a code smell if the UI control has more complex markup (e.g. an autocompleter).

I talked about that topic here ( http://lhorie.github.io/mithril-blog/an-exercise-in-awesomeness.html ). The gist is that while tying logic to controllers is not disallowed and it accomplishes the task at hand, it can cause maintenance problems down the road.

Changing the signature of a module from {controller: () -> {}, view: () -> {}} to () -> {view: () -> {}} doesn't fundamentally change anything. Getting rid of the word "controller" isn't enough to make controller-less components. All that happened is that now the constructor takes the role of the controller. You can still stuff logic there, and you can still run into the same maintenance problems.

The alternative is to move filtering logic to the model layer. This is essentially what is advocated w/ architectures like Flux and MVI (which, imho, are all classic MVC w/ different names). These architectures use the observer pattern to chain reactive dependencies. In my own code, I tend to simply have a two step process: various action handler methods to respond to specific actions (e.g. remove item from list), and a update everything method that gets called at the end of action handlers (this update-everything method may or may not be the same as the model object initializer, depending on whether I want to reload from the server or not). In the filtering case, the update-everything method would update a filteredItems property, and that is the property that would populate the list from the beginning. In this way, the list component is completely agnostic of whether filtering occurs or not.

The role of a component (in the m.tags sense), as I see it, is to encapsulate state that you don't care about outside of the component's scope (e.g. whether the suggestion list in an autocompleter is currently visible). State that pertain to the application should still follow the MVC flow if you want to be able to manage it effectively.

StephanHoyer commented 9 years ago

Thanks for clarification.

Can't we just grab the base element of the component and use the config -> context.onunload here?

lhorie commented 9 years ago

@StephanHoyer it gets ugly in some cases. As I mentioned, one consequence of having a component that exposes methods for controller consumption is that you then need to keep track on component instances in your own code. So, again, with the list example, removing an item from the list requires that you also remove the corresponding component from your list of components. If you have to sort the list, you're essentially going to be forced to re-implement the keys algorithm in app space.

Also I'm not super convinced that OOP-style cross-controller communication scales. Past a certain depth of components, you're likely going to be running into silly things like parent.parent.parent.child.child.child.doSomething()