I am trying to going through the book "TYPO3 Extbase - Modern Extension Development" from P. Lobacher. There I am on the point with the searchfield. Here my Editor (PHPStorm) tells me that $search isn't defined.
I have a listAction with:
/**
* #internal param string $search
*/
public function listAction() {
if ($this->request->hasArgument('search')){
$search = $this->request->getArgument('search');
}
$limit = ($this->settings['blog']['max']) ?: NULL;
$this->view->assign('blogs', $this->blogRepository->findSearchForm($search,$limit));
$this->view->assign('search', $search);
}
In my repository i try to setup a query like that:
/**
* #param string $search
* #param int $limit
* #return array|\TYPO3\CMS\Extbase\Persistence\QueryResultInterface
*/
public function findSearchForm($search,$limit) {
$query = $this->createQuery();
$query->matching(
$query->like('title','%'.$search.'%')
);
$query->setOrderings(array('title' => \TYPO3\CMS\Extbase\Persistence\QueryInterface::ORDER_ASCENDING));
$limit = (int)$limit; if ($limit > 0) {
$query->setLimit($limit);
}
return $query->execute();
}
In my list view i have the searchform like that:
<f:form action="list" additionalAttributes="{role:'form'}"> <div class="form-inline">
<div class="form-group">
<f:form.textfield name="search" value="{search}" class="form-control" /> <f:form.submit value="Search!" class="btn-xs btn-primary" />
</div> </div>
</f:form>
If i change the assign to the view in my listAction to:
$this->view->assign('blogs', $this->blogRepository->findSearchForm('Testblog',$limit));
Then he shows me the blogs with the titlefragment "Testblog" in it. But dynamically it won't work.
I am using TYPO3 v8.5.1
Thx for your help.
at first you should change the $limit line because its hard to read. Change it to:
$limit = $this->settings['blog']['max'] ? $this->settings['blog']['max'] : 0;
I changed NULL to 0 because in the repository annotation the parameter should be an integer.
Then you should also change the two assign() functions to an assignMultiple()-Array.
Have you ever debugged $search in your controller using
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($search);
Because your is not bound to an object, you are may not able to get $search with getArgument(). Can you please give us the rendered HTML output of that form field?
EDIT: Because of your search you may not cache the list action. Change list action in ext_localconf.php to NOT CACHED.
Related
In fluid I have select form
<f:form action="cityList" controller="City">
<f:form.select
class="js-select"
property="city"
name="cityId"
options="{cityList}"
optionLabelField="title"
optionValueField="uid" />
<f:form.submit value="Submit" />
</f:form>
In controller
/**
* action city list
*
*
* #return void
*/
public function cityListAction()
{
$cityList = $this->cityRepository->findAll();
$this->view->assign('cityList', $cityList);
$cityData = $GLOBALS['TSFE']->fe_user->setKey('ses', 'citySessionData', $cityId);
$cityData = $GLOBALS["TSFE"]->fe_user->getKey('ses', 'citySessionData');
echo $cityData;
}
But I don't have any data. If I set manual $cityId, I have session data. How I can set city id from form to $cityId
Your is not bound to an object so the cannot use the propery property="city" correctly. Your listAction is also not expecting to get any parameters passed to it.
Please change the ViewHelpers accordingly:
<f:form action="cityList" controller="City" objectName="filter" object="{filter}">
<f:form.select
class="js-select"
property="cityId"
options="{cityList}"
optionLabelField="title"
optionValueField="uid" />
<f:form.submit value="Submit" />
</f:form>
In the HTML output, the select field should become a name like name="tx_yourext_yourplugin[filter][cityId]". This is very important because otherwise the form values wont be submitted to your action.
Then change your Action:
/**
* action city list
* #param array $filter
* #return void
*/
public function cityListAction($filter = [])
{
$cityList = $this->cityRepository->findAll();
$this->view->assign('cityList', $cityList);
// Give $filter back to view so it will stay selected
$this->view->assign('filter', $filter);
$cityData = $GLOBALS['TSFE']->fe_user->setKey('ses', 'citySessionData', $filter['cityId']);
$cityData = $GLOBALS["TSFE"]->fe_user->getKey('ses', 'citySessionData');
// You shall not use echo in TYPO3
// echo $cityData;
\TYPO3\CMS\Extbase\Utility\DebuggerUtility::var_dump($cityData);
}
I did not test this!
After changing the action you must clear the cache using the installtool or reinstall the extension.
In my extbase extension I use select form for select city. I want to save the selected city in a session, then use the city's value to search. Please, tell me the direction to implement this functionality.
You could send your form data to an action which will fetch a result through your repository or you can store the argument as a session.
Fluid form example:
<f:form action="search" controller="">
<label for="city-select">Select the city to search for</label>
<f:form.select name="searchString" id="city-select" options="cities" optionValueField="title" optionLabelField="title" />
</f:form>
Controller class example:
/**
* The action search.
*
* #param string $searchString
*/
public function searchAction($searchString)
{
// Do a search by a repository function...
$result = $this->yourRepository->findbySearchSDtring($searchString);
// code ...
// Or save it to session and do other stuff.
if ($GLOBALS['TSFE']->loginUser) {
// if the user is logged in, store it in the current logged in fe_user session.
$myData = $GLOBALS['TSFE']->fe_user->setKey('user', 'mySessionData', $searchString);
} else {
// Otherwise store it in the user session.
$myData = $GLOBALS['TSFE']->fe_user->setKey('ses', 'mySessionData', $searchString);
}
// code ...
}
I'm combining a TypoScript CONTENT Object with a fluid template.
In the page template:
<f:cObject typoscriptObjectPath="lib.myItem" />
In TS:
lib.myItem = CONTENT
lib.myItem {
table = tt_content
select.where = colPos = 0
select.languageField = sys_language_uid
renderObj = FLUIDTEMPLATE
renderObj {
file = {$customContentTemplatePath}/Myfile.html
layoutRootPath = {$customContentLayoutPath}
partialRootPath = {$customContentPartialPath}
dataProcessing {
10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
10.references.fieldName = image
}
}
}
In Myfile.html:
{namespace v=FluidTYPO3\Vhs\ViewHelpers}
<div class="small-12 medium-6 large-4 columns">
<f:for each="{files}" as="file">
<v:media.image src="{file}" srcset="1200,900,600" srcsetDefault="600" alt="{file.alternative}" treatIdAsReference="1"/>
</f:for>
<div class="fp-ql-txt">
{data.header} >
</div>
</div>
But now I realized that because the template is applied by the renderObj for each content element, I don't have access to fluid's for-each information about iteration. So, I can't do this:
<f:for each="{data}" as="item" iteration="itemIterator">
{itemIterator.cycle}
</f:for>
to find out in which of the rendered items we are ... because each element is rendered individually by renderObj.
How can I get the iteration information about the renderObj's products? Only in TS with the old and terrifying counters as in http://typo3-beispiel.net/index.php?id=9 ?
You could make your own IteratorDataProcessor:
<?php
namespace Vendor\MyExt\DataProcessing;
use TYPO3\CMS\Core\SingletonInterface;
use TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer;
use TYPO3\CMS\Frontend\ContentObject\DataProcessorInterface;
use TYPO3\CMS\Frontend\ContentObject\Exception\ContentRenderingException;
/**
* This data processor will keep track of how often he was called and whether it is an
* even or odd number.
*/
class IteratorProcessor implements DataProcessorInterface, SingletonInterface
{
/**
* #var int
*/
protected $count = 0;
/**
* Process data for multiple CEs and keep track of index
*
* #param ContentObjectRenderer $cObj The content object renderer, which contains data of the content element
* #param array $contentObjectConfiguration The configuration of Content Object
* #param array $processorConfiguration The configuration of this processor
* #param array $processedData Key/value store of processed data (e.g. to be passed to a Fluid View)
* #return array the processed data as key/value store
* #throws ContentRenderingException
*/
public function process(ContentObjectRenderer $cObj, array $contentObjectConfiguration, array $processorConfiguration, array $processedData)
{
$iterator = [];
$iterator['index'] = $this->count;
$iterator['isFirst'] = $this->count === 0;
$this->count++;
$iterator['cycle'] = $this->count;
$iterator['isEven'] = $this->count % 2 === 0;
$iterator['isOdd'] = !$iterator['isEven'];
$processedData['iterator'] = $iterator;
return $processedData;
}
}
In Typoscript you pass your data through that processor:
dataProcessing {
10 = TYPO3\CMS\Frontend\DataProcessing\FilesProcessor
10 {
references.fieldName = image
}
20 = Vendor\MyExt\DataProcessing\IteratorProcessor
}
In fluid you can access the stuff you set in your DataProcessor with e.g. {iterator.isFirst}.
You should check out the DatabaseQueryProcessor shipped with the TYPO3 Core.
https://docs.typo3.org/typo3cms/TyposcriptReference/ContentObjects/Fluidtemplate/Index.html#dataprocessing
Please note that data processing only work inside the FLUIDTEMPLATE cObject.
You also find a working example inside the documentation.
I am working on a special template for the news extension tx_news in Typo3.
I am completely new to Typo3 and especially Fluid.
What I want is an output of exactly 4 news items, but each of these items must have an image.
What I need is programming logic, something like:
If the newsItem has an image, and less than 4 items have been rendered so far, then render. Else don't do anything.
I read this question and answer:
TYPO3 Fluid complex if conditions
so I suspect I need something like a viewhelper.
So far my templates has this code to output items:
<f:for each="{news}" as="newsItem" iteration="iterator">
<f:if condition="{newsItem.falMedia}">
<f:if condition="{iterator.cycle}<=4">
<f:render partial="List/TeaserItem" arguments="{newsItem: newsItem,settings:settings,iterator:iterator, contentObjectData:contentObjectData}" />
</f:if>
</f:if>
</f:for>
But this will of course stop after iterating through news 4 times. So if one entry without image didn't get rendered, I will only have three items output.
I'd need an if condition kind of like this:
if ({newsItem.falMedia} && {iterator.cycle}<=4){
render image }
else {iterator.cycle--}
but I can't figure out how to pass the iterator variable of my for-loop to the new viewhelper, and especially to pass it back to the for-loop.
In short words this kind of logic isn't possible in Fluid - reason is simple -it's template engine.
You need to create your own extension and create a ViewHelper in it, which will take the collection of News will check if it has required settings (falMedia existing in this case) and will return limited array which you can iterate. Indeed, reusing f:for will be fastest solution.
I'm afraid, that's only way.
Here's the sample (compare it to original f:for viewhelper):
<?php
namespace TYPO3\CMS\Fluid\ViewHelpers;
class ForNewsWithMediaViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
/**
* Iterates through elements of $each and renders child nodes
*
* #param array $each The array or \TYPO3\CMS\Extbase\Persistence\ObjectStorage to iterated over
* #param string $as The name of the iteration variable
* #param string $key The name of the variable to store the current array key
* #param boolean $reverse If enabled, the iterator will start with the last element and proceed reversely
* #param string $iteration The name of the variable to store iteration information (index, cycle, isFirst, isLast, isEven, isOdd)
* #param int $limit Limit of the news items to show
* #return string Rendered string
* #api
*/
public function render($each, $as, $key = '', $reverse = FALSE, $iteration = NULL, $limit = NULL) {
return self::renderStatic($this->arguments, $this->buildRenderChildrenClosure(), $this->renderingContext, $limit);
}
/**
* #param array $arguments
* #param \Closure $renderChildrenClosure
* #param \TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface $renderingContext
* #param int $limit Limit of the news items to show
* #return string
* #throws \TYPO3\CMS\Fluid\Core\ViewHelper\Exception
*/
static public function renderStatic(array $arguments, \Closure $renderChildrenClosure, \TYPO3\CMS\Fluid\Core\Rendering\RenderingContextInterface $renderingContext, $limit = NULL) {
$templateVariableContainer = $renderingContext->getTemplateVariableContainer();
if ($arguments['each'] === NULL) {
return '';
}
if (is_object($arguments['each']) && !$arguments['each'] instanceof \Traversable) {
throw new \TYPO3\CMS\Fluid\Core\ViewHelper\Exception('ForViewHelper only supports arrays and objects implementing \Traversable interface', 1248728393);
}
if ($arguments['reverse'] === TRUE) {
// array_reverse only supports arrays
if (is_object($arguments['each'])) {
$arguments['each'] = iterator_to_array($arguments['each']);
}
$arguments['each'] = array_reverse($arguments['each']);
}
$iterationData = array(
'index' => 0,
'cycle' => 1,
'total' => count($arguments['each'])
);
$limitCycle = 1;
$output = '';
/**
* #type $singleElement Tx_News_Domain_Model_News
*/
foreach ($arguments['each'] as $keyValue => $singleElement) {
if (is_null($singleElement->getFalMedia())
|| !is_null($limit) && $limitCycle > $limit
) {
continue;
}
$limitCycle++;
$templateVariableContainer->add($arguments['as'], $singleElement);
if ($arguments['key'] !== '') {
$templateVariableContainer->add($arguments['key'], $keyValue);
}
if ($arguments['iteration'] !== NULL) {
$iterationData['isFirst'] = $iterationData['cycle'] === 1;
$iterationData['isLast'] = $iterationData['cycle'] === $iterationData['total'];
$iterationData['isEven'] = $iterationData['cycle'] % 2 === 0;
$iterationData['isOdd'] = !$iterationData['isEven'];
$templateVariableContainer->add($arguments['iteration'], $iterationData);
$iterationData['index']++;
$iterationData['cycle']++;
}
$output .= $renderChildrenClosure();
$templateVariableContainer->remove($arguments['as']);
if ($arguments['key'] !== '') {
$templateVariableContainer->remove($arguments['key']);
}
if ($arguments['iteration'] !== NULL) {
$templateVariableContainer->remove($arguments['iteration']);
}
}
return $output;
}
}
So you can use it in your view as:
<f:forNewsWithMedia each="{news}" as="newsItem" iteration="iterator" limit="4">
<f:render partial="List/TeaserItem" arguments="{newsItem: newsItem,settings:settings,iterator:iterator, contentObjectData:contentObjectData}" />
</f:forNewsWithMedia>
In the MVC RC 2 docs, we find:
Expression-based helpers that render input elements generate correct name attributes when the expression contains an array or collection index. For example, the value of the name attribute rendered by Html.EditorFor(m => m.Orders[i]) for the first order in a list would be Orders[0].
Anyone care to link an example of the C# view code (using a List where the result can bind back to the Model upon post)?
Just as a reference, I use the following code to verify the model binds correctly round trip. It simply shows view that allows change, then displays a view with the edited data upon form submission.
[HandleError]
public class HomeController : Controller
{
public ActionResult Index()
{
var myStudents = new List<Student>();
myStudents.Add(new Student { Name = "Harry" });
myStudents.Add(new Student { Name = "Tom" });
myStudents.Add(new Student { Name = "Richard" });
var myClass = new Classroom {Students = myStudents};
return View(myClass); // EditorFor()
}
[HttpPost]
public ActionResult Index( Classroom myClass)
{
return View("IndexPost", myClass); // DisplayFor()
}
This code:
<% for (int count = 0; count < Model.Students.Count; count++ )
{ %><%=
Html.EditorFor(m => m.Students[count]) %><%
}
%>
Rendered this output:
<input class="text-box single-line" id="Students_0__Name" name="Students[0].Name" type="text" value="Harry" />
<input class="text-box single-line" id="Students_1__Name" name="Students[1].Name" type="text" value="Tom" />
<input class="text-box single-line" id="Students_2__Name" name="Students[2].Name" type="text" value="Richard" />
And when I posted the content, the display was this (because I have a Student.ascx):
<table>
<tr><td><span>Harry</span> </td></tr>
<tr><td><span>Tom</span> </td></tr>
<tr><td><span>Richard</span> </td></tr>
</table>
But that's it (I think). Next question is how to get rid of those name="" tags.