Difference between revisions of "ACF how to write"

From Alpine Linux
Jump to: navigation, search
m (myapp-controller.lua)
m (Added See Also: Separation between ACF functionality and web interface.)
 
(22 intermediate revisions by 5 users not shown)
Line 1: Line 1:
=How to Write an ACF <span style="color:red"> Under Construction</span> =
+
=How to Write an ACF=
  
For some examples please see svn
+
For some examples please see the Web Configuration Framework projects in the Alpine Linux git repository
  
svn co svn://svn.alpinelinux.org/acf
+
http://git.alpinelinux.org/
  
*shorewall
+
*acf-unbound - a simple ACF to control a service
*dhcp
+
*acf-awall - a slightly more complicated ACF for a firewall
 +
*acf-provisioning - a complicated database application based on ACF
 +
*...
  
 
+
==From <nil> to a running ACF example application==
==From <nil> to a running ACF example application <span style="color:red">Under Construction</span>==
 
  
 
===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 [http://www.lua.org/ lua.org] before starting.
  
===Step 2 - The Development Environment===
+
===Step 2 - The Application Environment===
* Setup an ACF Development Environment: [[Getting_started_with_ACF_development]]
+
* Setup the ACF web application by running ''setup-acf''
  
 
===Step 3 - Create A Development Directory===
 
===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)
* in your user home create a directory for your application (e.g. mkdir ~/myapp)
+
* And cd into it (e.g. cd ~/myapp)
* and cd into it (e.g. cd ~/myapp)
 
  
 
===Step 4 - MVC, How Does It Affect My Coding?===
 
===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.
+
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 may have one or more files.
* Model: In Model the 'real work' is done (e.g. modifying config files, starting/stopping services etc.)
+
* Controller: The event dispatcher. Most of the controller functionality is handled by the ACF mvc.lua code and some standard controllers (such as acf_www-controller.lua or acf_cli-controller.lua). For the controller layer of your new ACF package, you must export one lua function per action in a lua module named 'myapp-controller.lua'. The ACF controller code will interpret the user interaction to load your new controller and fire the appropriate action - the same-named function in your controller will be called.
* View: This is where you define what your application will look like. You can have one or more files, each presenting a dynamic html page which only as much code as neccessary to format the data you retrieve from Model.
+
* View: The view layer defines what your application will look like. For most actions, such as forms, your application can use the built-in automatic view generation. For others, you can link to standard views which are included in the acf-core package. For other actions, such as lists of data, you may create view files, each presenting a dynamic HTML page with only as much code as necessary to display the data you receive from the controller.
* Controller: The event dispatcher. In controller you place one function per event. If the user calls the respective 'event page' (web), acf will fire an action - the same-named function in controller will be called. This function then retrieves neccessary data from Model and passes it to View to be displayed to the user.
+
* Model: The 'real work' is done in the Model (e.g. modifying config files, starting/stopping services etc.). Each action exported by your controller will call into model functions to retrieve data and carry out actions.
  
 
===Step 5 - The Example Files To Start With===
 
===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:
 
Now let us have a look at the files we need to place into our application directory:
  
 +
* config.mk
 
* Makefile
 
* Makefile
* config.mk
+
* myapp-controller.lua
 
* myapp-model.lua
 
* myapp-model.lua
* myapp-myview-html.lsp
+
* myapp.roles
* myapp-controller.lua
 
 
* myapp.menu
 
* myapp.menu
 +
 +
 +
'''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
  
  
 
'''Makefile:'''
 
'''Makefile:'''
  
The Makefile once called does install our acf application so that we can look at it working.
+
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)
  VERSION=1.0_alpha1
+
  VERSION=0.1
 
   
 
   
  APP_DIST=myapp-model.lua       \
+
  APP_DIST=        \
          myapp-myview-html.lsp  \
+
        myapp*        \
          myapp-controller.lua  \
 
          myapp.menu
 
 
   
 
   
 
  EXTRA_DIST=README Makefile config.mk
 
  EXTRA_DIST=README Makefile config.mk
Line 63: Line 75:
 
  all:
 
  all:
 
  clean:
 
  clean:
        rm -rf $(tarball) $(P)
+
rm -rf $(tarball) $(P)
 
   
 
   
 
  dist: $(tarball)
 
  dist: $(tarball)
 
   
 
   
 
  install:
 
  install:
        mkdir -p "$(install_dir)"
+
mkdir -p "$(install_dir)"
        cp -a $(APP_DIST) "$(install_dir)"
+
cp -a $(APP_DIST) "$(install_dir)"
 
   
 
   
 
  $(tarball):    $(DISTFILES)
 
  $(tarball):    $(DISTFILES)
        rm -rf $(P)
+
rm -rf $(P)
        mkdir -p $(P)
+
mkdir -p $(P)
        cp $(DISTFILES) $(P)
+
cp $(DISTFILES) $(P)
        $(TAR) -jcf $@ $(P)
+
$(TAR) -jcf $@ $(P)
        rm -rf $(P)
+
rm -rf $(P)
 
   
 
   
 
  # target that creates a tar package, unpacks is and install from package
 
  # target that creates a tar package, unpacks is and install from package
 
  dist-install: $(tarball)
 
  dist-install: $(tarball)
        $(TAR) -jxf $(tarball)
+
$(TAR) -jxf $(tarball)
        $(MAKE) -C $(P) install DESTDIR=$(DESTDIR)
+
$(MAKE) -C $(P) install DESTDIR=$(DESTDIR)
        rm -rf $(P)
+
rm -rf $(P)
 
   
 
   
 
  include config.mk
 
  include config.mk
Line 88: Line 100:
 
  .PHONY: all clean dist install dist-install
 
  .PHONY: all clean dist install dist-install
  
Remark: Should you create additional 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-controller.lua:'''
 +
-- the myapp controller
 +
local mymodule = {}
 +
 +
mymodule.default_action = "myaction"
 +
 +
mymodule.myaction = function(self)
 +
    -- self.clientdata contains the user data
 +
    -- self.model points to our model
 +
    -- use the helper function to implement our form
 +
    return self.handle_form(self, self.model.getdata, self.model.setdata, self.clientdata, "Submit", "Edit data", "Data Submitted")
 +
end
 +
 +
return mymodule
  
'''myapp-model.lsp:'''
+
'''myapp-model.lua:'''
 
  -- acf model for myapp
 
  -- acf model for myapp
  -- Copyright(c) 2007 <Your name here> - Licensed under terms of GPL2
+
  local mymodule = {}
module (..., package.seeall)
 
 
   
 
   
  cfgfile = "/tmp/myfile"
+
  local cfgfile = "/tmp/myfile"
 
   
 
   
  -- This function returns a cfe (table of values) containing the files'
+
  -- This function returns a cfe (table of values) containing the file's
  -- value as string and an error code. If the file does not exist, we'll
+
  -- 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()
+
  mymodule.getdata = function(self, clientdata)
     retval = ""
+
     local retval = cfe({ type="group", value={}, label="Data" })
     error = 0
+
     retval.value.data = cfe({ type="longtext", label="Data" })
     fileptr = io.open( cfgfile, "r" )
+
 +
     local fileptr = io.open(cfgfile, "r")
 
     if fileptr ~= nil then
 
     if fileptr ~= nil then
       retval = fileptr:read( "*a" )
+
       retval.value.data.value = fileptr:read("*a") or ""
      if retval == nil then
 
          retval = ""
 
      end
 
 
       fileptr:close()
 
       fileptr:close()
 
     end
 
     end
     return error, cfe({ msg = retval })
+
 +
     return retval
 
  end
 
  end
 
   
 
   
 
  -- This function will write new contents into our file
 
  -- This function will write new contents into our file
  writefile = function( newcontents )
+
  -- The newdata parameter receives the same cfe as returned by getdata, now with the user data filled in
 +
mymodule.setdata = function(self, newdata, action)
 
     fileptr = io.open( cfgfile, "w+" )
 
     fileptr = io.open( cfgfile, "w+" )
 
     if fileptr ~= nil then
 
     if fileptr ~= nil then
       fileptr:write( newcontents )
+
       fileptr:write(newdata.value.data.value)
 
       fileptr:close()
 
       fileptr:close()
 +
    else
 +
      newdata.errtxt = "Failed to save data"
 
     end
 
     end
     return
+
     return newdata
end
 
 
 
 
 
'''myapp-myview-html.lsp:'''
 
<?
 
    form = ...
 
    option = form.option
 
?>
 
&lt;h1>MyApp - MyView&lt;/h1>
 
<form action="" method="POST">
 
  <textarea name="textdata"><? io.write( form.value.msg ); ?></textarea>
 
  <input type="submit" name="cmd" value="update">
 
</form>
 
 
 
 
 
'''myapp-controller.lua:'''
 
-- the myapp  controller
 
module (..., package.seeall)
 
 
--- default code up here
 
--- do not change anything except: self.conf.action for redirect
 
local list_redir = function( self )
 
    self.conf.action = "myview"
 
    self.conf.type = "redir"
 
    error (self.conf)
 
 
  end
 
  end
 
   
 
   
  local pvt = {}
+
  return mymodule
mvc= {}
 
mvc.on_load = function( self, parent )
 
    if ( rawget(self.worker, self.conf.action) == nil ) then
 
      list_redir(self)
 
    end
 
    pvt.parent_on_exec = parent.worker.mvc.post_exec
 
end
 
 
-- This is where 'our' code starts
 
 
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
 
    error, value = self.model.readfile()
 
    return cfe({ value = value })
 
end
 
  
 +
'''myapp.roles:'''
 +
GUEST=myapp:myaction
  
 
'''myapp.menu:'''
 
'''myapp.menu:'''
 
  # Cat  Group  Tab    Action
 
  # Cat  Group  Tab    Action
  Test    MyApp  MyView MyView
+
  Test    MyApp  MyAction myaction
  
 
===Step 6 - What Does It Do?===
 
===Step 6 - What Does It Do?===
This program only just displays a &lt;textarea> box and a submit "update" button. The user can enter the which is saved into a file once he presses "update".
+
This program just displays a &lt;textarea> box and a "Submit" button. The user can enter text that is saved into a file once he presses "Submit".
  
 
====In Depth====
 
====In Depth====
Now let us have a closer look at what the different files' contents:
+
Now let us have a closer look at the different files' contents:
 +
 
 +
=====myapp-controller.lua=====
 +
The controller is an event dispatcher. So, 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 ''myaction'' - a simple form.
 +
 
 +
This function can call the ''model's'' functions to update and/or retrieve data (e.g. self.model.getdata()).
 +
 
 +
Anything that this function returns will be passed on to the ''view''
 +
 
 +
=====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'.
 +
 
 +
In our case, we have implemented the getdata/setdata functions required for a form.
 +
 
 +
The getdata function receives a copy of 'self', a clientdata table, and a string containing the submit action. It will generate a 'CFE' table defining the form and including the current data.
 +
 
 +
The setdata function is only called when the form is submitted, and it receives a copy of 'self' and the updated form 'CFE' now containing the submitted data. The setdata function will attempt to perform the action, returning the same form 'CFE'. If there is an error, it will fill in the errtxt field of the 'CFE'.
 +
 
 +
=====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 file 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.
  
 
=====myapp.menu=====
 
=====myapp.menu=====
 
In this file you define:
 
In this file you define:
 
* '''The Category''' in which a menu entry for your program will appear
 
* '''The Category''' in which a menu entry for your program will appear
* '''The Group''', resp. the subheading's name under Category
+
* '''The Group''' menu name under Category for this controller
* '''The Action''' with-in your controller that will be called once the user klicks on the menu entry defined by Category and Group.
+
* '''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.
  
=====myapp-controller.lua=====
+
===Step 7 - How To Get It Going?===
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.
+
Once you have completed all the above mentioned steps, go on with:
 +
* sudo make install (this will install your app)
 +
* point your browser to https://ip-of-your-dev-host/
 +
 
 +
===More Info===
 +
====Where is the View?====
 +
The above example does not contain any code for a view. So, how is the action getting displayed?
 +
 
 +
For every action that you define in myapp-controller.lua, you can define a separate view file named: myapp-''action''-html.lsp
 +
 
 +
If there is no view file for a specific action, the application will look for a generic view file for the controller named: myapp-html.lsp
 +
 
 +
If that file does not exist, the ACF controller will attempt to display the 'CFE' using the built-in library functions. This works well for forms, and is what allows us to display our view here.
 +
 
 +
Here is a ''view'' file that displays our action using the built-in library functions. It looks exactly the same as when no view exists.
 +
 
 +
'''myapp-myaction-html.lsp:'''
 +
<%
 +
local form, viewlibrary, page_info, session = ...
 +
htmlviewfunctions = require("htmlviewfunctions")
 +
 +
htmlviewfunctions.displayitem(form, page_info)
 +
%>
  
In our case the action is ''myview''.
+
The view 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 second line)''.  The view can also load other libraries, but it should not directly access the ''controller'', ''model'', or any global variables.
  
This function can call the ''model's'' functions to update and/or retrieve
+
====How to exchange data between model-view-controller?====
data (e.g. self.model.readfile()).
+
To exchange data between model, view, and controller ACF uses ''Configuration Framework Entities (CFEs)''.
  
Anything that this function returns will be passed on to the ''view''
+
Please see [[ACF_core_principles]] for further details on CFEs.
  
=====myapp-model.lua=====
+
=See Also=
 +
*[[Acf-core-0.15_Release_Notes]] - Separation between ACF functionality and web interface.
  
=====myapp-myview-html.lsp=====
+
[[Category:ACF]]

Latest revision as of 13:31, 15 September 2017

How to Write an ACF

For some examples please see the Web Configuration Framework projects in the Alpine Linux git repository

http://git.alpinelinux.org/

  • acf-unbound - a simple ACF to control a service
  • acf-awall - a slightly more complicated ACF for a firewall
  • acf-provisioning - a complicated database application based on ACF
  • ...

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 before starting.

Step 2 - The Application Environment

  • Setup the ACF web application by running setup-acf

Step 3 - Create A Development Directory

  • 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 may have one or more files.

  • Controller: The event dispatcher. Most of the controller functionality is handled by the ACF mvc.lua code and some standard controllers (such as acf_www-controller.lua or acf_cli-controller.lua). For the controller layer of your new ACF package, you must export one lua function per action in a lua module named 'myapp-controller.lua'. The ACF controller code will interpret the user interaction to load your new controller and fire the appropriate action - the same-named function in your controller will be called.
  • View: The view layer defines what your application will look like. For most actions, such as forms, your application can use the built-in automatic view generation. For others, you can link to standard views which are included in the acf-core package. For other actions, such as lists of data, you may create view files, each presenting a dynamic HTML page with only as much code as necessary to display the data you receive from the controller.
  • Model: The 'real work' is done in the Model (e.g. modifying config files, starting/stopping services etc.). Each action exported by your controller will call into model functions to retrieve data and carry out actions.

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:

  • config.mk
  • Makefile
  • myapp-controller.lua
  • myapp-model.lua
  • myapp.roles
  • myapp.menu


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


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=0.1

APP_DIST=        \
        myapp*        \

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


myapp-controller.lua:

-- the myapp controller
local mymodule = {}

mymodule.default_action = "myaction"

mymodule.myaction = function(self)
   -- self.clientdata contains the user data
   -- self.model points to our model
   -- use the helper function to implement our form
   return self.handle_form(self, self.model.getdata, self.model.setdata, self.clientdata, "Submit", "Edit data", "Data Submitted")
end

return mymodule

myapp-model.lua:

-- acf model for myapp
local mymodule = {}

local 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)
mymodule.getdata = function(self, clientdata)
   local retval = cfe({ type="group", value={}, label="Data" })
   retval.value.data = cfe({ type="longtext", label="Data" })

   local fileptr = io.open(cfgfile, "r")
   if fileptr ~= nil then
      retval.value.data.value = fileptr:read("*a") or ""
      fileptr:close()
   end

   return retval
end

-- This function will write new contents into our file
-- The newdata parameter receives the same cfe as returned by getdata, now with the user data filled in
mymodule.setdata = function(self, newdata, action)
   fileptr = io.open( cfgfile, "w+" )
   if fileptr ~= nil then
      fileptr:write(newdata.value.data.value)
      fileptr:close()
   else
      newdata.errtxt = "Failed to save data"
   end
   return newdata
end

return mymodule

myapp.roles:

GUEST=myapp:myaction

myapp.menu:

# Cat   Group   Tab     Action
Test    MyApp   MyAction  myaction

Step 6 - What Does It Do?

This program just displays a <textarea> box and a "Submit" button. The user can enter text that is saved into a file once he presses "Submit".

In Depth

Now let us have a closer look at the different files' contents:

myapp-controller.lua

The controller is an event dispatcher. So, 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 myaction - a simple form.

This function can call the model's functions to update and/or retrieve data (e.g. self.model.getdata()).

Anything that this function returns will be passed on to the view

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'.

In our case, we have implemented the getdata/setdata functions required for a form.

The getdata function receives a copy of 'self', a clientdata table, and a string containing the submit action. It will generate a 'CFE' table defining the form and including the current data.

The setdata function is only called when the form is submitted, and it receives a copy of 'self' and the updated form 'CFE' now containing the submitted data. The setdata function will attempt to perform the action, returning the same form 'CFE'. If there is an error, it will fill in the errtxt field of the 'CFE'.

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 file 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.

myapp.menu

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.

Step 7 - How To Get It Going?

Once you have completed all the above mentioned steps, go on with:

More Info

Where is the View?

The above example does not contain any code for a view. So, how is the action getting displayed?

For every action that you define in myapp-controller.lua, you can define a separate view file named: myapp-action-html.lsp

If there is no view file for a specific action, the application will look for a generic view file for the controller named: myapp-html.lsp

If that file does not exist, the ACF controller will attempt to display the 'CFE' using the built-in library functions. This works well for forms, and is what allows us to display our view here.

Here is a view file that displays our action using the built-in library functions. It looks exactly the same as when no view exists.

myapp-myaction-html.lsp:

<%
local form, viewlibrary, page_info, session = ...
htmlviewfunctions = require("htmlviewfunctions")

htmlviewfunctions.displayitem(form, page_info)
%>

The view 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 second line). The view can also load other libraries, but it should not directly access the controller, model, or any global variables.

How to exchange data between model-view-controller?

To exchange data between model, view, and controller ACF uses Configuration Framework Entities (CFEs).

Please see ACF_core_principles for further details on CFEs.

See Also