Building a Website with PHP, MySQL and jQuery Mobile, Part 2
This is the second part of a two-part tutorial, in which we use PHP, MySQL and jQuery mobile to build a simple computer web store. In the previous part we created the models and the controllers, and this time we will be writing our views.
jQuery mobile
First, lets say a few words about the library we will be using. jQuery mobile is a user interface library that sits on top of jQuery and provides support for a wide array of devices in the form of ready to use widgets and a touch-friendly development environment. It is still in beta, but upgrading to the official 1.0 release will be as simple as swapping a CDN URL.
The library is built around progressive enhancement. You, as the developer, only need to concern yourself with outputting the correct HTML, and the library will take care of the rest. jQuery mobile makes use of the HTML5 data- attributes and by adding them, you instruct the library how it should render your markup.
In this tutorial we will be using some of the interface components that this library gives us - lists, header and footer bars and buttons, all of which are defined using the data-role attributes, which you will see in use in the next section.
Rendering Views
The views are PHP files, or templates, that generate HTML code. They are printed by the controllers using the render() helper function. We have 7 views in use for this website - _category.php, _product.php, _header.php, _footer.php, category.php, home.php and error.php, which are discussed later on. First, here is render() function:
includes/helpers.php
/* These are helper functions */ function render($template,$vars = array()){ // This function takes the name of a template and // a list of variables, and renders it. // This will create variables from the array: extract($vars); // It can also take an array of objects // instead of a template name. if(is_array($template)){ // If an array was passed, it will loop // through it, and include a partial view foreach($template as $k){ // This will create a local variable // with the name of the object's class $cl = strtolower(get_class($k)); $$cl = $k; include "views/_$cl.php"; } } else { include "views/$template.php"; } }
The first argument of this function is the name of the template file in the views/ folder (without the .php extension). The next is an array with arguments. These are extracted and form real variables which you can use in your template.
There is one more way this function can be called - instead of a template name, you can pass an array with objects. If you recall from last time, this is what is returned by using the find() method. So basically if you pass the result of Category::find()
to render, the function will loop through the array, get the class names of the objects inside it, and automatically include the _category.php template for each one. Some frameworks (Rails for example) call these partials.
The Views
Lets start off with the first view - the header. You can see that this template is simply the top part of a regular HTML5 page with interleaved PHP code. This view is used in home.php and category.php to promote code reuse.
includes/views/_header.php
<!DOCTYPE html> <html> <head> <title><?php echo formatTitle($title)?></title> <meta name="viewport" content="width=device-width, initial-scale=1" /> <link rel="stylesheet" href="http://code.jquery.com/mobile/1.0b2/jquery.mobile-1.0b2.min.css" /> <link rel="stylesheet" href="assets/css/styles.css" /> <script type="text/javascript" src="http://code.jquery.com/jquery-1.6.2.min.js"></script> <script type="text/javascript" src="http://code.jquery.com/mobile/1.0b2/jquery.mobile-1.0b2.min.js"></script> </head> <body> <div data-role="page"> <div data-role="header" data-theme="b"> <a href="./" data-icon="home" data-iconpos="notext" data-transition="fade">Home</a> <h1><?php echo $title?></h1> </div> <div data-role="content">
In the head section we include jQuery and jQuery mobile from jQuery's CDN, and two stylesheets. The body section is where it gets interesting. We define a div with the data-role="page" attribute. This, along with the data-role="content" div, are the two elements required by the library to be present on every page.
The data-role="header" div is transformed into a header bar. The data-theme attribute chooses one of the 5 standard themes. Inside it, we have a link that is assigned a home icon, and has its text hidden. jQuery Mobile comes with a set of icons you can choose from.
The closing tags (and the footer bar) reside in the _footer.php view:
includes/views/_footer.php
</div> <div data-role="footer" id="pageFooter"> <h4><?php echo $GLOBALS['defaultFooter']?></h4> </div> </div> </body> </html>
Nothing too fancy here. We only have a div with the data-role="footer" attribute, and inside it we print the globally accessible $defaultFooter variable, defined in includes/config.php.
Neither of the above views are printed directly by our controllers. They are instead used by category.php and home.php:
includes/views/home.php
<?php render('_header',array('title'=>$title))?> <p>Welcome! This is a demo for a ...</p> <p>Remember to try browsing this ...</p> <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b"> <li data-role="list-divider">Choose a product category</li> <?php render($content) ?> </ul> <?php render('_footer')?>
If you may recall, the home view was rendered in the home controller. There we passed an array with all the categories, which is available here as $content
. So what this view does, is to print the header, and footer, define a jQuery mobile listview (using the data-role attribute), and generate the markup of the categories passed by the controller, using this template (used implicitly by render()):
index.php/views/_category.php
<li <?php echo ($active == $category->id ? 'data-theme="a"' : '') ?>> <a href="?category=<?php echo $category->id?>" data-transition="fade"> <?php echo $category->name ?> <span class="ui-li-count"><?php echo $category->contains?></span></a> </li>
Notice that we have a $category
PHP variable that points to the actual object this view is being generated for. This is done in lines 24/25 of the render function. When the user clicks one of the links generated by the above fragment, he will be taken to the /?category=someid url, which will show the category.php view, given below.
<?php render('_header',array('title'=>$title))?> <div class="rightColumn"> <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="c"> <?php render($products) ?> </ul> </div> <div class="leftColumn"> <ul data-role="listview" data-inset="true" data-theme="c" data-dividertheme="b"> <li data-role="list-divider">Categories</li> <?php render($categories,array('active'=>$_GET['category'])) ?> </ul> </div> <?php render('_footer')?>
This file also uses the header, footer and _category views, but it also presents a column with products (passed by the category controller). The products are rendered using the _product.php partial:
<li class="product"> <img src="assets/img/<?php echo $product->id ?>.jpg" alt="<?php echo $product->name ?>" /> <?php echo $product->name ?> <i><?php echo $product->manufacturer?></i> <b>$<?php echo $product->price?></b> </li>
As we have an image as the first child of the li elements, it is automatically displayed as an 80px thumbnail by jQuery mobile.
One of the advantages to using the interface components defined in the library is that they are automatically scaled to the width of the device. But what about the columns we defined above? We will need to style them ourselves with some CSS3 magic:
assets/css/styles.css
media all and (min-width: 650px){ .rightColumn{ width:56%; float:right; margin-left:4%; } .leftColumn{ width:40%; float:left; } } .product i{ display:block; font-size:0.8em; font-weight:normal; font-style:normal; } .product img{ margin:10px; } .product b{ position: absolute; right: 15px; top: 15px; font-size: 0.9em; } .product{ height: 80px; }
Using a media query, we tell the browser that if the view area is wider than 650px, it should display the columns side by side. If it is not (or if the browser does not support media queries) they will be displayed one on top of the other, the regular "block" behavior.
We're done!
In the second and last part of this tutorial, we wrote our views to leverage the wonderful features of jQuery mobile. With minimal effort on our part, we were able to describe the roles of our markup and easily create a fully fledged mobile website.
Bootstrap Studio
The revolutionary web design tool for creating responsive websites and apps.
Learn more
Fantastic, thanks for the share.
Your articles are always very good and up to date with technology...
Hi,
Thank you for the tutorial. One question though. Why do you prefix some views with _ ?
This is actually a practice that is popular with the Ruby on Rails framework. When a view starts with an underscore, it would suggest that it is not meant to be used directly. It could be used by other views (like the header and footer), or used to visualize the models (like the _product and _category views). They are also called partials.
Great article as always. I really like the way you design things in your articles like database schema for the 1st part.
Great.
Thank you!
Great article for Free!!! that's amazing
thanks for all
Nice use of variable variable and partial view. Zine nanoFramework :)
Nice tut, now i know it's not only wine that gets better with time.
Thanks for great tutorial. You know my teachers in our faculty intend to make our portal be accessed/ viewed on mobile, because we always login to get updates but doing that on mobile is more convenient.
In the demo section when I click on a link my FF 6 freezes for 3 sec ?
I don't want to sound like tech support, but a restart should fix this. Transitions between pages are handled by jQuery Mobile entirely and it is optimized to run on even the slowest devices.
Thank you for the wonderful comments folks!
Awesome post Martin! Good of you to take the time to put such an in depth tut together :)
Thanks a lot for sharing this! Keep it up! :)
the header statements are in the wrong place - give warnings which you only see on a slow computer. Fix is to move the header statements from main.php to the very beginning of index.php ...
<?php
//header('Cache-Control: max-age=3600, public');
//header('Pragma: cache');
header('Cache-Control: no-cache, public');
header('Pragma: no-cache');
header("Last-Modified: ".gmdate("D, d M Y H:i:s",time())." GMT");
header("Expires: ".gmdate("D, d M Y H:i:s",time()+3600)." GMT");
Note, I switched of caching while debugging
Hi,
How would i add sub categories into this? i have added a column called SubID which will have the ID of the master category then i dont know the best way to change your code to display sub categories.
Steve
Very good article, Martin. One question. I'd like use your approach to build a site that looks good on not only on smartphones and tablets but desktop browsers as well. What technique(s) could one use to render desktop-browser specific views in addition to these jQuery Mobile views?
Great tutorial, can this be released in the android market or itunes with a apk etc files without affecting data and PHP codes.
Great Demo! I had it working till the hosting company upgraded to php 5.3... I changed the connect error message in the connect.php... I see PDOException' with message 'SQLSTATE[HY000] [2019] Can't initialize character set UTF-8 (path: /usr/share/mysql/charsets/)'
my hosting company says " What you are seeing are warning messages due to functions used that were deprecated in PHP 5.3. "
I'm not familiar with the PDO method... to use the MySql DB? Any ideas?
thanks!
fixed it... removed the charset in the Try statement in connect.php
works now...
Thanks Rob- this had me beat as well. Perfect demo and tut Martin - supurb resource...
I was looking at the wrong table, the download is fine apologies for this.
However I dfo encounter and error when creating the tables in MySQL
1064 - You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ''jqm_categories' ( 'id' int(6) unsigned NOT NULL auto_increment, 'name' varc' at line 1
Hi, I'm doing a project for my university and I feel good using mobile-store ...
but when I run the code in-store mobile xampp a problem occurs ...
"The problem is when I try to view the index.php, the browser will download the file without displaying the content ..."
I hope you can help and congratulations for this valuable tutorial. Hits!
hey,
m using your code and i want to add, add to cart option in the listview of each product, how do i get the product id of the product against which add to cart button is clicked??
so that i can match it with jqm_products table and retrieve the data.
its good!thank you!
Hi, after seeing the html code generated by php, I notice that home.php displays correctly the html code of _header2.php while that of category.php display the header.php code (called only by home.php).
I do not understand why category.php header2 displays well (liste, buttons...) but not its code?
Normally I would just change the _header of a rendered product (after selection from a list)
I probably have some difficulties to formulate the problem, but guess it's a problem specific to MVC hierarchy ?
It's aleady a old post, but I still want to thank yo for it :)
I learnd with your tutorial how to build a mobile page and also built one for a 3 days event and got a Samsung Tab 2 for it :D
Thanks!
Сърдечно благодаря, Мартин!
Can you please tell me how to set index.html file for using this code snippest in android phonegap framework for making a mobile websites
Truly amazing bro.. this is a very nice tutorial..
Hey Man, Great Tutorial, I am an ASP.NET MVC guy but have the need to do a PHP project and I love the framework here.
Do you have any samples of how to add login capabilities to this? I have tried but am struggling.
Very nice and lovely jquery mobile template collection. I really want to say Thanks.Thanks a lots for your pretty and lovely jquery mobile template.