This plugin enables Ajaxified Webflows

  • Tags: /
  • Latest: 0.2.4
  • Last Updated: 25 July 2012
  • Grails version: 1.3.4 > *
1 vote
Dependency:
compile ":ajaxflow:0.2.4"

 Documentation  Source  Issues

Summary

This plugin enables Ajaxified Webflows and is able to inject working customizable ajax webflows into your existing project(s)

Installation

Installation

In order to add ajaxflow to your project, install the plugin as follows:

grails install-plugin ajaxflow

The plugin also provides a command to easily set up a new ajaxflow controller and templates in your project. To quickly set up a working ajaxflow to customize execute the following command:

grails create-ajaxflow [package name] [ajaxflow name]

Where the package name is the location your controller will be created and ajaxflow name is the name of your ajaxflow. For example, you could issue the following command:

grails create-ajaxflow com.my.cool.project test

This would generate a testController.groovy in grails-app/controllers/com/my/cool/project, views in grails-app/views/test, css in web-app/css/test.css and images in web-app/images/test, and a functionaly ajaxflow which you can then start customizing.

This plugin heavily relies on webflow and jquery

Quick guide for setting up a new project with an initial ajaxflow

jeroen@mbp ~/Workspace/grails $ grails create-app myApp
…
jeroen@mbp ~/Workspace/grails $ cd myApp/
jeroen@mbp ~/Workspace/grails/myApp $ grails install-plugin ajaxflow
…
jeroen@mbp ~/Workspace/grails/myApp $ grails install-plugin webflow
…
jeroen@mbp ~/Workspace/grails/myApp $ grails create-ajaxflow com.my.cool.project test
…
jeroen@mbp ~/Workspace/grails/myApp $ grails run-app
…
Running Grails application..
Server running. Browse to http://localhost:8080/myApp

Browse to http://localhost:8080/myApp/test to see the newly created application and ajaxflow:

Description

This plugin enables ajaxified webflows and extends the webflow functionality to allow rendering of partials. In general you can define the ajaxflow definition like any other regular webflow definition (see the webflow documentation), but on the client-side (the view) everything will work through Ajax requests rendering partials (or pages). This plugin specifically aims at making wizard-like webflows easy to accomplish.

To quickly get started without diving too deep into the tags below, you can just generate a new ajaxflow within your project using the following command:

grails create-ajaxflow my.package.name wizard

This will create a ajaxflow called 'wizard' which is working and ready for customization at http://localhost:8080/myProject/wizard The command will set up an initial five page ajaxflow containing an initial controller, commons views, partial views, images and css.

It's easiest to first perform a svn update command before injecting a new ajaxflow as it allows you to easily remove the ajaxflow by reverting your project.

If you intend to create more than one ajaxflow in your project it's probably best to group images and css together. Currently every new ajaxflow will have it's own images and css. Grouping them can be easily done by
  • renaming the css to a generic name (e.g. web-app/css/ajaxflow.css)
  • moving the images into a generic folder (e.g. web-app/images/ajaxflow/*)
  • changing the image references in the css to the new image directory
  • changing the css include in the index.gsp view

Differences

Ajaxflows and rendering partials is different from generic webflows because the browser will not render full HTML pages but will only render small sections within the page. This means that when you want to bind handlers to DOM element, you need to do that every time a partial page is rendered and -hence- after every ajax request. This can be done by using the afterSuccess parameter for af:triggerEvent , af:ajaxSubmitJs , af:ajaxButton or af:navigation tags. When not specified, it will always default to onPage(); so make sure that Javascript function is available.

Example JS:

<script type="text/javascript">
// bind handlers on document ready
$(document).ready(function() {
    doSomethingImportant();
});

// do some really important stuff like bind DOM event handlers function doSomethingImportant() { console.log('i am so important');

// initialize jquery-ui accordeon(s) $("#accordion").accordion({autoHeight: false}); }

// define the onPage function to be used in the ajaxflows function onPage() { console.log('i am called after each ajax call'); doSomethingImportant(); } </script>

The onPage() will be called on every ajax call (except if you defined another function call using the afterSuccess parameter). Also see the common/_on_page.gsp view in the generated ajaxflow, if you used the grails create-ajaxflow command.

The af:flow tag

This tag is used to set up an ajaxflow.

<af:flow name="test" class="ajaxFlow" commons="common" partials="pages" controller="[controller: 'test', action: 'pages']">
	<af:triggerEvent name="next" afterSuccess="onPage();" />
</af:flow>

The initial ajaxflow template only renders the af:triggerEvent tag (see below). This is done because when using ajax the partials / pages need to be rendered using an ajax request. While the initial page could be rendered when starting the ajaxflow, this would mean that the webflow definition should contain duplicate logic: one for setting up the webflow, and one for the 'first partial'. This is needed because one cannot browse back to the initial page as that is not a partial page, but a complete page. To easily solve this issue the af:triggerEvent just triggers a next event in the webflow specification to render the initial page / partial.

parametertypedescriptionexampleoptionaldefault
nameStringthe name of the ajaxflowtestyeswizard
classStringthe class parameter given to the ajaxflow formajaxFlowyes-
commonsStringthe relative path where the common views are storedcommonrequiredn/a
partialsStringthe relative path where the partial (=page) views are storedpagesrequiredn/a
controllerMapthe controller and webflow definition[controller: 'test', action: 'pages']yes[controller: ajaxFlowName, action: 'pages']

The af:page tag

The af:page tag is used to encapsulate a partial / page view and does nothing more than render a page header, the page content and a page footer. It expects the following views to be available:

common/_page_header.gsp
common/_page_footer.gsp

Where the common path was specified by the commons parameter of the af:flow tag. Things one would expect in the page header and footer are for example:

  • tabs / breadcrumbs
  • navigation
  • generic error handling
  • etcetera
Example:
<af:page>
<h1>Page one of the '<i>test</i>' ajax flow</h1>
<p>
	Lorem ipsum dolor sit amet…
</p>
</af:page>

This tag takes no parameters

The af:error tag

The af:error tag is designed to be used together with the af:flow tag. Where the af:flow tag is used to set up the ajaxflow and will contain the partials / pages, the af:error tag will render a div element which will be updated with any ajax errors that occur. Generally this could be removed in a production environment but it may be wise or handy to keep this visible while in development.

Simple example:

<af:error class="ajaxFlowError">
	[ajax errors go in here, normally it's safe to delete the af:error part]
</af:error>

Complete example:

<div id="ajaxflow">
<af:flow name="test" class="ajaxFlow" commons="common" partials="pages" controller="[controller: 'test', action: 'pages']">
	<af:triggerEvent name="next" afterSuccess="onPage();" />
</af:flow>
<g:if env="development">
<af:error class="ajaxFlowError">
	[ajax errors go in here, normally it's safe to delete the af:error part]
</af:error>
</g:if>
</div>

parametertypedescriptionexampleoptionaldefault
classStringthe class parameter given to the error divajaxFlowErrornothe name of the ajaxFlow + Error appended

The af:tabs tag

The af:tabs tag will dynamically render a navigational element based on a map containing all pages in the ajaxflow, and the current page. This is easily accomplished by setting up the following two variables in the flowscope:

/**
	 * WebFlow definition
	 * @void
	 */
	def pagesFlow = {
		// start the flow
		onStart {
			// define variables in the flow scope which is availabe
			// throughout the complete webflow also have a look at
			// the Flow Scopes section on http://www.grails.org/WebFlow
			//
			// The following flow scope variables are used to generate
			// wizard tabs. Also see common/_tabs.gsp for more information
			flow.page = 0
			flow.pages = [
				[title: 'Page One'],
				[title: 'Page Two'],
				[title: 'Page Three'],
				[title: 'Page Four'],
				[title: 'Done']
			]
			...

success() } … }

You could then update the page variable in every page / partial:

// second wizard page
		pageTwo {
			render(view: "_page_two")
			onRender {
				flow.page = 2
				success()
			}
			on("next").to "pageThree"
			on("previous").to "pageOne"
		}

And use the following generic code to render the tabs:

<af:tabs pages="${pages}" page="${page}" clickable="${true}" />

The clickable="true" parameter makes the tabs / breadcrumbs clickable an result in triggering events in your webflow definition like toPageOne, toPageTwo, toPageThree, etc… However, enabling the clickable parameter would mean every part of your webflow should be able to handle jumps to other pages / partials and this might not always be possible (pages relying on input on a previous page).

In the quick setup (grails create-ajaxflow my.package name) the tabs will look like this:

The af:ajaxButton tag

As regular buttons are not possible from within an ajaxflow, the af:ajaxButton is the replacement tag to create buttons. They work similar to buttons in a normal webflow in the way that they also trigger a particular event in an ajaxflow's webflow definition.

<af:ajaxButton name="toPageFour" value="to page 4!" afterSuccess="onPage();" class="prevnext" />

Will render a button which will trigger the toPageFour event in the webflow definition:

// second wizard page
		pageTwo {
			render(view: "_page_two")
			onRender {
				flow.page = 2
				success()
			}
			on("next").to "pageThree"
			on("previous").to "pageOne"
			on("toPageFour").to "pageFour"
		}

As of version 0.1.18 it is also possible to pass an id with the button by adding the 'id' parameter:

<af:ajaxButton name="toPageFour" value="to page 4!" id="1234" afterSuccess="onPage();" class="prevnext" />

The id will be received by the controller, and can be used in your controller logic.

parametertypedescriptionexampleoptionaldefault
namestringthe button name and the event to call"next"required-
valuestringthe label of the button"Next"required-
classstringthe css class the rendered buttons should have"prevnext"yes-
afterSuccessstringthe JS to execute after a successful ajax call"doSomething();"yes"onPage()"
srcstringan image url to use for button../images/button.gifyes-
altstringthe alt tag to use with the button image"my alt text"yes-
idmixedsome data to pass to the controller${id}yes-

The af:navigation tag

The af:navigation tag will render a set of ajaxButtons resulting into diferent events in the webflow definition. The map contains a number of buttons, button labels, events to trigger and wether or not to show the particular button ( _show_: boolean). The map should look as follows:
[
  event1: [
    label: 'The button label',
    show: true/false
  ],
  …
  eventN: [
    label: 'The button label',
    show: true/false
  ]
]

Example:

<g:set var="showPrevious" value="${page>1 && page<pages.size}"/>
<g:set var="showNext" value="${page<pages.size}"/>
<af:navigation events="[previous:[label:'&laquo; prev',show: showPrevious], next:[label:'next &raquo;', show:showNext]]" separator="&nbsp; | &nbsp;" class="prevnext" />

parametertypedescriptionexampleoptionaldefault
eventsmapthe navigational buttons to generate[next:[label:'next &raquo;', show:showNext]]requiredn/a
seperatorstringthe seperator between the buttons"&nbsp; | &nbsp;"-
classstringthe css class the rendered buttons should have"prevnext"yes-
afterSuccessstringthe JS to execute after a successful ajax call"doSomething();"yes"onPage()"

The af:ajaxSubmitJs tag

The af:ajaxSubmitJs tag is actually an ajaxButton without button. It results in pure Javascript which you can use to create your own custom triggers. For example it is possible to bind an onChange handler to select elements automatically performing an ajax submit, or -how I use it in a project- trigger a refresh event in the webflow definition upon closing an opened jquery-ui dialog.

<script type="text/javascript">
function refreshFlow() {
	<af:ajaxSubmitJs name="refresh" afterSuccess="onPage()" />
}
</script>

See the ajaxButton tag for more information on the supported parameters.

The af:triggerEvent tag

The af:triggerEvent tag is basically a wrapper for the af:ajaxSubmit tag to immediately trigger the specific event in the webflow definition. This tag is mainly used when setting up a webflow to trigger an ajax event to load the initial partial page.

<div id="ajaxflow">
<af:flow name="test" class="ajaxFlow" commons="common" partials="pages" controller="[controller: 'test', action: 'pages']">
	<%	/**
	 	 * The initial rendering of this template will result
	 	 * in automatically triggering the 'next' event in
	 	 * the webflow. This is required to render the initial
	 	 * page / partial and done by using af:triggerEvent
		 */ %>
	<af:triggerEvent name="next" afterSuccess="onPage();" />
</af:flow>
<g:if env="development">
<af:error class="ajaxFlowError">
	[ajax errors go in here, normally it's safe to delete the af:error part]
</af:error>
</g:if>
</div>
<g:render template="common/on_page"/>