Üdvözöljük honlapunkon!

Bal box tartalma

Látogatók száma: 2692

Egyéb tartalom fantázia szerint.

Asztali- és mobilmegjelenés CodeIgniter keretrendszerrel

Egyre szélesedik a különféle hordozható eszközökkel szörfölők köre, ezért egy valamirevaló honlapnak megfelelő megjelenést kell biztosítania ezeken a - többnyire a monitoroknál jóval kisebb - megjelenítőkön is. Írásommal egy lehetséges megoldást mutatok be, ami talán sokaknak új és idegen lesz, viszont lévén sok más megoldási lehetőség is: követni nem kötelező, de mindenkinek tanulságos lehet.

Kezdetek

Eleinte én is - ahogy sokan mások - a <link ... media="handheld"> attribútummal operáltam, de hamar rá kellett jönnöm: ez nem a legjobb megoldás, mert nem minden mobileszköz használja megfelelően, illetve a mobilon nem megjelenítendő tartalmat is letölti a kliens, tehát a szűkös sávszélesség továbbra is problémát jelent.

Kicsit körülnéztem és azt láttam, hogy egyes honlapokon például m.valami.hu aldomainen szolgálják ki a mobilos látogatókat. Ezt sokszor összekötik egy user agent ellenőrzéssel, ami alapján - ha mobileszköz - átirányítanak az aldomainre. Ezt ma sem tartom egészen jó megoldásnak, mert szerintem egy honlap legyen egy domainen, illetve user agent kerülhet elő naponta is új, így az összehasonlításhoz szükséges adatbázist nagyon sűrűn kell(ene) frissíteni.

Ezek fényében lassan kialakult a koncepcióm:

  • Alapjában véve nem rossz dolog a user agent figyelése, de nem szabad mindent erre alapozni;
  • A látogatónak legyen lehetősége beavatkozni, ne döntsünk teljes mértékben helyette;
  • Ha a második pontot felhasználóbarát módon tudjuk kivitelezni, akkor már nem is olyan nagy baj, ha tévedünk a detektáláskor;
  • Azokat a tartalmi részeket, amelyeket a mobil-nézetben nem akarunk megjeleníteni, ne is küldjük ki a HTML-ben;
  • A honlap maradjon egy domainen, egyszerű, aránylag kevés munkát igénylő szoftver legyen (alacsony költség);
  • A megoldás újrahasznosítható legyen;
  • Természetesen mindkét megjelenítés alkalmazkodjon a többféle kijelzőmérethez.

A megoldás

Megelégedve használom a CodeIgniter keretrendszert, ezért most is ebben dolgozunk. A bemutatott példa eléggé egyszerűsített, szándékosan nem használunk most adatbázist és a HTML5 / CSS3 lehetőségeit.

Először is sablonokra lesz szükségünk: all_desktop.php, all_mobile.php és menu.php. A menüt azért tesszük külön fájlba, hogy minden nézethez ugyanazt használhassuk.

Tegyük fel, hogy azt a feladatot kaptuk, hogy asztali nézetben képes fejléc, alatta menü, alatta három sáv, alul pedig lábléc legyen. Valahol legyen üdvözlő üzenet, ha most érkezett a látogató. A mobilnézetet ránk bízták, annyi megkötéssel, hogy a jobb sáv tartalma nem kell, de a középső és a bal sávé igen, előbbi a fontosabb. Ezen kívül szabad a gazda.

application/views/templates/all_desktop.php:
<!DOCTYPE html>
<html lang="hu">
<head>
    <title><?php print($title);?></title>
    <link rel="stylesheet" type="text/css" href="<?php print(base_url());?>public/css/main.css" media="screen" />
    <link rel="shortcut icon" type="image/x-icon" href="<?php print(base_url());?>favicon.ico" />
    <meta content="text/html; charset=utf-8" http-equiv="content-type" />
    <meta name="robots" content="<?php print($robots);?>" />
    <meta name="keywords" content="<?php print($keywords);?>" />
    <meta name="description" content="<?php print($description);?>" />
    <script language="javascript" type="text/javascript" src="<?php print(base_url());?>public/js/jquery_14.js"></script>
    <?php if (!empty($js)) { print('<script language="javascript" type="text/javascript" src="' . base_url() . 'public/js/' . $js . '"></script>');} ?>

</head>

<body>
<div id="header">
	<p><a href="<?php print(base_url());?>nezet/mobil">Mobil</a></p>
	<h1><?php print($site_name);?></h1>
	<h2><?php print($slogan);?></h2>
</div>

<div id="message">
	<?php print($message);?>
	
</div>

<div id="nav">
<?php include('menu.php');?>
</div>

<div id="container">
	<div id="left">
		<?php print($left);?>
		
	</div>
	<div id="content">
		<?php print($content);?>
		
	</div>
	<div id="right">
		<?php print($right);?>
		
	</div>
</div>

<div id="footer">
	<p>Futásidő: <strong>0.1115</strong> s, Memória: <strong>2.35MB</strong></p>
</div>
    
<?php if (!empty($js2)) { print('<script language="javascript" type="text/javascript" src="' . base_url() . 'public/js/' . $js2 . '"></script>');} ?>

</body>
</html>

A sablon változóinak természetesen majd megfelelő értéket kell adnunk, ezt részben egy saját config fájlból fogjuk megtenni, másrészt ezekben lesznek a dinamikusan generált tartalmak is. A <div id="message">-ben felhasználói üzeneteket fogunk megjeleníteni.

public/css/main.css:
body {
	margin: 0;
	padding: 0;
	overflow: auto;
	background-color: #fbf7eb;
	font-family: "georgia", serif;
}
#header {
	width: 100%;
	background: url("fejlec_hatter.jpg") no-repeat center;
	height: 110px;
	overflow: hidden;
}
#message {
	width: 100%;
	background-color: #d5d5d5;
	color: #939a0a;
}
#nav {
	font-family: fantasy;
	font-variant: small-caps;
	padding-left: 201px;
	height: 26px;
	font-size: 16px;
	background-color: #d5d5d5;
}
#nav li {
	float: left;
	list-style: none;
	line-height: 26px;
	padding: 0 10px 0 10px;
}
#nav li:hover {
	background-color: #c3c3c3;
}
.active_menu {
	background-color: #fbf7eb;
	border: 1px solid #808080;
	border-bottom: none;
}
#container {
	width: 100%;
}
#left {
	position: absolute;
	padding-bottom: 30px;
	width: 200px;
	left: 0;
	border-right: 2px solid #d5d5d5;
	overflow: hidden;
}
#content {
	position: absolute;
	padding-bottom: 30px;
	left: 200px;
	right: 200px;
	border-right: 2px solid #d5d5d5;
	border-left: 2px solid #d5d5d5;
	overflow: visible;
}
#right {
	position: absolute;
	padding-bottom: 30px;
	width: 200px;
	right: 0;
	border-left: 2px solid #d5d5d5;
	overflow: hidden;
}
#footer {
	font-family: sans-serif;
	background-color: #ecd99d;
	position: fixed;
	bottom: 0;
	width: 100%;
	border-top: 2px solid #d5d5d5;
	height: 24px;
	line-height: 24px;
}

Ez csupán a boxok és a menü CSS-e, a többi elemé (heading-ek, p, stb) többnyire relatív méretezésű (em, %).

Nézzük a mobil sablont!

application/views/templates/all_mobile.php:
<!DOCTYPE html>
<html lang="hu">
<head>
    <title><?php print($title);?></title>
    <link rel="stylesheet" type="text/css" href="<?php print(base_url());?>public/css/main_mobile.css" media="all" />
    <link rel="shortcut icon" type="image/x-icon" href="<?php print(base_url());?>favicon.ico" />
    <meta content="text/html; charset=utf-8" http-equiv="content-type" />
    <meta name="robots" content="<?php print($robots);?>" />
    <meta name="keywords" content="<?php print($keywords);?>" />
    <meta name="description" content="<?php print($description);?>" />
    <script language="javascript" type="text/javascript" src="<?php print(base_url());?>public/js/jquery_14.js"></script>
    <?php if (!empty($js)) { print('<script language="javascript" type="text/javascript" src="' . base_url() . 'public/js/' . $js . '"></script>');} ?>

</head>

<body>
<div id="header">
	<p><a href="<?php print(base_url());?>nezet/asztali">Asztali</a></p>
	<h2><?php print($slogan);?></h2>
</div>

<div id="message">
	<?php print($message);?>
	
</div>

<div id="nav">
<?php include('menu.php');?>
</div>

<div id="content">
	<?php print($content);?>
	
</div>
	
<div id="left">
	<?php print($left);?>
	
</div>

<div id="footer">
	<p>Futásidő: <strong>0.1115</strong> s, Memória: <strong>2.35MB</strong></p>
</div>
    
<?php if (!empty($js2)) { print('<script language="javascript" type="text/javascript" src="' . base_url() . 'public/js/' . $js2 . '"></script>');} ?>

</body>
</html>

Itt container-re sincs szükségünk, mivel nem csinálunk sávokat, hadd legyenek a boxok minél szélesebbek. Vegyük észre, hogy a fejlécbe csak a szlogent írjuk ki, a főcímet (oldal neve) nem. Ezzel további helyet spórolunk, hogy minél kevesebbet kelljen görgetni. (Tegyük fel, hogy a Megrendelő beleegyezett.)

public/css/main_mobile.css:
body {
	margin: 0;
	padding: 0;
	overflow: auto;
	background-color: #fbf7eb;
	font-family: "georgia", serif;
}
#header {
	background: url("fejlec_hatter_mobile.png") repeat-x;
}
#message {
	width: 100%;
	background-color: #d5d5d5;
	color: #939a0a;
}
#nav {
	font-family: fantasy;
	font-variant: small-caps;
	height: 20px;
	font-size: 12px;
	background-color: #d5d5d5;
}
#nav li {
	float: left;
	list-style: none;
	line-height: 20px;
	padding: 0 4px 0 4px;
}
#left {
	width: 100%;
	border-top: 1px solid #d5d5d5;
	overflow: hidden;
}
#content {
	width: 100%;
	overflow: visible;
}
#footer {
	font-family: sans-serif;
	background-color: #ecd99d;
	width: 100%;
	border-top: 1px solid #d5d5d5;
	height: 16px;
	line-height: 16px;
}

Rövidebb lett a CSS is, a fejléc háttérképét lecseréltük egy egészen kicsire, amivel sávszélességet spórolunk. Mindkét sablon bal felső sarkában van a nézetváltó link: így görgetés nélkül azonnal látható. Már csak a menü van hátra:

application/views/templates/menu.php:
	<ul>
		<li><a href="<?php print(base_url());?>">Címlap</a></li>
		<li><a href="<?php print(base_url());?>oldal/bongeszo">Böngésző</a></li>
		<li><a href="<?php print(base_url());?>oldal/egyebek">Egyebek</a></li>
	</ul>

Ennyi kód után azt is hihetnénk, hogy hátradőlhetünk, de nem így van. Most kezdhetünk gondolkodni azon, hogy mikor és hol (hogyan) kellene detektálni a böngészőeszközt. Kézenfekvő megoldásnak tűnne kontroller előtti (pre_controller) hurkot (hook) használni, csakhogy van egy kis gond.

Úgy tudnánk kényelmesen megírni az osztályt, ha közben használhatnánk egy CI_Controller példányt is, ezen keresztül tudnánk betölteni és használni a szükséges session és user_agent osztályokat, valamint így tudunk közvetlenül kontroller-adatokat létrehozni. Ehhez egy kicsit csalnunk kell: nem hurkot írunk, hanem library-t, amit az autoload.php megfelelő sorával fogunk betölteni.

$autoload['libraries'] = array('session', 'client_detect', 'parser'); (a parser osztályt kontrollerben fogjuk használni). A sorrend fontos, a session-t előbb töltjük be, mint a client_detect-et.

Ezeket az osztályokat a CI_Controller konstruktora tölti be, ezért az általunk írt kontroller konstruktorában már elérhetjük illetve módosíthatjuk a library-nk által létrehozott adatokat, persze csak a parent::__construct(); sor után.

Szükségünk lesz még egy site.php nevű konfigurációs fájlra is, ebben tároljuk az alapértelmezett $title, $robots, stb értékeket:

<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

$config['def_title']	= 'Mobil-asztali megjelenítés';
$config['def_robots']	= 'all';
$config['def_keywords']	= 'mobilnézet, webdesign, webfejlesztés, mobileszköz';
$config['def_description']= 'Mobil és asztali megjelenés CodeIgniter framework-el.';
$config['site_name']	= 'Detektálás és választhatóság';
$config['def_slogan']	= 'Ez egy felhasználóbarát megoldás';
$config['welcome']	= '<h3>Üdvözöljük honlapunkon!</h3>';

Ezt a fájlt a $autoload['config'] = array('site'); sorral töltjük be, szintén az autoload.php-ben.

Mivel több egyidejű feladatunk is van az eszköz detektálásán kívül, ezeket is a Client_detect osztályban fogjuk megvalósítani.

application/libraries/Client_detect.php:
<?php  if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Client_detect {
	
	private $CI;
    
	public function __construct() {
		$this->CI =& get_instance();
		$this->CI->load->library(array('user_agent'));
		
		$this->Detect();
	} // public function __construct()

A konstruktorban lekérjük a kontroller mutatóját, majd ezzel betöltjük a user_agent osztályt, végül meghívjuk detektáló függvényünket.

	private function Detect() {
		$this->CI->data['message'] = '';
		
        if ($this->CI->session->userdata('usercount') === false) {
			// Most jött hozzánk, mert nincs usercount
			if ($this->CI->agent->is_mobile()) {
				// Mobil v. asztali?
				$this->CI->session->set_userdata('platform', 'mobile');
			}
			else {
				$this->CI->session->set_userdata('platform', 'desktop');
			}
			
            if ($this->CI->agent->is_robot()) {
				//Robot, nem számoljuk
				@$x = file_get_contents(APPPATH.'data/db.dat');
                $this->CI->session->set_userdata('usercount', $x);
            }
            else {
				//Nem robot, számolunk
                @$x = file_get_contents(APPPATH.'data/db.dat');
                $x += 1;
                @file_put_contents(APPPATH.'data/db.dat', $x);
                $this->CI->session->set_userdata('usercount', $x);
                $this->CI->session->set_userdata('welcome', true); // Máshol felhasználható
				$this->CI->data['message'] = config_item('welcome');
            }
        }
        else {
			// Már volt nálunk
            @$x=file_get_contents(APPPATH.'data/db.dat');
            $this->CI->session->set_userdata('usercount', $x);
            $this->CI->session->unset_userdata('welcome');
        }
		
		$this->Data_fill();
		
	} // private function Detect()

Itt a kontroller $data tömbjét használjuk, ebben lesznek a view számára átadandó adatok, a kontrollerben majd kiegészítjük és továbbadjuk.

A $this->CI->session->userdata('usercount') a látogatók száma, ez ha nincs (false), akkor a látogató most érkezett. A ('platform') fogja tárolni a megjelenítő típusát, a ('welcome') pedig arra jó, ha esetleg a kontrollerben (vagy modelben) más műveletet is akarunk végezni új látogató érkezésekor (pl. cookie-login). Az üdvözlő üzenetet helyben beállítjuk.

A user_agent osztályhoz tartozó minták a user_agents.php-ben találhatók (config), mindenki tapasztalatai alapján bővítheti.

Végül ott egy $this->Data_fill(); hívás: külön függvényben töltjük fel az alapértelmezett view adatokat, ezt a függvényt kell újrafelhasználáskor az adott honlapnak megfelelően átírnunk.

	private function Data_fill() {
		$this->CI->data['title']		= config_item('def_title');
		$this->CI->data['robots']		= config_item('def_robots');
		$this->CI->data['keywords']		= config_item('def_keywords');
		$this->CI->data['description']	= config_item('def_description');
		$this->CI->data['js2']			= ''; // Controllerben adjuk meg, ahol kell
		$this->CI->data['site_name']	= config_item('site_name');
		$this->CI->data['slogan']		= config_item('def_slogan');
		
		$this->CI->data['message'] .= $this->CI->session->flashdata('message');
		
		if ($this->CI->session->userdata('platform') == 'mobile') {
			$this->CI->data['js'] = 'mobile.js';
		}
		else {
			$this->CI->data['js'] = 'desktop.js';
		}
		
		$this->CI->data['left']	= '<h6>Bal box tartalma</h6><p>Látogatók száma: <strong>' . $this->CI->session->userdata('usercount') . '</strong></p><p>Egyéb tartalom fantázia szerint.</p>';
		$this->CI->data['right']	= '<h6>Jobb box tartalma</h6><p>Akár reklámok / linkek is lehetnének.</p>';
		
	} // private function Data_fill()
		
} // class Client_detect

Itt most egy nagyon leegyszerűsített adatfeltöltés van, természetesen az oldalsávokba valamilyen feldolgozott tartalom kellene, amit - pl. ha oldalanként különböző - kontrollerben vagy modelben állítanánk elő, és ha lehet, cache-elnénk.

A $this->CI->session->flashdata('message')-et átírányításkor (redirect) fogjuk használni felhasználói üzenet továbbítására. Fontos, hogy a $data['message'] csak detektáláskor lett beállítva, itt - és később kontrollerben - már hozzáfűzzük a további üzeneteket.

A $data['js']-t játékból állítjuk itt be, valójában inkább oldalanként különböző javascript csatolására hasznos. Ezt a két JS-t a sablonokban statikusan is megadhatnánk.

Detektáló osztályunk készen is van, szükségünk van még kontrollerre (Oldal), amit be is állítunk a routes.php-ben alapértelmezettnek: $route['default_controller'] = 'oldal';

application/controllers/oldal.php:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Oldal extends CI_Controller {
    
    public function __construct() {
        parent::__construct();
        // Itt már manipulálhatjuk az adatokat:
		$this->data['message'] .= '<p>Üzenet átírva.</p>';
    } // public function __construct()

Mint már említettem: a szülő konstruktora után már a Client_detect által létrehozott adatot manipulálhatjuk. Természetesen ilyen állandó üzenetre ritkán van szükség, de a többi adat érdekes lehet számunkra már a konstruktorban is.

Mivel ez az alapértelmezett kontrollerünk, célszerű egy index() függvényt készítenünk, ez szolgálja ki a főoldalt:

	public function index() {
		// Főoldal
		$this->data['content'] = $this->parser->parse('pages/cimlap', array(), true);
		$this->view();
		
	} // public function index()

A főoldal tartalma az application/views/pages/cimlap.php fájlban van. Parser-t használunk, mert egyelőre változóba kell nekünk a tartalom, és mert annak ellenére is lehet a fájlban PHP kód, hogy csak üres adattömböt adtunk át.

Látható, hogy nem betöltöttük a view osztályt, hanem hivatkoztunk egy függvényünkre, tehát meg is kell írnunk:

    private function view() {
		// Mobil v. asztali?
		if ($this->session->userdata('platform') == 'desktop') {
			$this->load->view('templates/all_desktop', $this->data);
		}
		else {
			$this->load->view('templates/all_mobile', $this->data);
		}
		
    } // private function view()
	
} // class Oldal extends CI_Controller

Végre felhasználjuk a detektálás óta létező 'platform' session-adatot, ennek függvényében választunk a két (vagy akár több) sablonunk közül. Természetesen több függvényünk is szokott lenni a kontrollerben, ezért vettük külön ezt a függvényt.

Most még hátra van a kézi nézetváltás, aztán egy kis díszítés. Második kontrollerünk:

application/controllers/nezet.php:
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');

class Nezet extends CI_Controller {
	
    public function __construct() {
        parent::__construct();
    } // public function __construct()
	
	public function mobil() {
		if ($this->session->userdata('platform') == 'mobile') {
			$this->session->set_flashdata('message', '<p>Már mobil-nézetet használ.</p>');
		}
		else {
			$this->session->set_flashdata('message', '<p>Mobil nézet.</p>');
			$this->session->set_userdata('platform', 'mobile');
		}
		
		$this->generate();
	} // public function mobil()
    
	public function asztali() {
		if ($this->session->userdata('platform') == 'desktop') {
			$this->session->set_flashdata('message', '<p>Már asztali-nézetet használ.</p>');
		}
		else {
			$this->session->set_flashdata('message', '<p>Asztali nézet.</p>');
			$this->session->set_userdata('platform', 'desktop');
		}
		
		$this->generate();
	} // public function mobil()
    
    private function generate() {
		$ref = $this->agent->referrer();
		if (($this->agent->is_referral()) and (mb_strpos($ref, base_url())) === 0) {
			redirect($ref, 'location', 302);
		}
		else {
			redirect(base_url(), 'location', 302);
		}
    } // private function generate()
    
} // class Nezet extends CI_Controller

Váltás végén egy átirányítással generáltatjuk újra az oldalt, ezért az üzenet továbbítására a Client_detect-ben látott 'message' flashdata-t használjuk.

A díszítést (JS) az így is nagy terjedelem miatt nem részletezném, asztali verzión az aktuális menühez adjuk az active_menu osztályt; mobilon egy SELECT-et készítünk a menüből, és végignézzük az IMG-ket, ha valamelyik szélesebb, mint a kijelző, akkor kicsinyítjük.

Ezzel a módszerrel lehet akár készülékenként eltérő design-t is megvalósítani - kellőképpen bővített és karbantartott user_agents.php-vel -, de mindig tartsuk szemelőtt, hogy agent string-et hamisat is kaphatunk, és a felhasználónak is hagyjuk meg a könnyű, egyszerű választás lehetőségét!

Remélem kezdők, haladók és profik számára is hasznos volt egy egyéni(?) megoldásról olvasniuk, és azt is, hogy kedvet csináltam azok számára, akik még nem foglalkoztak mobilmegjelenéssel.

Pepita