User's manual

From CMC for PHP
Jump to: navigation, search

The view structure

The view can be just regular javascript/HTML with just some 'data-cmc-id' attributes to identify frames closure.

However there are some extensions that can help to build views from pieces.

Keep in mind that each piece can have 'extra' content for testing preview, which will be just dropped at runtime. The frames can be associated to any pieces. As a result, a view can have multiple process frames, even for the same part of the view. This is up to the frame implementation to keep it consistent.

The view snippets

There is a simple way to use snippets: some html material to 'include' into the final view. Typical use of it are menus, side panels, page footer, ...

Snippet side

On snippet side, just use an id for the part you want to use as a snippet. This can be a part of a full html page, or only the snippet itself:

<div lang="en" id="no-javascript" class="no-javascript" data-cmc-id="snippets/no-javascript">
    <p>Please enable Javascript to gain full functionality of this site </p>
</div>

This also could be:

<html>
<head>

</head>
<body>
<div class="no-javascript" data-cmc-id="snippets/no-javascript">
    <p>Please enable Javascript to gain full functionality of this site </p>
</div>
</body>

Important is having defined an appropriate data-cmc-id:

  • 'data-cmc-id' is the identity of the section. Note that the same id is used to bind a frame to it.

Both will have same effect when used as a snippet

Master side

        <div class="no-javascript cmc-sect" data-cmc-id="snippets/no-javascript">
            <p>Snippet should be here instead of this text</p>
        </div>

Important here are the two values:

  • class="cmc-sect", which informs that a snippet must be seek
  • data-cmc-id="theid", which tells which is the id of the material to seek

Other remarks:

  • if the material is not present, this is not an error: the default value will be used instead
  • the attributes of the 'master' and included tag will be merged: class attribute will be merged, and existing attributes will be added or replaced.


Define a view model

A view model is very useful: it will define a common structure for many pages. The model can use snippets, so you can have a very modular document structure. Models are also cascadable, if a more complex structure is neede.

The model part

A model is like a regular view, with a special section for defining the 'client' view location.

[...]
        <div data-cmc-position="model_content">
<p> Some default content </p>
        </div>
[...]

So here we have again just one attribute:

  • data-cmc-position="aposition", which tells where the client will put his contents


The client view part

Again, this can be a regular html documet, or only the useful part. The definition is:

                <div data-cmc-parent="model" data-cmc-position="page_mcontent">
<p> the client content</p>

So here we have again just one attribute:

  • data-cmc-parent="modelname", which tells which is the model to use
  • data-cmc-position="aposition", which tells where the client will put his contents in the model


Client/model merge

Some contents that is outside the client parts may be useful to put in the result. Typical contents are:

  • some page-specific script items or includes
  • title parts
  • meta items

To handle this the framework defines some special classnames:

  • class="cmc-replace", to replace a whole tag from <head>:
    <title class="cmc-replace">My client title</title>
    
  • class="cmc-add", to add some items in the header (typically to add some scripting items, stylesheets):
    <script class="cmc-add" type="text/javascript">
    
  • class="cmc-merge", to merge the value of the tag or attributes :
    <title class="cmc-merge"> - Contact</title>
    
    (here it will add the suffix to the main title)
    <meta class="cmc-merge" name="keywords" content=", calmarsoft, nice software">
    
    (adds keywords )

Special attributes

  • class="latescript" on a <script> tag is to indicate that the script has low priority
  • class="nomove" on a <script> tag indicates that the script should not be moved lower in the document


Multilingual structure

Multilingual structure is intended to be flexible and easy to implement.

Default material paths are:

  • one/two/tree/... for a non-localized item
  • [lang]/one/two/tree/... for a localized item

Any material item (a model, snippet or client view) can be in localized or non-localized storage.

Each material item will be seek in the following priorities:

  1. the url language, if specified
  2. the base material (parent) language, if any, and found
  3. the current session localized language, if found
  4. the non-localized version

If all fails on the client view, this generates a fatal error.

Customized structure

The multilingual structure can be customized by implementing the following in the 'config' class:

    static function getUrlForm() {        
        return '#^(?:/|)($lang)/(.*)#';
    }
    static function buildUrlLang($lang, $url) {
        return $lang.'/'.$url;
    }

The getUrlForm() is the regular expression used to recognize a localized URL. The buildUrlLang() is used to build a localized URL from a non-localized URL.


Language related functions

  • \cmc\sess()->getLangName() returns the current language code
  • \cmc\app()->getLanguageList() return the available language list codes
  • \cmc\sess()->getTranslation() returns the updated translation object
  • \cmc\sess()->translate($key) returns the localized text (or identity if no translation was found)

When the application is processing through the frame's code, all those functions are available and the PHP locale's is set according to the current language.

The view class

The view class is instantiated with the view material, and accessible from the frame instances.

We will list here some view's methods that can be useful for frame's implementations.

setImageAnswer()

Call this method to build an 'image' answer instead of a traditional html content.

some informative methods

  • getName() to know the view's name. Useful for example if a frame is linked to several views (for example linked to a part of the model)
  • getLogicPath() to get the full path of the view
  • is_dynamic() know if the view is in static or dynamic stage (see [[view_lifecycle view lifecycle])

setRedirect()

Used to decide a redirection to another page. This function can be used either in page render or ajax process (for example button click handler).

If it is used in page render a traditional redirect will occur; otherwise the ajax message will contain the redirect information which will automatically be performed by the client side if the framework.

application lifecycle

The program logic will mainly be start from frames implementation, however it is important to know how the application lifestyle is made.

PHP is basically a 'one request' - 'one execution' architecture. As it performs easy high availability, load balancing and scalability, it can on the other hand be a problem for building application that need to hold session information.

As CMC for PHP as a DOM approach of the view material it slightly impacts how the application lifecycle is handled. In fact, we have several parallel lifecycles:

  • application lifecycle
  • session lifecycle
  • view lifecycle
  • frame lifecyle

application is initialized on first request, and terminates when the request ends or, if cache is enabled, when the cache expiry is reached. session is initialized on each request in sessionless mode, or the first time in session enabled requests.

  • an application contains static views and static frames.
  • a session contains dynamic views and dynamic frames.

When a request is performed here are the main processing steps:

  1. application create/cached retrieval
  2. session create/cached retrieval
  3. analyze query and route to appropriate view
  4. static view retrieval:
    1. seek in session
    2. if fail: seek a static version in application
    3. if fail: build a new static version:
      • build view from materials
      • load/retrieve frames related to the view, and call static handlers
  5. process dynamic frames:
    • update local values from ajax message
    • call dynamic handlers (either hanler bindings or viewUpdate)
  6. view renders result with either can be:
    • document result
    • ajax result
    • image result
    • redirection
    • http error
  7. store session with dynamic frames
  8. store application with current view (if changed)

The static frame

cmc\frame reference

Implements view aspects that are common to all users. The result will be cached in the application's cache if enabled (Config#APP_cache APP_cache).


lifecycle events

viewStaticUpdate($view)

This event method is called to process components before it is cached in the application. In other words, it is called each time an entire recalculation is made from the source (if there is no application or session cache, or if the cache expires or if a material source was modified, and so on)

The typical implementation of this method is:

  • setting up default values for objects
  • alter DOM by adding attributes, ...
  • initializing local variables for further uses (those variables will be serialized at application level)

When this method is called, the declared widget components are already initialized, and ready for DOM operations.

Note: this is not necessary to call parent method which is empty.


viewInitialUpdate($view)

This event method is called:

  • for first update of a view if the actual class is dynamic (and so the view). In this case the result will be cached in the session if configured for it.
  • similarly to viewStaticUpdate if the frame is not dynamic

The typical implementation of this method is:

  • setting up default values for objects
  • alter DOM by adding attributes, ...
  • initializing local variables for further uses (those variables will be serialized at application level)

If the actual class is dynamic, and the view aims to be dynamic too, the implementation also can:

  • bind events on the widgets
  • define default values or other UI attributes.


When this method is called, the declared widget components are already initialized, and ready for DOM operations.

Note: this is not necessary to call parent method which is empty.

bIsSessionValid()

This event method is used to implement a custom test of session validity. Typical use is login check, and redirect to a login view:

        $this->login = $sess->getLoginName();   // is current session authenticated?
        if (!$this->login) {
            $view->setRedirect('/admin/login', true);   //no? => redirect to login view
            return false;
        }
        return true;


Serialize()/Unserialize()

Those methods are called during custom serialize/unserialisze process (before caching, and after uncaching). Implement those methods for example to exclude some member variables from serialization. Don't forget to call parent method when overriding it

initialize()

This method is called on first creation of object, and can be overridden to dynamically create and add widgets. Call the parent method when overriding this, otherwise the _widgetdef collection won't be used

viewPreStaticUpdate()/viewPostStaticUpdate()

Normally those methods are not indented to be overridden.

Utility methods

w()

This shortcut function access a given widget by it's name. Like:

$this->w('bt_test')->enable();
$this->w('txt_testresult')->setHtml('result text');

It changes the 'enable' state of the 'bt_test' button, and changes the content of the txt_textresult component.

qry(), sess(), app()

Shortcut methods to get request, session and application instances


datasource(), dataSourceFirst(), dataSourceExec()

Shortcut functions for database resources:

  • datasource($dsName) is to get a datasource object from its name, or null if none found
  • datasourceFirst($dsName, [$parms]) is to get a datasource object, execute it with parameters, fetch the first row, and return the result in an associative array
  • dataSourceExec($dsName, [$params]) is to get a datasource object, execute it with parameters and return if execution was successful or not

The dynamic frame

The dynamic frame is for implementing live part of the application.

It allows updating widget properties, widget level events (through POST events), handle REST or URL parameters, view level events (like refresh, upload, ...).

viewUpdate($view)

This method is typically used when we need to update the items on each refresh. It is called on the initial calculation, and also in refresh of the pages.

We can perform session aware tasks like checking current user, and so on. Typical usage is resetting components like labels, ...

event binding - standard

There are several event binding functions, which can be initialized in the viewInitialUpdate (typically) or in the viewUpdate methods.

AddClickEvent

Adds a Ajax bind on a component, and setup a listener for it. Example of use:

public function viewInitialUpdate($view) {
[...]
  $this->AddClickEvent('bt_upd', array($this, 'btUpdate'));
}
[...]
public function btBack($view) {
    // set result text        
    $view->setRedirect('/postlist');
}

The methods make two actions: setup the event in the widget, and binds the listener in the frame.

AddChangeEvent,

Identical to AddClickEvent, but on value change. Must take care of this to avoid network flood!

event binding - client side

clientAddClickCB

Binds a 'click' event to a client method. Allows custom client behavior.

clientSetTypingCB

Binds a 'type' event (keyboard action) to a client method. Allows custom client behavior.

clientSetValidationCB

Setups a client method to be called when validation state changes (becomes valid or invalid). Takes method name and timing granularity (triggers the event if it did no longer change in that timing)

event binding - misc

AddEventListener

Add a event listener without setting the event on the widget. Allows the client side to decide when to trigger the event.

addEventPost

General event setup for all widgets and events. With this we need to use a method override like onClick, or onEvent to implement the behavior of events.

It takes directly the event name in parameter:

    public function viewUpdate($view, $sess) {
        $this->w('bt_test')->addEventPost('click');

event binding - method overrides

onClick

Global 'click' event listener for all widgets of the frame. This is an alternate of AddClickEvent method use.

    public function onClick($view, $widget, $eventData) {
        switch ($eventData['widget']) {
            case 'bt_test':
                $this->w('bt_cancel')->show();
                $this->w('bt_test')->disable();
                $this->w('tab_result')->setCompData(array());
                $this->w('txt_testresult')->setHtml('Resolving ' . $this->w('ed_host')->getValue() . ' ...');
                $this->setPending(array('act' => 'resolve'));
                break;
            case 'bt_cancel':
                $this->w('bt_cancel')->hide();
                $this->w('bt_test')->enable();
                $this->w('txt_testresult')->setHtml('Canceled !');
                $this->cancelPend();
                break;
        }
    }


onEvent

Global event listener for all widgets of the frame.

onEvent( view $view, string $name, widget $widget, array $eventData)

It provides the event name, the widget component, and the event data parameters. It is required to call the parent method in order to have other methods work correctly.

onUpload

This method is called when a file upload has been processed related the frame.


background process methods

The framework provides an environment to easily allow background process. It works as follows: the servers answers a temporary response while telling what will be the next 'step'. Then the next 'step' is automatically asked by the framework as 'long' request. During this delay, an out of band request can be performed to cancel the current process (in case the process can't be canceled so it will be ignored, and session status will be overridden by the out of band request)

setPending

Used to initiate a 'long' process. It tells what must be the next step, to be processed by the 'process' event listener. For example, in a 'onclick' or 'update' method:

$this->w('txt_testresult')->setHtml('This can take a long time: processing step 1');
$this->setPending(array('act' => 'step 1'));


AddProcessEvent

Defines a 'background process' event listener. This method is called when the client requests a background process.

The callback can call the 'setPending' if there are other steps of processing, or just return if the whole process is finished. Of course the callback can act on any widget in real time to update the current status display.

public function viewUpdate($view, $sess) {
    // process - 'background' requests
    $this->AddProcessEvent(array($this, 'OnProcessEvent'));
[...]
}

public function OnProcessEvent($view, $eventData) {
     $msg = '';
     switch ($eventData['pendata']['act']) {
            case 'step 1':  // was trigered by the 'click' button
               [...] // long process
               $this->w('txt_testresult')->setHtml($result1);
               $this->setPending(array('act' => 'step 2'))
               break;
            case 'step 2':
               [...] // long process
               $this->w('txt_testresult')->setHtml($finalresult);
               break;
            default:
      }
      // update components depending on pending status
      if (!$this->isPending()) {
          $this->w('bt_cancel')->hide();
          $this->w('bt_test')->enable();             
      }
}


isPending

(Used below): test if the pending state is valid (altered by setPending)

cancelPend

Can be called in any POST event in the session (like button click done by a standard button that would not be disabled), in order to immediately cancel the process. Note that is will cancel it in appearance, but the 'running' process will continue and 'see' that the answer is no longer requested, and so won't save the session data (however will return an answer that will be ignored on client)

Example:

    public function onClick($view, $widget, $eventData) {
        switch ($eventData['widget']) {
            case 'bt_cancel':
                $this->w('bt_cancel')->hide();
                $this->w('bt_test')->enable();
                $this->w('txt_testresult')->setHtml('Canceled !');
                $this->cancelPend();
                break;
        }
    }

The widgets

widgets is the relationship between the frame and the view. Basically it alters the DOM on initial page request and keep information for the session. This information is mostly properties like visibility, value, etc. and depends of course of the widget behavior.

widget class

This is the 'mother' class for all widget; most logic and implementation is made on this class. However the 'widget' class is not designed to be instantiated directly, and must be implemented in a child class. Each widget also may have some implementation in the javascript part of the framework (to make DOM<->properties relationship, manage property changes in both ways,...)

widget class - developer methods

widget instantiation

The instantiate is made using the widget's factory. For example: areafactory::makewidget($frame, 'id'); note: 'id' is the id inside the document; however an xpath expression (with an unique result) can be used instead.

information methods

  • getName() / setName()

implementation methods

  • setConstants() : used to define constants for later use (currently used by the 'compositelist' widget)

For altering the DOM result:

  • setProperty : defines any property for the widget (The property does not need to exist)
  • setPropertyDatasource: use a datasource to setup the property
  • setPropertyQuery: use a query (referenced in the application) to setup the property.
  • getProperty: retrives a property value
  • getValue()/setValue(): get/set the 'value' part of the widget (typically the 'input' widget)
  • setCaption()/getCaption: setups the text value for the widget, with any text value
  • setHTML(): setups the text value by adding HTML data. The data must be valid piece of HTML
  • setVisible()/show()/hide(): changes the visibility status
  • setEnabled()/enable()/disable(): enables or disables the component

All those methods can alter the DOM result if the property is supported by the widget. The property value will also (again if supported by the widget) be managed by the client part to have proper Ajax data.

how to implement a widget class

  1. First we need to create a child class for the widget and its factory. Fastest way is copying a short implementation like 'area' (ui/widgets/area.php).
  2. Then simply add this implementation in your project, and augment or modify the 'cmc' javascript object, if needed. In the widget constructor, add a call to setJSObject if a jQuery or other object is to be created on the client (see an example in ui/widgets/button.php)
  3. Override applyPropertyDOM and modelPropertyDOM
  • applyPropertyDOM override is useful if the widget accept a new property, or if the 'value' property has to be managed a different way. applyPropertyDOM is called when a property value has to be applied to the DOM.
  • modelPropertyDOM override is somewhat the opposite: it is used to retrieve the property value from the DOM. This allows to have a default value in the document, and handle it.

The Javascript customization

The database queries

The database files