ACF how to write: Difference between revisions
No edit summary |
(Updating to make current) |
||
| Line 1: | Line 1: | ||
=How to Write an ACF <span style="color:red"> Under Construction</span> = | =How to Write an ACF <span style="color:red"> Under Construction</span> = | ||
For some examples please see | For some examples please see git | ||
http://git.alpinelinux.org/cgit/ | |||
*acf-shorewall | |||
*acf-dhcp | |||
*... | |||
==From <nil> to a running ACF example application== | ==From <nil> to a running ACF example application== | ||
===Step 1 - The Programming Language=== | ===Step 1 - The Programming Language=== | ||
* ACF uses lua as programming language. Have a look at lua.org [http://www.lua.org/] before starting. | * ACF uses lua as its programming language. Have a look at lua.org [http://www.lua.org/] before starting. | ||
===Step 2 - The Development Environment=== | ===Step 2 - The Development Environment=== | ||
| Line 25: | Line 25: | ||
ACF is an MVC based framework. What does this mean to you? Your application is separated into three layers: Model, View, Controller - each of which has one or more files. | ACF is an MVC based framework. What does this mean to you? Your application is separated into three layers: Model, View, Controller - each of which has one or more files. | ||
* Model: The 'real work' is done in the Model (e.g. modifying config files, starting/stopping services etc.) | * Model: The 'real work' is done in the Model (e.g. modifying config files, starting/stopping services etc.) | ||
* View: This is where you define what your application will look like. You can have one or more View files, each presenting a dynamic | * View: This is where you define what your application will look like. You can have one or more View files, each presenting a dynamic HTML page with only as much code as necessary to format the data you receive from the Controller. | ||
* Controller: The event dispatcher. In the Controller, you create one function per action. If the user loads the respective 'event page' (web), | * Controller: The event dispatcher. In the Controller, you create one function per action. If the user loads the respective 'event page' (web), ACF will fire an action - the same-named function in controller will be called. This function then retrieves necessary data from the Model and passes it to the View to be displayed to the user. | ||
===Step 5 - The Example Files To Start With=== | ===Step 5 - The Example Files To Start With=== | ||
| Line 42: | Line 42: | ||
'''Makefile:''' | '''Makefile:''' | ||
The Makefile is called to install our | The Makefile is called to install our ACF application so that we can see it working. | ||
APP_NAME=myapp | APP_NAME=myapp | ||
PACKAGE=acf-$(APP_NAME) | PACKAGE=acf-$(APP_NAME) | ||
| Line 109: | Line 109: | ||
'''myapp-model.lua:''' | '''myapp-model.lua:''' | ||
-- acf model for myapp | -- acf model for myapp | ||
-- Copyright(c) | -- Copyright(c) 2010 <Your name here> - Licensed under terms of GPL2 | ||
module (..., package.seeall) | module (..., package.seeall) | ||
cfgfile = "/tmp/myfile" | cfgfile = "/tmp/myfile" | ||
-- This function returns a cfe (table of values) containing the | -- This function returns a cfe (table of values) containing the file's | ||
-- value as string | -- value as a string. If the file does not exist, we'll | ||
-- simply return "" (an empty string, but NOT nil) | -- simply return "" (an empty string, but NOT nil) | ||
readfile = function() | readfile = function() | ||
| Line 142: | Line 142: | ||
'''myapp-myview-html.lsp:''' | '''myapp-myview-html.lsp:''' | ||
< | <% form, viewlibrary, pageinfo, session = ... %> | ||
<h1>MyApp - MyView</h1> | <h1>MyApp - MyView</h1> | ||
<form action="" method="POST"> | <form action="" method="POST"> | ||
<textarea name="textdata">< | <textarea name="textdata"><% io.write( form.value.file.value ) %></textarea> | ||
<input type="submit" name="cmd" value="update"> | <input type="submit" name="cmd" value="update"> | ||
</form> | </form> | ||
| Line 157: | Line 155: | ||
myview = function( self ) | myview = function( self ) | ||
-- self.clientdata contains the data from the | -- self.clientdata contains the data from the HTML form | ||
-- in your myapp-myview-html.lsp | -- in your myapp-myview-html.lsp | ||
local clidat = self.clientdata | local clidat = self.clientdata | ||
| Line 173: | Line 171: | ||
'''myapp.roles:''' | '''myapp.roles:''' | ||
GUEST=myapp:myview | |||
'''myapp.menu:''' | '''myapp.menu:''' | ||
| Line 191: | Line 189: | ||
=====myapp-myview-html.lsp===== | =====myapp-myview-html.lsp===== | ||
This is our ''view''. It receives the data to be displayed from the ''controller''. The view has access to the table returned by the controller action along with a helper library, a table of page information, and the session data ''(see the first | This is our ''view''. It receives the data to be displayed from the ''controller''. The view has access to the table returned by the controller action along with a helper library, a table of page information, and the session data ''(see the first line .. <% .. %>)''. The view can also load other libraries, but it should not directly access the ''controller'', ''model'', or any global variables. | ||
=====myapp-controller.lua===== | =====myapp-controller.lua===== | ||
| Line 210: | Line 208: | ||
This file determines which users have access to which controllers and views. A separate ''roles'' file is generally defined for each ACF. The format of the files is as follows: | This file determines which users have access to which controllers and views. A separate ''roles'' file is generally defined for each ACF. The format of the files is as follows: | ||
group=controller:action[,controller:action] | group=controller:action[,controller:action] | ||
Each line defines controller:action combinations that are permitted for a particular group. ''' | Each line defines controller:action combinations that are permitted for a particular group. '''GUEST''' is a special group to which all users, including anonymous users, are members. | ||
=====myapp.menu===== | =====myapp.menu===== | ||
Revision as of 08:35, 29 June 2010
How to Write an ACF Under Construction
For some examples please see git
http://git.alpinelinux.org/cgit/
- acf-shorewall
- acf-dhcp
- ...
From <nil> to a running ACF example application
Step 1 - The Programming Language
- ACF uses lua as its programming language. Have a look at lua.org [1] before starting.
Step 2 - The Development Environment
- Setup an ACF Development Environment: Getting_started_with_ACF_development
Step 3 - Create A Development Directory
Once you entered the ACF Development Environment as described in step 2:
- in your user home create a directory for your application (e.g. mkdir ~/myapp)
- and cd into it (e.g. cd ~/myapp)
Step 4 - MVC, How Does It Affect My Coding?
ACF is an MVC based framework. What does this mean to you? Your application is separated into three layers: Model, View, Controller - each of which has one or more files.
- Model: The 'real work' is done in the Model (e.g. modifying config files, starting/stopping services etc.)
- View: This is where you define what your application will look like. You can have one or more View files, each presenting a dynamic HTML page with only as much code as necessary to format the data you receive from the Controller.
- Controller: The event dispatcher. In the Controller, you create one function per action. If the user loads the respective 'event page' (web), ACF will fire an action - the same-named function in controller will be called. This function then retrieves necessary data from the Model and passes it to the View to be displayed to the user.
Step 5 - The Example Files To Start With
Now let us have a look at the files we need to place into our application directory:
- Makefile
- config.mk
- myapp-model.lua
- myapp-myview-html.lsp
- myapp-controller.lua
- myapp.roles
- myapp.menu
Makefile:
The Makefile is called to install our ACF application so that we can see it working.
APP_NAME=myapp
PACKAGE=acf-$(APP_NAME)
VERSION=1.0_alpha1
APP_DIST=myapp-model.lua \
myapp-myview-html.lsp \
myapp-controller.lua \
myapp.roles \
myapp.menu
EXTRA_DIST=README Makefile config.mk
DISTFILES=$(APP_DIST) $(EXTRA_DIST)
TAR=tar
P=$(PACKAGE)-$(VERSION)
tarball=$(P).tar.bz2
install_dir=$(DESTDIR)/$(appdir)/$(APP_NAME)
all:
clean:
rm -rf $(tarball) $(P)
dist: $(tarball)
install:
mkdir -p "$(install_dir)"
cp -a $(APP_DIST) "$(install_dir)"
$(tarball): $(DISTFILES)
rm -rf $(P)
mkdir -p $(P)
cp $(DISTFILES) $(P)
$(TAR) -jcf $@ $(P)
rm -rf $(P)
# target that creates a tar package, unpacks is and install from package
dist-install: $(tarball)
$(TAR) -jxf $(tarball)
$(MAKE) -C $(P) install DESTDIR=$(DESTDIR)
rm -rf $(P)
include config.mk
.PHONY: all clean dist install dist-install
Remark: Should you create additional application files (view files for example), don't forget to place their names in Makefile under APP_DIST otherwise they will not be installed later on and your application will fail with an error message.
config.mk:
For use with the Makefile. Just copy/paste it. We will look at it later.
prefix=/usr
datadir=${prefix}/share
sysconfdir=${prefix}/etc
localstatedir=${prefix}/var
acfdir=${datadir}/acf
wwwdir=${acfdir}/www
cgibindir=${acfdir}/cgi-bin
appdir=${acfdir}/app
acflibdir=${acfdir}/lib
sessionsdir=${localstatedir}/lib/acf/sessions
myapp-model.lua:
-- acf model for myapp
-- Copyright(c) 2010 <Your name here> - Licensed under terms of GPL2
module (..., package.seeall)
cfgfile = "/tmp/myfile"
-- This function returns a cfe (table of values) containing the file's
-- value as a string. If the file does not exist, we'll
-- simply return "" (an empty string, but NOT nil)
readfile = function()
retval = ""
fileptr = io.open( cfgfile, "r" )
if fileptr ~= nil then
retval = fileptr:read( "*a" )
if retval == nil then
retval = ""
end
fileptr:close()
end
return cfe({ value = retval, label="File data" })
end
-- This function will write new contents into our file
writefile = function( newcontents )
fileptr = io.open( cfgfile, "w+" )
if fileptr ~= nil then
fileptr:write( newcontents )
fileptr:close()
end
return
end
myapp-myview-html.lsp:
<% form, viewlibrary, pageinfo, session = ... %> <h1>MyApp - MyView</h1> <form action="" method="POST"> <textarea name="textdata"><% io.write( form.value.file.value ) %></textarea> <input type="submit" name="cmd" value="update"> </form>
myapp-controller.lua:
-- the myapp controller
module (..., package.seeall)
myview = function( self )
-- self.clientdata contains the data from the HTML form
-- in your myapp-myview-html.lsp
local clidat = self.clientdata
-- user did submit the form (not just call the page)
if clidat.cmd then
if clidat.cmd == "update" then -- user pressed update button
self.model.writefile( clidat.textdata )
end
end
value = self.model.readfile()
return cfe({ type="form", value={file=value} })
end
myapp.roles:
GUEST=myapp:myview
myapp.menu:
# Cat Group Tab Action Test MyApp MyView myview
Step 6 - What Does It Do?
This program just displays a <textarea> box and a submit "update" button. The user can enter text that is saved into a file once he presses "update".
In Depth
Now let us have a closer look at the different files' contents:
myapp-model.lua
The functions defined in here can be accessed by the controller to update/set/retrieve data, start/stop services, basically do any 'real work'.
myapp-myview-html.lsp
This is our view. It receives the data to be displayed from the controller. The view has access to the table returned by the controller action along with a helper library, a table of page information, and the session data (see the first line .. <% .. %>). The view can also load other libraries, but it should not directly access the controller, model, or any global variables.
myapp-controller.lua
The controller is an event dispatcher. So, in here you define all the actions that the user can call or that are defined in the menu. Each action is a separate function that will receive self as the only parameter.
In our case the action is myview.
For every action you define here, you can define a separate view file using the nameage: myapp-action-html.lsp
If there is no view file for a specific action, the application will look for a generic view file using the nameage: myapp-html.lsp
This function can call the model's functions to update and/or retrieve data (e.g. self.model.readfile()).
Anything that this function returns will be passed on to the view
myapp.roles
This file determines which users have access to which controllers and views. A separate roles file is generally defined for each ACF. The format of the files is as follows:
group=controller:action[,controller:action]
Each line defines controller:action combinations that are permitted for a particular group. GUEST is a special group to which all users, including anonymous users, are members.
In this file you define:
- The Category in which a menu entry for your program will appear
- The Group menu name under Category for this controller
- The Tab name on the controller page
- The Action with-in your controller that will be called once the user clicks on the menu entry or tab defined by Category, Group, and Tab.
How to exchange data between model-view-controller?
To exchange data between model, view and controller ACF uses Configuration Framework Entities (CFE).
Please see ACF_core_principles for further details on CFEs.
Step 7 - How To Get It Going?
Once you have completed all the above mentioned steps, go on with:
- sudo make install (this will install your app with the http server)
- point your browser to http://ip-of-your-dev-host/