How to Build a Simple Website With Wave Framework

Introduction

Wave is a PHP micro-framework that is built loosely following model-view-control architecture and factory method design pattern. It is made for web services, websites and info-systems and is built to support a native API (Application Programming Interface) architecture, caching and smart resource management. Wave is a compact framework that does not include bloated libraries and features and is developed keeping lightweight speed and optimizations in mind.

This tutorial takes a look at how to build a simple website with Wave Framework. It is recommended that you first go through the Web Service Tutorial, since we will be using the same Models and Controllers here - albeit slightly modified - to build a website that relies on our API architecture. If you do not wish to go through the Web Service Tutorial first, then you should first set up Wave Framework following the Installation guide and then upload the final example files from the Web Service Tutorial folder '/doc/examples/webservice/' on top of your installation. It is however recommended to go through the first tutorial, since it teaches you some of the core features behind Wave Framework.

This tutorial focuses only on building a website that works through URL's only. It does not do any asynchronous requests with JavaScript, which means that every view will be on a separate URL. This tutorial will give an overview about how to build your Sitemap, how to build your Views and implement functionality of Controllers and Models and tie it all together into one simple working website.

Wave Framework comes with a View and URL Controllers to build websites with. These two Controllers can be edited by you at any time - they are stored in '/controllers/' subfolder. You can read more about tweaking the URL and View Controllers in documentation about those Controllers as this tutorial will use the default URL and View Controllers.

Complete files of this tutorial are available in '/doc/examples/website/' subfolder in the archive. If you wish to test the example without writing the tutorial code yourself, then you should copy these files to root folder of your Wave Framework. Note that this will overwrite your default '/resources/api.profiles.ini' as well as '/resources/en.sitemap.ini' and '/resources/styles/style.css' files. You should make a backup of these files in case you have modified these files yourself for some reason.

Planning

The Web Service Tutorial was about creating a simple Web Service that you can use to store information about your favorite DVD movies and then return information about those movies. In this tutorial we will be using that web service and build a website around it.

If you have already gone through the previous Web Service Tutorial where you created the basic Controllers and Models for your web service, then you already have a good starting point for this tutorial, but if you did not go through the first tutorial or wish to start from the default example, then you can get the Controller and Model files of the previous tutorials finished example from '/doc/examples/webservice/' folder.

This type of website will require different page views that will be shown under different URL's for the user agent or browser. Every View acts essentially as a template and each view stores its own view-logic based on what information is being requested and how. This would be the list of different Views we would need:

While our web service also allows to remove movies from the database, we will not be using that part of the service as part of this tutorial, since the mechanics of doing it are similar to adding a movie. If you want then you can develop this feature by yourself after this tutorial is finished.

We will also not be needing a database for this service, just like we did not need a database in Web Service Tutorial, as we will be storing movie data in Filesystem as a serialized array. But of course you are free to update the Model file to make database requests for storing and querying movie information and refer to documentation about databases when doing so.

Configuration and Set-Up

The first thing that you need to do, when setting up Wave Framework for a website, is to think about the languages that you need to implement and then about the actual pages that your website will have. You can always later on expand the project by including additional languages and pages, but it is always a good to have some general idea when starting up.

Languages are stored in your Configuration in '/config.ini' file. The Configuration option 'languages' is a comma-separated list of languages that Wave Framework will use in your system. The keywords and/or abbreviations in that value are used by Wave Framework to find files that are related to these languages: Sitemap and Translations files. We will keep the Configuration setting at 'en' for this tutorial, which means that our system uses just one language and requires just these two files (both files already exist by default in the archive):

Translations

Translations file stores keys and their translation values for your website. While this tutorial will build a website in just one language, it is always a good idea to include any type of text that you implement in your User Interface - and thus Views - in the Translations file. This makes it easier to later on add new languages to your website, since all you have to do is translate the Translations file.

While you do not have to populate your translations file before starting development, we will be doing it here so that we do would have to keep returning to the Translations file in other sections of the tutorial. Below is the list of translation keys and values that we are using, store these rows in your '/resources/en.translations.ini' file:

	
	welcome="Welcome to my DVD List website!"
	about-content="This is an example website made with Wave Framework"
	contact-content="Contact us at info@waveframework.com"
	title="Title"
	year="Year"
	problem-adding-movie="There was a problem adding a movie!"
	movie-added="Movie added successfully!"
	cannot-find-movies="Cannot find movies!"
	cannot-find-movie="Cannot find this movie!"
	movie-info="Movie information"
	add-movie="ADD MOVIE"
	back-to-list="Back to list"
	

In later sections of this tutorial we will be using these translations keywords to implement these translations within our User Interface.

One more thing: if you are building a large website that has language specific content that spans entire pages, then it is recommended to store page content itself not in translations, but in the database and thus load it from database. While we load content for About and Contact pages in this tutorial from the same translations file, this is only done for the purpose of having a simple example. In real projects you would generally load such page content from databases. It is recommended to only store User Interface translations in translations file, for example for buttons, error messages and form labels.

Sitemap

Sitemap is the file that includes all the URL's that can be requested from the website without a '404 Not Found' message being returned to the user agent or browser. Wave Framework has a very detailed Sitemap system that gives you an almost unrestricted control over how URL's work and how they are interpreted.

Note that Wave Framework Configuration has some settings that deal with URL handling as well. One of them is the 'enforce-first-language-url' setting. If this setitng is set to 1, then this means that if you go to Home page, like 'http://www.example.com/', then you would be redirected automatically to URL that includes the appended language keyword as a URL node. By default this would redirect to 'http://www.example.com/en/'. If you do not wish to use this behavior then you can turn this setting off (note that other languages would still have to use the language node in order to separate one language from another).

The language node itself should never be stored in the Sitemap file. Before Wave Framework starts looking for a match from Sitemap file, it has already decided what language is being requested based on Configuration settings and the first URL node (if used). This means that the URL's, that are declared in the Sitemap file, should not have a language node as part of the URL string. This will be shown in the examples below as well.

Wave Framework Configuration also has two Sitemap-related settings that are used in Wave Framework for situations when the URL is not set (which is considered the Home page) or when a match is not found (which would be the 404 Not Found page). By default the Home page will call a view named 'home' and 404 page will call a view named '404'. You can edit these values in Configuration: they are the 'home-view' and '404-view' settings.

These two settings tell Wave Framework which keywords in Sitemap files are considered for 'home' View. So if home-view is set in Configuration file to 'home', then Wave Framework looks for settings from Sitemap file under [home] keyword. This home-view URL node should be defined in all Sitemap files for all languages as the same one, as this URL (/home/) is never actually shown in the browser. Note that whenever Wave Framework finds a URL that has 'view' value set to the same as 'home-view', then it redirects the user to home URL of the website.

404 View and Home View are stored in these two files by default configuration (as per 'home-view' and '404-view' settings):

Previously, in the Planning section, we listed the Views that we would need in our system. Before we can start working on them and create files for them, it is a good idea to declare them in our Sitemap which is stored in '/resources/en.sitemap.ini' file.

Sitemap file consists of URL string, which is declared in square brackets as an INI-file group, and their settings, which are defined right below them. These URL's can be different for each language Sitemap in your system while having the same settings. This means that you can use a different URL for two different languages that load the exact same View with the same settings. This allows you to build websites and infosystems that have multiple languages, but the same functionality.

The first view we would need to define would be the Home View (note that 404 View is not defined in Sitemap, since it does not carry additional functionality and is loaded always when a URL match is not found). This here would be a simple Home View definition in our Sitemap:

	
	[home]
	view="home"
	meta-title="Home"
	cache-timeout=30
	

The keyword in the brackets is the URL where the page would be accessed. But this is technically not required for a Home View, since Home Views are automatically redirected by URL Controller to a URL that does not include a URL node. This means that when go to an address of 'http://www.example.com/en/home/' then the client browser would be redirected to 'http://www.example.com/en/', or 'http://www.example.com/' - latter example only when you have the 'enforce-first-language-url' setting set to 0 in Configuration.

This Sitemap file includes a number of settings for our Home View too. The 'view' variable tells Wave Framework that if this URL is matched, then the View Controller should load the View named 'home'. This View would be loaded from '/views/view.home.php' file.

The 'meta-title' keyword tells View Controller what will be the title name for the view. This title is often shown on tabs or browser windows as a title for the page.

The 'cache-timeout' flag tells Wave Framework that this page can be entirely cached for 30 seconds, which means that if another request is made to the page with similar conditions within 30 seconds, then it will return a result from cache rather than generate it again. This setting is useful on pages that might take a long time to generate or cause website slowdowns during high web traffic.

The next two URL's that we need to define are for the About and Contact pages. Example declarations for these two are here:

	
	[about]
	view="page"
	subview="about"
	meta-title="About"

	[contact]
	view="page"
	subview="contact"
	meta-title="Contact"
	

As you can see, the same 'view' setting is used by both of these URL's. This means that they both load the same View from the same '/views/view.page.php' file. This is useful when you have multiple Views that are very similar to one another and are only different in terms of content. These two pages are indeed very similar, since they only display content from our Translations file.

What is different about the settings of these two pages is the 'subview' variable, which is set to 'about' on one and 'contact' on another. This variable can be detected by the View and thus load different content for that specific View. Example of how to do this is also covered below.

These views would be loaded when user goes to a URL that matches the value that is defined in the square brackets in Sitemap file. About page would be loaded on either 'http://www.example.com/en/about/' or 'http://www.example.com/about/' URL and so on.

Next we need a page where we will display a form that will be used for movie-adding functionality. We will also need a View for the page where we will show a list of movies. Both of these URL's will load their own View files and in this tutorial we will define them like this:

	
	[movies/add]
	view="add"
	meta-title="Add a movie"

	[movies/list]
	view="list"
	meta-title="List of movies"
	

Compared to the previously defined URL's, these pages have URL's that include two URL nodes that are separated by a forward-slash. URL Controller will still match these URL's exactly the same way as before, which means that if the client makes a request for 'http://www.example.com/en/movies/list/' URL, then 'list' View will be returned. Same for URL for the 'add' View.

Last but not least, we need a URL which will be used when we are showing a page about a single movie. Movie page is different from other pages in one very notable way: you can have multiple movie pages but you can never really define all movies in your Sitemap file. This would be difficult to maintain and would quickly grow your Sitemap file too large to easily handle.

Client would still have to request data about a specific movie based on the request URL. One way to do this would be to use GET variables the way a lot of old websites are built. For example, movie with an ID of 7 would be displayed in the URL of 'http://www.example.com/movie/?id=7', while movie with an ID of 20 would be 'http://www.example.com/movie/?id=20'. But in the last ten years, websites are using better and cleaner methods to do the same.

This means that for the purpose of this tutorial, we want that our system would display information about a movie with an ID of 7 from this URL instead: 'http://www.example.com/movie/7/', which is much cleaner to write, share and remember.

In Wave Framework there is no need to define all of those URL's in your Sitemap file for all of your movies. In order to implement a dynamic URL like that, it needs to be defined like this:

	
	[movie/:numeric:]
	view="movie"
	meta-title="Movie information"
	

This ':numeric:' URL keyword means that the second URL node must be numeric in order for this URL to be matched. In case the second URL node is not numeric, then a 404 page is shown to the user agent. Other dynamic URL options are also available, such as :alpha: and :alphanumeric: and you can read more about them in URL Controller documentation.

This allows us to later use that dynamic URL as a value when generating the content for the View, such as for loading a movie with a specific ID. This will be shown later in the tutorial.

Stylesheets and JavaScript

Websites also implement stylesheets and JavaScript. While this tutorial does not cover the topic of JavaScript integration and AJAX calls with Wave Framework, both of these topics are available and documented in other parts of the documentation extensively.

The main CSS stylesheet and JavaScript files are stored in '/resources/styles/style.css' '/resources/script.js' respectively. These files, together with jQuery, are loaded automatically by the default View Controller. If you wish to alter this behavior in any way, then you can edit the View Controller file in '/controllers/controller.view.php'.

As an addition, Wave Framework also supports view-specific on-demand stylesheets and scripts. This means that if the View Controller is loading a view for 'home', for example, then it would also load the Stylesheet file from '/resources/styles/home.style.css' and JavaScript from '/resources/home.script.js' if they are available. This is very useful since it allows you to not have to load all the JavaScript or CSS for all of the pages of your website. If one page requires to use JavaScript and CSS files that are very large, then there is no reason to load them for every other page of your website or infosystem.

View Controller also does other things when loading resources, such as reducing the number of HTTP request by unifying or even minifying some script resources. You can read about these features in other parts of the documentation.

In this tutorial, the only thing we will be doing is setting up the main CSS Stylesheet. Without much explanation - and only to make our tutorial website at least somewhat visually appealing - this is the stylesheet that will be used:

	
	body,html {
		background-color: #e8decc;
		font: 13px Verdana;
		color: #221e18;
	}
	p { 
		margin:6px 0px; 
	}
	a {
		font:12px Verdana;
		color:#000000;
	}
	#header { 
		font: 20px Verdana;
		color: #ffffff;
		border-width: 2px;
		border-radius: 8px 8px 8px 8px; 
		background-color: #c0ac8a;
		text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.25); 
		margin: 10px;
		padding: 10px;
		width: 500px;
		height:32px;
		margin-left: auto;
		margin-right: auto;
	}
	#header a {
		display: block;
		float: right; 
		border-width: 2px;
		border-radius: 8px 8px 8px 8px; 
		background-color: #736449;
		margin: 3px;
		padding: 5px;
		font: 14px Verdana;
		color: #ffffff;
		text-shadow: 0px -1px 0px rgba(0, 0, 0, 0.25); 
		text-decoration: none;
	}
	#header a.active {
		background-color: #9d8663;
	}
	#header a:hover {
		background-color: #847256;
	}
	#body {
		margin: 10px;
		padding: 10px;
		width: 500px;
		margin-left: auto;
		margin-right: auto;
		border-radius: 8px 8px 8px 8px; 
		background-color: #ffffff;
	}
	input,select {
		font: 13px Verdana;
		color: #221e18;
		padding: 4px;
		width:150px;
	}
	#movies td, #movies th {
		padding:4px 8px;
	}
	#movies td, #movies th, #movies td a {
		font: 15px Verdana;
		color: #000000;
	}
	#movies th {
		font-weight: bold;
		background-color:#d6d6d6;
	}
	#movies td a {
		font-weight: bold;
		text-decoration: underline;
	}
	

These styles do very basic things, like assign our web page colors as well as the style the header and menu that will be used as part of the HTML content. If you are not familiar with CSS, then you can find tutorials about CSS styling in various resources across the web. Wave Framework implements stylesheets similarly to how they would be implemented in other web systems.

Preparing for Views

Large part of website creation - once you have the Web Service functionality already - is about building Views. Wave Framework loads its Views through View Controller.

View Controller behavior can be altered in any way you find suitable for your project. View Controller can load multiple View components from other Views and return the output to client browser, or it can render and return part of the HTML frame by itself. Default View Controller in Wave Framework renders the main HTML frame by itself, while loading the body content from a View file.

While it is recommended to edit View Controller when developing your own project - such as to load website header and footer through that directly - then in this tutorial we will not edit View Controller and instead add menu headers to all of our Views directly. This is because it is easier for you to handle every View as independently as possible in the beginning. This gives a better understanding about how to build views since you can focus on one View at the time.

Later on you can take out the duplicated code from the Views and implement it in View Controller or through other API calls, if you wish.

Home View

The first View that we will build is the Home View, which is loaded whenever the URL Controller detects that the entered URL should be for the Home page. This is most commonly when no URL - other than website root - is entered or only the language node - like http://www.example.com/en/ - is entered.

The first step should be to create the file for the View. First create a file in '/views/' subfolder and name it 'view.home.php'. This file will contain the View class declaration. View filenames - just like Model and Controller filenames - have to be standardized in the way they are written in order for Wave Framework to be able to load them dynamically.

View filenames should always be in 'view.[name].php' format and their class name in 'WWW_view_[name]' format. The 'name' section there is the same name that is referenced in Configuration of 'home-view' and '404-view' values, as well as the 'view' setting in Sitemap declarations.

It is recommended for the View to be extended from WWW_Factory class and named as 'WWW_view_home'. When rendering the View, the View Controller loads 'render()' method by default from the View with the input data sent to the server, so you should define 'render()' method in the class as well. It is actually possible to define a different method to be loaded, based on Sitemap configuration, but 'render()' is always the default. There are no parameters that are required for the View class.

This is a base frame for the home View class, declared according to previous description:

	
	class WWW_view_home extends WWW_Factory {

		// WWW_controller_view calls this function as output for page content
		public function render($input){
		
			// Render something here
		
		}

	}
	

Inside this 'render()' method you should build your HTML Frame. For this tutorial, we will keep the HTML frame as simple as possible, so it will just consist of a header that contains menu links and a body that shows the content.

The header will be the same for all of our Views and will be duplicated. It is recommended to either store the header in a different View and load it through View Controller, or declare it in View Controller directly, in order to reduce code-duplication. But in this tutorial we will duplicate the header for all of our Views for simplicity

So, to start things up, we would be using a HTML body like the one below. Place this HTML to your 'render()' method call, but outside the PHP tags, like this:

	
	...
	public function render($input){
		?>
			<div id="header">
				<a href="#">Some Link</a>
				<a href="#">Some Other Link</a>
				<a href="#">Some Third Link</a>
			</div>
			<div id="body">
				<p>Some page content</p>
			</div>
		<?php
	}
	...
	

If you now upload your edited files and make a request to the root page of your website, then you should see a beige-colored HTML as your new website. If some of the files, such as CSS stylesheet, is in the cache from previous requests that you might have made, then you should refresh your browser again with CTRL+F5 or by clearing cache and loading the page once more.

It is also recommended to make a request to '/tools/debugger.php' script every now and then to see if your system is throwing any PHP errors or warnings at any point. This is also the file you should refer to whenever your application throws that critical error message, since this Debugger script gives detailed information about what went wrong. Wave Framework automatically suppresses PHP errors from the User Interface directly in order to make sure no sensitive information about the software is ever shown to the client directly. Note that the Debugger requires an HTTP authentication with the username and password that are stored in your systems Configuration file.

The HTML frame given above is not really suitable for our website and is merely a starting point. At the moment it does not have the working links that we would expect, nor does it have the content from our Translations files or the movies from our movie database.

We will focus on implementing our Translations first. Wave Framework Factory has methods that can return Translations information as an array for you. This array is populated from values of Translations keywords and values from '/resources/[language].translations.ini' file. Wave Framework returns the Translations of currently detected language by default, which in our case is 'en' for English.

We have to make the following method call within our View in order to access our translations:

	
	...
	// Loading translations
	$translations=$this->getTranslations();
	...
	

Note that this method of 'getTranslations()', as well as various other methods we will be calling, require the classes to be extended from WWW_Factory class. Factory gives us a variety of helper functions that we can use when building our Views, just like it gives us additional tools when building Models and Controllers.

Since the new $translations variable carries an array of our translations, then we can use those Translations in the page content directly. This is how you should update your View class:

	
	...
	// Get translations
	$translations=$this->getTranslations();
	...
		<div id="body">
			<p><?=$translations['welcome']?></p>
		</div>
	...
	

If you upload the files again and test the home page, then you should see that the value from Translations file is used within the content. If this did not work, then look at the example files that are provided for this tutorial, which shows how to implement this in case you ran into a problem.

But we are still missing the correct links for our menu in the header. In order to know what the actual URL's are, we need to refer to our current Sitemap. Similarly to Translations, this Sitemap is returned based on what language is used by Wave Framework and with a method call 'getSitemap()'.

The returned Sitemap array is not a direct representation of the Sitemap declaration file in '/resources/en.sitemap.ini'. Wave Framework parses through the Sitemap and generates an array that is useful for building a website. This array has keys that refer to 'view' and the 'subview' values from Sitemap file, rather than URL's directly - this allows you to easily create links that work in multiple languages at the same time. To get the Sitemap that is generated from the Sitemap file that we set up earlier, you have to call this method:

	
	...
	$sitemap=$this->getSitemap();
	...
	

And here are some (not all) of the array elements of our returned Sitemap, shown as an example:

	
    [list] => Array
        (
            [view] => list
            [meta-title] => List of movies
            [url] => /w/en/movies/list/
        )
		
    [page/about] => Array
        (
            [view] => page
            [subview] => about
            [meta-title] => About
            [url] => /w/en/about/
        )

    [movie] => Array
        (
            [view] => movie
            [meta-title] => Movie information
            [url] => /w/en/movie/:0:/
        )
	

The way this Sitemap works within Wave Framework is that the keys in that array are not the URL itself anymore (like they were in the Sitemap file) and they are a combination of 'view' and 'subview' settings instead. This is useful, since if you have a website with multiple languages, then you can use that same address for all of your languages and Wave Framework itself will know what language URL to place.

In case multiple URL's use the same view, then you can refer to them based on the 'subview' value you set in Sitemap declaration file. This is why the Sitemap array returns 'page/about', since there was a Sitemap URL that used the view of 'page' and had subview of 'about' set for it.

Every array element in this Sitemap array includes both the settings that were stored in Sitemap declaration file as well as new generated values, such as the 'url' variable, which is a link to the page from the Sitemap based on currently active language.

Note that if this 'url' key might include dynamic URL nodes, which are are set as ':0:', ':1:' and so on in the URL. We defined 'movie' URL in our Sitemap as having a secondary URL that is numeric, so in the returned Sitemap array we have a ':0:' value in the URL. This should be replaced before use by the actual URL you wish to use, which in our case would be the movie ID.

It also includes the the meta-title value from our Sitemap delcaration which you can (but don't have to) use as the title for the link. We will use them as the title. You can add more values to Sitemap declaration file - if you wish - and they would be accessible from the same array (so you can create another entry for the link title, if you wish).

We will simply use the variables from the array returned by 'getSitemap()' method call and build a header menu from our Sitemap like this:

	
	// Loading Sitemap
	$sitemap=$this->getSitemap();
	...
		<div id="header">
			<a href="<?=$sitemap['page/contact']['url']?>"><?=$sitemap['page/contact']['meta-title']?></a>
			<a href="<?=$sitemap['page/about']['url']?>"><?=$sitemap['page/about']['meta-title']?></a>
			<a href="<?=$sitemap['add']['url']?>"><?=$sitemap['add']['meta-title']?></a>
			<a href="<?=$sitemap['list']['url']?>"><?=$sitemap['list']['meta-title']?></a>
			<a href="<?=$sitemap['home']['url']?>"><?=$sitemap['home']['meta-title']?></a>
		</div>
	...
	

Note that we are adding the links in the reverse order to our HTML because our CSS has a rule that floats all of the links to the right. This means that the first element will be the rightmost menu element. You can change this behavior by using different CSS for your menus if you want.

If you now upload the project files to the server, then you should see your actual menu in the header. But note that the other links do not work yet and if you click on any of them, then you will get the dreaded 'WE ARE CURRENTLY EXPERIENCING A PROBLEM WITH YOUR REQUEST' message. This error message means that Wave Framework encountered a critical error within the request. System will also write the encountered error to an error log whenever a message like this appears and you can see the detailed description about the error when loading the Debugger script at '/tools/debugger.php'.

The reason why this error would be shown - instead of the 404 Not Found page - is because we have defined the URL's in our Sitemap, but Wave Framework is unable to find the View files and class definitions for these Views. This means that the '/views/' folder does not have the Views that are being requested.

We should also add a separate method call to getState('view') to our Views 'render()' method. This method call returns information about our currently loaded View and we will use it in the next section. Since we will be duplicating this Home View for our other Views, we won't have to declare this method call again for other Views.

This is our finished Home View:

	
	class WWW_view_home extends WWW_Factory {
		// View Controller calls this function as output for page content
		public function render($input){
			// Loading translations
			$translations=$this->getTranslations();
			// Loading sitemap
			$sitemap=$this->getSitemap();
			// Loading view data
			$view=$this->getState('view');
			?>
				<div id="header">
					<a href="<?=$sitemap['page/contact']['url']?>"><?=$sitemap['page/contact']['meta-title']?></a>
					<a href="<?=$sitemap['page/about']['url']?>"><?=$sitemap['page/about']['meta-title']?></a>
					<a href="<?=$sitemap['add']['url']?>"><?=$sitemap['add']['meta-title']?></a>
					<a href="<?=$sitemap['list']['url']?>"><?=$sitemap['list']['meta-title']?></a>
					<a href="<?=$sitemap['home']['url']?>"><?=$sitemap['home']['meta-title']?></a>
				</div>
				<div id="body">
					<p><?=$translations['welcome']?></p>
				</div>
			<?php
		}
	}
	

About and Contact Pages

About and Contact pages are technically similar to our Home View. To start things up, it is recommended to simply copy the 'view.home.php' file in '/views/' folder and rename the file to 'view.page.php'. About and Contact pages referred to 'page' View in the Sitemap declaration file, so both URL's will actually load the same View from the same file.

You also have to rename the class name to 'WWW_view_page' for the View, otherwise Wave Framework will encounter an error, since it expects to load a class with that exact name.

We will be building our About and Contact pages at the same time. The main difference between our previous 'home' View and the new 'page' View is that About and Contact pages are both using the same 'view' setting in the Sitemap declaration. The only difference between these two URL's in our Sitemap was that one had the 'subview' value set to 'about' and the other had the value set to 'contact'. This means that in the body section we need to separate which content is actually loaded in order to separate the two contents.

We are loading our page content from translations file, but it could just as easily be loaded from a database or from some content management system or even over API from some other system. In this tutorial, the page content is stored in Translations file for example purposes.

In order to learn what 'subview' is currently being used we have to request the currently known View data from our system State. Wave Frameworks State stores information about the entire current system, environment and its Configuration. You can make the following call to get the information about the currently loaded View:

	
	...
	$view=$this->getState('view');
	...
	

This code snippet was actually added in the end of the previous section, so make sure you don't accidentally declare it twice.

The returned array stores a lot more information about the currently loaded View, most of it is also from the Sitemap file, based on the declaration that we set up. You can preview what this variable contains by printing out the contents of the array, but the only thing we actually want from the array right now is one of the values from view data: 'subview'. This is the key from the array that we will be using to check which of the 'subview' pages is currently loaded: 'about' or 'contact'.

Our previously built Home View - that we used as a base code for our Page View - populated our main body 'div' tag with just a single translation. For About and Contact pages it should be replaced with a 'switch' statement for both of the 'subview' values, like this:

	
	// Loading view data
	$view=$this->getState('view');
	...
		<div id="body">
			<?php
				switch($view['subview']){
					case 'about':
						echo '<p>'.$translations['about-content'].'</p>';
						break;
					case 'contact':
						echo '<p>'.$translations['contact-content'].'</p>';
						break;
				}
			?>
		</div>
	...
	

If you upload the files to the server now, then your Home, About and Contact pages should all work without any problems when you click on their links in the header menu. If you run into any problems, then please refer to the finished example files or see if Debugger script has logged any errors.

Adding a Movie - Part I

Movie adding page consists of a HTML form that submits form data to Wave Framework API over HTTP. This means that in Wave Framework your data submissions should never be sent to another website page or a View itself. Views should be entirely independent from any form submission logic and related hooks and are intended only to show relevant information to the client browser.

We will make a copy of 'view.home.php' and rename it to 'view.add.php' in order to use the Home View as a template for this new View. Also rename the class name to 'WWW_view_home', similarly to how we did in the previous sections.

The reason we make copies of the Home View is because it is the most basic of Views in our system. While it would be useful to actually define the header menu elsewhere, such as in View Controller directly or loaded through View Controller, for the purpose of the tutorial it is better to handle each View as a self-contained and functioning page.

You should replace the body content with a simple HTML form that also calls some of our translation values. We will generate just two fields: one for the movie title and another for the year that movie was released. Like this:

	
	...
	<form method="post" action="#">
		<p><?=$translations['title']?>:</p>
		<p><input type="text" name="title" value=""/></p>
		<p><?=$translations['year']?>:</p>
		<p>
			<select name="year">
			<?php
			for($i=date('Y');$i>=1980;$i--){
				?>
				<option value="<?=$i?>"><?=$i?></option>
				<?php
			}
			?>
			</select>
		</p>
		<p><input type="submit" value="<?=$translations['add-movie']?>"/></p>
	</form>
	...
	

This is not the final form however. Submitting data right now would attempt to submit the data to the View itself. If the form 'action' parameter is left empty, then forms are submitted to the same page that the client is on. While it is possible for View to detect that input was submitted to itself and then make that View insert data to database and check for errors, this is not recommended. A lot of older websites are built in this manner and even many web programming tutorials have examples of this, but such an approach is old and outdated. A View should primarily only deal with logic and content of the HTML and not with other functionalities of the system.

There is also a technical reason why you should never submit data from a form to a View: if you submit data to a View and then click the Back button in your browser, then browsers will throw the 'Re-submit POST' warning to the user. This can be quite an annoyance for the usability of the system and can even cause some data to be submitted unintentionally to the system twice.

In order to not run into this Back button problem, as well as to keep the View itself clean from functionality logic of our system, we should make sure that the data is submitted to our API directly. Since we named our form values exactly the same as the values that we use in our Web Service - which was covered in detail in the Web Service Tutorial - then we can make the client browser submit the data exactly the same way as we manually submitted it in the previous tutorial.

Movie adding page is the first View that incorporates the Controllers and Models that were developed in the previous tutorial about the Web Service. If you did not do that tutorial, then you should copy the example files from the Web Service Tutorial - namely files from '/models/' and '/controllers/' folders as well as '/resources/api.profiles.ini' file - to your current files.

We will be editing these files slightly in this tutorial in order to add additional functionality that we need.

Once you have implemented the Model and Controller file from the previous tutorial, then you should try it out and change the 'action' parameter in the form tag to submit to the API instead of the page itself. Change your form 'action' parameter to the following:

	
	...
	<form method="post" action="json.api?www-command=movies-add">
	...
	

After you have uploaded the files to the server, you can try to actually submit data to our Web Service. For example: fill the movie title with 'Brave' and select the year 2012 and click the submit button. After submitting you should be confronted with a white page that says the following:

	
	{"success":"Movie saved!"}
	

If you did the Web Service Tutorial, then the above message will seem quite familiar to you.

Because we made a request to JSON API file (filename 'json.api') then Wave Framework returned the data to us as a JSON string. We will be changing this later on, but you can always use JSON API (or XML API as 'xml.api') for debugging and see what the response is when you submit data from your forms. We will change this API URL in the Part II of this section later on.

If the above JSON string is not the result that you got, then you should take a look at the example files and see if there was anything different in your code that might have caused it to make your code work differently. If you run into any errors, then refer to Debugger script in '/tools/debugger.php'.

Of course this API response is not an ideal solution yet, since the user should never see a page like this. Instead we should redirect the user to the movie list View after submitting the movie. But for now, we will leave this page like it is and continue working on it in a later section. This is because the List View is still missing and we cannot redirect our form submission to that page just yet.

Movie List View

It is recommended to start building the 'list' View by first copying the 'view.home.php' file and renaming it to 'view.list.php' file in '/views/' subfolder, similarly to how we did with the previous Views. You should also rename the class name to 'WWW_view_list'. You could also then try and upload the files to the server and click on the List link on your website to make sure that the new View works correctly.

Now we can start implementing the functionality in the View that loads the list of movies and then generates a list table, with links, for the movies in our database. These links would later on redirect to movie information pages where we show information about only a single movie at the time.

While it would be possible to load the Controller dynamically from within the View and get the list of movies this way, it is better for this tutorial to show an example of how to use the API internally. internal API calls are very similar to HTTP API calls, except that internal API calls never have to be authenticated with API Profile and internal calls also return a PHP variable by default, so you do not have to unserialize JSON.

In our previous tutorial we built a Web Service that had an API command 'movies-all' that returned the list of all of our movies from database. We can now incorporate this API call in our View generation directly and replace the default content from the Home view with the following code:

	
	...
	<?php
		$movies=$this->api('movies-all');
		if(!isset($movies['error'])){
			echo 'Found!';
		} else {
			echo '<p>'.$translations['cannot-find-movies'].'</p>';
		}
	?>
	...
	

Note that the $movies['error'] is simply the way we returned the result from our API in the Web Service Tutorial. You can use whatever returned keys in the array to check if your API call was successful or not. Wave Framework actually also has helper arrays for this purpose, which are recommended over a custom solution like this, but we will stick to the returned 'error' key for now for the sake of keeping things simpler in this tutorial.

After you have added the code above, then you should test your page by uploading the files and clicking on the movie list link and seeing if you get a 'Found!' response on your List page or not. You should get a 'Found!' message on the page, since we added a movie through the API in the previous section.

This 'Found!' response is obviously not what we actually want and we should generate a table in the page content instead. This table should also include links. In uor Sitemap declaration file, the 'movie' page included a dynamic URL node (declared as ':numeric:'). In the returned Sitemap array from 'getSitemap()' method, this value would be ':0:' in the URL, so we should replace it with the movie ID we are linking to.

Here is a simple example of a table that has the list of movies as well as the links to those movies pages. Use this to replace the previous 'Found!' line in the code:

	
	...
	echo '<table id="movies">';
	echo '<tr>';
		echo '<th>'.$translations['title'].'</th>';
		echo '<th>'.$translations['year'].'</th>';
	echo '</tr>';
	foreach($movies as $m){
		echo '<tr>';
			echo '<td><a href="'.str_replace(':0:',$m['id'],$sitemap['movie']['url']).'">'.$m['title'].'</a></td>';
			echo '<td>'.$m['year'].'</td>';
		echo '</tr>';
	}
	echo '</table>';
	...
	

When you upload the files and run the movie list page again, then you should get a nice list of movies (or a list that only has a single movie, if you have only submitted movies once so far). Links of these movies direct to Movie View that we have not yet made, but will make in the next section.

Single Movie View

Movie View is the view that will be used to display information about a single movie. Our Web Service only stores movie title and year, but you can easily extend this to include other information, like a link, description, a picture and more.

You should copy your 'view.home.php' file and rename it to 'view.movie.php' file in the '/views/' subfolder. Class name should also be renamed to 'WWW_view_movie'. This is similar to how we did the previous Views.

Movie View is the only View that was declared in our Sitemap file with the permission to use dynamic URL nodes. This means that we will be able to use the dynamic part of the URL and use it for loading a movie with that ID from our database.

We will be loading data to our View similarly to how we loaded it for the list View: through an API call. This time we will be making a 'movies-get' request to the API that also requires a variable 'id'. This 'id' variable defines what movie ID we are requesting.

In Wave Framework, when making internal API calls, the second variable sent to the 'api()' method is an array of all variables that are used as an input to the API call. For example, to make a request to movie that has an ID of 1, then you would make the following request:

	
	$movieData=$this->api('movies-get',array('id'=>1));
	

We want to use the dynamic URL as the ID for our API call. We can fetch this data from the State for the currently loaded View with our 'getState()' call that we already used in the previous sections. This 'dynamic-url' key in that $view variable actually stores an array instead of one single string, since it is possible to have more than one dynamic URL node at the same time.

To make our system load the information about the specific movie we have listed, then we have to get the first key in that 'dynamic-url' array. You should replace the current page content with the following:

	
	...
	<p><?=$translations['movie-info']?></p>
	<?php
		$movie=$this->api('movies-get',array('id'=>$view['dynamic-url'][0]));
		if(!isset($movie['error'])){
			?>
			<p><?=$translations['title']?>: <?=$movie['title']?></p>
			<p><?=$translations['year']?>: <?=$movie['year']?></p>
			<?php
		} else {
			$this->setHeader('HTTP/1.1 404 Not Found');
			echo '<p>'.$translations['cannot-find-movie'].'</p>';
		}
	?>
	<a href="<?=$sitemap['list']['url']?>"><?=$translations['back-to-list']?></a>
	...
	

If you visit your List page again and click on one of the movies, then you will see that the page now shows a simple table that includes information about the requested movie. At the same time your URL address is clean and easy to remember, rather than an intimidating URL with appended query variables.

If you manually change the URL to have an ID that you know doesn't exist, then the page shows a translation for 'cannot-find-movie' keyword as well. The above code example also incorporates a 'setHeader()' call which makes sure that the page would be served with a 404 Not Found header as a result.

Adding a Movie - Part II

Now that we have built all of our Views in the system we can get back to the movie-adding functionality that previously simply produced a JSON string once the form data was submitted. This JSON string was displayed because the HTTP API file that we used was for JSON API - as it had filename 'json.api'.

Another problem with the API was that our Controller - that the API loaded - also did not know where to redirect the client browser after the data was submitted. It did not know what page to show after submitting the form. Thus we need a way to define the URL where the client will be redirected to after the movie is submitted. This also means that we have to add some new functionality to our Controller for the purpose of redirecting the client.

So these two problems must be solved:

In order to hide the output we can easily tell Wave Framework API to return data to us in PHP format instead of JSON. If data is submitted to PHP API, then this data is never output - which means that everything works in the API as it would otherwise, but no data is returned to the client browser except HTTP headers.

All we have to do is change the file URL that we make the request to. This means that we have to change the 'json.api' to 'www.api' and that change alone will make the Wave Framework API return data in PHP format by default. The 'www' simply tells Wave Framework that API is used without actual output being requested. Change the form action URL to this:

	
	<form method="post" action="<?=$view['url-web']?>www.api?www-command=movies-add">
	

Now if you add a new movie then no output is shown anymore - page will be entirely blank - but the movie is actually added to the database, if the title and year input fields were not empty.

This still does not solve the problem of how to tell the Controller to redirect us to the 'list' View after movie has been added. Wave Framework has no general rule about how to build such a functionality because there can be so many different use cases that require different URL's, so it entirely depends on how you, as a developer, want to implement such a feature.

For the purpose of this tutorial I will be adding two hidden variables to my movie-adding form that will tell the Controller where to redirect the client if the movie adding has failed and where to redirect the client if the movie adding was a success. I will be adding these two hidden input tags inside the HTML form:

	
	...
	<input type="hidden" name="success-url" value="<?=$sitemap['list']['url']?>">
	<input type="hidden" name="failure-url" value="<?=$sitemap['add']['url']?>">
	...
	

The success URL will redirect the client to the single movie page and failure URL will redirect the client back to the movie-adding page.

This alone is not enough however. We also need to change our Controller a little to notify Wave Framework about these redirects and actually set these headers. Wave Framework API has certain callback keywords that can be used to set headers as respone, such as ones that are required for redirecting the client. You can also use the 'setHeader()' method for this in the Controller, but if you use 'setHeader()' method then this will not be shown in the actual output array in the response and sometimes it is useful to see the redirect links in the output array as well.

Before we add a new feature to our Controller, let's take a look at our current movie-adding Controller method for 'add()', the way we built it in Web Service Tutorial:

	
	// This function adds a movie in database
	public function add($input){
		if(!isset($input['title']) || $input['title']==''){
			$this->returnData['error']='Title is missing';
		} else if(!isset($input['year']) || $input['year']==''){
			$this->returnData['error']='Year is missing';
		} else {
			// This loads the model object from the class we created
			$movie=$this->getModel('movie');
			$movie->setTitle($input['title']);
			$movie->setYear($input['year']);
			if($movie->saveMovie()){
				$this->returnData['success']='Movie saved!';
			} else {
				$this->returnData['error']='Could not save movie!';
			}
		}
		return $this->returnData;
	}
	

As you can see, this method returns an error for cases when the movie title or year are missing or when the movie-saving was not a success in Model and the method call failed. We can use this, together with the input value for success and failure URL's, to detect if the client has to be redirected or not. Add this code snippet right before the method returns value to Wave Framework:

	
	...
	// If an error is detected
	if(isset($this->returnData['error']) && isset($input['failure-url'])){
		// This is a callback keyword that sets the redirect header
		$this->returnData['www-temporary-redirect']=$input['failure-url'];
	} elseif(isset($input['success-url'])){
		// This is a callback keyword that sets the redirect header
		$this->returnData['www-temporary-redirect']=$input['success-url'];
	}
	...
	

Wave Framework has a small number of callback array keywords - like the 'www-temporary-redirect' one - which can be used for setting headers, sessions, cookies or doing redirects. These are detailed in API documentation further, but for now what this code does is very simple: it checks if redirect URL's are set and if they are set, then returns headers with the response that redirect the client to the URL's that we sent to the API with the request.

If we upload the files now and try adding a movie again, then we will be successfully redirected to movie list where the new movie is listed. Same applies to when the movie-adding was not a success: we would be redirected back to the form. This technically is all the core functionality we need to make the website work.

Handling Notifications and Errors

While our website now works and everything seems to be as expected, we still haven't gotten any error handling in our system. This means that we are not actually notifying a user that a movie was added or that adding a movie has failed.

In this tutorial we will be dealing with errors the 'old school' way. This means that we will simply return a GET variable to the redirected pages and detect their presence in the Views when generating the page.

This is not the best way of doing such a functionality. Wave Framework actually includes a State Messenger , as well as session and cookie managers, which can be used for these situations. But to keep this tutorial as simple as possible, then the 'old school' is the quickest and easiest way to accomplish this.

We will be changing our 'success-url' and 'failure-url' by appending GET variables to both. These are the variables that we then check for in the Views. Modify your hidden form fields to the following:

	
	<input type="hidden" name="success-url" value="<?=$sitemap['list']['url']?>?ok-notification">
	<input type="hidden" name="failure-url" value="<?=$sitemap['add']['url']?>?fail-notification">
	

The above code includes GET variables in the end of the URL that we now need to detect in our movie list page and form page. Add this to your 'list' View, right after the list table:

	
	// If this is set, then movie was added
	if(isset($input['ok-notification'])){
		echo '<p><b>'.$translations['movie-added'].'</b></p>';
	}
	

And add this code snippet to the 'add' View, right below the form:

	
	<?php
		// If this is set, then movie adding failed
		if(isset($input['fail-notification'])){
			echo '<p><b>'.$translations['problem-adding-movie'].'</b></p>';
		}
	?>
	

If you upload the changes and test your website again then you should see that when you add a movie now, then your website will also display notifications about the success (or failure) of your action. If everything works as expected, then this concludes the code-writing part of this tutorial!

Where to Next?

If you already did not do this after the first Web Service Tutorial, then I recommend you take a look at the API Documentation section of this documentation, which has multiple documentation pages about all of the options that Wave Framework API offers.

This documentation also has a section called Feature Guides, which mostly focuses on features that you would use when building a website. These cover a broad range of topics from caching to encryptions, user sessions, cookies and more. Feature Guides should teach you more or less everything of importance about Wave Framework and its features.

If you want to continue working on this tutorials website, then you can add things like deleting a movie or build a better notification system when movie is added or an error is encountered.

This tutorial is also used as the basis for the next tutorial about making AJAX requests with Wave Framework as well as the tutorial about how to set up Users and Permissions for your web system. You can continue to those tutorials from the same code you developed in here.

Congratulations!

You have created your first fully functional website with Wave Framework! It is easy to take what this tutorial has shown and apply it to making other websites and infosystems and benefit from the set of features that Wave Framework offers.

And at the same time you still have your Web Service and API that you can use and build a User Interface for from other platforms and not just the web.

So congratulations and I hope you will have fun experimenting and developing software with Wave Framework!

Good luck!