If this tutorial is helpful to you, you can donate some money to the tutorial author via Paypal.

This ajax is one way how it can be done, since I'm new to FLOW3 there might be other ways ;)

This is the fluid tag that we want in the end:

<da:ajax object="{transaction}" property="desc" action="accounting/transaction/edit"/>
or with more options (extra arguments and changed var-name)
<da:ajax object="{transaction}" property="desc" arguments="{account: account}" action="accounting/transaction/edit" name="updateTransaction" /></td>

and it should translate to something like this:

<div>
  <span class="ajaxValue">Payment for rent</span>
  <input type="button" onclick="if($(this).attr('value') == 'Edit') {..." value="Edit">
</div>


Download jQuery and save it under Public/js in your package
Add jQuery to your view by adding

<script src="{f:uri.resource(path: 'js/jquery-1.7.2.min.js')}"></script>

to your layout in the head part (Private/Layouts/Default.html)
Create a new ViewHelper under Classes/ViewHelpers named AjaxViewHelper.php
Now the fun part open and edit your AjaxViewHelper.php

<?php
namespace Accounting\ViewHelpers;

use TYPO3\FLOW3\Annotations as FLOW3;

/**
 * AjaxViewHelper
 * 
 * = Examples =
 * {namespace da=Accounting\ViewHelpers}
 * <da:ajax object="{transaction}" property="desc" arguments="{account: account}" action="accounting/transaction/edit" />
 */

 class AjaxViewHelper extends \TYPO3\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper {

	/**
	 * @var \TYPO3\FLOW3\Persistence\PersistenceManagerInterface
	 */
	protected $persistenceManager;

	/**
	 * Injects the FLOW3 Persistence Manager
	 *
	 * @param \TYPO3\FLOW3\Persistence\PersistenceManagerInterface $persistenceManager
	 * @return void
	 */
	public function injectPersistenceManager(\TYPO3\FLOW3\Persistence\PersistenceManagerInterface $persistenceManager) {
		$this->persistenceManager = $persistenceManager;
	}

	/**
	 * To generate trustedProerties token
	 *
	 * @FLOW3\Inject
	 * @var \TYPO3\FLOW3\Mvc\Controller\MvcPropertyMappingConfigurationService
	 */
	protected $mvcPropertyMappingConfigurationService;

	/**
	 * @var string
	 */
	 protected $tagName = 'div';

This is the first part, we implement AbstractTagBasedViewHelper and inject the persistanceManager which will be used later on to get the identifier of our objects.

The mvcPropertyMappingConfigurationService is needed to generate the trustedProperties output which is mandatory since FLOW3 1.1 if you don't want configure the PropertyMapping in your controller (see here)

$tagName defines which tag is surrounding our output.

 /**
	  * ViewHelper that generates an ajax div
	  * 
	  * @param mixed $object Object of the property we want to modify
	  * @param string $property Property we want to modify
	  * @param string $action Path to controller ex.: package/controller/action
	  * @param array $arguments optional
	  * @param string $name optional
	  * @return string
	  */
	 public function render($object, $property, $action, $arguments = NULL, $name = NULL) {

		// Get objectidentifier
		$objectID = $this->persistenceManager->getIdentifierByObject($object);

		// Retrieve objectname from class or if set from $name
		if(is_null($name)) {
			$objectClassNameTemp = explode('\\', get_class($object));
			$objectClassName = strtolower(end($objectClassNameTemp));
		} else {
			$objectClassName = $name;
		}

For each attribute that you want to use in your fluid tag like property, action, ... a @param annotation is needed. The values of the attributes will be passed to the render-function.

First we get the object identifier of the object we want to edit, then we determine the classname of the object or set it according to $name. This will be the name of the parameter passed to our controller.

// Generate allowed propertymapping
$formFieldNames = array(
$objectClassName."[__identity]",
$objectClassName."[".$property."]");

$trustedProperties = $this->mvcPropertyMappingConfigurationService->generateTrustedPropertiesToken($formFieldNames);

// Value of property
$ajaxValue = \TYPO3\FLOW3\Reflection\ObjectAccess::getPropertyPath($object, $property);

We generate the trustedProperties string and retrieve the value of the object we want to edit later.

$jsCode="
	 	if($(this).attr('value') == 'Edit') {		
					$(this).attr('value','Update');
					text = $(this).parent().find('span.ajaxValue').text();
					// Create input field
					$(this).parent().find('span.ajaxValue').replaceWith($('<input>', {
						'class' : 'ajaxValue',
						'type' : 'text',
						'value' : text
					}));					 
				} else if($(this).attr('value') == 'Update'){
					$(this).attr('value','InProgress');
					newValue = $(this).parent().find('input.ajaxValue').val();
					current = $(this);
					// Make Ajaxrequest
					$.ajax({
						type: 'POST',
						url: '" . $action . "',
						data: {";
						// Add identities of arguments
						if(!is_null($arguments)) {
							foreach ($arguments as $key => $value) {
								$jsCode .= "'".$key."[__identity]' : '".$this->persistenceManager->getIdentifierByObject($value)."',";
							}
						}	
						// Object identity + value of object 
						$jsCode .= "'" . $objectClassName."[__identity]' : '" . $objectID . "',";
						$jsCode .= "'" . $objectClassName."[".$property."]' : newValue  ,";
						// Trusted properties
						$jsCode .= "'__trustedProperties' : '" . htmlspecialchars($trustedProperties) . "'";
						$jsCode .= "
						}
					}).complete(function(jqXHR,msg) {
						if(msg == 'SUCCESS' || msg == 'success') {
							current.parent().find('input.ajaxValue').replaceWith($('<span/>', {
								'class' : 'ajaxValue',
								text : newValue
							}));
							current.attr('value','Edit');
						} else {
							alert('ERROR: ' + msg);
						}
					});
				} else {
					alert('Please wait...');
				}
	 	";

This is the javascript code that will be added to the onClick event of our edit-button. It sends an ajax-request to our controller ($action) with:

  • the identifier of each object in our arguments
  • identifier of the object we want to change
  • the new property value
  • the trustedProperties string
		// Add code and render
	 	$this->tag->setContent("<span class=\"ajaxValue\">" .$ajaxValue . "</span>
	 							<input type=\"button\" value=\"Edit\" OnClick=\"". $jsCode ."\">");

		return $this->tag->render();
	 }
 }

Finally we put everything together, print the current value and the edit-button with our jscode in the OnClick-attribute.

How does the controller look like if we use this tag?

	{namespace da=Accounting\ViewHelpers}

<da:ajax object="{transaction}" property="desc" arguments="{account: account, secondattribute: other object}" action="Accounting/transaction/edit" name="editedTransaction" />

{namespace da=Drinkaccounting\ViewHelpers} needs to be added so fluid knows which namespace to use for the da-tag.

 

Here's the controller Accounting\Controller\TransactionController.php:

       /**
	 * Edits a transaction
	 *
	 * @param \Accounting\Domain\Model\Account $account The account which will contain the new transaction
	 * @param \Accounting\Domain\Model\OtherModel $secondattribute The account which will contain the new transaction
	 * @param  \Accounting\Domain\Model\Transaction $editedTransaction The edited transaction object
	 * @return void
	 */
	public function editAction(\Accounting\Domain\Model\Account $account, \Accounting\Domain\Model\OtherModel $secondattribute, \Accounting\Domain\Model\Transaction $editedTransaction) {
		$transactions = $account->getTransactions();
		// get the index of the unchanged transaction and replace it with the edited one
		$transactions->set($account->getTransactions()->indexOf($editedTransaction), $editedTransaction);
		$account->setTransactions($transactions);
		$this->accountRepository->update($account);

		return "SUCCESS";

	}

The second attribute is completely useless, I just wanted to demonstrate how it works. First parameters are the arguments, after that our edited object. The name of the object is the one specified with the attribute name or if name was not set the name of the objects model (it would be $transaction but we specified name="editedTransaction" so it is $editedTransaction).

And this is how it looks like in the end:
View:

...
	{namespace da=Accounting\ViewHelpers}
...
<f:for each="{account.transactions}" as="transaction" iteration="transactionIterator">
			<tr>
				<td>{f:format.date(date: transaction.date, format: 'Y-m-d')}</td>
				<td><f:format.currency currencySign="€" decimalSeparator="," thousandsSeparator=".">{transaction.sum}</f:format.currency></td>
				<td><f:format.currency currencySign="€" decimalSeparator="," thousandsSeparator=".">{transaction.balanceNew}</f:format.currency></td>
				<td><da:ajax object="{transaction}" property="desc" arguments="{account: account}" action="accounting/transaction/edit" /></td>
				<td></td>
			</tr>
		</f:for>
...

Controller:

/**
	 * Edits a transaction
	 *
	 * @param \Accounting\Domain\Model\Account $account The account which will contain the new transaction
	 * @param  \Accounting\Domain\Model\Transaction $transaction A fresh Transaction object which has not yet been added to the repository
	 * @return void
	 */
	public function editAction(\Accounting\Domain\Model\Account $account, \Accounting\Domain\Model\Transaction $transaction) {
		$transactions = $account->getTransactions();
		$transactions->set($account->getTransactions()->indexOf($transaction), $transaction);
		$account->setTransactions($transactions);
		$this->accountRepository->update($account);

		return "SUCCESS";

	}

Result in the browser:

Edit

Update

Result:


The post-request:

That's it. Feel free to comment on possible improvements or questions you have.

Here the complete ViewHelper:

<?php
namespace Accounting\ViewHelpers;

use TYPO3\FLOW3\Annotations as FLOW3;

/**
 * AjaxViewHelper
 * 
 * = Examples =
 * {namespace da=Accounting\ViewHelpers}
 * <da:ajax object="{transaction}" property="desc" arguments="{account: account}" action="accounting/transaction/edit" />
 */

 class AjaxViewHelper extends \TYPO3\Fluid\Core\ViewHelper\AbstractTagBasedViewHelper {

	/**
	 * 
	 * @var \TYPO3\FLOW3\Persistence\PersistenceManagerInterface
	 */
	protected $persistenceManager;

	/**
	 * Injects the FLOW3 Persistence Manager
	 *
	 * @param \TYPO3\FLOW3\Persistence\PersistenceManagerInterface $persistenceManager
	 * @return void
	 */
	public function injectPersistenceManager(\TYPO3\FLOW3\Persistence\PersistenceManagerInterface $persistenceManager) {
		$this->persistenceManager = $persistenceManager;
	}

	/**
	 * To generate trustedProerties token
	 *
	 * @FLOW3\Inject
	 * @var \TYPO3\FLOW3\Mvc\Controller\MvcPropertyMappingConfigurationService
	 */
	protected $mvcPropertyMappingConfigurationService;

	/**
	 * @var string
	 */
	 protected $tagName = 'div';

	 /**
	  * ViewHelper that generates an ajax div
	  * 
	  * @param mixed $object Object of the property we want to modify
	  * @param string $property Property we want to modify
	  * @param string $action Path to controller ex.: package/controller/action
	  * @param array $arguments optional
	  * @param string $name optional
	  * @return string
	  */
	 public function render($object, $property, $action, $arguments = NULL, $name = NULL) {

		// Get objectidentifier
		$objectID = $this->persistenceManager->getIdentifierByObject($object);

		// Retrieve objectname from class or if set from $name
		if(is_null($name)) {
			$objectClassNameTemp = explode('\\', get_class($object));
			$objectClassName = strtolower(end($objectClassNameTemp));
		} else {
			$objectClassName = $name;
		}

		// Generate allowed propertymapping
		$formFieldNames = array(
	 		$objectClassName."[__identity]",
	 		$objectClassName."[".$property."]");

		$trustedProperties = $this->mvcPropertyMappingConfigurationService->generateTrustedPropertiesToken($formFieldNames);

		// Value of property
		$ajaxValue = \TYPO3\FLOW3\Reflection\ObjectAccess::getPropertyPath($object, $property);

	 	$jsCode="
	 	if($(this).attr('value') == 'Edit') {		
					$(this).attr('value','Update');
					text = $(this).parent().find('span.ajaxValue').text();
					// Create input field
					$(this).parent().find('span.ajaxValue').replaceWith($('<input>', {
						'class' : 'ajaxValue',
						'type' : 'text',
						'value' : text
					}));					 
				} else if($(this).attr('value') == 'Update'){
					$(this).attr('value','InProgress');
					newValue = $(this).parent().find('input.ajaxValue').val();
					current = $(this);
					// Make Ajaxrequest
					$.ajax({
						type: 'POST',
						url: '" . $action . "',
						data: {";
						// Add identities of arguments
						if(!is_null($arguments)) {
							foreach ($arguments as $key => $value) {
								$jsCode .= "'".$key."[__identity]' : '".$this->persistenceManager->getIdentifierByObject($value)."',";
							}
						}	
						// Object identity + value of object 
						$jsCode .= "'" . $objectClassName."[__identity]' : '" . $objectID . "',";
						$jsCode .= "'" . $objectClassName."[".$property."]' : newValue  ,";
						// Trusted properties
						$jsCode .= "'__trustedProperties' : '" . htmlspecialchars($trustedProperties) . "'";
						$jsCode .= "
						}
					}).complete(function(jqXHR,msg) {
						if(msg == 'SUCCESS' || msg == 'success') {
							current.parent().find('input.ajaxValue').replaceWith($('<span/>', {
								'class' : 'ajaxValue',
								text : newValue
							}));
							current.attr('value','Edit');
						} else {
							alert('ERROR: ' + msg);
						}
					});
				} else {
					alert('Please wait...');
				}
	 	"; 

		// Add code and render
	 	$this->tag->setContent("<span class=\"ajaxValue\">" .$ajaxValue . "</span>
	 							<input type=\"button\" value=\"Edit\" OnClick=\"". $jsCode ."\">");

		return $this->tag->render();
	 }
 }

This article is a copy of the one in my blog: http://blog.blubyte.de/2012/07/flow3-how-to-ajax-viewhelper-using-jquery/ 


Was this tutorial helpful? So, you can donate some money to the tutorial author via Paypal.