Skin:Lift/Development guide

The Lift skin was developed using the SkinMustache class, available in Mediawiki 1.36 and above. This is a development guide to show how it was made so that other skin developers will have an idea as to what they can do when developing skins using the SkinMustache class.

Goals
This development guide will show how to:


 * Perform initial set up:
 * The initial folder for a skin using SkinMustache
 * SkinJSON to aid in developing and troubleshooting
 * Incorporate and customize resources
 * Frameworks, such as Bootstrap, Hover, Animate, and Font Awesome, in addition to incorporate the Google Roboto font
 * Custom css and js
 * Perform basic styling with Mustache templates
 * Fixed-top header and search bar using Bootstrap
 * Extend SkinMustache method to provide more control over the JSON returned
 * Return html attributes rather than html-items
 * Rearranging JSON data coming from SkinMustache
 * Taking advantage of the API
 * Adding a Watch/unwatch control


 * Clean up templates, skin.json, and skin directory
 * Pass additional information to the skin through hooks

The initial folder for a skin using SkinMustache
Although a skin folder can have a variety of structures, the Example skin provides a decent starting point. I personally had difficulties downloading and installing the files from the Github repository, so I recommend downloading from the Mediawiki Skin:Example page.

Install the skin per the directions and in the wiki under Preferences change the skin to Example. For a new wiki installation, the Main Page should now look something like this:

To convert this to our new skin:
 * 1) Rename the "Example" folder to "Lift"
 * 2) Now in the "Lift" folder, delete the .phan folder and all files in the root EXCEPT skin.json
 * 3) Open skin.json and change all instances of "Example" to "Lift" being careful to maintain case sensitivity
 * 4) In LocalSettings.php change wfLoadSkin( 'Example' ); to wfLoadSkin( 'Lift' );
 * 5) In the wiki under Preferences change the skin to Lift

Checking our wiki, the Main Page should look the same as it did when the skin was named Example.

Finally, edit skin.json to update the other information for your new skin (i.e., "version", "author", etc.).

SkinJSON to aid in developing and troubleshooting
SkinJSON is a useful tool to allow us to see the data returned by SkinMustache. Download the files from the SkinJSON Github repository into a SkinJSON folder under our Skins directory and enter wfLoadSkin( 'SkinJson' ); in LocalSetting.php.

You will see SkinJSON now available as a skin under your preferences as jsonskin but DO NOT UPDATE YOUR PREFERENCES TO USE jsonskin. We will access the json returned by SkinMustache class indirectly through the following url:

basewikiurl/Main_Page?useskin=lift&useformat=json

This will show all the data that is passed to the Lift skin in a json format.

When viewing the wiki normally, the SkinMustache class is merging this json data with the mustache files inside the templates folder to form the HTML to render the page.

Of interest here are the various items under "data-portlets", for example the "data-personal":

Data-portlets are used to form our menus, with each portlet being an individual menu. For the data-personal, the core Mediawiki software determines what menu options will be available to the user on each page, the SkinMustache class converts those options into json data (shown as html-items here), and the mustache files in the templates directory form it into html to display on the wiki.

Frameworks
To incorporate other frameworks, in this case Bootstrap, Animate, Hover, and Font Awesome, download the folders and files of those frameworks into the resources folder:

To incorporate the Roboto front from Google, create a font_roboto.css file and place it in the resources folder:

The resources folder:

Next, the location of these resources need to be identified to the skin using ResourcesModules in skin.json (note that I have only kept two of the original four style sheets, screen-common.less and screen-desktop.less, and I am no longer using conditional loading; later we'll also be deleting those two style sheets as we move toward using Bootstrap for the skin):

At this point all of our resources are located in the "resources" directory, so we can simplify our skin.json file by updating the localBasePath under ResourceFileModulePaths to point to the "resources" folder (and remember to remove the leading "resources/" fragment from the styles and scripts themselves):

Custom css and js
We can also use the ResourceModules to load our own css and js files.

For now, create two files, lift.css and lift.js in the resources root directory:

Now edit skin.json to identify the two custom files under ResourceModules:

Finally, edit the lift.css file to specify Roboto should be used as the font for the entire body element:

At this point, we have added all the resources we'll use and reserve the ability to edit lift.css and lift.js more later. However, looking at our wiki's Main page it will show that the font is now Roboto because it was loaded under font_roboto.css and the font for the body element was specified in lift.css (compare this to the screenshot above for the Example skin without any modifications to see the difference in fonts):



In the next few sections, we'll use move all of the "Navigation menu" portion of this page into two different navigation bars, one at the top of the page and one just below the page title.

Create a fixed-top header with the site logo and search bar
Now that all the resources are loaded, let's create a fixed-top header with the site logo and search bar for the site.

First, let's use the SkinJSON skin to look at the relevant JSON returned from the SkinMustache class by navigating to the url:

yourwikibaseurl/wiki/Main_Page?useskin=lift&useformat=json

For my test wiki this shows:

Next, let's look in the templates folder at the skin.mustache file. The SkinMustache class uses to template construct the HTML from the above JSON. Note that lines 5 through 42 is the specific portion we are moving into a main menu across the top of the page and the menu below the page title. If you're not familiar with Mustache (and Handlebars), then it would be worth reviewing a good tutorial first. It would also be worth quickly comparing the JSON and the template with the actual html generated for the main page to see how the page is constructed.

At the very top of the skin.mustache template, add to include header.mustache as a partial template. Also, start to introduce Boostrap classes into the templates by adding the classes "container-xxl", "pt5", "mt-5" to the mw-wrapper div. This will help keep the size of the main content consistent while also adding some space at the top for our header: Now create the file header.mustache in the templates directory and enter this code. Your templates folder should now look like this: Now refresh the wiki and the main page should look like this: We were able to previously change the site to the Roboto font, and now, using Boostrap classes, we are able to easily make the new header in a fixed position at the top of the page. It has the site logo and a fully functional search bar using Bootstrap's floating labels. The search bar as a button, but in the template we added the class "d-none", so while it is operational it does not display. Additionally, we've taken the first steps to move fully toward a Bootstrap skin by adding the "container-xxl" class to the mw-wrapper div, although it didn't change the appearance.

Take a moment to navigate around the site to see how various pages look with the header at the top and the Roboto font.

Next we'll tackle the menu items.

Extend SkinMustache method to provide html-free menu links
Currently the menus are returned in data-portlets and data-portlets-sidebar:

A closer look at one of the menu items, data-personal, shows that the JSON returned from SkinMustache already forms the html for us under the member html-items:

While this preformmated html from SkinMustache may help many skin developers, at the same time it prevents us from templating the JSON to take advantage of Bootstrap classes to style our own menus.

To fix this, we must make our own skin class, in this case SkinLift, that will extend SkinMustache so that we have control over the JSON that is returned for the menus.

First, create a new folder called includes, and inside that folder create a file called SkinLift.php:

For now, enter this into the SkinLift.php file: Update the json.skin file to 1) autoload the SkinLift class from "includes/SkinLift.php" and 2) change the class under "ValidSkinNames"."lift" from "SkinMustache" to "SkinLift":

Refresh the wiki to ensure everything works. The page should be the same although the class returning the JSON is now SkinLift.

To get our new class to return an array of html attributes as array-links rather than fully formed html, we need to overwrite SkinMustache's getPortletData function to the following: Save SkinLift.php and now look at the JSON returned from SkinLift using SkinJSON: The new function in SkinLift has added a new member to each data-portlet called array-links, which holds the html attributes to make links rather than the preformed html.

With this new JSON available we can return to header.mustache to add dropdown menus to the header as well, such as for the data-personal data-porlet, along with some added css to make our header responsive when the time comes:

Also, without too much effort using the template's if and unless functions. we can separate our login/logout button as well: And the resulting page:



Additionally, we can add a menu below the Main Page title for our data-namespaces, data-views, data-actions, and data-variants data-portlets through the mustache partial pagemenu: The pagemenu.mustache file: And the resulting wiki page:

Finally, let's remove the original components that are now included in the header and the page menu from the skin.mustache template by removing lines 8 through 38 from the mw-navigation div: This should leave us with: And the resulting main page would look like this:

Rearranging JSON data coming from SkinMustache
While the navigation area is cleaner and arguably more intuitive since moving those options to our new header and the page menu, our goal is to eliminate that navigation area altogether.

The remaining data-portlets are returned to the main page inside the "data-portlets-sidebar" member, which we can see using SkinJSON:

The sidebar is a legacy component in many past Mediawiki skins, but the choice as to whether to have one and what goes in it should be abstracted out so that future skin developers can determine if a sidebar meets their needs. For us, we'll move those menus into the "data-portlets" member.

The first part of "data-portlets-sidebar" is simply called "data-portlets-first". This contains either the menu derived from the Mediawiki:Sidebar page or, if that is empty, the toolbox. The "array-portlets-rest" member houses either the Toolbox (if Mediawiki:Sidebar contains a navigation menu) or the toolbox.

The first task is to extract the toolbox portlet, regardless of whether it is located in the "data-portlets-first" or "array-portlets-rest", into a "data-portlets" member.

We will do this by extending the getTemplateData method of SkinMustache with our child class SkinLift: Now using SkinJSON we can see the following is returned: Notice data-toolbox and data-navigation are our new data-portlets and also notice that data-portlets-sidebar is now gone from the JSON. Also notice that because the navigation menu might contain several sections although in this case it only contains one.

Next we can use this new JSON to modify the header template to include the toolbox and a navigation menu: Notice the more complicated structure of the data-navigation template to allow for the different sections. We'll see those in action momentarily.

And now, our new skin: Note that nothing appears in the old navigation area because we have gotten rid of the "data-portlets-sidebar" member which was used in the template to render them. Because it is now gone, there is nothing to show. We'll clean up the template in a moment, but first let's test adding new options to Mediawiki:Sidebar to see what happens: And now our navigation menu shows: The getTemplateData method is also a place to do any other manipulation of the Mediwiki-generated menus as well, such as deleting the data-user-menu, which is a duplicate of data-personal used for backward compatibility, and moving the Edit options from the Views menu to the Actions menu:

Take advantaging of the API
Now that we've moved some of the menus around, let's look at how the skin can take advantage of the API, specifically by adding a Watch/unwatch switch to the page. In our pagemenu template, let's separate out the watch option from the Actions menu: And the resulting page looks like: At this point, the Watch/unwatch option has been moved, but aside from actuating the switch, nothing actually happens, so we need to add the following code to our lift.js file: Now when a particular page is visited it will automatically show whether to page is being watched by the visitor and the visitor can turn the watch on or off by simply using the Bootstrap switch control. This could also have been done using a checkbox or any similar control.

You can refer to the link in the code to learn more about this particular use of the API.

Applying this approach to Categories
Using the same approach, let's look at categories.

Start by adding the following categories to the Main Page:

Now we can see how categories are returned as JSON by viewing SkinJSON: This is what it looks like in the current skin:

Here we see again that SkinMustache returns the preformed html and forces us into a specific styling, so we want to overwrite the getCategories method in SkinLift to return html attributes instead (notice also the addition of "use Mediawiki/MediaWikiServices;" at the top): Now the JSON returns: Finally, in our skin.mustache tempalte, we can replace the reference to with the following code: The resulting skin looks like this:

Clean up templates, skin.json, and skin directory
At this point we've made several changes to our original files and should take a few minutes to clean up the skin directory.

First, completely remove the mw-navigation div from the skin.mustache template and modify the footer so that the file looks like this: Modify the FooterList.mustache file: Clean up skin.json by removing the extra js and less files, including the ResourceModuleSkinStyles which is not used in this skin: Delete the extra resources from the resources folder:


 * extensions folder
 * main.js file
 * print.css file
 * screen-common.less
 * screen-desktop.less
 * screen-mobile.less
 * variables.less

Your resources folder should now look like this: And now refresh. Notice there will be other minor changes to the styling since the : A note about Features:

Features are options passed to the ResourceLoaderSkinModule to take advantage (or not) of some preset styling offered by Mediawiki. Turning on and off various features and refreshing the skin is probably the best way to see the impact of each one. Essentially, the fewer features that are set to true, the more styling responsibility the skin developer must take.

We'll leave the features set as they were originally from the example skin, but feel free to turn them off if you want more control.

More details can be found at the ResourceLoaderSkinModule page.

Passing additional information to the skin through hooks
Finally, let's look at how we can pass additional information to the skin for it to be rendered. In this example we'll use the hooks for the skin itself, but extensions can pass information to the skin through hooks as well.

The first part is to modify our SkinLift to add:


 * 1) A private member to hold the additional data ($additionalTempalteData)
 * 2) A method to accept the additional data (setTemplateVariable)
 * 3) A method to consolidate that data with similar array keys (array_merge_recursive_distinct)
 * 4) A way to add the additional data to the data already passed ($data += $this->additionalTemplateData; under the current getTemplateData method):