Note:

If you want to create a new page for developers, you should create it on the Moodle Developer Resource site.

User:Sam Hemelryk/Creating renderable components: Difference between revisions

From MoodleDocs
(Replaced content with "This page has now being published at Guide to creating output elements")
 
(21 intermediate revisions by 2 users not shown)
Line 1: Line 1:
A guide to writing output components and the render methods for them.
This page has now being published at [[Guide to creating output elements]]
 
==Terminology==
 
Don't worry if you don't understand these yet, this document is going to explain them.
However for the sake of providing a quick understanding of some of the terminology used in output here it is:
 
* '''Atom''' A simple building block, think of it like a HTML tag as that is what it is in many cases.
* '''Molecule''' A collection of atoms combined together to serve a single purpose or offer a single interaction.
* '''Organism''' A collection of molecules and atoms combined together to form a part of the user interface, serving one or more related purposes.
* '''Element''' An element is an meta concept for the above, a base from which all atoms, molecules, and organisms begin.
* '''Component''' Used to refer to any or all of the above. A component is a general term for renderable something relating to this output approach.
* '''Subcomponent ''' When we speak of subcomponents we are talking about the smaller components that are going to be of a larger component. e.g the molecules that are present in an organism.
* '''Renderable''' Used to mark the object as something that can be converted into output.
 
==Understanding output in Moodle==
 
In 2.8 the direction of output in Moodle was changed, or more accurately re-organised.<br />
Most Moodle sites out there use a third party theme, or have a had a theme created for them. We can assume that themes are the most widely created plugin within Moodle.
They deal with the design of the site, and design by nature is a moving, shifting, trending, fluid.<br />
How Moodle produces its look need to be flexible enough to cater to current trends, requirements such as usability and accessibility, be able to keep up with trends, and cater to personal tastes.<br />
We've long had renderers now that allow us to cater to this flexibility, however as Moodle has grown so has its interfaces. There are more interfaces, more controls, more possible interactions.<br />
The move in 2.8 was to bring organisation to what was being done, to bring consistency to the party and the make this flexible a manageable possibility by having an organised set of components to style rather than an endless array of interfaces and unique parts.
 
The goals for the output project were:
* Add an element library to Moodle (A page in Moodle listing all of the renderables and how they look in the current theme).
* Add a complete set of core renderables that should be capable of completely rendering every new user interface.
* Update the Output API documentation.
* Create a output guide documentation including best practices.
 
All of this leans towards making Moodle framework agnostic, so that any frontend framework can be applied to Moodle with as little work as possible.
 
After reflection and research the decision to use a style of Atomic Design was made. Atomic Design is as you will come to understand later a compartmentalisation approach to interface design that facilitates simplicity and re-use.
 
===Atomic Design, Renderables and Moodle===
 
Renderables have been around for a long time now, they were one of the initial preferred means of creating a reusable output components.<br />
As of Moodle 2.8 there is a new kid on the block, Elements.<br />
Elements within Moodle is based upon the Atomic Design principles outlined by Brad Frost in his [http://bradfrostweb.com/blog/post/atomic-web-design/ Atomic Design blog post] and can be one of three distinct types in Moodle, they are either Atoms, Molecules, or Organisms.<br />
These elements are within Moodle actually renderables by inheritance, all elements (atoms, molecules, and organisms) must have an associated render method to produce HTML, but more on that later.
 
Lets look at the three types of elements within Moodle.
 
====Atoms====
 
Atoms are the smallest part of the puzzle. Easily understood as HTML elements, an image, a table, list etc.<br />
Individually they offer very little, but are essential as they are what all larger things are built out of.
 
Simply - Atoms don't serve any explicit purpose nor do they provide any interaction, they are just building blocks.
 
====Molecules====
 
Molecules are the combination of atoms into structures that begin to form part of the picture.<br />
Atoms by themselves aren't useful, but by sticking a few of them together we create a molecule.<br />
Think of molecules as simple objects that the user can interact with for a single purpose, a login prompt (title + two inputs + a button), a search form (title + input + button), or a menu (several links).
 
Simply - Molecules provide a single interaction or purpose. They are constructed of Atoms only.
 
====Organisms====
 
Organisms make things interesting, the are more complex than molecules but not so clearly differentiated.<br />
An organism is a construct of molecules that when combined together form a distinct part of an interface.<br />
They tend to both interesting, and obviously more than a molecule.<br />
The following are examples of molecules:
* A navigation bar containing a title, some navigation, and a user picture.
* A user content block (like a forum post) containing a user picture, a title, some content, and a date.
* A Moodle block containing a header, some actions, content, and a footer.
 
Simple - Organisms can group several purposes or interactions into one related structure. They are constructed of Molecules.
 
====What about templates and pages?====
 
Atomic Design defines two additional compartmentalisations, templates and pages.<br />
* '''Templates''' it states are the grouping of predominantly organisms into a structure, think of it like a layout. It is organisation without content.<br />
* '''Pages''' are specific instances of the above templates, loaded with content as the user would see them.
 
In regards to Moodle neither of these two ideas is explicitly dealt with, both ideas however are already partly included in Moodle in the form of theme layouts and current renderers.<br />
Pages are a direct match to this approach, content in Moodle is dynamic and those layouts are where the output and content is joined.<br />
Templates on the other hand are partly covered by layouts (for the page as a whole), and partly covered by renderers.
 
Renderers form part of that picture because it is within a script that things are assembled.<br />
Again as of Moodle 2.8 with the work on output a best practices guide for renderers has been written which will outline how best to write renderers to match our chosen approach and why they should be written as such.
 
===How we made this fit within the Moodle dodecahedron===
 
Within Moodle we've decided to use the terminology from the Atomic Design approach. As discussed above templates and pages are not explicitly handled within Moodle, however atoms, molecules, and organisms are.<br />
Class auto-loading has being used, and as these things belong to the Output API they have been organised by namespace under classes/output.
 
The following are the base structures:
 
; core\output\element implements renderable : This is the absolute bottom unit, it can only be utilised by the core Output API. It contains basic functionality to all atoms, molecules and organisms.
; core\output\atom extends element : This matches the Atomic Design atom concept. It is a basic building block often equating to just a single HTML tag.<br />Within Moodle we don't created atom classes for each HTML tag. That approach was considered but the clutter, the classes it would introduce, and the render methods for those classes was deemed to outweigh the advantages having them would provide. Instead in many cases our smallest "renderable" element will be the molecule.
; core\output\molecule extends element : This matches the Atomic Design molecule concept. It serves a single purpose/interaction and can consist of atoms (as properties) but because we don't have classes/objects for every atom can also in some situations contain no atom objects at all.
; core\output\organism extends element : This matches the Atomic Design organism concept. It is a collection of other molecules and atoms, and can have serve several purposes and/or interactions that have all being grouped under a single umbrella, this organism.
 
All of these base structure are abstract and cannot be directly instantiated. Instead actual atoms, molecules, and organisms must derive from one of atom, molecule, or organism.<rb />
The element class is deemed to be internal to the Output API and therefore cannot be extended outside of the core Output API (at least the integrators won't accept it).
 
===The importance of inheritance in rendering===
 
By now you have a good picture of how we've organised output in Moodle in 2.8.<br />
What is left to discuss is the importance of this hierarchy and its impact on how we produce output in renderers.
 
We know that Atoms form Molecules, which in turn form Organisms.<br />
We try to apply this same inheritance within our renderers so that when you render an organism we start by producing the encompassing structure, then for each of the molecules that make it up we render on each. This passes the molecule to its own render method to produce the molecule. The same goes for the molecules that contain atoms, we produce the structure for the molecule and call render on each atom at the appropriate time for the structure.<br />
Through this means there should in the perfect world scenario be one way to render each organism, molecule, and atom. Thus when a theme designer wishes to change how the search molecule looks for instance they need only change the render method for that one molecule, and every place that molecule is used, either stand alone or as part of a larger organism will be updated at the same time.
 
Unfortunately, with the present state of Moodle themes this idea of having the minimal possible render methods to produce the most consistent output is but a pipe-dream.<br />
However as time goes on that will slowly correct itself, as more of Moodle is converted and themes begin to develop with this new approach.<br />
For the time being the consistency that having a specific set of components and the organisation of those components is going to be a big win.
 
==The element library==
 
Hopefully you are already familiar with the element library in Moodle; if not then you really should take a look at it and get to understand what is it doing.
 
The element library is a very important piece in the puzzle that is Output within Moodle.<br />
It shows how a component looks in one or more scenarios, the scenarios usually revolving around varying contents.
 
==Designing and writing a component==
 
A step by step approach to working through the creation and rendering of an element so that it meets the Output standards for Moodle.<br />
If you want a quick overview of what to do have a look at the [[#Peer-review checklist|peer-review checklist]] in the next section
 
===Step 1: Are you sure you need to create a component===
We want Moodle to contain a limited number of components. The problem that existed before this work was that every bit of code was creating its own output, and we do not want to end up back there.
 
The very first thing to do is to be absolutely sure that you need to create this component.
 
'''Atoms and molecules'''
After the initial release this should be rare. It should be especially rare that you need to write an atom or molecule for a plugin.<br />
Moodle should provide 95% of the atoms and molecules you could ever need. However if you have something that is truly new and unique in some way then perhaps indeed you do need to write a component.
 
'''Organisms'''
Writing an organism isn't going to be as rare, certainly not at first anyway. As time progresses obviously we want to settle upon a finite selection of organisms, however it is a reality that we will not be able to cater for all core code and plugins initially.
If you find yourself with a unique interface requirement, perhaps for the new and original plugin you are working on then you probably have met the requirement for creating an organism.
 
In both cases it is important that you search through the components already in Moodle and see if there are any that meet your needs.<br />
If there is something close in Moodle core already then perhaps you need to adjust your design to make use of the existing component rather than introducing something similar but slightly different.<br />
If you find a component you could use in a core plugin then you should create an issue in our Tracker and request that the component be moved from the plugin to core.<br />
Then for the time being copy the component from the plugin you found it in, into your plugin. This way it will look consistent, you can use it and if it does get accepted into core you don't have to change anything about how you use it.
 
===Step 2: Define the component===
 
It very important to be sure of what you want to create.
 
The first thing to do is to work out what is it going to be, is it an atom, a molecule, or an organism?<br />
Remember the following, they describe each of those in a single sentence:
; Atom : A basic building block that severs no purpose or interaction.
; Molecule :  Serves just a single purpose OR interaction, not usually a part of an interface by itself, grouped with other molecules and atoms to form an organism.
; Organism : A part of the interface, it is constructed of molecules and atoms, it can facilitate several like purposes and interactions
 
It should be possible to create a flow chart to aid this decisions, unfortunately one has not being created yet.
 
Once you've decided what type of component it is going to be you are ready to start designing what other components (if any) will make it up.
 
Now is a good time to be very clear about the purpose of your component, as well as any interactions that you want it facilitate.
Consider writing down a clear and concise description of your component and sharing what you are planning in the forums, with colleagues or just generally with other developers.
This output approach is still fresh and we are all still learning how it works in detail so the more exposure you can get now the easier it will be down the track.
 
===Step 3: Create the component class===
 
Creating a component is really quite simple, the following headings explain what you need to know.
Remember if in doubt there are a lot of examples that can be easily found in Moodle core (within lib/output).
 
====Component location====
All components belong to the Output API and should be namespaced within it for autoloading and consistency with Moodle core components.
This makes locating components very easy, core components will be located within an output subdirectory of the classes directory as shown below:
 
: lib/classes/output/'''mycomponent.php'''
 
Plugin components will be located in a similar fashion within an output subdirectory of the classes directory. e.g.
 
: mod/''myplugin''/classes/'''mycomponent.php'''
 
====Class name and inheritance====
Classes are namespaced for the output API and located in the file above. Because classes are namespaced the actual class name is simply the component name. The same name as was used for the file.
They must however extend the appropriate component type, if you are creating a molecule it must extend ''\core\output\molecule'', if its an organism it must extend ''\core\output\organism''.
For following is an example of how this would look for a core component:
 
<code php>
<?php
namespace core\output;
defined('MOODLE_INTERNAL') || die();
 
class mycomponent extends organism {
</code>
 
And for a component you are creating within a module plugin:
 
<code php>
<?php
namespace mod_myplugin\output;
defined('MOODLE_INTERNAL') || die();
 
class mycomponent extends \core\output\organism {
</code>
 
====Class properties====
 
We like to keep components simple to interact with. For that reason we encourage people to store data and subcomponents that will be used during rendering as public class properties.
This is done so that data is accessed in a consistent fashion when interacting with a component. For this reason we would rather '''not''' see developers adding methods to a component unless truly required.
 
Subcomponents are a good example. If you were writing a navigation bar organism for example you may have a title molecule, a menu molecule, and a search molecule. The title, menu and search molecules are the components.<br />
These will become public class properties of your component.
 
====Class methods====
 
Components should only have methods to support their own construction. As expressed above all information useful to a renderer should be stored in publicly accessible properties, none of it should have to be accessed via method calls.
This is done so that interaction with components is consistent across all components. Methods to aid the construction of the component are encouraged so that information can be easily added to the component.
 
====Your constructor====
The constructor for your component should allow the developer to pass in the required data and subcomponents.
Consider when writing the constructor how developers will pass information into the component. In many cases there are going to be two types of data that will commonly be passed in:
# Components (or arrays of) that make up parts of your component.
# Scalars (or arrays of) e.g. strings, integers, floats and booleans.
 
Make the data that is absolutely required part of the constructor and simply have publicly accessible properties for the optional data that can be set by calling code if required.
 
Its also worth considering if you are creating a molecule that perhaps data handling for the atoms that make it up may be worth including in your constructor.<br />
For instance a dropdown consists of a trigger element (button, link, image) and a menu, the menu is a molecule that consists of items. The constructor of the dropdown could aid construction by accepting two arguments, the first a way to pass in a trigger, and the second either an already build menu component OR an array of items that get used to construct a menu component within the dropdown constructor.
 
Some points to remember:
* Don't forget all properties should default to null if they do not have data set.
* If you have a collection of something as a property consider adding add_xxx() and add_xxxs() methods to aid the construction of components.
 
===Step 4: Write the render methods===
 
There are 3 categories of render methods that will exist within every renderer and depending upon the type of your component you will be creating one or more of these methods for it.
 
Why the three types of render method? There are two key reasons to take the above apporach.
 
'''Re-use'''<br />
By ensuring we call the render method to produce components and subcomponents when ever possible we will hopefully be making it easier to style Moodle as it won't be necessary to style absolutely everything individually. Instead if render has being used consistently to produce a button for example styling the HTML produced by ''render_button()'' should style all buttons throughout Moodle.
 
'''Make overriding renderers in themes more controlable'''<br />
The three types of render methods are designed to separate the logic and display that is permissable in renderers.<br />
Render method should contain as little logic and PHP as possible, however in many situations a bit of PHP and perhaps a bit of logic is going to be required in order to take data from one form and make it ready to present.<br />
If overriding a renderer within a theme the theme developer can look at what they want to achieve and choose the suitable method to override.<br />
If they are looking to change the markup for a component then they can override the render method, they will not need to know anything about how the component was constructed or what information went into it. They can focus on the data the component has and produce the HTML they desire.<br />
If they choose to change data, or want to display something different to what is there by default then they can override the translator method and continue to hack away to their hearts content.
 
 
Hopefully as you read about the render methods below the advantages to this approach will become clear.
 
====Render method====
 
This is the easiest to understand and no matter what type of component you have created you will need to write a render method.
The render method takes a component and returns HTML to display it.
 
The render method takes a strict format:
<code php>
/**
* Produce HTML to display mycomponent.
*
* @param \core\output\mycomponent $mycomponent The component to display.
* @return string An HTML version of the component.
*/
protected function render_mycomponent(\core\output\mycomponent $mycomponent) {
    // Generate HTML for the component.
    return $html;
}
</code>
 
The following are rules to follow when creating a render method.
 
* It display the component, nothing else.
* It is a protected method.
* The name of the method is always "render_" followed by the '''non-namespaced''' name of your component.
* There is only ever '''one''' argument, the component to render and it should be typehinted.
* It will contain no logic.
* It uses the public properties of the component for data.
* It supplments this by using the available methods '''ONLY IF ABSOLUTELY REQUIRED'''
* It returns a string, the HTML to display the component.
 
This method is very simple to understand, and providing you have properly researched and proposed your component it should be even easier to write.
 
If you are writing a render method for molecule or organism then you likely will have subcomponents that have being used within your component.
Where possible you should call '''$this->render()''' on the subcomponents to produce HTML for them that can then be included in the HTML that you are producing for your component.
Only if you absolutely must should you handle the generation of HTML for subcomponents directly within the render method for your component.<br />
If you do produce the HTML for a subcomponent without calling render than '''you must''' manually call the subcomponents ''prerender'' method to ensure that it has an opertunity to complete and requisits it has before rendering.
 
This is done to ensure that where possible we re-use the HTML for a component. By doing it we reduce the number of components that a theme must style if all it is doing is styling.<br />
If a theme overrides a render method it is not obligated to take this same approach. Theme overridden renderers can do as they please, any duplication of structure they introduce is their own business.
 
====Convenience method====
 
The name for this type of method provide a big hint as to what it is, it is a method to conveniently construct and render an instance of your component.
This method also takes a very simple, predicatable form.
 
<code php>
/**
* Create and render a new mycomponent.
*
* @param mixed $arg1
* @param mixed $arg2
* @param mixed $arg3
* @return string HTML for mycomponent.
*/
public function mycomponent($arg1, $arg2, $arg3 = null) {
    $obj = new \core\output\mycomponent($arg1, $arg2, $arg3);
    return $this->render($obj);
}
</code>
 
This type of method always does three things:
# It takes the arguments necessary to create a new instance of mycomponent.
# It creates a new instance of mycomponent given the arguments it is given and sets it up as required.
# It calls '''$this->render()''' and gives it the object, returning the outcome.
 
There are a few things to observe when writing a convenience method:
# It is always public.
# Its arguments should be complete.
#* Meaning that they should be arguments that can be immediately to the component
#* OR can be checked in conditions to alter the component.
#* They should not be objects to support the generation of data. The data should be complete.
# It should '''not''' produce any HTML itself, the render method is responsible for this.
# It returns a string, the HTML to display for the given component.
 
In the case of molecules and organisms it is permissable for the convencience method to take actual subcomponents OR the arguments required to create necessary subcomponents. This is at the discretion of the developer and may be determinent on the number of arguments required to construct the component.<br />
Complex components that require a lot of data to set up are probably not ideal candidates to create convenience methods for.
 
====Translator method====
 
These methods take complex arguments such as data and objects from outside of the Output scope and manipulate/smooth the data into a complex organism or molecule.<br />
They should be used to pass in the information the current component requires as well as any information that may be used by an overriding renderer to access additional data that may be desired.
 
These are likely to be rare for core components, however just about essential for components being introduced by plugins for example where having retrieving data from plugin objects for use in a component or subcomponents is going to be required.<br />
If for example you consider how your would render a forum discussion the translator method would likely take a forum discussion object and use a method of it to retrieve a nested array of forum posts that can then be used to create a discussion component, and its post subscomponents.
 
Just like the above convenience method the translator method should not produce HTML itself, instead it should create the necessary component and any required subcomponents before calling '''$this->render()''' on the subcomponent.
 
===Step 5: Write generator samples===
 
===Step 6: Style those samples in bootstrapbase and base===
 
Hopefully when designing your component you have already considered how it is going to look, and had taken into consideration what is available in at least bootstrap base.<br />
Because you have already written the component generator in the steps above you can easily refer to the element library when styling your component and testing.
 
Its recommended to start with bootstrapbase as this is the scaffolding theme that our default theme ''clean'' is build upon.
All CSS for your component within the bootstrapbase theme should be added to '''theme/bootstrapbase/less/output_components.less''' and be written in hierarchical less so that it is clear which styles apply to your component.<br />
The CSS should be preceeded by a clear heading that identifies which component the styles belong to as well.
 
===Step 7: Write tests===
 
Both acceptance and unit tests may be applicable to your component depending upon what you've done, and just like all of new code arriving for Moodle tests are now an expectation.
You can not of course create tests to automatically check the display of your component, however the element library facilitates a user manually doing that.
 
'''PHPUnit tests'''
If your constructor contains any logic for smooth data into its properties then this should certainly be tested.<br />
Likewise if you've added any methods to aid the construction of your component they should be tested as well.
 
'''Acceptance tests'''
These are particularly applicable if your component facilitates any kind of interaction. If so then it is worthwhile creating a test scenario for this interaction.<br />
If the interaction triggers a visual response, like clicking a button and a dropdown appears then this can be easily tested within the element library.<br />
If a more complex interaction is taking place then you should consider writing a test that belongs to a code that makes use of your component.
For instance if your component is a search form and it is used within the forum consider writing an acceptance test for the forum testing your component if there is not already one there.
 
===Step 7: Use your component===
 
==Peer-review checklist==
The following is a checklist guide of what to look for when reviewing a component and/or component render methods.
Items already on the peer-review checklist have being excluded.
 
'''The component class'''
[ ] The file is located in classes/output/componentname.php
[ ] Autoloading is used, the file has not being included anywhere.
[ ] The component is correctly namespaced to the Output API (namespace core\output OR mod_plugin\output).
[ ] The component name is accurate to its function and concise.
[ ] It is a unique component and not a duplicate of another component.
[ ] It extends either atom, molecule or organism
[ ] All data used during rendering is accessed through public properties.
[ ] All public properties of the class are data that can be used during rendering.
[ ] All optional public properties (not set within the constructor) default to null.
[ ] All/any methods the class has aid in its construction (importantly they don't return data)
 
'''Styles for a core component'''
[ ] The component has being styled in at least the bootstrapbase and base themes.
[ ] The styles in bootstrap base have being added to theme/bootstrapbase/less/output_components.less
[ ] Bootstrapbase styles have been written in less format and are preceeded by a clear heading of the component they belong to.
 
'''Styles for a plugin component'''
[ ] The component has being styled in at least the bootstrapbase and base themes.
 
==Tracker issues==
The following tracker issues relate in one way or another to the development and continued work on Output.
 
* MDL-45885 Decide how output components (the issue that saw this document created)
* MDL-45770 Implement stage 1 tasks from the Render Library specification
* MDL-41663 Allow renderers and renderables in a namespace to be auto loaded.
* MDL-45828 Create the element library admin tool.
 
 
 
==See also==
 
* [[Render library specification]]
* [https://moodle.org/mod/forum/discuss.php?d=261202 Render library specification discussion]
* [[User:Sam Hemelryk/Render library element planning]]

Latest revision as of 22:31, 23 June 2014

This page has now being published at Guide to creating output elements