I really would like to discuss this idea of renderables for everything.
I apologise in advance, this is going to be long. Perhaps the integrator in me coming out to describe fully what I want to convey.
As Fred mentioned at the start of his post, prototype B is appealing in that the "magic" lies documentation and understanding, the actual code is simple and familiar.
But there are hurdles for it.
Prototype A is object heavy, really too object heavy. But it tries to solve a couple of the hurdles faced by prototype B.
I wrote that approach and even I am not a fan of it, but it does the following things that I think are important and from it I've forms the following impression of the situation.
** disclaimer, I am discussing ideas, not specific elements. Please DO NOT tell me that element A should not be an element, I am not discussing what should and shouldn't be, but how generic things should and shouldn't be. Sad faces for anyone who chimes in to tell me that a search component is silly.
- When within a render method and a renderable "property" (coming from anywhere) is encountered the render method calls render on it. It never renderers it itself unless it is absolutely necessary.
Why. Because when in my theme I override the render_button method I want to see it take effect on every button used in every component, and then if I need to change how a button looks for a component I want to target that component explicitly, overriding how it produces the button (more on that shortly).
If you have a search component, and you've styled how buttons look in your theme of course you want the button in the component to inherit initially from your "standard" button.
This implies the following:
a. the button must be a renderable, in turn as button is just an example, anything you can design must be a renderable.
It assumes:
b. we want to re-use our design to achieve consistency. Starting at the smallest element we want to design up, if you've a component that needs an element to look unique the component has to do the work, and I would expect justify why it is unique (another sad face for the first person to say "but that doesn't fit with bootstrap")
- When rendering an element output context (not to be confused with Moodle contexts) should be given to provide background and flexibility on what is being rendered.
I think this is a real devil, its very tempting when rendering a component that contains a button onto which you must set attributes that the component render method creates the HTML for the button with the requires HTML attributes.
If you consider point 1 above a rule, then this approach would break it. By having the component render method produce the button your button no longer inherits the button design of the theme. It is not consistent through the render chain.
To allow for this we give the render method context of what is being rendered.
When rendering an element by itself there is no context given, when rendering the same element within a component the component gets indicated to the render method.
That sounds a bit convoluted, and I myself misinterpreted the design of this in prototype A.
This is how I achieved it in prototype A.
There I introduced an output chain. When you called render on a component a "prerender" method was called, that populated a heirarchy for the component, so that each element contained within the component knew who it belonged to.
That hierarchy was then used when render was called, the magic render method we already did looked for additional render methods that may or may not exist within the render chain.
Using the menu as an example:
- Their is a menu component
- The menu component contains elements Menu > Element[]
If you had a link that contained a button the following render methods would be looked for.
- render_link_button
- render_button (this you should have anyway, if element is a button this is render_button)
Within core/a plugin you could define the first method if you had specific needs that were not already being met by the default method such as attributes.
When overriding the renderer in a theme methods would be looked for in the following order.
- theme renderer :: render_menu_element
- theme renderer :: render_element
- core renderer :: render_menu_element
- core renderer :: render_element
So you can see how defaults are respected here. When the theme says "this is how an element looks" that is the default that is then applied to the element in all of its output contexts.
Worth mentioning is that I don't think that I got the approach quite correct in Prototype A.
I've used the component in the method name to provide context, however thats not the complete context.
The complete context would be to include the purpose the element serves as well somehow.
So in the case of a complex component where more than one element of the same type were used for different things they would be distinguishable.
It is already complex enough as it is of course.
- Components can impose properties onto elements that belong to them.
This comes about again so that we adhere to point 1. We call render on everything. In point 2 you can see how render methods allow for output customisation to occur, the default is preferred for consistency but you can take control if you want.
Point 2 however is very much limited to output. You can add attributes, change HTML etc. However there is also this notion of properties.
Properties describe something about an element in relation to the component it is used within/
A great example of this is that a menu node can be active, to describe the element in the menu as relating to the page the user is looking at.
The element in the menu could be a number of different elements (link, button, search component etc) and this property should apply to all of them. However it only applied when those elements are used within this component.
It's super duper important to differentiate active as a property from say a CSS class ".active".
.active is how the active property may be described. But that won't apply to all frontend designs. Some designs may want to use a different class, or another attribute, or even a different tag.
Prototype A facilitates this on the core_ui_element class, in which methods exist for adding, and setting properties.
- There is a finite number of elements and components
This doesn't really fit as a rule, but I think its extremely important to mention.
Don't get the idea into your head that you will be writing elements. You won't be. Elements are the basic building blocks they will be defined within core and they will change rarely. Written once, used often.
Don't get the idea into your head that you will have to write components often. You won't. Components should be reusable. They should not be specific to what you are creating unless what you are creating is truly absolutely unique and new.
We want to be able to create 99% of our interfaces with the elements and components in core.
Even then you will need to write one component, maybe a couple if you're determined to make themers lives hard again.
Tim I think I can hear you arguing this already. But really I don't believe it will happen but once in a blue moon.
Those points I consider to be important.
It's very important to note one other thing.
All of this magic in regards to which method gets used, I think these rules only apply core/plugins.
Themes, I would like to think that themes can do what they want.
This approach is about building a consistent system that they can use, and that favours minimal work as I see it in themes.
If they choose to override the render_menu and render absolutely everything in there I think that is entirely acceptable.
It is still up to the themer to craft the end result that they are looking for in the way they want.
But hopefully this magic provides them a way to do it easily, making the page easy to restyle.
I am sure that with strict rules and proper understanding we can apply these to prototype B.
We can require people to always call the render method, we can invent a way to publish theme defaults methods past the core more exclusive methods, and we can continue to employ dark magic (dom manipulation, I really dislike this, I wish there was a better solution).
Obviously we've got to make a decision, the decision we make is what we will live with.
There is more unknown with the OO approach, and for sure it is complex.
But I don't think it should be dismissed lightly.
I've tried to simplify the approach a bit and have come up with these two alternatives:
- https://github.com/samhemelryk/moodle/compare/output_prototype_2
This again takes the OO approach but tries to simplify the classes and method chain, making them more simplistic and perhaps more appealing. - https://github.com/samhemelryk/moodle/compare/output_prototype_3
This takes output_prototype_2 one step further and simplifies the renderers, not sure this is a great approach but it feels more comfortable. Truthfully I think we loose a little of the noted advantages with this approach.
Ok enough for now.
Please please please give the OO approach some thought and let me know.
I myself an not sold on it, but I think we are dismissing it to lightly.
Cheers
Sam
Edit: We'll utilise MDL-45885 to discuss and document the fine details once this conversation on approach matures.