Saturday, March 5, 2011

How to make a mobile theme in Yii

Making a mobile theme in Yii is pretty easy. Getting it running smoothly is more work. Here's how:

Making the Theme

1. under /themes/ create a folder "mobile"

2. copy main.php from /protected/views/layouts/ to the /themes/mobile/views/layouts/ folder. You don't need to copy anything else. Views, etc will be inherited.

3. The blueprint CSS framework doesn't seem to be too mobile friendly. By default, there will be a lot of horizontal scrolling. So, in /themes/mobile/views/layouts/main.php, remove these lines:

<!-- blueprint CSS framework -->
<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/screen.css" media="screen, projection" />
<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/print.css" media="print" />
<!--[if lt IE 8]>
<link rel="stylesheet" type="text/css" href="<?php echo Yii::app()->request->baseUrl; ?>/css/ie.css" media="screen, projection" />

You can test it out by setting 'theme'=>'mobile' in /protected/config/main.php

Now, you want to detect the browser type and set the right theme (which i'll cover next), BUT you also want to give users a choice. For example, you should be able to test the mobile site on your desktop web browser, without changing the user-agent string.

* one thing you may not know is that Yii looks for the classic theme on start, which is empty. But you can put views or layouts there and they will be used without configuration change.

You can add some styling to the mobile theme to make it look better. Here are some items to consider:

    input[type=text], input[type=password], textarea {max-width:260px;}
    h1,h2,h3,h4,h5,h6 {font-weight:normal;}
    html, body, #content {margin:0; padding:0;}
    #content h1, #logo, #footer, .portlet-title {display:none;}
    #sidebar {padding:0; margin:0; margin-top:5px;}
    * {float:none; }

Wiring up the Theme

So, do this. In /protected/config/main.php, set
'onBeginRequest'=>array('MyClass', 'BeginRequest'),
in /protected/components/ create a file MyClass.php like this:
class MyClass
  public function BeginRequest(CEvent $event)
    Yii::app()->theme = Yii::app()->session['theme'];
This will look for a theme name in the session. If it does not exist, Yii will use no theme (it will look like it does normally).

To switch themes, make a new controller. In /protected/controllers/ create BrowserController.php, and add this text:
class BrowserController extends Controller
    public function actionIndex($theme)
      Yii::app()->session['theme'] = $theme;
      $referrer = Yii::app()->request->urlReferrer ? Yii::app()->request->urlReferrer : "/mob"; //or whatever your website root is
The above will take a theme name as a querystring parameter and put it in session, and redirect to either the HTTP Referrer or to the root of your site. You could also look for this directly in BeginRequest

To switch to the mobile theme, go to webroot/browser/mobile and to switch back go to webroot/browser/anythingelse


  1. When exactly following each step of this, I get to "call_user_func() expects parameter 1 to be a valid callback, class 'MyClass' not found"

    And you write, 'and in /themes/mobile/views/layouts/column2.php, put the content div BELOW the Operations menu div.' but then later you say, 'and in /themes/mobile/views/layouts/column2.php, put the content div BELOW the Operations menu div.', which also got me a little confused.

  2. I had the same problem. I moved the BeginRequest() method to the Controller.php controller and changed

    'onBeginRequest'=>array('MyClass', 'BeginRequest'),
    'onBeginRequest'=>array('Controller', 'BeginRequest'),

    I'm using yii-1.1.7

  3. I find the solution clearer if you just add a layouthandler. And put your code after the init() instead of using the onBeginRequest. Example available here

  4. Following your tutorial,
    i got this error:

    Non-static method MyClass::BeginRequest() cannot be called statically

  5. Open MyClass.php and change this line:

    public function BeginRequest() to
    public static function BeginRequest()