Skip to main content
Version: 2.0.0

View

The view is the interface between the controller and the template. In the framework, the packages\base\view class has been created for the view. You must create view classes in the frontend section, and these classes must inherit from the packages\base\view class.

note

As in the previous version, you can define the view in two sections, backend and frontend. These two sections are connected to each other through a parent-child relationship. To keep files organized, it is best to create a folder named views in each section and define the files related to that section inside it. If you define the view in two sections, you must specify the relationship between the backend and frontend views under the views key in the theme.json file (in the theme's folder).

For example:

views/packagename/index.php
namespace themes\themename\views\packagename;

use packages\base\View;

class index extends View {
public function __beforeLoad() {}
}

If the page has a form, the view class must inherit from the packages\base\views\form class.

info

For more information, refer to the Form page.

views/packagename/users/add.php
namespace themes\themename\views\packagename\users;

use packages\base\views\Form;

class add extends Form {
public function __beforeLoad() {}
}

If the page contains a list that requires pagination, the view class must inherit from the packages\base\views\listview class.

info

For more information, refer to the Pagination page.

views/packagename/users/Search.php
namespace themes\themename\views\packagename\users;

use packages\base\views\Listview;

class Search extends Listview {
public function __beforeLoad() {}
}

The frontend section

info

For more information, refer to the Frontend page.

This file is created in the folder registered as the frontend section (it is best to create a folder named views for this section's view files).

To link the view part to the template part (the part that contains the HTML), you need to create a file — corresponding to the view class file — inside a folder named html.

You also need to register the name of the view classes folder (views) under the autoload key in the theme.json file located in the frontend folder.

In the view, you can define the __beforeLoad() method. This method is automatically called before the program's template part is loaded.

info

For more information, refer to the Autoloader page.

A sample theme.json file:

theme.json
{
"name": "themename",
"title": "Site Frontend",
"version": "1.0.0",
"autoload": {
"directories": ["views"]
}
}
note

In the namespace of the view class, you must use the name specified in the theme.json file.

views/packagename/news/Show.php
namespace themes\themename\views\packagename\news;

use packages\packagename\Post;
use packages\base\View as ParentView;

class Show extends ParentView {

protected $post;

public function __beforeLoad() {}

public function setPost(Post $post): void {
$this->post = $post;
}
}

The backend section

For the backend view, a file is defined in the package's root directory (it is best to create a folder named views for this section's files). This file is connected to the packages\base\view file through a parent-child relationship.

For this file to be automatically detected and loaded by the framework, it must be registered under the autoload key in the package's configuration file, package.json.

A sample package.json file:

package.json
{
"routing": "routing.json",
"frontend": ["frontend"],
"autoload": {
"directories": ["controllers", "Models", "views"]
}
}

A sample backend view file:

views/news/Show.php
namespace packages\packagename\views\news;

use packages\base\View;
use packages\packagename\Post;

class Show extends View {
public function setPost(Post $post) {
$this->setData($post, "post");
}
public function getPost(): Post {
return $this->getData("post");
}
}

A sample template file:

views/packagename/news/Show.php
namespace themes\themename\views\packagename\news;

use packages\packagename\Post;
use packages\packagename\views\news\Show as ParentView;

class Show extends ParentView {

protected $post;

public function __beforeLoad() {
$this->post = $this->getPost();
}
}
note

You can skip creating the backend section and call the view directly from the frontend in the controller.

Calling the view

info

For more information, refer to the Controller page.

Each controller uses the packages\base\view class and the byName method to call a view and, consequently, a template. If the namespace passed to the byName method is incorrect, this class stops the program's execution by throwing an exception of type packages\base\NoViewException.

Calling the frontend view in a controller

To call the frontend view in a controller, you must use the theme's namespace.

controllers/News.php
namespace packages\packagename\controllers;

use packages\packagename\Post as Model;
use themes\themename\views\packagename\news as views;
use packages\base\{Controller, Response, View, NotFound};

class News extends Controller {

public function view(array $data): Response {

$model = new Model();
$model->where("id", $data["post"]);
$model->where("status", Post::PUBLISHED);
$post = $model->getOne();
if (!$post) {
throw new NotFound();
}

$view = View::byName(views\Show::class);
$this->response->setView($view);

$view->setPost($post);

$this->response->setStatus(true);

return $this->response;
}
}

Calling the package view in a controller

To call the package view in a controller, you must use the package's namespace.

controllers/News.php
namespace packages\packagename\controllers;

use packages\base\{Controller, Response, View, NotFound};
use packages\packagename\{Post as Model, views\news as views};

class News extends Controller {

public function view(array $data): Response {

$model = new Model();
$model->where("id", $data["post"]);
$model->where("status", Post::PUBLISHED);
$post = $model->getOne();
if (!$post) {
throw new NotFound();
}

$view = View::byName(views\Show::class);
$this->response->setView($view);

$view->setPost($post);

$this->response->setStatus(true);

return $this->response;
}
}

Setting the page title

Every web page must have a title. In the framework, the setTitle method is defined for setting the title. This method's argument is a string. You can also pass an array of strings to the method, and the array elements are automatically converted to a string. The setTitle method is called inside the __beforeLoad method.

Setting the title in the view lets you write the common, opening portion of your HTML pages once and reuse it across all pages. To improve your code, you can use translators and stored-text (language) files. By using stored-text files, you will not have to write text inline within your code.

info

For more information, refer to the Translator page.

Example 1

views/packagename/ContactUs.php
namespace themes\themename\views\packagename;

use packages\base\views\Form;

class ContactUs extends Form {

public function __beforeLoad() {
$this->setTitle('Contact Us');

/**
* Using the translator
*
* $this->setTitle(t("title.contactUs"));
*/
}
}

Example 2

views/packagename/Index.php
namespace themes\themename\views\packagename;

use packages\base\View;

class Index extends View {

public function __beforeLoad() {
$this->setTitle(['View', 'Documentation', 'Jalno']);
}
}

Getting the page title

After setting the page title, you can retrieve the set title by calling the getTitle method. If the setTitle method received an array as input, the array is converted to a string and the elements are separated by "|". If you want to use a different character as the separator, you must pass that character as an argument to the getTitle method.

Example 1

/**
* $this->setTitle('Contact Us');
*/

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
<?php echo $this->getTitle();?>
/**
* output:
* Contact Us
*/
</title>
</head>

Example 2

/**
* $this->setTitle(
* ['View', 'Documentation', 'Jalno']
* );
*/

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
<?php echo $this->getTitle();?>
/**
* output:
* View | Documentation | Jalno
*/
</title>
</head>

Example 3

/**
* $this->setTitle(
* ['View', 'Documentation', 'Jalno']
* );
*/

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>
<?php echo $this->getTitle("-");?>
/**
* output:
* View - Documentation - Jalno
*/
</title>
</head>

Setting the description

The setDescription method is used to set the page's description, which is placed in the meta tag. This description can be very important in search engine results. The setDescription method is called inside the __beforeLoad method.

Example 1

views/packagename/Index.php
namespace themes\themename\views\packagename;

use packages\base\View;

class Index extends View {

public function __beforeLoad() {
$this->setDescription("Practical tutorials and articles for webmasters");
}
}

To improve your code, you can use translators and stored-text (language) files. By using stored-text files, you will not have to write text inline within your code.

info

For more information, refer to the Translator page.

Example 2: Using the translator

views/packagename/Index.php
namespace themes\themename\views\packagename;

use packages\base\View;

class Index extends View {

public function __beforeLoad() {
$this->setDescription(t("site.decription"));
}
}

Getting the description

After setting the page description, you can retrieve the description text by calling the getDescription() method.

Example

<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<?php
$description = $this->getDescription();
if ($description) {
echo("<meta content=\"{$description}\" name=\"description\" />");
}
?>
</head>

Adding and removing CSS

CSS files are registered in the theme.json file in the frontend folder. However, sometimes you need to define some code inline, or only for a single page; in these cases, you can call the addCSS and addCSSFile methods.

note

The addCSS and addCSSFile methods must be called inside the __beforeLoad method.

For each style you define, you can specify a name; then, if you need to remove the style under certain conditions, you can pass the style's name as the argument to the removeCSS method. You can also remove all styles by calling the clearCSSAssets method, which takes no arguments.

By calling the clearAssets method, the JavaScript code is removed in addition to the CSS code.

In addition, by calling the getCSSAssets method, the CSS files added to the page are available.

views/packagename/Index.php
namespace themes\themename\views\packagename;

use packages\base\View;

class Index extends View {
public function __beforeLoad() {
if (isset($this->getData('bg'))) {
$this->removeCSS("bodyStyle");
}
/**
* $this->clearCSSAssets();
* $this->clearAssets();
*/
}
}

The addCSS method takes two arguments. The first is the style code, and the second is a name for the style. The second argument is optional.

views/packagename/Index.php
namespace themes\themename\views\packagename;

use packages\base\View;

class Index extends View {
public function __beforeLoad() {
$this->addCSS("body{background: antiquewhite}", "bodyStyle");
}
}

The addCSSFile method adds a new style file. It takes three arguments. The first argument takes the file path as a string, the second is the style name, and the third specifies whether or not the file is loaded; if the third argument is true, the specified file is not loaded on the page. Its default value is false. The second and third arguments are optional.

views/packagename/Index.php
namespace themes\themename\views\packagename;

use packages\base\View;

class Index extends View {
public function __beforeLoad() {
$this->addCSSFile("assets/css/style.css", 'newStyle');
}
}

Adding and removing JavaScript

JavaScript files are registered in the theme.json file in the frontend folder. However, sometimes you need to define some code inline, or only for a single page; in these cases, you can call the addJS and addJSFile methods.

note

The addJS and addJSFile methods must be called inside the __beforeLoad method.

For each piece of JavaScript you define, you can specify a name; then, if you need to remove the code under certain conditions, you can pass the code's name as the argument to the removeJS method. You can also remove all JavaScript code by calling the clearJSAssets method, which takes no arguments.

By calling the clearAssets method, the CSS code is removed in addition to the JavaScript code.

In addition, by calling the getJSAssets method, the JavaScript files added to the page are available.

views/packagename/Index.php
namespace themes\themename\views\packagename;

use packages\base\View;

class Index extends View {
public function __beforeLoad() {
$this->removeJS("indexJS");
/**
* $this->clearJSAssets();
* $this->clearAssets();
*/
}
}

The addJS method takes two arguments. The first is the JavaScript code, and the second is a name for the code. The second argument is optional.

views/packagename/Index.php
namespace themes\themename\views\packagename;

use packages\base\View;

class Index extends View {
public function __beforeLoad() {
$this->addJS("alert('hello')", "helloAlert");
}
}

With the addJSFile method, you can add a new file specific to a single page. Its first parameter takes the file path relative to the theme's root directory (the location of the theme's configuration file). In the second argument, you can optionally specify a name for it. You can use this name to remove the added file.

views/packagename/Index.php
namespace themes\themename\views\packagename;

use packages\base\View;

class Index extends View {
public function __beforeLoad() {
$this->addJSFile("assets/js/index.js", 'indexJS');
}
}

Passing data

By calling the setData($data, string $key) method, you can set a piece of data and access it whenever you need it by calling the getData() method.

The setData method takes two arguments. The first is the data itself — data can be of any type — and the second is the key for the data. The second argument is optional.

With this approach, defining several values requires calling the setData method multiple times. To avoid repetitive code, you can pass the first argument as a key-value array, where the array keys are the data keys and its values are the data values. With this approach, you do not need to specify the second argument. If an array is passed as the first argument and the second argument is also provided, the entire array is stored under that key.

Example 1: Sample controller file

controllers/News.php
namespace packages\packagename\controllers;

use packages\packagename\Post;
use themes\themename\views\packagename\news as views;
use packages\base\{Response, Controller, View, NotFound};

class News extends controller {

public function view($data): Response {
$post = Post::byId($data["post_id"]);
if (!$post) {
throw new NotFound();
}

$view = View::byName(views\Show::class);
$this->response->setView($view);

$view->setData($post, "post");

$this->response->setStatus(true);

return $this->response;
}
}

Example 2: Sample HTML file

<div class="container">
<h1> <?php echo $this->getData('post')->title; ?> </h1>
</div>

Setting the file

Through the setFile method, each view can specify the HTML page to load. However, using the method directly is not required; the framework can detect and activate the HTML file in the following ways:

An HTML file with exactly the same name as the view

That is, if your View file is frontend/views/posts/View.php, its HTML file must be at the path frontend/html/posts/ with the name View.php.

Defining the file in the View class

Specify the HTML file path relative to the theme's root directory (the location of the theme's defining file, theme.json) in a property named $file in the view class.

views/packagename/news/Show.php
namespace themes\themename\views\packagename\news;

use packages\base\View;

class Show extends View {

protected $file = 'html/view-post.php';

public function __beforeLoad() {}
}

Registering the View and HTML files in the theme configuration file

If you use the Backend view in your project, you must register the backend file, as well as its view and HTML files, in the theme's configuration file (theme.json).

theme.json
{
"name": "themename",
"autoload": {
"directories": ["views"]
},
"assets": [
{"type": "css", "file": "assets/css/style.css"},
{"type": "js", "file": "assets/js/pages/index.js"}
],
"views": [
{
"name": "/themes/themename/views/packagename/news/Show",
"parent": "/packages/packagename/views/news/Show",
"file": "html/news/Show.php"
}
]
}

Errors

The addError method is defined for displaying errors that need to be shown in the template. It takes one argument, which is an object of the packages\base\view\Error class.

info

For more information, refer to the View Error page.

You can retrieve the registered errors with the getError and getErrors methods. The getError method returns an Error object — the first registered error — and the getErrors method returns an array of Error objects containing all registered errors.

Example 1: Sample controller file

controllers/Classes.php
namespace packages\packagename\controllers;

use themes\themename\views\packagename\classes as views;
use packages\base\{Controller, Response, NotFound, View, Http};
use packages\packagename\{Classroom as ClassModel, Student as StudentModel};

class Classes extends Controller {

public function addStudent(array $data): Response {

$model = ClassModel::byId($data["class"]);

if (!$model) {
throw new NotFound();
}

$view = View::byName(views\AddStudent::class);
$this->response->setView($view);

if (Http::is_post()) {
$this->response->setStatus(false);
try {
$inputs = $this->checkinputs(array(
'student' => array(
'type' => StudentModel::class,
'query' => function ($q) {
$q->where("status", StudentModel::ACTIVE);
},
)
));

$model->addStudent($inputs["student"]);

$this->response->setStatus(true);
} catch (InputValidationException $e) {
$error = new Error("student_notfound_or_disabled");
$error->setMessage("No active student was found.");
$view->addError($error);
}
} else {
$this->response->setStatus(true);
}
return $this->response;
}
}

In the example above, if the user's status is inactive, an object of the Error class is created; this class defines the setMessage method for specifying the error text. Finally, the object created from the Error class is passed to the addError method to display the error in the template.

Example 2: Displaying the error in the template

<body>
<?php
if ($this->getError()) {
$error = $this->getError()->getMessage();
echo '<div class="alert alert-danger" role="alert">
<strong>'. $error. '</strong>
</div>';
}
?>

To make displaying errors in the template easier, you can use the getErrorsHTML() method defined in the userpanel package. To use the getErrorsHTML method, the view class must use the themes\clipone\viewTrait trait.

A sample view file:

views/packagename/Index.php
namespace themes\themename\views\packagename;

use packages\base\View;
use themes\clipone\viewTrait;

class Index extends View {
use viewTrait;

public function __beforeLoad() {}
}

Example 3

<div class="container">
<?php
$errorcode = $this->getErrorsHTML();
if ($errorcode) {
?>
<div class="error-container"><?php echo $errorcode; ?></div>
<?php } ?>
</div>

In the example above, however many errors have been registered are displayed. Based on the type of each error, the package displays its text in the appropriate alert classes.

Adding asset files

When the setView method is called in the controller, the theme's theme.json file is read, and its assets section is searched for CSS and JS files, which are then added to the page. If the files configured under the assets key are not of type CSS or JS, you need to convert the other formats to these formats before loading the file on the page. To manage and convert files, add the node_webpack package alongside your main package. This package traverses all packages, detects the files that must be loaded on the pages, and converts them to the CSS and JS formats that browsers can recognize. Using Webpack technology, this package also bundles all files into a single file. This is one of the most fundamental and principled steps for improving a site's ranking and grade.

info

For more information, refer to the node_webpack page.

Adding dynamic data

To add dynamic data to the page and use it in JavaScript, you can use the dynamicData method. With this method, you can access the dynamic-data event (Event) and add information to it. This information is automatically converted into data readable by JavaScript and placed on the page before the main files are loaded. Settings defined in the framework — such as multilingual support, the allowed languages and the active language, how the language is placed in the URL, and other settings — are added to this event by default.

Default output

var options = {"packages.base.translator.defaultlang":"fa_IR","packages.base.translator.changelang":"uri","packages.base.translator.changelang.type":"short","packages.base.routing.www":"nowww"};var translator = {"lang":"fa_IR"};

Usage

$(() => {
alert(options["packages.base.translator.defaultlang"]);
alert(JSON.stringify(translator));
});

Example 1: Adding data in a separate variable

views/packagename/Index.php
namespace themes\themename\views\packagename;

use packages\base\View;

class Index extends View {
public function __beforeLoad() {
$this->dynamicData()->setData("permissions", array(
array(
"title" => "Search users",
"permission" => "userpanel_users_search"
),
array(
"title" => "Add new user",
"permission" => "userpanel_users_add",
),
array(
"title" => "Edit users",
"permission" => "userpanel_users_edit",
),
array(
"title" => "Delete users",
"permission" => "userpanel_users_delete",
),
));
}
}

Output of Example 1

var options = {"packages.base.translator.defaultlang":"fa_IR","packages.base.translator.changelang":"uri","packages.base.translator.changelang.type":"short","packages.base.routing.www":"nowww"};var translator = {"lang":"fa_IR"};var permissions = [{"title": "Search users", "permission": "userpanel_users_search"}, {"title": "Add new user", "permission": "userpanel_users_add"}, {"title": "Edit users", "permission": "userpanel_users_edit"}, {"title": "Delete users", "permission": "userpanel_users_delete"}];

Usage of Example 1

$(() => {
const $tbody = $("#permission-table tbody");
if ($tbody.length) {
for (const permission of permissions) {
$tbody.append('<tr><td><div class="checkbox checkbox-primary"><label><input type="checkbox" name="permissions[' + permission.permission + ']" value="' + permission.permission + '">' + permission.title + '</label></div></td></tr>');
}
}
});

Example 2: Adding a new setting

views/packagename/Index.php
namespace themes\themename\views\packagename;

use packages\base\{View, Options};

class Index extends View {

protected $hasAccessToDelete = false;

public function __beforeLoad() {
Options::set("packages.packagename.has_access_to_delete_service", $this->hasAccessToDelete);
$this->dynamicData()->setOption("packages.packagename.has_access_to_delete_service");
}
public function hasAccessToDelete(bool $permission) {
$this->hasAccessToDelete = $permission;
}
}

Output of Example 2

var options = {"packages.base.translator.defaultlang":"fa_IR","packages.base.translator.changelang":"uri","packages.base.translator.changelang.type":"short","packages.base.routing.www":"nowww", "packages.packagename.has_access_to_delete_service": true};var translator = {"lang":"fa_IR"};

Usage of Example 2

$(() => {
if (!options["packages.packagename.has_access_to_delete_service"]) {
$(".services .service .delete-service-btn").prop("disabled", true);
} else {
$(".services .service .delete-service-btn").prop("disabled", false);
}
/**
* Or
* $(".services .service .delete-service-btn").prop("disabled", !options["packages.packagename.has_access_to_delete_service"]);
*/
});