ENHANCEMENT Updated tutorial documentation (thanks to Cam Findlay and Andy Adiwidjaja)

This commit is contained in:
Andy Adiwidjaja 2012-06-08 00:37:16 +02:00 committed by Ingo Schommer
parent 013abcbe8b
commit 71b3fe5760
66 changed files with 357 additions and 533 deletions

View File

@ -14,9 +14,6 @@ We are going to create a site in which all the content can be edited in the Silv
navigation system, which will be generated on the fly to include all pages you add in the CMS. We will use two different
templates - one for the home page, and one for the rest of the site.
![](_images/home-small.jpg)![](_images/menu-two-level-small.jpg)
## Installation
You need to [download the SilverStripe software](http://www.silverstripe.org/stable-download) and install it to your local
@ -24,13 +21,13 @@ machine or to a webserver.
For more infomation about installing and configuring a webserver read the [Installation instructions and videos](../installation).
If you want to follow this tutorial please choose "empty template" when installing SilverStripe. If you want a fully
featured theme then select the 'BlackCandy' option.
This tutorial uses the SilverStripe CMS default theme 'Simple' which you will find in the themes folder. We will investigate the existing template files that make up the theme as well as create some new files to build upon the theme.
## Exploring the installation
After installation, open up the folder where you installed SilverStripe. If you installed on windows with WAMP, it will
likely be at *c:\wamp\wwww*.
After installation, open up the folder where you installed SilverStripe.
If you installed on windows with WAMP, it will likely be at *c:\wamp\wwww*. On Mac OS X with MAMP, it will likely be at */Applications/MAMP/htdocs/*
Let's have a look at the folder structure.
@ -46,40 +43,59 @@ When designing your site you should only need to modify the *mysite*, *themes* a
## Using the CMS
The CMS is the area in which you can manage your site content. You can access the cms at http://localhost/admin. You
### User Interface Basics
![](_images/tutorial1_cms-basic.jpg)
The CMS is the area in which you can manage your site content. You can access the cms at http://localhost/admin (or http://yourdomain.com/admin if you are using you own domain name). You
will be presented with a login screen. You can login with the details you provided at installation. After logging in you
should be greeted with the CMS, pictured below (we've entered some test content).
should be greeted with the CMS and a list of the pages currently in the CMS. Here you can add, delete and reorganize the pages using the buttons at the top. Clicking on a page will open it in the page editing interface pictured below (we've entered some test content).
![](_images/cms-numbered.jpg)
![](_images/tutorial1_cms-numbered.jpg)
1. These buttons allow you to move between the different sections in the CMS. There are four core sections in the CMS - "Pages", "Files", "Users" and "Settings". Modules may have their own sections here as well, if any are installed. In this tutorial we will be focusing on the "Pages" section.
2. While in "Pages" you can quickly move between the pages in the CMS by clicking the vertical bar between the CMS menu and the editor. This will slide out a sidebar. To hide this, click the arrow at the bottom of the sidebar.
![](_images/tutorial1_cms-numbered-2b.jpg)
1. These buttons allow you to move between the different sections in the CMS. There are three core sections in the CMS - Site Content, Files & Images and Security. Modules may have their own sections here as well, if any are installed. In this tutorial we will be focusing on the Site Content section.
2. All the pages in the site are laid out in the site tree. You can add, delete and reorganize the pages using the buttons at the top. Clicking on a page will open it in the editor on the right.
3. This section allows you to edit the content for the currently selected page, as well as changing other properties of the page such as the page name and URL. The content editor has full [WYSIWYG](http://en.wikipedia.org/wiki/WYSIWYG) abilities, allow you to change formatting and insert links, images and tables.
4. There are two copies of each page: draft & published. These buttons allow you to save your changes to the draft copy, publish your draft copy, or revert your draft copy to the published copy. By having separate draft & published copies, we can preview draft changes in the site before publishing them to the live site.
5. The navigator will open the current page in the CMS, the draft site, or the published site.
4. These buttons allow you to save your changes to the draft copy, publish your draft copy, unpublish from the live website or remove a page from the draft website.
The SilverStripe CMS workflow stores two copies of a page, a draft and a published one. By having separate draft & published copies, we can preview draft changes in the site before publishing them to the live website. You can quickly preview your draft pages without leaving the CMS by clicking the "Preview" button.
### Page Editor
Once you have selected a page to modify from the Pages section your page will open in the Page Editior.
The Edit Page section has 3 main areas in which you can edit the content of the page, change the settings and track your revision history (These will be covered in more detail further on in the tutorials).
![](_images/tutorial1_editpage-numbered.jpg)
1. *Content* - Allows you to set the title, wysiwyg content, URL and Meta data for your page.
2. *Settings* - Here you set the type of page behavior, parent page, show in search, show in menu, and who can view or edit the page.
3. *History* - This allow you to view previous version of your page, compare change and revert to previous version if need be.
### Try it
There are three pages already created for you - "Home", "About Us" and "Contact Us", as well as a 404 page. Experiment
with the editor - try different formatting, tables and images. When you are done, click "Save" to save the page or "Save
& Publish" to post the content to the live site. In our screenshot we've entered some test content.
& Publish" to post the content to the live site.
> Don't worry that we currently have no way of navigating from page to page without using the CMS - we will build a navigation system soon.
When you create a new page, you are given a drop down that allows you to select the page type of the page. The page type
specifies the templates used to render the page, the fields that are able to be edited in the CMS, and page specific
When you create a new page, you are given a drop down that allows you to set the structure of the page (Top level or Under another page) and the page type.
The page type specifies the templates used to render the page, the fields that are able to be edited in the CMS, and page specific
behavior. We will explain page types in more depth as we progress; for now, make all pages of the type "Page".
![](_images/tutorial1_addpage.jpg)
![](_images/home-first.jpg)
**SilverStripe's virtual URLs**
**SilverStripe's friendly URLs**
While you are on the draft or live SilverStripe site, you may notice the URLs point to files that don't exist, e.g.
http://localhost/contact. SilverStripe uses the URL field on the Meta-Data tab of the editor to look up the appropriate
http://localhost/contact or http://yourdomainname.com/about-us etc. SilverStripe uses the URL field on the Meta-Data tab of the Edit Page -> Content section to look up the appropriate
page in the database.
![](_images/url.jpg)
Note that if you have sub-pages, changing the Top level URL field for a page will affect the URL for all sub-pages. For example, if we changed the URL field "/about-us/" to "/about-silverstripe/" then the sub-pages URLs would now be "/about-silverstripe/URL-of-subpage/" rather than "/about-us/URL-of-subpage/".
![](_images/tutorial1_url.jpg)
When you create a new page, SilverStripe automatically creates an appropriate URL for it. For example, *About Us* will
become *about-us*. You are able to change it yourself so that you can make long titles more usable or descriptive. For
@ -96,22 +112,30 @@ control codes. Because of this, you can have as much control of your sites HT
Every page in your site has a **page type**. We will briefly talk about page types later, and go into much more detail
in tutorial two; right now all our pages will be of the page type *Page*. When rendering a page, SilverStripe will look
for a template file in the *tutorial/templates* folder, with the name `<PageType>`.ss - in our case *Page.ss*.
for a template file in the *simple/templates* folder, with the name `<PageType>`.ss - in our case *Page.ss*.
Open *themes/tutorial/templates/Page.ss*. It uses standard HTML with three exceptions: `<% base_tag %>`, *$Content* and
*$SilverStripeNavigator*. These template markers are processed by SilverStripe into HTML before being sent to your
browser.
Open *themes/simple/templates/Page.ss*. It uses standard HTML apart from these exceptions:
`<% base_tag %>` is replaced with the HTML [base element](http://www.w3.org/TR/html401/struct/links.html#h-12.4). This
ensures the browser knows where to locate your site's images and css files.
*$MetaTitle, $Title, and $SiteConfig.Title* in the html <title> tag are replaced by the title set in the Meta tags, Page Name, or Settings -> Site Title.
*$Title* is simply replaced with the name of the page ('Page name' on the 'Main' tab in the editor).
*$MetaTags* adds meta tags for search engines, as well as the page title ('Title' on the 'Meta-data' tab in the
editor). You can define your metatags in the meta-data tab off the content editor in the CMS.
*$Layout* is replaced with the contents of a template file with the same name as the page type we are using.
Open *themes/simple/templates/Layout/Page.ss*. You will see more HTML and more SilverStripe template replacement tags and variables.
*$Content* is replaced with the content of the page currently being viewed. This allows you to make all changes to
your site's content in the CMS.
*$SilverStripeNavigator* inserts the HTML for the navigator at the bottom of the page, which allows you to move
quickly between the CMS and the draft and published version of your page.
These template markers are processed by SilverStripe into HTML before being sent to your
browser and are formatted either with a *$* at the beginning or are between the SilverStripe template tags *`<% %>`*.
![The SilverStripe Navigator](_images/navigator.jpg)
**Flushing the cache**
@ -119,85 +143,38 @@ Whenever we edit a template file, we need to append *?flush=1* onto the end of t
http://localhost/home/?flush=1. SilverStripe stores template files in a cache for quicker load times. Whenever there are
changes to the template, we must flush the cache in order for the changes to take effect.
## Inserting the page title
## The Navigation System
Let's introduce two new template variables - *$Title* and *$MetaTags*.
We are now going to look at how the navigation system is implemented in the template.
*$Title* is simply replaced with the name of the page ('Page name' on the 'Main' tab in the editor).
Open up *themes/simple/templates/Includes/Navigation.ss*
Open *themes/tutorial/templates/Page.ss* and find the following code:
:::ss
<div id="Header">
<h1>&nbsp;</h1>
</div>
and replace it with
:::ss
<div id="Header">
<h1>$Title</h1>
</div>
*$MetaTags* adds meta tags for search engines, as well as the page title ('Title' on the 'Meta-data' tab in the
editor). You can define your metatags in the meta-data tab off the content editor in the CMS.
Add *$MetaTags* to the head so that it looks like this:
:::ss
<head>
<% base_tag %>
$MetaTags
<% require themedCSS(layout) %>
<% require themedCSS(typography) %>
<% require themedCSS(form) %>
</head>
> Don't forget to flush the cache each time you change a template by adding *?flush=1* onto the end of the URL.
Your page should now look something like this (with your own content of course):
![](_images/title.jpg)
## Making a Navigation System
So far we have made several pages, but we have no way to navigate between them. We can create a menu for our site using
a **control block**. Control blocks allow us to iterate over a data set, and render each item using a sub-template. The
**page control** *Menu(1)* returns the set of the first level menu items. We can then use the template variable
*$MenuTitle* to show the title of the page we are linking to.
Open up *themes/tutorial/templates/Page.ss*, and insert the following code inside `<div id="Main">`:
:::ss
<ul id="Menu1">
<% control Menu(1) %>
<li><a href="#">$MenuTitle</a></li>
<% end_control %>
</ul>
Here we've created an unordered list called *Menu1*, which *themes/tutorial/css/layout.css* will style into the menu.
Then, using a control block over the page control *Menu(1)*, we add a link to the list for each menu item.
All going to plan, your page should look like this:
![](_images/menu.jpg)
The menu isn't really very useful until each button links to the relevant page. We can get the link for the menu item in
question by using the *$Link* template variable.
Replace the list item line with this one:
:::ss
<li><a href="$Link" title="Go to the &quot;{$Title}&quot; page">$MenuTitle</a></li>
Menu for our site are created using a **loop**. Loops allow us to iterate over a data set, and render each item using a sub-template. The
**loop** *Menu(1)* returns the set of the first level menu items. We can then use the template variable
*$MenuTitle* to show the title of the page we are linking to, $Link for the URL of the page and $LinkingMode to help style our menu with CSS (explained in more detail shortly).
> $Title refers to *Page Name* in the CMS, whereas $MenuTitle refers to (the often shorter) *Navigation label*
:::ss
<ul>
<% loop Menu(1) %>
<li class="$LinkingMode"><a href="$Link" title="$Title.XML">$MenuTitle.XML</a></li>
<% end_loop %>
</ul>
Here we've created an unordered list called *Menu1*, which *themes/simple/css/layout.css* will style into the menu.
Then, using a loop over the page control *Menu(1)*, we add a link to the list for each menu item.
This creates the navigation at the top of the page:
![](_images/tutorial1_menu.jpg)
## Highlighting the current page
A useful feature is highlighting the current page the user is looking at. We can do this with the template variable
*$LinkingMode*. *$LinkingMode* returns one of three values:
*$LinkingMode* which we mentioned before. *$LinkingMode* returns one of three values:
* *current* - This page is being visited, and should be highlighted
* *link* - The page is not currently being visited, so shouldn't be highlighted
@ -205,58 +182,56 @@ A useful feature is highlighting the current page the user is looking at. We can
> For example: if you were visiting a staff member such as "Home > Company > Staff > Bob Smith", you would want to highlight 'Company' to say you are in that section.
Highlighting the current page is easy, simply assign a css class based on the value of *$LinkingMode*. Then provide a different style for current/section in css, as has been provided for you in *tutorial/css/layout.css*.
Highlighting the current page is easy, simply assign a css class based on the value of *$LinkingMode*. Then provide a different style for current/section in css, as has been provided for you in *simple/css/layout.css*.
Change the list item line in *Page.ss* so it looks like this:
![](_images/tutorial1_menu-highlighted.jpg)
:::ss
<li class="$LinkingMode">
<a href="$Link" title="Go to the &quot;{$Title}&quot; page">$MenuTitle</a>
</li>
## A second level of navigation
You should now have a fully functional top menu
The top navigation system is currently quite restrictive. There is no way to
nest pages, we have a completely flat site. Adding a second level in SilverStripe is easy. First (if you haven't already done so), let's add some pages.
![](_images/menu-highlighted.jpg)
The "About Us" section could use some expansion.
## Adding a second level
Select "Add New" in the Pages section, and create two new pages nested under the page "About Us" called "What we do" and "Our History" with the type "Page".
Although we have a fully functional navigation system, it is currently quite restrictive. Currently there is no way to
nest pages, we have a completely flat site. Adding a second level in SilverStripe is easy. First, let's add some pages. The "About Us" section could use some expansion.
Select "About Us", and create two new pages "What we do" and "Our History" of the type "Page" inside. You can also create the pages elsewhere on the site tree, and use the reorganize button to drag and drop the pages into place.
You can also create the pages elsewhere on the site tree, and drag and drop the pages into place.
Either way, your site tree should now look something like this:
![](_images/2nd_level-cut.jpg)
![](_images/tutorial1_2nd_level-cut.jpg)
Great, we now have a hierarchical site structure, let's now look at how this is created and displayed in our template.
Great, we now have a hierarchical site structure, but we still have no way of getting to these second level pages.
Adding a second level menu is very similar to adding the first level menu.
Open up our *Page.ss* template, and find the `<div id="ContentContainer">` tag. Underneath it, add the following code:
Open up */themes/simple/templates/Includes/Sidebar.ss* template and look at the following code:
:::ss
<ul id="Menu2">
<% control Menu(2) %>
<li class="$LinkingMode"><a href="$Link" title="Go to the &quot;{$Title}&quot; page">$MenuTitle</a></li>
<% end_control %>
<ul>
<% loop Menu(2) %>
<li class="$LinkingMode"><a href="$Link" title="Go to the $Title.XML page"><span class="arrow">&rarr;</span><span class="text">$MenuTitle.XML</span></a></li>
<% end_loop %>
</ul>
This should look very familiar. It is exactly the same as our first menu, except we have named our linked list *Menu2*
and we are using the control *Menu(2)* instead of *Menu(1)*. As we can see here, the *Menu* control takes a single
This should look very familiar. It is the same idea as our first menu, except the loop block now uses *Menu(2)* instead of *Menu(1)*.
As we can see here, the *Menu* control takes a single
argument - the level of the menu we want to get. Our css file will style this linked list into the second level menu,
using our usual *$LinkingMode* technique to highlight the current page.
We now have our second level menu, but we also have a problem. The menu is displayed on every page, even those that
don't have any nested pages. We can solve this problem with an **if block**. Simply surround the menu with an if block
To make sure the menu is not displayed on every page, even those that *don't* have any nested pages. We use an **if block**.
Look again in the *Sidebar.ss* file and you will see that the menu is surrounded with an **if block**
like this:
:::ss
<% if Menu(2) %>
<ul id="Menu2">
<% control Menu(2) %>
<li class="$LinkingMode"><a href="$Link" title="Go to the &quot;{$Title}&quot; page">$MenuTitle</a></li>
<% end_control %>
...
<ul>
<% loop Menu(2) %>
<li class="$LinkingMode"><a href="$Link" title="Go to the $Title.XML page"><span class="arrow">&rarr;</span><span class="text">$MenuTitle.XML</span></a></li>
<% end_loop %>
</ul>
...
<% end_if %>
The if block only includes the code inside it if the condition is true. In this case, it checks for the existence of
@ -265,38 +240,52 @@ be processed and the menu will not be shown.
Now that we have two levels of navigation, it would also be useful to include some "breadcrumbs".
Find *`<div id="Content" class="typography">`* and underneath it add:
Open up */themes/simple/templates/Includes/BreadCrumbs.ss* template and look at the following code:
:::ss
<div class="breadcrumbs">
<% if Level(2) %>
<div id="Breadcrumbs">
$Breadcrumbs
</div>
<% end_if %>
Breadcrumbs are only useful on pages that aren't in the top level. We can ensure that we only show them if we aren't in
the top level with another if statement.
:::ss
<% if Level(2) %>
<div class="breadcrumbs">
$Breadcrumbs
</div>
<% end_if %>
The *Level* page control allows you to get data from the page's parents, eg if you used *Level(1)*, you could use
The *Level* page control allows you to get data from the page's parents, e.g. if you used *Level(1)*, you could use
*$Level(1).Title* to get the top level page title. In this case, we merely use it to check the existence of a second
level page; if one exists then we include the breadcrumbs.
We now have a fully functioning two level navigation system. Both menus should be updating and highlighting as you move
This shows how the two level navigation system functions. Both menus should be updating and highlighting as you move
from page to page. They will also mirror changes done in the SilverStripe CMS, such as renaming pages or moving them
around.
![](_images/menu-two-level.jpg)
![](_images/tutorial1_menu-two-level.jpg)
Feel free to experiment with the if and loop blocks, for example you could create a drop down style menu from the top navigation using a combination of the if blocks, loop blocks and some CSS to style it. This uses a *Children* if and loop block which checks to see if there is any sub-pages available within each top level navigation item, you will need to come up with your own CSS to correctly style this approach.
::ss
<ul>
<% loop Menu(1) %>
<li class="$LinkingMode">
<a href="$Link" title="$Title.XML">$MenuTitle.XML</a>
<% if Children %>
<ul>
<% loop Children %>
<li class="$LinkingMode"><a href="$Link" title="Go to the $Title.XML page"><span class="arrow">&rarr;</span><span class="text">$MenuTitle.XML</span></a></li>
<% end_loop %>
<ul>
<% end_if %>
</li>
<% end_loop %>
</ul>
## Using a different template for the home page
So far, a single template *Page.ss* is being used for the entire site. This is useful for the purpose of this
So far, a single template layout *Layouts/Page.ss* is being used for the entire site. This is useful for the purpose of this
tutorial, but in a finished website we can expect there to be several page layouts.
To illustrate how we do this, we will create a new template for the homepage. This template will have a large graphical
@ -305,7 +294,7 @@ banner to welcome visitors.
### Creating a new page type
Earlier we stated that every page in a SilverStripe site has a **page type**, and that SilverStripe will look for a
template corresponding to the page type. Therefore, the first step to get the homepage using a different template is to
template or template layout corresponding to the page type. Therefore, the first step to get the homepage using a different template is to
create a new page type.
Each page type is represented by two php classes: a *data object* and a *controller*. Don't worry about the details of page
@ -332,8 +321,9 @@ Create a new file *HomePage.php* in *mysite/code*. Copy the following code into
Every page type also has a database table corresponding to it. Every time we modify the database, we need to rebuild it.
We can do this by going to [http://localhost/dev/build?flush=1](http://localhost/dev/build?flush=1). It may take a
moment, so be patient. This add tables and fields needed by your site, and modifies any structures that have changed. It
We can do this by going to [http://localhost/dev/build?flush=1](http://localhost/dev/build?flush=1) or replace *localhost* with your own domain name.
It may take a moment, so be patient. This add tables and fields needed by your site, and modifies any structures that have changed. It
does this non-destructively - it will never delete your data.
As we have just created a new page type, SilverStripe will add this to the list of page types in the database.
@ -342,9 +332,9 @@ As we have just created a new page type, SilverStripe will add this to the list
After building the database, we can change the page type of the homepage in the CMS.
Under the "Behaviour" tab. Change it to *HomePage*, and click "Save Draft" and "Publish".
Navigate in the CMS to the "Home" page and under the "Behaviour" tab in the "Edit Page > Settings" section. Change it to *Home Page*, and click "Save & Publish".
![](_images/homepage-type.jpg)
![](_images/tutorial1_homepage-type.jpg)
Our homepage is now of the page type *HomePage*. However, even though it is of the *HomePage* page type, it is still
rendered with the *Page* template. SilverStripe still renders the homepage using the *Page* template because when we
@ -352,118 +342,39 @@ created the *HomePage* page type, we inherited from *Page*. So when SilverStripe
will use the *Page* template. SilverStripe always attempts to use the most specific template first, and then falls back
to the template of the page type's parents.
### Creating a new template
To create a new template, create a copy of *Page.ss* (found in *themes/tutorial/templates*) and call it *HomePage.ss*. If we flush the cache (*?flush=1*), SilverStripe should now be using *HomePage.ss* for the homepage, and *Page.ss* for the rest of the site. Now let's customize the *HomePage* template.
To create a new template layout, create a copy of *Page.ss* (found in *themes/simple/templates/Layouts*) and call it *HomePage.ss*. If we flush the cache (*?flush=1*), SilverStripe should now be using *HomePage.ss* for the homepage, and *Page.ss* for the rest of the site. Now let's customize the *HomePage* template.
First, remove the breadcrumbs and the secondary menu; we don't need them for the homepage. Let's replace the title with our image. Add this line above the *Content* div:
First, remove the breadcrumbs and the secondary menu by removing the `<% include SideBar %>` line of code; we don't need them for the homepage. Let's replace the title with an image. Add this line above the *$Content* variable.
Now add the following to replace the `<h1>$Title</h1>` code in your template:
:::ss
<div id="Banner">
<img src="themes/tutorial/images/welcome.png" alt="Homepage image" />
<img src="http://www.silverstripe.org/themes/silverstripe/images/sslogo.png" alt="Homepage image" />
</div>
![](_images/home-template.jpg)
### Using a subtemplate
Having two templates is good, but we have a lot of identical code in the two templates. Rather than having two
completely separate templates, we can use subtemplates to specify only the part of the template that has changed.
If we compare *Page.ss* and *HomePage.ss*, we can see the only differences are within the *ContentContainer* div.
Copy the contents of the *ContentContainer* div from *themes/tutorial/templates/Page.ss* to a new file
*themes/tutorial/templates/Layout/Page.ss*, and do the same from *themes/tutorial/templates/HomePage.ss* to
*themes/tutorial/templates/Layout/HomePage.ss*.
The files should look like this:
**themes/tutorial/templates/Layout/Page.ss**
:::ss
<% if Menu(2) %>
<ul id="Menu2">
<% control Menu(2) %>
<li class="$LinkingMode"><a href="$Link" title="Go to the &quot;{$Title}&quot; page">$MenuTitle</a></li>
<% end_control %>
</ul>
<% end_if %>
<div id="Content" class="typography">
<% if Level(2) %>
<div class="breadcrumbs">
$Breadcrumbs
</div>
<% end_if %>
$Content
$Form
</div>
Your Home page should now look like this:
**themes/tutorial/templates/Layout/HomePage.ss**
![](_images/tutorial1_home-template.jpg)
:::ss
<div id="Banner">
<img src="themes/tutorial/images/welcome.png" alt="Homepage image" />
</div>
<div id="Content" class="typography">
$Content
</div>
We can then delete *themes/tutorial/templates/HomePage.ss*, as it is no longer needed.
Replace the code we just copied out of *themes/tutorial/templates/Page.ss* with *$Layout*, so it looks like this:
**themes/tutorial/templates/Page.ss**
:::ss
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN" "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" >
<head>
<% base_tag %>
$MetaTags
<% require themedCSS(layout) %>
<% require themedCSS(typography) %>
<% require themedCSS(form) %>
</head>
<body>
<div id="Main">
<ul id="Menu1">
<% control Menu(1) %>
<li class="$LinkingMode"><a href="$Link" title="Go to the &quot;{$Title}&quot; page">$MenuTitle</a></li>
<% end_control %>
</ul>
<div id="Header">
<h1>$Title</h1>
</div>
<div id="ContentContainer">
$Layout
</div>
<div id="Footer">
<span>Visit <a href="http://www.silverstripe.com" title="Visit www.silverstripe.com">www.silverstripe.com</a> to download the CMS</span>
</div>
</div>
$SilverStripeNavigator
</body>
</html>
> As you have just done a few template changes, remember to add *?flush=1* in your URL to flush the cache so see your changes in the browser
SilverStripe first searches for a template in the *themes/tutorial/templates* folder. Since there is no *HomePage.ss*,
SilverStripe first searches for a template in the *themes/simple/templates* folder. Since there is no *HomePage.ss*,
it will use the *Page.ss* for both *Page* and *HomePage* page types. When it comes across the *$Layout* tag, it will
then descend into the *themes/tutorial/templates/Layout* folder, and will use *Page.ss* for the *Page* page type, and
*HomePage.ss* for the *HomePage* page type.
then descend into the *themes/simple/templates/Layout* folder, and will use *Page.ss* for the *Page* page type, and
*HomePage.ss* for the *HomePage* page type. So while you could create a HomePage.ss in the *themes/simple/templates/* it is better to reuse the navigation and footer common to both our Home page and the rest of the pages on our website and lets you write less code to achieve the end result.
![](_images/tutorial1_subtemplates-diagram.jpg)
![](_images/subtemplates-diagram.jpg)
## Summary
Starting from a basic template, we have introduced template variables, controls and if blocks, and we have used these
We have introduced template variables, controls and if blocks, and we have used these
to build a basic but fully functional site. You have also been briefly introduced to page types, and seen how they
correspond to templates and subtemplates. By using these templates, you have seen how to customize the site content
correspond to templates and sub-templates. By using these templates, you have seen how to customize the site content
according to the page type of the page you are displaying.
In the next tutorial, [Extending a Basic Site](2-extending-a-basic-site), we will explore page types on a

View File

@ -16,7 +16,9 @@ Throughout this tutorial we are going to work on adding two new sections to the
first is a news section, with a recent news listing on the homepage and an RSS feed. The second is a staff section,
which demonstrates more complex database structures by associating an image with each staff member.
![](_images/news-with-rss-small.jpg)![](_images/einstein-small.jpg)
![](_images/tutorial2_newslist.jpg)
![](_images/tutorial2_einstein.jpg)
@ -57,7 +59,7 @@ the database, which fields can be edited in the CMS, and can use code to make ou
A more in-depth introduction of Model-View-Controller can be found
[here](http://www.slash7.com/articles/2005/02/22/mvc-the-most-vexing-conundrum).
![](_images/pagetype-inheritance.jpg)
![](_images/tutorial2_pagetype-inheritance.jpg)
## Creating the news section page types
@ -171,8 +173,10 @@ method to the *ArticlePage* class.
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Content', new DateField('Date'), 'Content');
$fields->addFieldToTab('Root.Content', new TextField('Author'), 'Content');
$datefield = new DateField('Date');
$datefield->setConfig('showcalendar', true);
$fields->addFieldToTab('Root.Main', $datefield, 'Content');
$fields->addFieldToTab('Root.Main', new TextField('Author'), 'Content');
return $fields;
}
@ -192,13 +196,14 @@ Firstly, we get the fields from the parent class; we want to add fields, not rep
returned is a `[api:FieldList]` object.
:::php
$fields->addFieldToTab('Root.Content', new DateField('Date'), 'Content');
$fields->addFieldToTab('Root.Content', new TextField('Author'), 'Content');
$fields->addFieldToTab('Root.Main', new TextField('Author'), 'Content');
$fields->addFieldToTab('Root.Main', new DateField('Date'), 'Content');
We can then add our new fields with *addFieldToTab*. The first argument is the tab on which we want to add the field to:
"Root.Content" is the tab which the content editor is on. The second argument is the field to add; this is not a
database field, but a `[api:FormField]` documentation for more details.
"Root.Main" is the tab which the content editor is on (another is "Root.Metadata". The second argument is the field to add; this is not a database field, but a `[api:FormField]` - see the documentation for more details.
We add two fields: A simple `[api:TextField}` and a `[api:DateField]`. There are many more FormFields available in the default installation, please refer to [Form Field Types](form-field-types) for the list.
:::php
return $fields;
@ -210,14 +215,15 @@ to edit the fields in the CMS.
Now that we have created our page types, let's add some content. Go into the CMS and create an *ArticleHolder* page
named "News", and create some *ArticlePage*s inside it.
![](_images/news-cms.jpg)
![](_images/tutorial2_news-cms.jpg)
## Modifing the date field
**Please note:** As of version 2.4, the DateField type no longer automatically adds a javascript datepicker. Your date field will look just like a text field.
At the moment, your date field will look just like a text field.
This makes it confusing and doesn't give the user much help when adding a date.
To make the date field a bit more user friendly, you can add a dropdown calendar, set the date format and add better title.
To make the date field a bit more user friendly, you can add a dropdown calendar, set the date format and add better title. By default,
the date field will have the date format defined by your locale.
:::php
<?php
@ -229,11 +235,11 @@ To make the date field a bit more user friendly, you can add a dropdown calendar
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab('Root.Content', $dateField = new DateField('Date','Article Date (for example: 20/12/2010)'), 'Content');
$fields->addFieldToTab('Root.Main', $dateField = new DateField('Date','Article Date (for example: 20/12/2010)'), 'Content');
$dateField->setConfig('showcalendar', true);
$dateField->setConfig('dateformat', 'dd/MM/YYYY');
$fields->addFieldToTab('Root.Content', new TextField('Author','Author Name'), 'Content');
$fields->addFieldToTab('Root.Main', new TextField('Author','Author Name'), 'Content');
return $fields;
}
@ -243,7 +249,7 @@ Let's walk through these changes.
:::php
$fields->addFieldToTab('Root.Content', $dateField = new DateField('Date','Article Date (for example: 20/12/2010)'), 'Content');
*$dateField* is added only to the DateField in order to change the configuration.
*$dateField* is declared only to in order to change the configuration of the DateField.
:::php
$dateField->setConfig('showcalendar', true);
@ -258,8 +264,8 @@ Set *showCalendar* to true to have a calendar appear underneath the Date field w
:::php
$fields->addFieldToTab('Root.Content', new TextField('Author','Author Name'), 'Content');
By default the first argument *'Date'* or *'Author'* is shown as the title, however this might not be that helpful so to change the title,
add the new title as the second argument. See the `[api:DateField]` documentation for more details.
By default the field name *'Date'* or *'Author'* is shown as the title, however this might not be that helpful so to change the title,
add the new title as the second argument. See the `[api:DateField]` documentation for more details of the DateField configuration.
## Creating the templates
@ -273,131 +279,102 @@ page layout.
First, the template for displaying a single article:
**themes/tutorial/templates/Layout/ArticlePage.ss**
**themes/simple/templates/Layout/ArticlePage.ss**
:::ss
<% if Menu(2) %>
<ul id="Menu2">
<% control Menu(2) %>
<li class="$LinkingMode"><a href="$Link" title="Go to the $Title page">$MenuTitle</a></li>
<% end_control %>
</ul>
<% end_if %>
<div id="Content" class="typography">
<% if Level(2) %>
<div class="breadcrumbs">
$Breadcrumbs
</div>
<% end_if %>
<div class="content-container">
<article>
<h1>$Title</h1>
$Content
<div class="newsDetails">
Posted on $Date.Nice by $Author
<div class="news-details">
<p>Posted on $Date.Nice by $Author</p>
</div>
<div class="content">$Content</div>
</article>
$Form
$PageComments
</div>
<% include SideBar %>
The first block of code is our regular second level menu; we also have our regular breadcrumbs code here. We will see
how to remove these blocks of repetitive code in a bit.
Most of the code is just like the regular Page.ss, we include an informational div with the date and the author of the Article.
We use *$Date* and *$Author* to access the new fields. In fact, all template variables and page controls come from
either the data object or the controller for the page being displayed. The *$Breadcrumbs* variable comes from the
*Breadcrumbs()* method of the `[api:SiteTree]` class. *$Date* and *$Author* come from the *Article* table through
your data object. *$Content* comes from the *SiteTree* table through the same data object. The data for your page is
either the data object or the controller for the page being displayed. The *$Title* variable comes from the
*Title* field of the `[api:SiteTree]` class. *$Date* and *$Author* come from the *ArticlePage* table through
your custom Page. *$Content* comes from the *SiteTree* table through the same data object. The data for your page is
spread across several tables in the database matched by id - e.g. *Content* is in the *SiteTree* table, and *Date* and
*Author* are in the *Article* table. SilverStripe matches these records by their ids and collates them into the single
*Author* are in the *ArticlePage* table. SilverStripe matches these records by their ids and collates them into the single
data object.
![](_images/data-collation.jpg)
![](_images/tutorial2_data-collation.jpg)
Rather than using *$Date* directly, we use *$Date.Nice*. If we look in the `[api:Date]` documentation, we can see
that the *Nice* function returns the date in *dd/mm/yyyy* format, rather than the *yyyy-mm-dd* format stored in the
database.
![](_images/news.jpg)
![](_images/tutorial2_news.jpg)
Now we'll create a template for the article holder: we want our news section to show a list of news items, each with a
summary.
**themes/tutorial/templates/Layout/ArticleHolder.ss**
**themes/simple/templates/Layout/ArticleHolder.ss**
:::ss
<div id="Content" class="typography">
<div class="content-container">
<article>
<h1>$Title</h1>
$Content
<ul id="NewsList">
<% control Children %>
<li class="newsDateTitle"><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></li>
<li class="newsDateTitle">$Date.Nice</li>
<li class="newsSummary">$Content.FirstParagraph <a href="$Link" title="Read more on &quot;{$Title}&quot;">Read more &gt;&gt;</a></li>
<% end_control %>
</ul>
<div class="content">$Content</div>
</article>
<% loop Children %>
<article>
<h2><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></h2>
<p>$Content.FirstParagraph</p>
<a href="$Link" title="Read more on &quot;{$Title}&quot;">Read more &gt;&gt;</a>
</article>
<% end_loop %>
$Form
</div>
<% include SideBar %>
Here we use the page control *Children*. As the name suggests, this control allows you to iterate over the children of a
page, which in this case is our news articles. The *$Link* variable will give the address of the article which we can
use to create a link, and the *FirstParagraph* function of the `[api:HTMLText]` field gives us a nice summary of the
article.
article. The function strips all tags from the paragraph extracted.
![](_images/articleholder.jpg)
Remember that the visual styles are not part of the CMS, they are defined in the tutorial CSS file.
![](_images/tutorial2_articleholder.jpg)
### Using include files in templates
The second level menu is something we want in most, but not all, pages so we can't put it in the base template. By
putting it in a separate file in the *tutorial/templates/Includes* folder, we can use `<% include templatename %>` to
include it in our other templates. Separate the second level menu into a new file *themes/tutorial/templates/Includes/Menu2.ss*.
**themes/tutorial/templates/Includes/Menu2.ss**
:::ss
<% if Menu(2) %>
<ul id="Menu2">
<% control Menu(2) %>
<li class="$LinkingMode"><a href="$Link" title="Go to the $Title page">$MenuTitle</a></li>
<% end_control %>
</ul>
<% end_if %>
And then replace the second level menu with `<% include Menu2 %>` in *Page.ss* and *ArticlePage.ss* like so:
**themes/tutorial/templates/Layout/Page.ss**, **themes/tutorial/templates/Layout/ArticlePage.ss**
:::ss
<% include Menu2 %>
<div id="Content" class="typography">
...
Do the same with the breadcrumbs:
**themes/tutorial/templates/Includes/Breadcrumbs.ss**
:::ss
<% if Level(2) %>
<div class="breadcrumbs">
$Breadcrumbs
</div>
<% end_if %>
**themes/tutorial/templates/Layout/Page.ss**, **themes/tutorial/templates/Layout/ArticlePage.ss**
:::ss
...
<div id="Content" class="typography">
<% include Breadcrumbs %>
...
You can make your templates more modular and easier to maintain by separating commonly-used pieces into include files.
You are already familiar with the `<% include Sidebar %>`-Line for the menu.
We'll separate the display of linked articles as we want to reuse this code later on.
Replace the code in *ArticleHolder.ss** with an include statement:
**themes/simple/templates/Layout/ArticleHolder.ss**
:::ss
...
<% loop Children %>
<% include ArticleTeaser %>
<% end_loop %>
...
and paste the code in a new include snippet:
**themes/simple/templates/Includes/ArticleTeaser.ss**
:::ss
<article>
<h2><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></h2>
<p>$Content.FirstParagraph</p>
<a href="$Link" title="Read more on &quot;{$Title}&quot;">Read more &gt;&gt;</a>
</article>
### Changing the icons of pages in the CMS
@ -405,21 +382,18 @@ Let's now make a purely cosmetic change that nevertheless helps to make the info
Add the following field to the *ArticleHolder* and *ArticlePage* classes:
:::php
static $icon = "themes/tutorial/images/treeicons/news";
static $icon = "framework/docs/en/tutorials/_images/treeicons/news-file.gif";
And this one to the *HomePage* class:
:::php
static $icon = "themes/tutorial/images/treeicons/home";
static $icon = "framework/docs/en/tutorials/_images/treeicons/home-file.gif";
This will change the icons for the pages in the CMS.
> Note: that the corresponding filename to the path given for $icon will end with **-file.gif**,
> e.g. when you specify **news** above, the filename will be **news-file.gif**.
![](_images/icons2.jpg)
![](_images/tutorial2_icons2.jpg)
## Showing the latest news on the homepage
@ -432,28 +406,25 @@ control. We can get the data for the news articles by implementing our own funct
:::php
...
public function LatestNews($num=5) {
$news = DataObject::get_one("ArticleHolder");
return ($news) ? DataObject::get("ArticlePage", "ParentID = $news->ID", "Date DESC", "", $num) : false;
$holder = DataObject::get_one("ArticleHolder");
return ($holder) ? DataList::create('ArticlePage')->where('"ParentID" = '.$holder->ID)->sort('Date DESC')->limit($num) : false;
}
...
This function simply runs a database query that gets the latest news articles from the database. By default, this is
five, but you can change it by passing a number to the function. See the `[api:DataObject]` documentation for
five, but you can change it by passing a number to the function. See the [Data Model](../topics/datamodel) documentation for
details. We can reference this function as a page control in our *HomePage* template:
**themes/tutorial/templates/Layout/Homepage.ss**
:::ss
...
$Content
<ul id="NewsList">
<% control LatestNews %>
<li class="newsDateTitle"><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></li>
<li class="newsDateTitle">$Date.Nice</li>
<li class="newsSummary">$Content.FirstParagraph<a href="$Link" title="Read more on &quot;{$Title}&quot;">Read more &gt;&gt;</a></li>
<% end_control %>
</ul>
<div class="content">$Content</div>
</article>
<% loop LatestNews %>
<% include ArticleTeaser %>
<% end_loop %>
...
@ -466,7 +437,7 @@ page is referenced in other pages, e.g. by page controls. A good rule of thumb i
page currently being viewed in the controller; only if a function needs to be used in another page should you put it in
the data object.
![](_images/homepage-news.jpg)
![](_images/tutorial2_homepage-news.jpg)
@ -491,7 +462,7 @@ that name on the controller if it exists.
Depending on your browser, you should see something like the picture below. If your browser doesn't support RSS, you
will most likely see the XML output instead.
![](_images/rss-feed.jpg)
![](_images/tutorial2_rss-feed.jpg)
Now all we need is to let the user know that our RSS feed exists. The `[api:RSSFeed]` in your controller, it will be
called when the page is requested. Add this function to *ArticleHolder_Controller*:
@ -505,9 +476,9 @@ called when the page is requested. Add this function to *ArticleHolder_Controlle
This automatically generates a link-tag in the header of our template. The *init* function is then called on the parent
class to ensure any initialization the parent would have done if we hadn't overridden the *init* function is still
called. In Firefox you can see the RSS feed link in the address bar:
called. Depending on your browser, you can see the RSS feed link in the address bar:
![](_images/rss.jpg)
![](_images/tutorial2_rss.jpg)
## Adding a staff section
@ -553,7 +524,7 @@ insert an image in the *$Content* field).
public function getCMSFields() {
$fields = parent::getCMSFields();
$fields->addFieldToTab("Root.Content.Images", new UploadField('Photo'));
$fields->addFieldToTab("Root.Images", new UploadField('Photo'));
return $fields;
}
@ -569,74 +540,71 @@ a simple database field like all the fields we have seen so far, but has its own
array, we create a relationship between the *StaffPage* table and the *Image* table by storing the id of the respective
*Image* in the *StaffPage* table.
We then add an `[api:UploadField]` in the *getCMSFields* function to the tab "Root.Content.Images". Since this tab doesn't exist,
the *addFieldToTab* function will create it for us. The *ImageField* allows us to select an image or upload a new one in
We then add an `[api:UploadField]` in the *getCMSFields* function to the tab "Root.Images". Since this tab doesn't exist,
the *addFieldToTab* function will create it for us. The *UploadField* allows us to select an image or upload a new one in
the CMS.
![](_images/photo.jpg)
![](_images/tutorial2_photo.jpg)
Rebuild the database ([http://localhost/dev/build?flush=1](http://localhost/dev/build?flush=1)) and open the CMS. Create
a new *StaffHolder* called "Staff" in the "About Us" section, and create some *StaffPage*s in it.
![](_images/create-staff.jpg)
a new *StaffHolder* called "Staff", and create some *StaffPage*s in it.
![](_images/tutorial2_create-staff.jpg)
### Creating the staff section templates
The staff section templates aren't too difficult to create, thanks to the utility methods provided by the `[api:Image]` class.
**themes/tutorial/templates/Layout/StaffHolder.ss**
**themes/simple/templates/Layout/StaffHolder.ss**
:::ss
<% include Menu2 %>
<div id="Content" class="typography">
<% include Breadcrumbs %>
<div class="content-container">
<article>
<h1>$Title</h1>
$Content
<ul id="StaffList">
<% control Children %>
<li>
<div class="staffname"><a href="$Link">$Title</a></div>
<div class="staffphoto">$Photo.SetWidth(50)</div>
<div class="staffdescription"><p>$Content.FirstSentence</p></div>
</li>
<% end_control %>
</ul>
<div class="content">$Content</div>
</article>
<% loop Children %>
<article>
<h2><a href="$Link" title="Read more on &quot;{$Title}&quot;">$Title</a></h2>
$Photo.SetWidth(150)
<p>$Content.FirstParagraph</p>
<a href="$Link" title="Read more on &quot;{$Title}&quot;">Read more &gt;&gt;</a>
</article>
<% end_loop %>
$Form
</div>
<% include SideBar %>
This template is very similar to the *ArticleHolder* template. The *SetWidth* method of the `[api:Image]` class
will resize the image before sending it to the browser. The resized image is cached, so the server doesn't have to
resize the image every time the page is viewed.
![](_images/staff-section.jpg)
![](_images/tutorial2_staff-section.jpg)
The *StaffPage* template is also very straight forward.
**themes/tutorial/templates/Layout/StaffPage.ss**
:::ss
<% include Menu2 %>
<div id="Content" class="typography">
<% include Breadcrumbs %>
<div id="StaffPhoto">
$Photo.SetWidth(150)
<div class="content-container">
<article>
<h1>$Title</h1>
<div class="content">
$Photo.SetWidth(433)
$Content</div>
</article>
$Form
$PageComments
</div>
$Content
</div>
<% include SideBar %>
Here we also use the *SetWidth* function to get a different sized image from the same source image. You should now have
a complete staff section.
![](_images/einstein.jpg)
![](_images/tutorial2_einstein.jpg)
## Summary

View File

@ -12,7 +12,7 @@ follow this tutorial on any site of your own.
We are going to add a search box on the top of the page. When a user types something in the box, they are taken to a
results page.
![](_images/searchresults-small.jpg)
![](_images/tutorial4_search.png)
## Creating the search form
@ -27,110 +27,54 @@ After including that in your `_config.php` you will need to rebuild the database
The actual search form code is already provided in FulltextSearchable so when you add the enable line above to your
`_config.php` you can add your form as `$SearchForm`.
### Custom CSS code
In the simple theme, the SearchForm is already added to the header. We will go through the code and explain it.
Add the following css code to the *themes/tutorial/css/layout.css* file. This will style the header form and search
results page.
:::css
#Header form {
float:right;
width:160px;
margin:25px 25px 0px 25px;
}
#Header form * {
display:inline !important;
}
#Header form div {
}
#Header form input.text {
width:110px;
color:#000;
background:#f0f0f0;
border:1px solid #aaa;
padding:3px;
}
#Header form input.action {
font-weight:bold;
}
.searchResults h2 {
font-size:2.2em;
font-weight:normal;
color:#0083C8;
margin-bottom:15px;
}
.searchResults p.searchQuery {
color:#333;
margin-bottom:10px;
}
.searchResults ul#SearchResults li {
margin-bottom:20px;
}
ul#SearchResults p {
font-size:1.1em;
font-weight:normal;
line-height:2em;
color:#333;
}
ul#SearchResults a.searchResultHeader {
font-size:1.3em;
font-weight:bold;
color:#0083C8;
text-decoration:none;
margin:20px 0 8px 0;
padding-left:20px;
background:url(../images/treeicons/search-file.gif) no-repeat left center;
}
ul#SearchResults a {
text-decoration:none;
color:#0083C8;
}
ul#SearchResults a:hover {
border-bottom:1px dotted #0083C8;
}
## Adding the search form
We then just need to add the search form to the template. Add *$SearchForm* to the 'Header' div in
*themes/tutorial/templates/Page.ss*.
To add the search form, we can add `$SearchForm` anywhere in our templates. In the simple theme, this is in
*themes/simple/templates/Includes/Header.ss*
*themes/tutorial/templates/Page.ss*
*themes/simple/templates/Includes/Header.ss*
:::ss
<div id="Header">
...
<% if SearchForm %>
<span class="search-dropdown-icon">L</span>
<div class="search-bar">
$SearchForm
<h1>$Title</h1>
<span class="search-bubble-arrow">}</span>
</div>
<% end_if %>
<% include Navigation %>
This results in:
![](_images/searchform.jpg)
![](_images/tutorial4_searchbox.png)
## Showing the results
Next we need to create the *results* function.
The results function is already included in the `ContentControllerSearchExtension` which
is applied via `FulltextSearchable::enable()`
*mysite/code/Page.php*
*cms/code/search/ContentControllerSearchExtension.php*
:::php
class Page_Controller extends ContentController {
class ContentControllerSearchExtension extends Extension {
...
public function results($data, $form){
function results($data, $form, $request) {
$data = array(
'Results' => $form->getResults(),
'Query' => $form->getSearchQuery(),
'Title' => 'Search Results'
'Title' => _t('SearchForm.SearchResults', 'Search Results')
);
$this->Query = $form->getSearchQuery();
return $this->customise($data)->renderWith(array('Page_results', 'Page'));
return $this->owner->customise($data)->renderWith(array('Page_results', 'Page'));
}
}
First we populate an array with the data we wish to pass to the template - the search results, query and title of the
The code populates an array with the data we wish to pass to the template - the search results, query and title of the
page. The final line is a little more complicated.
When we call a function by its url (eg http://localhost/home/results), SilverStripe will look for a template with the
@ -153,15 +97,15 @@ function, and then attempt to render it with *Page_results.ss*, falling back to
## Creating the template
Lastly we need to create the template for the search page. This template uses all the same techniques used in previous
Lastly we need the template for the search page. This template uses all the same techniques used in previous
tutorials. It also uses a number of pagination variables, which are provided by the `[api:DataObjectSet]`
class.
*themes/tutorial/templates/Layout/Page_results.ss*
*themes/simple/templates/Layout/Page_results.ss*
:::ss
<div id="Content" class="searchResults">
<h2>$Title</h2>
<h1>$Title</h1>
<% if Query %>
<p class="searchQuery"><strong>You searched for &quot;{$Query}&quot;</strong></p>
@ -179,7 +123,9 @@ class.
<% end_if %>
</a>
<p>$Content.LimitWordCountXML</p>
<a class="readMoreLink" href="$Link" title="Read more about &quot;{$Title}&quot;">Read more about &quot;{$Title}&quot;...</a>
<a class="readMoreLink" href="$Link"
title="Read more about &quot;{$Title}&quot;"
>Read more about &quot;{$Title}&quot;...</a>
</li>
<% end_control %>
</ul>
@ -212,8 +158,7 @@ class.
Then finally add ?flush=1 to the URL and you should see the new template.
![](_images/searchresults.jpg)
![](_images/tutorial4_search.png)
## Summary

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 84 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 39 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 91 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 65 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 35 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 63 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 58 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 173 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 176 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 170 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 62 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 92 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 82 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 75 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 85 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 19 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB