This document is meant as an introduction into writing Pages in Knickers. At this point it primarily deals with Pages from the web side of things.
Terminology
There are some vocabulary words you should know before we get into how things work:
- Panel: A class that has a template and has basic init/isAvailable/run functionality. May contain and be contained by other Panels and Pages. An example of this might be a login form that you want to place on multiple pages
- Page: Pretty much just like a Panel, but has the added ability to add a child Page or Panel to itself based on the URL. The idea is that Pages are directly accessible from a URL whereas Panels are contained within pages. An example of a Page would be your user account creation page.
- Controller Object: The main object that does all the work that all of your Pages need. Can instantiate a Page based on the URL, sets up the Ecosystem, User, Locale, Perm, OG, and Config objects, runs your Page, and displays the output.
- Controller Script: A runnable script that you put into your common directory that instantiates a Controller and sets it going.
Creating a basic page
(Note: These instructions assume you have made your common directory web-accessible)
To create a basic Page you will need to:
- Setup an executioner script. The most basic script will look like this:
require_once '../cfg/knickers_app.cfg.php'; // load up the base Executioner class and run! require_once KNICKERS_ROOT.'/common/cls/Controller/Executioner.class.php'; $executioner = new Executioner(); $executioner->go();
If you want to skip the .php extension on your executioner, you can make it runnable using a .htaccess file.<Files [executioner script name]> ForceType application/x-httpd-php </Files>
- Create a Page class by extending the Page class in Knickers. You're going
to want to name your page "Page_[my page].class.php" and put it in your
Controller directory.
- Create a template for your page and put it in the tpl/html directory
- Set a member var in your Page class called $baseTemplate to the name of your
template file.
class Page_MyPage extends Page { var $baseTemplate = 'my_page.tpl'; }
- You can now access your page at http://[your domain]/[your executioner]/[your page without Page_]
http://www.yourdomain.com/page/MyPage, for example
Setting a default page
Say you want people to be able to come to just to yourdomain.com/page (or you're going to redirect them to page) and end up on your page. To make this happen you can set a default page in your executioner script.
$executioner = new Executioner('Page_', 'MyPage');
Setting an outer page
It's very likely that you'll want all of your pages to share some basic appearance and behaviors. This might include things like css, javascript, a navbar, etc. To accomplish this, you're probably going to want to setup an outer page class for yourself.
class Page_MyOuterPage extends Page { var $baseTemplate = 'outer.tpl'; }Now you need to tell the executioner about this page:
$executioner->setOuterPageClass('Page_MyOuterPage');This will have the effect of running your Page_MyOuterPage class every time someone comes to yourdomain.com/page/[something]. That's great, but how do you get your pages to show up?
To do this, you're probably going to want to add a Panel using the URL. Assuming you have a tag called "MAIN_CONTENT" in your outer template, it would look something like this:
class Page_MyOuterPage extends Page { var $baseTemplate = 'outer.tpl'; function init() { if($this->addPanelFromURL('MAIN_CONTENT') === CDMJ_ERROR) return $this->setCDMJError($this->getCDMJError()); // don't forget this! It will init the Page you've just added if(parent::init() === CDMJ_ERROR) return $this->setCDMJError($this->getCDMJError()); } }Note that it's possible to have multiple executioners with different outer page classes. You may want to do this if you have different sections of your site meant to be accessed by different users:
http://www.yourdomain.com/admin/Homevs
http://www.yourdomain.com/lowly_user/Home
Outer Pages and Default Pages
Unfortunately, once you've set an outer page class, the executioner is going to ignore the default page you've told it to use. To replicate this functionality from within your outer page, simply send your default panel class name to addPanelFromURL() as a second param.class Page_MyOuterPage extends Page { var $baseTemplate = 'outer.tpl'; function init() { // get our panel from the URL, but fall back to the my page if not found if($this->addPanelFromURL('MAIN_CONTENT', 'Page_MyPage') === CDMJ_ERROR) return $this->setCDMJError($this->getCDMJError()); // don't forget this! It will init the Page you've just added if(parent::init() === CDMJ_ERROR) return $this->setCDMJError($this->getCDMJError()); } }
Including Panels within Panels
Okay, now you've got your pages rendering in a consistent outer template, but you also want to add a navbar that's in a separate template. How do you add a child panel yourself?
require_once KNICKERS_APP_ROOT.'/common/cls/Controller/Panel_MyNavbar.class.php'; class Page_MyOuterPage extends Page { var $baseTemplate = 'outer.tpl'; function init() { // get our panel from the URL, but fall back to the my page if not found if($this->addPanelFromURL('MAIN_CONTENT', 'Page_MyPage') === CDMJ_ERROR) return $this->setCDMJError($this->getCDMJError()); // create your navbar object $navbar = new Panel_MyNavbar($this->cfg, $this->ecos, $this->lm, $this->params, $this->og, $this->locale, $this->perm); // and add it to a tag called 'NAVBAR' if($this->addPanelForTag('NAVBAR', $navbar) === CDMJ_ERROR) return $this->setCDMJError($this->getCDMJError()); // don't forget this! It will init the Page and Panel you've just added if(parent::init() === CDMJ_ERROR) return $this->setCDMJError($this->getCDMJError()); } }Now you've got two panels - your navbar and the page itself.
Page Titles
Okay, so now your page is showing up and you've got your navbar, but how do you get the page's title to parse into your outer template's Documentation tag?
You'll do this by setting a 'titleMessageKey' member variable to the key for a defined message in your dictionary:
class Page_MyPage extends Page { var $baseTemplate = 'my_page.tpl'; var $titleMessageKey = MSG_MY_PAGE_TITLE; }Then, your outer page needs to ask your page for this information (note that child panels may be accessed by $this->childPanels['TAG_NAME']):
require_once KNICKERS_APP_ROOT.'/common/cls/Controller/Panel_MyNavbar.class.php'; class Page_MyOuterPage extends Page { var $baseTemplate = 'outer.tpl'; function init() { // get our panel from the URL, but fall back to the my page if not found if($this->addPanelFromURL('MAIN_CONTENT', 'Page_MyPage') === CDMJ_ERROR) return $this->setCDMJError($this->getCDMJError()); $this->og->setTag('PAGE_TITLE', $this->childPanels['MAIN_CONTENT']->getTitle()); // create your navbar object $navbar = new Panel_MyNavbar($this->cfg, $this->ecos, $this->lm, $this->params, $this->og, $this->locale, $this->perm); // and add it to a tag called 'NAVBAR' if($this->addPanelForTag('NAVBAR', $navbar) === CDMJ_ERROR) return $this->setCDMJError($this->getCDMJError()); // don't forget this! It will init the Page and Panel you've just added if(parent::init() === CDMJ_ERROR) return $this->setCDMJError($this->getCDMJError()); } }
Parent Page Permissions
Okay, that's great, but it seems kind of open, doesn't it? What if you don't want just any panel being added as a child of any other panel? Maybe you don't want your navbar getting tossed onto a page's template. How do you prevent that?
You're going to want to setup a member array in your child panel that explicitly lists which parent page/panel classes are allowed to run it:
class Panel_MyNavbar extends Panel { var $allowedParents = array('Page_MyOuterPage'); var $baseTemplate = 'navbar.tpl'; }(Note that the parent check is done with is_a, so you may make a child panel runnable by any page/panel class that extends a certain base class simply by listing the parent class.)
This will have the effect checking permissions only on those child page/panels that specify their allowed parents. But what if you want to enforce listing these parents?
You can do this by setting a constant in your knickers_app.cfg file:
define('CHILD_PANEL_RUNNABLE_BY_DEFAULT', FALSE);
More specific Permissions
Okay, so now you've got your navbar only showing in the outer template, but what if you want it to be only visible to users with certain permissions and give an error otherwise? You might do something like this:
class Panel_MyNavbar extends Panel { var $allowedParents = array('Page_MyOuterPage'); var $baseTemplate = 'navbar.tpl'; function isAvailable() { return $this->perm->canSeeNavbar(); } }This will cause your entire page to error out if anyone attempts to view the navbar without the proper permissions. However, the error message provided my not be to your liking. To override it, setup a message for your error message and do:
class Panel_MyNavbar extends Panel { var $allowedParents = array('Page_MyOuterPage'); var $baseTemplate = 'navbar.tpl'; function isAvailable() { if(!$this->perm->canSeeNavbar()) { $this->setNotAvailableMessage(new Message('MSG_NO_NAVBAR')); return FALSE; } return TRUE; } }But what if you want to do more than just show a certain message? What if you want to, say, redirect your user to another page? For this you'll want the runWhenNotAvailable function, which, as its name implies, will run when your panel is not available:
class Panel_MyNavbar extends Panel { var $allowedParents = array('Page_MyOuterPage'); var $baseTemplate = 'navbar.tpl'; function isAvailable() { if(!$this->perm->canSeeNavbar()) { $this->setNotAvailableMessage(new Message('MSG_NO_NAVBAR')); return FALSE; } return TRUE; } function runWhenNotAvailable() { header('Location: [somewhere]'); } }(Note that the default behavior is to show a permissions error and not run if any child panel is not available.)
Making your page do something
The default behavior of a page is nothing more than setting up its template and parsing it. To get it to do something, you're going to need to implement its run function:
class Page_MyOuterPage extends Page { var $baseTemplate = 'outer.tpl'; function init() { // get our panel from the URL, but fall back to the my page if not found if($this->addPanelFromURL('MAIN_CONTENT', 'Page_MyPage') === CDMJ_ERROR) return $this->setCDMJError(new CDMJ_ERROR($this->getCDMJError())); $this->og->setTag('PAGE_TITLE', $this->childPanels['MAIN_CONTENT']->getTitle()); // create your navbar object $navbar = new Panel_MyNavbar($this->cfg, $this->ecos, $this->lm, $this->params, $this->og, $this->locale, $this->perm); // and add it to a tag called 'NAVBAR' if($this->addPanelForTag('NAVBAR', $navbar) === CDMJ_ERROR) return $this->setCDMJError(new CDMJ_ERROR($this->getCDMJError()); // don't forget this! It will init the Page and Panel you've just added if(parent::init() === CDMJ_ERROR) return $this->setCDMJError(new CDMJ_ERROR($this->getCDMJError())); } function run() { // this just sets the date into our outer template $this->og->setTag('TODAYS_DATE', date('m/d/Y'); // don't forget this! It will run your child panels if(parent::run() === CDMJ_ERROR) return $this->setCDMJError(new CDMJ_ERROR($this->getCDMJError())); } }It's very important that you not forget to run the parent::run function when you have child panels. This is what causes them to run and be parsed.
Changing your template dynamically
Now that you've got some pages that do some stuff, you may find that what you want your page to display differs dramatically depending on what happens in your run function. How do you completely swap out your template?
To do this, first forgo the $baseTemplate member variable. You will have no base template added and your $this->og object will be the one from your parent Page (or the Executioner, if there is no parent Page). You can then add templates yourself but you will have to make them printable to get them to automatically display:
class Page_MyCrazyPage extends Page { function run() { if([something]) { if(($pageTpl =& $this->og->addTemplate('crazy_true.tpl')) === CDMJ_ERROR) return $this->setCDMJError(new CDMJ_ERROR($this->og->getCDMJError())); } else { if(($pageTpl =& $this->og->addTemplate('crazy_false.tpl')) === CDMJ_ERROR) return $this->setCDMJError(new CDMJ_ERROR($this->og->getCDMJError())); } // make whichever template we picked show up automatically $this->addPrintableOutputGenerator($pageTPL); } }
Fetch
Finally, you are probably going to want to include some css or js in your page (many of the Knickers Components require common.js so you're definitely going to want to include that) but you may not want to make all of your css and js directories web-accessible. To make this happen, you'll want to use the fetch script. There is a version in the Knickers/common directory but you will probably want to create a version for your app as well:
require_once '../cfg/knickers_app.cfg.php'; // make sure we use the right config object (you can change this to your app's // config if you have one require_once KNICKERS_ROOT.'/common/cls/Config.class.php'; require_once KNICKERS_ROOT.'/common/fetch';Then, make this runnable using the same .htaccess trick as above:
<Files fetch> ForceType application/x-httpd-php </Files>You may then load up css and js files by using the URL:
http://www.yourdomain.com/fetch/[your file](Note: This does the same custom/common/knickers search that happens when Config tries to find a class or template file, meaning that you can override css or javascript at the custom level if you wish. -- This isn't true but it should be!!)