Jump to content

How to implement navigation for a web app


leart

Recommended Posts

Hey guys,

 

I'm pretty sure this is a common topic and so I was wondering if there's a common/design pattern solution to this problem.

The problem - my web application navigation is split into Sections and it's modules so it's a 2-levels navigation.

For example, there's Management, and inside it: Users, Groups etc.

 

I have chosen to go with CodeIgniter framework and I was wondering about how to support that?

 

If it wasn't made clear until now then the importance of the navigation isn't only to draw HTML links but rather, each module in a section

would have to know which is it's parent section (for the Users module it'll be the Management section). And also, each section and module

would need to have attributes like 'active' (for css markup) and such as 'location' for the link to redirect somewhere (to the controller

of the module for example).

 

 

Hoping to get your help on this...

Link to comment
Share on other sites

1) You could use the Singleton pattern to create a Menu object, which you would be able to call inside your module to add a navigation element. Although due to it's global nature it is not encouraged.

 

class Menu {
  private static $instance = null;
  
  private function __construct() {}
  private function __clone() {}
  
  public static function getInstance() {
    if (null === self::$instance) {
      self::$instance = new self();
    }
    return self::$instance;
  }
}

$menu = Menu::getInstance();

 

Sources: http://en.wikipedia.org/wiki/Singleton_pattern

 

2) Pass the $module structure to a $menu object. This uses the Composite pattern and is presumably the most encouraged method.

 

$menu = new Menu($moduleComposite);

interface MenuInterface {}

class Menu {
  public function __construct(MenuInterface $a) {/*allows other objects to be passed besides the composite*/}
}

 

Sources: http://en.wikipedia.org/wiki/Composite_pattern

Link to comment
Share on other sites

 

Thanks for the quick reply.

 

My 'requirements' for the Sections -> Modules hierarchy is that it be dynamic, meaning, it would be easy to add/remove either.

 

So for Sections, if each of those is a class, then it's easy to just add a new class to form a new Section. For Modules, which essentially encompass the actual functionality I have chosen to implement HMVC so that each Module contains it's own controllers/ views/ models/ etc... and it's easy to add/remove that.

 

Is that falling well with the Composite design pattern? While new to that, I'm trying to digest how it all fits together in actual implementation.

 

I'm not really following on your example to 'pass the $module structure to the $menu object'

$menu = new Menu($moduleComposite);

You didn't mean to pass the actual module controller, right? So we have another entity (if so to speak) which would describe the module then?

 

As I described above - the Modules as implemented in the HMVC way, will each have a details.xml where I can have a 'parent' element to point to it's parent section.

 

 

Hope you could shed some more light on this and some more of your thoughts and suggestions.

Link to comment
Share on other sites

The $moduleComposite is an "array" which $menu iterates over and builds the menu. Saying that you may want to take a look at the Builder Pattern.

 

This has ofcourse the disadvantage of building the entire composite tree (instantiating every section and it's containing modules) on the other hand it could aswell write the info to a XML file and read later on by a class that builds the Menu from the XML source.

 

I have no idea of how your application is architect of course, but in order to build your Menu you will have to know all available links for the Sections and their containing Modules or you'll end up with a half menu.

 

You could of course also just add it to a database when the module is installed. Afterwards you read out the database table and build (and Cache, the menu is not that important to query the database on every request) the Menu.

Link to comment
Share on other sites

 

It's somewhat hard for me to see the "big picture" of how all of this comes together.

 

Ignace, I really appreciate the patience and advise.

Any chance you are familiar with the CodeIgniter framework? I could maybe pass some code of how I approached things until now (not really a whole lot of progress but I'm sure it'll give a better understanding).

Link to comment
Share on other sites

Don't say it, do it! What will my reply now be? Sure? And you'll have to wait even longer for a response. Efficiency is key :)

 

Cool, thanks for baring with me :)

 

Ok so, in terms of controller's inheritance what I have is:

MY_Controller

-> Management_Section

        -> Users

 

 

The MY_Controller class does some high level things like setting the the template's layout, views, etc... it also gets the available sections (Management, Reports, etc) which I'll explain on them next....

class MY_Controller extends Controller
{

// the data member keeps views-related information 
protected $data;

function __construct()
{
	parent::Controller();

	// load modules, set template views, partials, etc...
	$this->load->model('modules_m');		
}


protected function _get_sections()
{

	$sections = array();

	foreach(glob(APPPATH . 'sections/*.php') as $filename)
	{

		$section = pathinfo($filename, PATHINFO_FILENAME);
		require_once($filename);

		// perform PHP introspection to get class attributes information
		$classVars = get_class_vars($section);
		$sections[$classVars['sectionSettings']['name']] = $classVars['sectionSettings'];

	} 

	return $sections;

}

}

 

 

Each section is a class in the sections/ directory. I decided to do that instead of something drastically simple like array("Management", "Reports) because I wanted it to be somewhat dynamic. Like, if in the next update of the software I need another section I can just drop a class to that sections/ directory.

<?php
class Management_Section extends MY_Controller
{

public static $sectionSettings = array(	"name" => "Management",
									"priority" => 0,
									"enabled" => true,
									"active" => false,
									"location" => 'users/users',
									);

function __construct()
{

	parent::__construct();

	$this->setSectionSettings(array("active"=>true));

	$this->data->view['modules'] = $this->modules_m->get_modules();
	$this->data->view['sections'] = $this->_get_sections();

}


function _getSectionModules()
{

	$modules = $this->data->view['modules'];

	foreach($modules as $index => $module) {
		if ($module['section'] == self::$sectionSettings['name']) {
			$sectionModules[$module['name']] = $module; 	
		}
	}

	return $sectionModules;
}


function getSectionName()
{
	return self::$sectionSettings['name'];
}


protected function setSectionSettings($setting)
{
	foreach($setting as $key => $value)
		self::$sectionSettings[$key] = $value;
}


}

 

 

 

 

This is the Users module. To "manage" the modules I have a model which scans the modules/ directory and parses a details.xml file which has some general info as well as information like section => "Management"

<?php

class Users extends Management_Section {

public static $moduleSettings = array("name" => "Users",
									"active" => false,
									"location" => 'users/users',
									);


function Users()
{
	parent::__construct();
	parent::setSectionSettings(array("active"=>true));


}

function index()
{

	$data = array();

	$this->data->title = "My Real Title";
	$this->data->view['sectionModules'] = $this->_getSectionModules(); 

	$this->data->view['sectionModules'][self::$moduleSettings['name']]['active'] = true;

	$this->template->build('base/body', $this->data);

}
}

 

 

 

To sum it up - this code as it is works ok.

Though I don't know if I'm making design mistakes which I will later regret and since I am now re-designing a spaghetti code into an OOP manner, I want it to be good so I have the time to spare on designing it as best I can.

 

One of my thoughts on a possible "mistake" with that code is that maybe Users should be inheriting from a modules class though I can't do multiple inheritance and inherit from both Modules and a section like I do right now so... I don't know. tell me what you think :-)

Link to comment
Share on other sites

You are mixing Controller & Model logic, and I was on the right track in my second post.

 

Yeah, I actually thought of that too, to take out the _get_sections out of the MY_Controller and instead have a model to deal with the sections and load that model up in the MY_Controller.

 

So, a couple of questions:

 

1. is your suggestion towards the composite/builder fits in my current implementation where I have a class for each section and I am building the menu array like that?

 

2. is it better to have my mvc hierarchy such as:

MY_Controller -> Module -> Users instead of MY_Controller -> Management_Section -> Users ?

 

 

Thanks

 

 

Link to comment
Share on other sites

QA #1 Yes, you will need those patterns but not in the way I envisioned. For an example implementation I advice you to take a look at the link I provided in my previous post (Zend framework).

 

QA #2 Your menu and your controller hierarchy are completely decoupled, they have no business with each other. Plus, your menu can also contain external links. Therefor it would be best to store your menu hierarchy in a database table, read out the table and build the menu. Apply logic to indicate active items.

Link to comment
Share on other sites

 

Alright so this is what I've come up with.

I'm actually feeling pretty ok with it but I'd appreciate your feedback and comments:

 

Hierarchy is now:

MY_Controller

  -> MY_Module

        -> Users

 

So the menu is completely decoupled. Users is a module which inherits from a parent general module class (MY_Module) which inherits from the controller class.

 

Now on to the code:

 

Nothing new in the MY_Controller except I am now loading the Menu model

require_once("MY_Module.php");

class MY_Controller extends Controller
{

// the data member keeps views-related information 
protected $data;


function __construct()
{
	parent::Controller();

	$this->load->model('modules_m'); // load the modules model
	$this->load->model('menu');   // load the menu model


	// controller set layout/view/general stuff...

}



}

 

 

The Menu model loads all the sections which still remain classes for now and gets the section's info via variable introspection.

/*
* Loop through all sections and include them
*/
foreach(glob(APPPATH . 'sections/*.php') as $filename)
{	
require_once($filename);
}



class Menu extends Model
{

private $sections;
private $modules;


public function __construct()
{

	parent::__construct();

	$this->modules = $this->modules_m->get_modules();
	$this->createSections();

}


public function getSections()
{

	if ($this->sections === null)
		$this->createSections(); 

	return $this->sections;
}




public function setSectionActive($sectionName)
{
	$this->sections[$sectionName]['active'] = true;		
}


public function setModuleActive($sectionName, $moduleName)
{
	$this->sections[$sectionName]['active'] = true;
	$this->sections[$sectionName]['modules'][$moduleName]['active'] = true;
}


public function createSections()
{
	$this->sections = null;

	foreach(glob(APPPATH . 'sections/*.php') as $filename)
	{			
		$section = pathinfo($filename, PATHINFO_FILENAME);
		require_once($filename);

		// perform PHP introspection to get class attributes information
		$classVars = get_class_vars($section);

		// build an array of sections with key being the section name pointing
		// to an array with the section attributes.

		$sectionName = $classVars['sectionSettings']['name'];
		$this->sections[$sectionName] = $classVars['sectionSettings'];

		// each section has a modules attribute which is a pointer to an array of
		// modules

		$this->sections[$sectionName]['modules'] = $this->getSectionModules($sectionName);

	} 
}


function getSectionModules($sectionName)
{
	$sectionModules = array();

	foreach($this->modules as $index => $module) {
		if ($module['section'] == $sectionName) {
			$sectionModules[$module['name']] = $module; 	
		}
	}

	return $sectionModules;

}

}

 

 

 

The MY_Module class:

class MY_Module extends MY_Controller
{
protected $menuItems;
protected $moduleData;

function __construct($moduleClass)
{

	parent::__construct();

	$this->moduleData = $this->modules_m->get($moduleClass);

	$this->menu->setModuleActive(
									$this->moduleData['section'], 
									$this->moduleData['name']
								);

	// sets up the menu tree array which is later passed-on to the view
	$this->data->view['sections'] =& $this->menu->getSections();

}

}

 

 

 

class Users extends MY_Module {

function Users()
{

	$moduleClass = basename(__FILE__, ".php");

	parent::__construct($moduleClass);

	//var_dump($this->data->view['sections']['Management']);

}

function index()
{

	$data = array();

	$this->data->title = "Test Title";

	$this->template->build('base/body', $this->data);

}
}

 

 

What do you think?

Link to comment
Share on other sites

This thread is more than a year old. Please don't revive it unless you have something important to add.

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Restore formatting

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.