Writing ACF Views
At its most basic, a view is simply a way to display the result of an action. In practice, a view can be used to display results, handle an HTML form, launch other actions, combine several actions on the same page, provide dynamic interaction, or all of the above.
View Basics
A view is a Lua Server Page (.lsp) file loaded by haserl. The file can contain a combination of HTML and LUA (and anything else that can be on a web page, i.e. javascript). The lsp file must be stored in a specific file location in order to be loaded by the acf_www-controller.lua module.
According to the haserl documentation, the lsp file is parsed to find and execute any Lua code. The Lua code must be enclosed in <% %> or the older <? ?> token markers. While parsing the lsp file and executing the Lua code, the resulting text is output via socket to the web browser.
View Inputs
Since a view is simply a means to display data to the user, it should not directly access any controllers, models, or global variables. The inputs to a view fall into two categories:
Parameters
There are four parameters passed to each html view page. They are generally accessed as follows:
<? local data, viewlibrary, page_info, session = ... ?>
- form - This is the table returned by the executed action.
- viewlibrary - discussed below
- page_info - A table containing information about the current page, such as the name of the controller and action.
- session - The user session table. This table contains information about the current user, permissions, and command results.
Libraries
The view has access to all of the common Lua libraries, although some are not designed to be used by the view. There are two libraries specifically designed for use in the view:
- viewlibrary - This library is passed to the view as a parameter. The functions in this library are the only way that a view is allowed to interact with the MVC self variable. Currently the library only contains one function - dispatch_component
- viewfunctions - This library contains common functions used by many views. It should be used for displaying cfe items and forms where possible to maintain compatibility.
Templates
ACF uses templates to display common application information on all views. The acf_www-controller module searches for an appropriate template file before displaying a view. If one is found, the template is displayed and it will load and display the view using haserl. The standard template will display a header, menu, footer, ... If you do not want the standard template to be used for your view, you must define a template that will be loaded instead. To launch your view from within a template:
<? local viewtable, viewlibrary, page_info, session = ... local func = haserl.loadfile(pageinfo.viewfile) func (viewtable, viewlibrary, page_info, session) ?>
Common Views
To ease development, there are several common views available for use in any controller.
- debug-html.lsp - Used during development to display view parameters.
- filedetails-html.lsp - Displays a form that allows a user to directly edit a file.
- form-html.lsp - Display a basic form with no ordering of fields.
- startstop-html.lsp - Displays Start, Stop, and Restart buttons and the result of a preceding action. Buttons are disabled based upon the process status.
- status-html.lsp - Displays basic status for a process.
- expert-html.lsp - Displays status and startstop components, and uses filedetails-html.lsp to edit a configuration file.
The common views can be used in two ways. The first is to use a symlink to the common view instead of an lsp file. The common view will handle the entire display. Keep in mind that the relative path is based upon the installed location, not the working directories.
ln -s ../filedetails-html.lsp logfiles-view-html.lsp
The other method is to load the common view from within your view using haserl. This allows the view to display the common view along with other material. Keep in mind that the absolute path is based upon the installed location, not the working directories.
<? local form, viewlibrary, page_info, session = ... local pattern = string.gsub(page_info.prefix..page_info.controller, "[%(%)%.%%%+%-%*%?%[%]%^%$]", "%%%1") local func = haserl.loadfile(page_info.viewfile:gsub(pattern..".*$", "/") .. "filedetails-html.lsp") func(form, viewlibrary, page_info, session) ?>
Links and Commands
A view may contain links to other actions. For example, a list view could contain links to various actions that could be performed on the elements of that list. When the link is to an action that has its own view, no special processing is necessary. When linking to a command action, an action that redirects to the referrer rather than having its own view, the view must consider how to display the command results. Results of a command are stored in session.actionresult. The referring view should process and clear this session data or it will remain in the session, possibly showing up on another page. The viewfunctions displaycommandresults function can be used to display and clear these results.
Advanced
There are also some advanced concepts in view development.
Javascript
The ACF application is designed mostly for server-side generation of static HTML pages. In order to allow client-side interaction, some pages use Javascript. The Javascript code is included near the beginning of the lsp and may include Lua code (which will be run on the server before transmitting the page to the browser). Because of the use of templates, it is difficult to include Javascript code in the page header. However, Javascript initialization code may be placed anywhere on a page and will be run when it is encountered by the browser. So, in most cases, including the code in the lsp file is not a problem. For examples, see openssl-html.lsp, tinydns-edit-html.lsp, and tinydns-view-html.lsp.
Components
Components are an advanced feature of ACF that allows a view to launch another action and display the result. The feature is based upon the Ruby render_component and render_component_as_string functions, but is only allowed from within a view. To launch a component, use the dispatch_component function in the viewlibrary.
local dispatch_component = function(str, clientdata, suppress_view)
- str - A string containing the prefix, controller, and/or action. The string is parsed from right to left, and any missing element is assumed to be the same as the current action. i.e. "expert" refers to the expert action in the same controller as this action.
- clientdata - A table of clientdata to pass to the action.
- suppress_view - A flag telling the dispatch function to call the action but not display its view.
- returns viewtable - The table returned by the action.
A view can simply dispatch a component with suppress_view false or undefined and allow the component action to display itself within the view. The dispatch function will not display a template when displaying a component, or you would see nested templates. Or, a view can dispatch a component with suppress_view true. Since the dispatch_component function returns the viewtable, the view can then display or use the component result as it sees fit.
Component Forms
Special processing may be necessary when a view includes a component that displays a form. If a component includes a form submittal button, that button should point to the component's action, not the current action. Also, when the form is processed, the browser should be redirected to the original action so that the form is once again displayed as a component. Finally, the result of the form processing must be stored somewhere (in the session) so that it will be displayed when the form is reloaded as a component. This functionality is implemented in the form-html.lsp file (among many others) and the controller redirect_to_referrer function. No special code is necessary in the parent view, but the component view must properly set its form action.
Views without Actions
The dispatch function will allow a user to launch an action for which there is no matching action function, or even controller, as long as there is a specific view file for that action. This allows for views that simply launch components and for having multiple views for the same action. The view will receive the clientdata as its first parameter, rather than the action result.
An example of this is tinydns-edit-html.lsp for which there is no edit function in the controller. This view simply calls the editfile action as a component with suppressed view and displays the result using javascript. If the user presses the Save button, the edit view is launched again and passes its clientdata to the editfile action. The result is a new action without having to modify the controller. Another alternative would have been to point the form action to the editfile action. The redirect_to_referrer function in editfile would cause the edit view to be launched again, and the dispatch of editfile would then read the result from the session. This method would look the same but be slightly slower due to the redirection.