In FlightPath, controllers (the C in MVC) are represented by the Controller class. See Overview
| Controller | In FlightPath, controllers (the C in MVC) are represented by the Controller class. |
| Overview | In FlightPath, controllers (the C in MVC) are represented by the Controller class. |
| Conventions | |
| Defining Controllers | Controller definitions can be stored in app/controllers/<controller_name>_controller.sjs or in a module:app/modules/module_name/controllers/<controller_name>_controller.sjs. |
| Properties | |
| $page | $page metadata object |
| data | local request-specific data to be passed to views |
| name | The name of this controller |
| Functions | |
| addLayout | adds a layout layer to this this request |
| setLayout | Sets a single layout layer to this this request, clearing any existing layouts |
| addFilter | Adds an action filter |
| applyBehavior | loads a behavior and applies it to this object |
| getFilters | returns the filter array for the requested event and action. |
| getActions | returns an array of objects for each action that contain the action name and action function. |
| callAction | Call an action as an inline function. |
| include | Call an action and includes its output in the current request |
| init | Initializes a controller instance |
| render | renders a view |
| renderContent | Directly render content, bypassing default render |
| getElement | renders an HTML template returns the content as a string |
| set | Sets a property on data |
In FlightPath, controllers (the C in MVC) are represented by the Controller class.
Controllers are responsible for connecting requests to Models and Views The consist of one or more actions: functions that take parameters from the route, perform any necessary business logic with Models and render a response, typically with Views
Controllers are normally loaded via a route (See: Routes), but they can also be accessed via $FP.getController.
Controller definitions can be stored in app/controllers/<controller_name>_controller.sjs or in a module:app/modules/module_name/controllers/<controller_name>_controller.sjs. The primary source of configuration is the init function. This is executed after the init() function in app/controllers/global.sjs. Any functions or properties defined in this file will be added to final Controller instance.
// app/controllers/employee_action.sjs
function init(){
// See: Behavior: MynaAuth in the docs
// If this was in app/controlelr.global.sjs, it would aplpy to all
// controllers, not just EmployeeActino
this.applyBehavior("MynaAuth",{
whitelist:[],
providers:Myna.Permissions.getAuthTypes(),
redirectParams:{}
})
}
function edit(params){
// Generate ID for new record if none-supplied. That way ID will be in
// the URL and refreshing the page won't create a new record
if (!params.id){
$FP.redirectTo({
id:this.model.genKey()
})
}
// only get here if not re-directed
this.set("bean",this.model.get(params);
// this controller will automatically render
// app/views/employee_action/edit.ejs
}
$page metadata object
This can contain arbitrary metadata about the current page, but the following properties are used by the default layout file in app/views/layouts/default.ejs
| css | String Array. each entry should be either a complete <link> tag or a URL to a css file, e.g $FP.url +”static/css/global.css”. If a URL does NOT start with “/” or “http” it is assumed to be relative to app/static/ |
| icon | String. favicon url. Defaults to $server.rootUrl +”favicon.ico” |
| scripts | String/Object Array. String entries should be URLs to JavaScript source files. Object entries should be in the form of {type:”mime/type”,src:”url”}. If a URL does NOT start with “/” or “http” it is assumed to be relative to app/static/ |
| title | String. Page Title. Defaults to $controller.name + “.”+$params.action |
| tags | Object Array. Meta tags to add to header. Entries in the form of {name:”meta tag name”,content:”tag content”} |
| keywords | String Array. “keywords” meta tag. Each entry will be joined into a comma-separated list |
| description | String. Description meta tag |
| content | Set by render when including layout files. This represents the current content that needs to be included in the layout |
//in test_controller.sjs
function init(){
This.$page.title ="Test"
}
function index(params){
this.$page.css.push("css/default.css")
this.$page.scripts.push("/app/admin.js")
}
//loads js and CSS to support a ExtJs application with Ext.Direct support
function extIndex(params){
// using concat instead of directly assigning values preserves any
// existing values set in init()
this.$page.css =this.$page.css.concat([
"extjs/resources/css/ext-all.css",
"css/default.css"
])
this.$page.scripts =this.$page.scripts.concat([
"extjs/ext-all-debug.js",
// this is a JSONP callback to load the API from Controller: Direct
$FP.helpers.Html.url({
controller:"Direct",
action:"api",
params:{
callback:"Ext.Direct.addProvider",
namespace:"$FP"
}
})
])
}
Controller.prototype.addLayout = function addLayout( layout )
adds a layout layer to this this request
| layout | String. Layout name, or path relative to app/layouts |
This layout will in included after the global default layout, the controller default layout, and any previously defined layouts for this controller
Controller.prototype.setLayout = function setLayout( layout )
Sets a single layout layer to this this request, clearing any existing layouts
| layout | String or Boolean. Layout name, or path relative to app/layouts, or false to disable all layouts |
disables global and controller default layouts. If layout is a layout name or path, then that will be the only layout applied
Controller.prototype.addFilter = function addFilter( filter, options )
Adds an action filter
| filter | Function or Array. Filter function to execute, or Array of filter functions to execute. See Filter Function Parameters below. Note that the “this” scope when executed will be the controller instance being filtered |
| options | Optional, default see below JS struct, see Options below |
| when | Optional, default “beforeAction” When the filter should be run. see Events below |
| only | Optional, default null Array of String/RegExp. If defined, limit this filter to these action names. Mutually exclusive with except |
| except | Optional, default null Array String/RegExp. If defined, exclude this filter from these action names. Mutually exclusive with only |
| action | String name of action requested |
| params | the “params” argument passed to the action |
| response | The return from the action. This is always null for “beforeAction” filters |
| beforeAction | fired before executing an action function. Return false to cancel execution, return a JS object to supply new “params” to the action. Normally used to alter params or cancel routing, such as for authentication |
| afterAction | Fired after the action is complete but before default rendering. If the action contains an explicit render or renderContent call then that will be executed first, without triggering “beforeRender” or “afterRender”. Return false or make a call to render or renderContent to cancel the default render. Normally used for altering/adding properties to controller.data prior to rendering. Might also be used for custom rendering, but “beforeRender” is generally a better choice for that. |
| beforeRender | Fired before rendering the default view. Return false or make a call to render or renderContent to cancel the default render. Normally used for custom renders that replace the default, such as rendering as RSS, or returning JSON |
| afterRender | Fired after the default render. Any changes to output should be made through $res.clear, $res.getContent, $res.print, etc. Normally used to convert generated output such as converting to PDF or XHTML |
Filters are functions run before or after actions and/or before and after rendering the view for an action. Filters, by default, are run before every action, but can be applied to only specific actions. Great uses of filters are authentication and logging. Filters are only executed in standard request handling, callAction, and include. Direct execution of an action will not trigger filters.
function init(){
// exec an auth function before every action except
// "home" and "status"
this.addFilter(
this._auth,
{
except:["home","status"]
}
)
//log only actions whose name starts with "save"
this.addFilter(
this._audit,
{
only:[/^save/i]
}
)
this.addFilter(
this._pdfView,
{
when:"afterRender"
}
)
}
// Functions that begin with "_" are not considered actions and
// are excluded from browser requests
function _auth(controller,action,params){
if (!$cookie.getAuthUser()){
$FP.redirectTo({
controller:"Auth",
action:"login"
})
//unnecessary, becasue redirecTo halts processing, but good practice
return false; //cancels action
}
}
function _audit(controller,action,params){
Myna.log(
"audit",
controller.name + "." + action,
Myna.dump({
params:params,
user:$cookie.getAuthUser()
})
);
}
function _pdfView(controller, action, params){
if (params.format =="pdf"){
// calling render or renderContent prevents default view from rendering
this.renderContent(
Myna.xmlToPdf(
Myna.htmlToXhtml(
$res.clear()
)
),
"application/pdf",
params.controller + "." + params.action + ".pdf"
);
}
}
Controller.prototype.applyBehavior = function applyBehavior( name, options )
loads a behavior and applies it to this object
| name | The ProperCased name of the behavior to load, or an array of these names, |
| options | Optional, defaul null JS Object containing options to pass to the behavior’s init function |
Loads a behavior by name from app/behaviors/controllers/ or framework/behaviors/controllers/, whichever is found first. Behaviors are functions that are applied to the current object. applyBehavior should be called from init
//in some_controller.sjs
function init(){
// loads the built-in PDF andJSON filters
// Each of these has an "init" function that adds the appropriate
// before and after filters
this.applyBehavior([
"FormatPdf",
"FormatJson"
]);
//loads a single behavior with options
this.applyBehavior("MynaAuth",{
whitelist:[
"Main.index",
"Main.logout"
],
redirectParams:{
message: "Enter your AD domain credentials",
providers:["ldap_ad"]
}
});
}
Controller.prototype.getFilters = function addFilter( event, action )
returns the filter array for the requested event and action.
| event | event name |
| action | action name |
Controller.prototype.getActions = function getActions()
returns an array of objects for each action that contain the action name and action function.
| action | String action name |
| handler | Function reference to action function |
Controller.prototype.callAction = function callAction( action, params )
Call an action as an inline function.
| action | action name to execute |
| params | Optional, default {} params to pass to the action |
Calling this function on a controller will execute the specified action “inline”, meaning without generating output, and will return whatever the action returns. Before and after action filters are also triggered. This can be useful for marshaling several actions in one request, or for providing alternate ways of calling actions (see <framework.controllers.DirectController>)
//triggers filters, so will fail if cur user is not authorized
var empList = $FP.getController("employee").callAction("list",params);
You can also call actions directly from a controller, but filters won’t be triggered.
// does not run filters, but might render output if render() or
// renderContent() is called inside this action
var empList = $FP.getController("employee").list(params);
Controller.prototype.include = function include( action, params )
Call an action and includes its output in the current request
| action | action name to execute |
| params | Optional, default {} params to pass to the action |
Calling this function on a controller will execute the specified action, appending any output generated to the current request. This will also return any response from the action. Before and after action filters are also triggered. This can be useful for including several actions in one request.
// triggers filters, so will fail if cur user is not authorized
// includes employee listing table in the current view
$FP.getController("employee").callAction("list",params);
Controller.prototype.init = function init( controllerName )
Initializes a controller instance
Whenever a controller is instantiated, init() is run to configure the instance. First the init() in the controller base class is run, then the init() in app/controllers/global.sjs, and finally the init() in app/controllers/<name>_controller.sjs
This is a good place to call this.applyBehavior, this.addFilter and this.add/setLayout
Controller.prototype.render = function render( options )
renders a view
| options | Optional String or Object or null. If a string, the filename of the view in app/views to load, minus the extension. If this is null, then rendering is canceled. If this is an object or undefined, see Options below: |
| controller | Optional, default current controller ProperCased controller folder within app/views to reference. This will be converted to file format automatically |
| action | Optional, default current action camelCased action name. This will be converted to file format automatically |
This function is called automatically with default options whenever an action function returns. This function can be called explicitly from inside an action function to override this default behavior
When rendering a view, all previously generated content is cleared, and the view is included. Once the view is rendered, it is wrapped in any defined layouts (see <addLayout>) The following variables are available in view scope, and can be directly referenced:
| $controller | A reference to the controller that called this view |
| $model | A reference to this controllers default model, may be null |
| $params | A reference to the params of the most recent action |
| $page | a reference to this controller’s $page property |
| getElement | a reference to getElement |
| [data properties] | all of this controller’s data properties are available as global variables |
| [$FP.helpers classes] | ”Html” refers to $FP.helpers.Html, etc |
//in app/controllers/person_controller.sjs
function doStuff(params){
//bulk sets/replaces this.data
this.set({
firstName:"Bob",
lastName:"Dobb"
})
//sets an individual property
this.set("bean",this.Person.get({id:params.id}))
// implied render, same as
// this.render({controller:this.name,action:"doStuff"})
}
//in app/views/person/person_do_stuff.ejs
<%=getElement("form_wrap",{title:"Edit Employee"})%>
<form action="<%=Html.url({action:"save",id:bean.id})%>" method="post">
<table width="100%" height="1" cellpadding="0" cellspacing="0" border="0" >
<tr>
<th class="">ID:</th><td><%=bean.id%><td>
</tr>
<tr>
<th><%=bean.getLabel("first_name")%>:</th>
<td><input name="first_name" value="<%=String(bean.data.first_name||"").escapeHtml()%>"><td>
</tr>
<tr>
<th><%=bean.getLabel("last_name")%>:</th>
<td><input name="last_name" value="<%=String(bean.data.last_name||"").escapeHtml()%>"><td>
</tr>
<tr>
<td colspan="2" align="right">
<button type="submit">Save</button>
</td>
</tr>
</table>
</form>
<%=getElement("form_wrap",{type:"end"})%>
//in app/elements/form_wrap.ejs
<@if type == "end">
</div>
<@else>
<div class="form_wrap">
<div class="caption"><%=title||""%></div>
</@if>
//in app/elements/form_footer.ejs
</div>
Controller.prototype.renderContent = function renderBinary( data, contentType, filename )
Directly render content, bypassing default render
| data | String content, or binary data from Myna.File.readBinary or from binary database query (byte []) |
| contentType | Optional, default text/html for strings, application/octet-stream for binary MIME type of data. If “” or null, the default will be used |
| filename | Optional, default null if defined, a “Content-disposition” response header is set to present the standard “Save or Open?” dialog to the client. Use this if offering a file for download, but not if you expect the content to be rendered inline |
Controller.prototype.getElement = function renderElement( element, options )
renders an HTML template returns the content as a string
| element | name of an element template, should map to app/views/elements/element (can be a path like “common/header.ejs”) app/views/elements/<current controller name>/element.ejs, or app/views/elements/element.ejs, |
| options | any extra global properties to pass to the template |
| $controller | A reference to the controller that called this element |
| $model | A reference to this controllers default model, may be null |
| $params | A reference to the params of the most recent action |
| $page | a reference to this controller’s $page property |
| [data properties] | all of this controller’s data properties are available as global variables |
| [options properties] | all options properties are available as global variables |
| [$FP.helpers classes] | ”Html” refers to $FP.helpers.Html, etc |
This function is normally used inside view templates. See render for example usage
//in app/views/test/index.ejs
<%=getElement("shared/common_commonlinks.ejs")%>
<%=getElement()%>
Controller.prototype.set = function set( prop, val )
Sets a property on data
| prop | Either String property name to set, or JS object containing key/value pairs. This will overwrite same-named properties but will preserver any other existing properties |
| val | value to set |
All top level properties of data become global variables in views and elements.
Returns val
function myAction(params){
//set single property
this.set("firstName","Bob")
//set multiple properties, preserves "firstName" set earlier
this.set({
lastName:"Dobb",
age:56
})
//you can also directly manipulate the data object
this.data.position="Slackmaster"
}adds a layout layer to this this request
Controller.prototype.addLayout = function addLayout( layout )
Sets a single layout layer to this this request, clearing any existing layouts
Controller.prototype.setLayout = function setLayout( layout )
Adds an action filter
Controller.prototype.addFilter = function addFilter( filter, options )
loads a behavior and applies it to this object
Controller.prototype.applyBehavior = function applyBehavior( name, options )
returns the filter array for the requested event and action.
Controller.prototype.getFilters = function addFilter( event, action )
returns an array of objects for each action that contain the action name and action function.
Controller.prototype.getActions = function getActions()
Call an action as an inline function.
Controller.prototype.callAction = function callAction( action, params )
Call an action and includes its output in the current request
Controller.prototype.include = function include( action, params )
Initializes a controller instance
Controller.prototype.init = function init( controllerName )
renders a view
Controller.prototype.render = function render( options )
Directly render content, bypassing default render
Controller.prototype.renderContent = function renderBinary( data, contentType, filename )
renders an HTML template returns the content as a string
Controller.prototype.getElement = function renderElement( element, options )
Sets a property on data
Controller.prototype.set = function set( prop, val )
factory function to find a a controller by name, instantiate it, initialize it, and return it.
function getController( controllerName )
Clears response buffer and returns the previous contents.
clear:function ()
Returns current response buffer contents.
getContent:function ()
Appends content to the response buffer.
print:function ( text )
returns the contents of this file as a java byte[] array
Myna.File.prototype.readBinary = function()
sends binary data to the browser
printBinary:function( data, contentType, filename )