Ajax call get Route error after upgrade FLOW 2.2.3 to 2.3.6

Hi,

After I upgrade FLOW from 2.2.3 to 2.3.6, I have a problem once I click on widget pagination http://local.dev/?currentPage=2&%40action=retrieveData&__widgetId=1

But if I set ajax false it works.

<f:widget.link action="retrieveData" ajax="FASLE" class="ajax-link" arguments="{currentPage: page.number}">{page.number}</f:widget.link>

Ajax call

$('.ajax-grid').on('click', '.ajax-link', function(event) {
  event.preventDefault();
  var $clickedLink = $(this);
  $.ajax({
    'url': $clickedLink.attr('href'),
    'context': $clickedLink
  }).done(function(html) {
  this.closest('.ajax-container').html(html);
  });

  return false;
});

Anyone have any hint?

Thank you,
Rayuth

So you’re trying to use the default Fluid pagination widget via AJAX, correct?
I wonder how this could have worked with 2.2.3, because widgets must be configured to support AJAX and the pagination widget isn’t.

Could you please provide some more details on what you’re actually trying to achieve?
Also the log entry for that exception would be helpful (in Data/Logs/System_Development.log)

Hi,

I don’t use default Fluid pagination widget but customize own pagination. I have a list of links

<f:link.action controller="Event" action="show" arguments="{event: event._id}" class="link" subpackage="" useParentRequest="{isMainRequest}">{event._source.title}</f:link.action>

And then render a pagination and call via AJAX

<f:widget.link action="retrieveData" ajax="TRUE" class="ajax-link" arguments="{currentPage: page.number}">{page.number}</f:widget.link>

Here is error log:

15-08-13 15:45:35 5858       WARNING   Flow                 Router resolve(): Could not resolve a route for building an URI for the given route values.
event => 059ad6ad-6733-11e4-925d-005056010c5f
@action => show
@controller => event
@package => event.management
@subpackage => 

15-08-13 15:45:35 5858       CRITICAL  Fluid                Uncaught exception #1301610453: Could not resolve a route and its corresponding URI for the given parameters. This may be due to referring to a not existing package / controller / action while building a link or URI. Refer to log and check the backtrace for more details. - See also: 201508131545341eae33.txt
    previousException => Uncaught exception #1301610453 in line 354 of /home/013-049/public_html/Data/Temporary/Development/Cache/Code/Flow_Object_Classes/TYPO3_Flow_Mvc_Routing_UriBuilder.php: Could not resolve a route and its corresponding URI for the given parameters. This may be due to referring to a not existing package / controller / action while building a link or URI. Refer to log and check the backtrace for more details.

Thank you,
Rayuth

Can you share the code of your custom widget, too?

Here my custom widget

<f:if condition="{pagination.numberOfPages} > 1">
<div class="page-navigation">
	<ul class="typo3-widget-paginator pagination pagination-sm">
		<f:if condition="{pagination.previousPage}">
			<li class="previous">
				<f:if condition="{pagination.previousPage} > 1">
					<f:then>
						<f:widget.link action="retrieveData" ajax="TRUE" class="ajax-link" arguments="{currentPage: pagination.previousPage}">previous
						</f:widget.link>
					</f:then>
					<f:else>
						<f:widget.link action="retrieveData" ajax="TRUE" class="ajax-link">previous</f:widget.link>
					</f:else>
				</f:if>
			</li>
		</f:if>
		<f:if condition="{pagination.displayRangeStart} > 1">
			<li class="first">
				<f:widget.link action="retrieveData" ajax="TRUE" class="ajax-link">1</f:widget.link>
			</li>
		</f:if>
		<f:if condition="{pagination.hasLessPages}">
			<li>
				<f:widget.link action="retrieveData" ajax="TRUE" class="ajax-link" arguments="{currentPage: pagination.linkPageForLeftDot}">...</f:widget.link>
			</li>
		</f:if>
		<f:for each="{pagination.pages}" as="page">
			<li{f:if(condition: page.isCurrent, then: ' class="active"')}>
				<f:if condition="{page.number} > 1">
					<f:then>
						<f:widget.link action="retrieveData" ajax="TRUE" class="ajax-link" arguments="{currentPage: page.number}">{page.number}
						</f:widget.link>
					</f:then>
					<f:else>
						<f:widget.link action="retrieveData" ajax="TRUE" class="ajax-link">{page.number}</f:widget.link>
					</f:else>
				</f:if>
			</li>
		</f:for>
		<f:if condition="{pagination.hasMorePages}">
			<li>
				<f:widget.link action="retrieveData" ajax="TRUE" class="ajax-link" arguments="{currentPage: pagination.linkPageForRightDot}">...</f:widget.link>
			</li>
		</f:if>
		<f:if condition="{pagination.displayRangeEnd} < {pagination.numberOfPages}">
			<li class="last{f:if(condition: pagination.nextPage, else: ' active')}">
				<f:widget.link action="retrieveData" ajax="TRUE" class="ajax-link" arguments="{currentPage: pagination.numberOfPages}">
					{pagination.numberOfPages}
				</f:widget.link>
			</li>
		</f:if>
		<f:if condition="{pagination.nextPage}">
			<li class="next">
				<f:widget.link action="retrieveData" ajax="TRUE" class="ajax-link" arguments="{currentPage: pagination.nextPage}">next</f:widget.link>
			</li>
		</f:if>
	</ul>
</div>

</f:if>

Sorry for the confusion. I meant the PHP code that implements the widget (the ViewHelper and Controller classes).
In the ViewHelper you need to set protected $ajaxWidget = TRUE; for example

In Controller:

public function indexAction($searchOptions = array()) {

	// override paginateConfiguration by search option
	if (!empty($searchOptions['itemsPerPage'])) {
		$this->paginateConfiguration['itemsPerPage'] = $searchOptions['itemsPerPage'];
	} else {
		$searchOptions['itemsPerPage'] = $this->paginateConfiguration['itemsPerPage'];
	}

	$storedSearchOptions = $this->session->getData('gridWidget_' . $this->getWidgetContext()->getAjaxWidgetIdentifier() . '_searchOptions');
	if (!is_array($storedSearchOptions)) {
		$storedSearchOptions = array();
	}
	$searchOptions = Arrays::arrayMergeRecursiveOverrule($storedSearchOptions, $searchOptions);
	$this->session->putData('gridWidget_' . $this->getWidgetContext()->getAjaxWidgetIdentifier() . '_searchOptions', $searchOptions);
	$this->applySearchOptions($this->queryBuilder, $searchOptions);

	$this->resultCount = $this->queryBuilder->count();
	$pagination = $this->buildPagination(1);
	$resultCounts = $this->buildResultCount($pagination);

	$itemsPerPage = (integer)$this->paginateConfiguration['itemsPerPage'];
	$this->queryBuilder->limit($itemsPerPage);

	// sorting data
	$this->setQuerySorting('', '');

	$elements = $this->queryBuilder->execute();

	$this->view->assign('isMainRequest', TRUE);
	$this->view->assign('elements', $elements);
	$this->view->assign('searchOptions', $searchOptions);
	$this->view->assign('pagination', $pagination);
	$this->view->assign('eventTypes', $this->securityContext->getParty()->getEventTypesByUser());
	$this->view->assign('eventStatuses', $this->eventStatusRepository->findAll());
	$this->view->assign('venues', $this->venueRepository->findAll());
	$this->view->assign('resultCounts', $resultCounts);
}

and function build pagination:

protected function buildPagination($currentPage) {
	$this->calculateDisplayRange($currentPage);
	$this->currentPage = (integer)$currentPage;
	if ($this->currentPage < 1) {
		$this->currentPage = 1;
	} elseif ($this->currentPage > $this->numberOfPages) {
		$this->currentPage = $this->numberOfPages;
	}

	$pages = array();
	for ($i = $this->displayRangeStart; $i <= $this->displayRangeEnd; $i++) {
		$pages[] = array('number' => $i, 'isCurrent' => ($i === $this->currentPage));
	}
	$pagination = array(
		'pages' => $pages,
		'current' => $this->currentPage,
		'numberOfPages' => $this->numberOfPages,
		'displayRangeStart' => $this->displayRangeStart,
		'displayRangeEnd' => $this->displayRangeEnd,
		'hasLessPages' => $this->displayRangeStart > 2,
		'hasMorePages' => $this->displayRangeEnd + 1 < $this->numberOfPages,
		'linkPageForRightDot' => (integer)(($this->numberOfPages + $this->currentPage) / 2),
		'linkPageForLeftDot' => (integer)($this->currentPage / 2)
	);
	if ($this->currentPage < $this->numberOfPages) {
		$pagination['nextPage'] = $this->currentPage + 1;
	}
	if ($this->currentPage > 1) {
		$pagination['previousPage'] = $this->currentPage - 1;
	}
	return $pagination;
}

AJAX calls function:

public function retrieveDataAction($currentPage = 1, $sortingField = '', $sortingValue = '') {
	$searchOptions = $this->session->getData('gridWidget_' . $this->getWidgetContext()->getAjaxWidgetIdentifier() . '_searchOptions');
	// override paginateConfiguration by search option
	if (!empty($searchOptions['itemsPerPage'])) {
		$this->paginateConfiguration['itemsPerPage'] = $searchOptions['itemsPerPage'];
	}

	$this->applySearchOptions($this->queryBuilder, $searchOptions);

	$this->resultCount = $this->queryBuilder->count();
	$pagination = $this->buildPagination($currentPage);

	$itemsPerPage = (integer)$this->paginateConfiguration['itemsPerPage'];
	$this->queryBuilder->limit($itemsPerPage);

	// sorting data
	$this->setQuerySorting($sortingField, $sortingValue);

	$resultCounts = $this->buildResultCount($pagination);

	if ($this->currentPage > 1) {
		$this->queryBuilder->from((integer)($itemsPerPage * ($this->currentPage - 1)));
	}

	$this->view->assign('configuration', $this->paginateConfiguration);
	$this->view->assign('isMainRequest', FALSE);
	$this->view->assign('pagination', $pagination);
	$this->view->assign('resultCounts', $resultCounts);

	$elements = $this->queryBuilder->execute();
	$this->view->assign('elements', $elements);
}

If you use the f:widget.link ViewHelper with ajax="TRUE" Fluid won’t actually use the UriBuilder to create the URL, it will just create a string like “?__widgetId=xyz” or “?__widgetContext=xyz”. So the error above is probably triggered by retrieveData Template and, yes, probably by this VH that can’t be resolved:

<f:link.action controller="Event" action="show" arguments="{event: event._id}" class="link" subpackage="" useParentRequest="{isMainRequest}">{event._source.title}</f:link.action>

Are you sure you have a Route that resolves the values

event => 059ad6ad-6733-11e4-925d-005056010c5f
@action => show
@controller => Event
@package => Event.Management

?

and if so, try to omit the subpackage="" or replace it with subpackage="{null}"

Actually I do not configure Route for show event and also for other action. So what I need to now is configure route for event and set subpackage="{null}" ?

the subpackage argument defaults to NULL, so you can just omit it, but it should also work by setting it to an empty string as you did actually.
As to the Route: if the useParentRequest argument is set and the parent request is the main ActionRequest (which it would be by default), you do need a Route for it of course. Are you sure the exact same code worked with 2.2.3 ?
Maybe it’s easier to discuss this in the #flow-general package on slack

FYI a little update on this one: Our on-site task force (aka @aertmann) is currently in Cambodia looking at the issue up close. I think we found the change that broke this and will be able to fix it (btw: I misread the title and thought this was about a patch-level update only)

Confirmed it’s https://jira.neos.io/browse/FLOW-205 and the fix applies cleanly. Re-opened the issue so we can fix it for the next patch level release 2.3.7, but currently we have to wait for the Github move before we can do that.

@bwaidelich and @aertmann thank you for your help and advice :smile: