📜 ⬆️ ⬇️

Jii - a JavaScript framework with architecture from Yii 2


Introduction


Hello to all habrovchanam, fans of Yii and Node.js.
I continue a series of articles about the Jii framework and its parts. In the previous parts, we looked at parts of the framework that can be used without initializing the application, namely Query Builder and Active Record . From the vote (as well as letters and comments), it became clear that it was worthwhile to continue. And this time we will talk about the architecture and structural components of the Jii framework.

Ideology



Transferring the framework functionality from PHP to JavaScript is not a trivial task and cannot be performed by a robot. Languages ​​have very different features and differences, the main one being the asynchrony of JavaScript.
Below is how these problems were solved and what practices I followed. These same practices should be followed by other developers when developing Jii and its extensions.

Promise


Each asynchronous operation returns a Promise object implementing the ES6 standard. For this, the when library is used.
Methods starting with the prefix load (loadData ()) return Promise, methods with the prefix get (getUser (), get ('id')) - return data synchronously.

Saving API Yii 2


The original idea of ​​Jii is to bring a beautiful and rich PHP framework to JavaScript. So that php developers who want to try / go to node.js can quickly and easily master the framework on
other programming language, even without reading reference class!
')

Buildability


Jii is designed taking into account the fact that it should work wherever possible - browser, server, phonegap, node-webkit, etc.
Despite the fact that Yii generates quite a few global constants ( YII_ * ), the class Yii and the naming space yii , Jii do not clog up
global space. Browsers have access to a single Jii object, which can be hidden by calling Jii.noConflict () . In the server part, nothing is written to global, and Jii is returned as a result.
call require ('jii') .

Npm packages


Jii is distributed as a set of jii- * packages and does not have its own package managers (hello, Meteor). This means that with Jii you can connect any other npm packages.
Jii is broken into several packages, so it can be used in parts. For example, if you want to start using only ActiveRecord, then you install jii-ar-sql and do not have controllers, views, http server, and other code that you do not need.
If you have read the previous articles , you have already seen this one.
The main packages for Jii at the moment are:

Getters and Setters


One of the main features of Yii is the ease of accessing the properties and methods of objects through getters and setters, for example, by accessing $ model-> user, you can get the model property, or call the getUser () method, or even get the user relation, where DB - the magic is complete, but everyone likes it.
In JavaScript, not all browsers support getters and setters, and many projects still need to support IE8, so in Jii they are implemented as get ('user') and set ('user', 'Ivan') methods. This approach can be found in many libraries, for example in Backbone .
In the future, when the need for old browsers disappears, then real getters and setters will be added in parallel with get / set methods that will work through them.

Classes and Neimspaces


As they say, every self-respecting programmer should write his own implementation of classes in JavaScript. So here, for the implementation of classes, my Neatness library is used, which has already performed well in other internal projects.

Why not ES6 classes (and not CoffeeScript / TypeScript)?



In the comments and on the githaba there is already a small holivar about this.
I will sound here the reasons for the absence of ES6 in Jii:
  1. For node.js, you need preprocessing (io.js supports)
  2. In es6, there are no namespaces, but they are needed to repeat the features of Yii. They can be implemented through modules, but it will be a crutch. Change one crutch to another - it makes no sense.
  3. In ES6, you will want to use getters and setters, but they are not supported by older browsers, as mentioned above.
  4. Many developers do not know the format es6 / coffeescript / typescript and this will be an additional threshold of entry
  5. The difficulty of working with code of different formats (ES5 and ES6). It is not always possible in an existing project to change all the code to es6, and you still need to inherit the es6 classes, and you still have to use Jii.defineClass () to create classes - the crutch does not go away.

Below I added a poll on this topic, vote, comment!

Middlewares


In Yii, the framework components are available globally via Yii :: $ app-> ... However, in Jii, not all components can be arranged globally, since some of them (Request, Response, WebUser, Session, ...) are bound to the context (request).
Such components will be created in context ( Jii.base.Context ) and passed as a parameter to the action - an analogy with the transfer of request and response to express .

/** * @class app.controllers.SiteController * @extends Jii.base.Controller */ Jii.defineClass('app.controllers.SiteController', /** @lends app.controllers.SiteController.prototype */{ __extends: Jii.base.Controller, /** * * @param {Jii.base.Context} context * @param {Jii.httpServer.Request} context.request * @param {Jii.httpServer.Response} context.response */ actionIndex: function(context) { context.response.data = this.render('index'); context.response.send(); } }); 

Entities



Jii applications are organized according to the model-view-behavior design pattern (MVC) and have the following entities:

Examples of their use can be seen in the demo .

Applications


Applications are objects that control the entire structure and life cycle of an application system Jii. Usually on one worker (process) of Node.js comes one instance of the Jii application, which is accessible via Jii.app.
When the input script creates an application, it loads the configuration and applies it to the application, for example:

 var Jii = require('jii'); //    var config = { application: { basePath: __dirname, components: { http: { className: 'Jii.httpServer.HttpServer' } } }, context: { components: { request: { baseUrl: '/myapp' } } } }; //       Jii.createWebApplication(config); 

An important difference from Yii is that the configuration is divided into two parts - the application configuration (application section) and the context (request) configuration (context section). Application configuration creates and configures components, modules and configures the application itself (Jii.app) - all this happens when you start the worker. In turn, the context configuration creates and configures components with each action call — http request, console command call, etc. The created components are passed as the first argument to the action method.

Due to the fact that the configuration of the application is often very complex, it is put into files and broken into several configuration files.

Application Components


Applications store many application components that provide various means for the operation of the application. For example, the components urlManager is responsible for routing web requests to the desired controller; the db component provides tools for working with the database; etc.

Each component of the application has its own unique ID, which allows you to identify it among other various components in the same application. You can access the component as follows:

 Jii.app.ComponentID 

Embedded Application Components


Jii has several built-in application components, with fixed IDs and default configurations.

Below is a list of embedded components of the application. You can configure them as well as other components of the application. When you configure the embedded component of the application and do not specify the class of this component, the default value will be used.

Context components


The set of context components depends on where it is applied. The most common option is the HTTP request of the user, for which we will consider the built-in set of components.

As with application components, each application component has its own unique ID, which allows it to be identified among other various components in the same context. You can access the component as follows:

  actionIndex: function(context) { context.ComponentID } 

Built-in query components


Below is a list of embedded query components. You can configure them as well as other components in the context section of the configuration file.


Controllers


Controllers are part of the MVC architecture. These are objects of the classes inherited from Jii.base.Controller and are responsible for processing the request and generating the response. In essence, when processing the HTTP request by the server ( Jii.httpServer.HttpServer ), the controllers will analyze the input data, transmit it
into models, insert model results into [views] (structure-views), and ultimately generate outgoing responses.

Actions


Controllers consist of actions that are basic units that the end user can access and request the execution of a particular
functional. The controller may have one or more actions.

The following example shows a post controller with two actions: view and create :

 /** * @class app.controllers.PostController * @extends Jii.base.Controller */ Jii.defineClass('app.controllers.PostController', /** @lends app.controllers.PostController.prototype */{ __extends: Jii.base.Controller, actionView: function(context) { var id = context.request.get('id'); return app.models.Post.findOne(id).then(function(model) { if (model === null) { context.response.setStatusCode(404); context.response.send(); return; } context.response.data = this.render('view', { model: model }); context.response.send(); }); }, actionCreate: function(context) { var model = new app.models.Post(); Jii.when.resolve().then(function() { // Save user if (context.request.isPost()) { model.setAttributes(context.request.post()); return model.save(); } return false; }).then(function(isSuccess) { if (isSuccess) { context.request.redirect(['view', {id: model.get('id')}]) } else { context.response.data = this.render('create', { model: model }); context.response.send(); } }); } }); 

In the view action (defined by the actionView () method), the code first loads the model according to the requested model ID; If the model is successfully loaded, the code will display it using a view called view .

In the create action (defined by the actionCreate () method), the code is similar. He first tries to load the model using the data from the query and save the model. If everything went well, the code redirects the browser to the action view with the ID of the newly created model. Otherwise, it displays the create view, through which the user can fill in the required data.

Routes


End users access actions via so-called * routes *. The route is a string consisting of the following parts:

Routes can be in the following format:

 ControllerID/ActionID 

or the following format, if the controller belongs to the module:

 ModuleID/ControllerID/ActionID 

Creating action


Creating actions is not as difficult as declaring the so-called * action methods * in the controller class. An action method is a method whose name begins with the word action . The return value of the action method is the response data that will be sent to the end user. The code below defines two actions, index and hello-world :

 /** * @class app.controllers.SiteController * @extends Jii.base.Controller */ Jii.defineClass('app.controllers.SiteController', /** @lends app.controllers.SiteController.prototype */{ __extends: Jii.base.Controller, actionIndex: function(context) { context.response.data = this.render('index'); context.response.send(); }, actionHelloWorld: function(context) { context.response.data = 'Hello World'; context.response.send(); } }); 

Action classes


Actions can be defined as classes inherited from the Jii.base.Action or its descendants.

To use such an action, you must specify it by overriding the Jii.base.Controller.actions () method in your controller class, as follows:

 actions: function() { return { //  "error"      error: 'app.actions.ErrorAction', //  "view"      view: { className: 'app.actions.ViewAction', viewPrefix: '' } }; } 

As you can see, the actions () method should return an object whose keys are action IDs, and the values ​​are the corresponding action class names or [configuration] (concept-configurations). Unlike inline actions, individual action IDs can contain arbitrary characters, as long as they are defined in the actions () method.

To create a separate action, you must inherit from the Jii.base.Action class or its descendants, and implement a public run () method. The role of the run () method is similar to other action methods. For example,

 /** * @class app.components.HelloWorldAction * @extends Jii.base.Action */ Jii.defineClass('app.components.HelloWorldAction', /** @lends app.components.HelloWorldAction.prototype */{ __extends: Jii.base.Action, run: function(context) { context.response.data = 'Hello World'; context.response.send(); } }); 

Modules


Modules are self-sufficient program blocks consisting of models, views, controllers, and other auxiliary components. When installing modules into an application, the end user gets access to their
controllers. For this reason, modules are often considered as miniature applications. Unlike applications, modules cannot be created separately; they must be inside applications.

Creating modules


A module is placed in a directory called the base module path ( Jii.base.Module.basePath ). As well as in the application directory, in this directory there are subdirectories controllers , models , views and others, in which controllers, models, views and other elements are located. The following example shows the approximate contents of the module:

 modules/ forum/ Module.js    controllers/     DefaultController.js      models/     views/       layouts/     default/     DefaultController index.ejs    

Module Classes


Each module is declared using a unique class that inherits from Jii.base.Module . This class must be placed in the root of the base path of the module. During the launch of the application (worker) will be
One instance of the corresponding module class has been created. Like application instances, module instances are needed so that the module code can share data and components.

Here is an example of what a module class might look like:

 /** * @class app.modules.forum * @extends Jii.base.Module */ Jii.defineClass('app.modules.forum.Module', /** @lends app.modules.forum.Module.prototype */{ __extends: Jii.base.Module, init: function(context) { this.params.foo = 'bar'; return this.__super(); } }); 

Module Controllers


When creating module controllers, it is customary to place controller classes in the controllers subspace of the module class namespace. This also implies that the controller class files must be located in the controllers directory of the module's base path . For example, to describe the post controller in the forum module from the previous example, the controller class is declared as follows:

 var Jii = require('jii'); /** * @class app.modules.forum.controllers.PostController * @extends Jii.base.Controller */ Jii.defineClass('app.modules.forum.controllers.PostController', /** @lends app.modules.forum.controllers.PostController.prototype */{ __extends: Jii.base.Controller, // ... }); 

You can change the namespace of controller classes by setting the Jii.base.Module.controllerNamespace property. If any controllers fall out of this namespace, they can be accessed by setting the Jii.base.Module.controllerMap property, similar to how it is done in the application.

Using Modules


To use a module in an application, it is enough to include it in the Jii.base.Application.modules property in the application configuration. The following code in the application configuration uses the forum module:

 var config = { application: { // ... modules: { forum: { className: 'app.modules.forum.Module', // ...    ... } } }, context: { // ... } }; 

The Jii.base.Application.modules property is assigned an object containing the module configuration. Each object key is a * module identifier *, which uniquely identifies a module among other application modules, and the corresponding object is a configuration for creating a module.

Access to modules


Access to the module instance can be obtained in the following way:

 var module = Jii.app.getModule('forum'); 

Having an instance of a module, you can access the parameters and components registered in the module. For example,

 var maxPostCount = module.params.maxPostCount; 

In custody



Below, I offer a survey about the format of the Jii code. If you choose a non-JavaScript format, then indicate in the comments how you would solve the problems described in the section “Why not ES6 classes (and not CoffeeScript / TypeScript)?” In this article.
Let me remind you, Jii is an open source project, so I will be very happy if someone joins its development. Write to affka@affka.ru.

Framework Site - jiiframework.ru
GitHub - https://github.com/jiisoft
Talk features on githab

Liked? Put a star on the githaba!

Source: https://habr.com/ru/post/260931/


All Articles