How to access the validator results in an TYPO3 Extbase action? - fluid

I have an Extbase module with a new/create-action. The model has the #validate annotation in it.
So far so good, everything's working.
But: I don't like how the form-errors are presented in the view. I'd like to add a CSS class like error in the view to the fields that are not correctly filled in.
But the only way to access the errors in the form seems to be through the <f:form.validationResults>-Viewhelper.
When I try to debug the results with <f:debug>{validationResults}</f:debug> I get a NULL value.
How do I access an error for a single field?
Actually, I'd prefer to access the errors in the controller, so I could pass an array to the view with the fields that contain an error.
I'm using Fluid and TYPO3 9.5

Such Fluid template should work out-of-the-box:
<f:form.validationResults>
<f:if condition="{validationResults.flattenedErrors}">
<ul>
<f:for each="{validationResults.flattenedErrors}" key="propertyPath" as="errors">
<li>
{propertyPath}: <ul>
<f:for each="{errors}" as="error">
<li>{error}</li>
</f:for>
</ul>
</li>
</f:for></ul>
</f:if>
</f:form.validationResults>
Another approach,
You can also write own ViewHelper to display error messages as you want in your own form HTML markup:
<?php
namespace VENDOR\Yourext\ViewHelpers;
use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
/**
* #author Marcus Biesioroff biesior#gmail.com>
*
* ViewHelper for displaying custom-designed errors
*
* Usage:
* {namespace yvh=VENDOR\Yourext\ViewHelpers}
* or in ext_tables.php:
* $GLOBALS['TYPO3_CONF_VARS']['SYS']['fluid']['namespaces']['yvh'] = ['VENDOR\Yourext\ViewHelpers'];
*
* <yvh:myError key="yourObj.somefield" flattenedErrors="{validationResults.flattenedErrors}"/>
*/
class MyErrorViewHelper extends AbstractViewHelper
{
protected $escapeOutput = false;
public function initializeArguments()
{
parent::initializeArguments();
$this->registerArgument('key', 'sring', 'Name of the field for which errors should be displayed');
$this->registerArgument('flattenedErrors', 'mixed', 'Flatenned errors if any');
}
public function render()
{
$flattenedErrors = $this->arguments['flattenedErrors'];
if (is_null($flattenedErrors)) return null;
// DebuggerUtility::var_dump($flattenedErrors);
$key = $this->arguments['key'];
if (is_array($flattenedErrors) && array_key_exists($key, $flattenedErrors)) {
$errMsg = $flattenedErrors[$key][0]->getMessage();
return "<div class='my-very-own-error-class'>$errMsg</div>";
}
return null;
}
}
Remember, that you'll need to wrap your fields with <f anyway to get the flattenErrors array;
{namespace yvh=VENDOR\Yourext\ViewHelpers}
<f:form.validationResults>
<div>
<label for="name">Name (required)</label>
<f:form.textfield property="name"/>
<yvh:myError key="yourObj.name" flattenedErrors="{validationResults.flattenedErrors}"/>
</div>
<div>
<label for="slug">Slug (required)</label><br/>
<f:form.textfield property="slug"/>
<yvh:myError key="yourObj.slug" flattenedErrors="{validationResults.flattenedErrors}"/>
</div>
<div>
<label for="buildYear">Year of build (required)</label>
<f:form.textfield property="buildYear"/>
<yvh:myError key="yourObj.buildYear" flattenedErrors="{validationResults.flattenedErrors}"/>
</div>
</f:form.validationResults>

Related

Load partial view through controller on Search button click

I am working on an ASP.NET Core 2.1 MVC app using razor. I have searchQuery.cshtml and a (individually working perfectly) viewQuery.cshtml pages. In my searchQuery page, I let user enter queryId and on clicking "Search" button I want to run the action of ViewQuery that displays the results in viewQuery.cshtml and show the viewQuery below the search button area.
I am not good working with Ajax or so. On Search btn click, I call the viewQuery Get action thru ajax. In the button click, I pass the entered queryId of type int. But, when I load searchQuery page, it throws null exception for passing the queryId. I searched few hous, but didn't get any solution.
searchQuery.cshtml UPDATED
<div>
<div class="col-md-6">
<dl class="dl-horizontal">
<dt>
#Html.DisplayNameFor(model => model.QueryId)
</dt>
<dd>
<input asp-for="QueryId" class="form-control" />
</dd>
</dl>
</div>
<input type="submit" value="Show" />
<!-- CHANGE IN CALL -->
Search
</div>
<div class="modal fade" id="myModal">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
×
<h3 class="modal-title">Query Answer</h3>
</div>
<div class="modal-body" id="myModalBodyDiv">
</div>
<div class="modal-footer">
Ok
</div>
</div>
</div>
</div>
<script>
function ShowResult() {
// Retrieve queryId
var queryId = $("#QueryId").val();
// DisplayS PROPERLY
alert("Entered ID " + queryId);
// TRIED '/query/viewQuery' ALSO
$.ajax({
type: 'GET',
url: '../query/viewQuery',
data: { queryId: queryId },
success: function (response) {
alert(response); // **DISPLAYS [Object: object]**
$("#myModalBodyDiv").html(response);
$('#myModal').modal("show");
}, error: function (response) {
alert("Error: " + response);
}
});
}
</script>
My ViewQuery action in controller UPDATED
[Route("[controller]/viewQuery/{queryId:int}")]
public async Task<IActionResult> ViewQuery(int queryId)
{
// Retrieve Data from api using HttpClient
....
return PartialView("ViewQuery", qVM); // PartialView(qVM); //View(qVM);
}
Search Query Action UPDATED
[Route("searchQuery")] // customer/searchQuery
public IActionResult SearchQuery()
{
return View();
}
Can anyone please help me how do I achieve my goal. Simple - a text box were user enters queryId. A button on click, want to pass the entered queryId, call a GET action on controller and get the response. Finally show the response below the search button. I was just trying with the above modal dialog, I prefer text and not dialog.
Try & isolate the issue.
Instead of using model.QueryId in the searchQuery.cshtml, simply hardcode any reference to "modelid" - that way at least you are eliminating the possibility that Model is null on that page. Then instead of onclick="ShowResult(#Model.QueryId)"> , hard code some known id instead of #Model.QueryId. Then debug to see if your ViewQuery action method id hit. If the method is hit, then you can take it from there.
Also, I noticed that your jquery calls may need to be modified:
Instead of: $('myModalBodyDiv').html(response); it should probably be $('#myModalBodyDiv').html(response); (the "#" is missing ..) - same for $('myModal').
You can use Partial Pages(ViewQuery page) , in your searchQuery page , you could use Ajax to call server side action with parameter ID . On server side , you can query the database with ID and return PartialView with models :
[HttpPost]
public IActionResult Students (StudentFilter filters)
{
List students = Student.GetStudents(filters);
return PartialView("_Students", students);
}
Then in success callback function of Ajax , you can load the html of partial view to related area in page using jQuery :
success: function (result) {
$("#searchResultsGrid").html(result);
}
You can click here and here for code sample if using MVC template . And here is code sample if using Razor Pages .

Conditional HTML tag on Body tag

On an ASP.NET Core 2.1 view I have the following:
<div #{if (1==1) { <text>style="background-image: url('image.png')"</text> }}></div>
Note: I am using 1==1 just for testing ...
This renders fine but I need to apply this to body tag:
<body #{if (1==1) { <text>style="background-image: url('image.png')"</text> }}>
In this case I get the error:
The tag helper 'body' must not have C# in the element's attribute declaration area.
How to solve this?
What you are writing doesn't seem to result in valid HTML.
Here are a few ideas (in order of complexity) to get you started.
Traditional conditional
<body>
#if (1 == 1)
{
<div style="background-image: url('image.png')"></div>
}
else
{
<div></div>
}
</body>
Ternary Operator
<div style="#((1 == 1) ? "background-image: url('image.png')" : "")"></div>
Move logic to separate block
#{
var divStyle = "";
if (1 == 1)
{
divStyle = "background-image: url('image.png')";
}
}
<div style="#divStyle"></div>
Logic done server side and stored in model
#model MyViewModel
<div style="#Model.DivStyle"></div>
Inject service into View (Dependency Injection)
#inject StyleService _styleService
<div style="#_styleService.GetStyleIfTrue(1 == 1)"></div>

How update view depending on user selection on Asp.Net core

I want to be able to display a form which changes depending on the value of a select on Dot.Net Core. I've seen many things like dynamic forms, View Components, razor and partials and also there is a lot of info out there but very confusing. Any info about the proper way to do what I want would be very appreciated.
I Have Categories>SubCategories>>Products
An order can be for one Category>>SubCategory only. So I want to display a select and depending on what category the user selects for the new Order, the products that I have to display. So I dont want to pick the user selection and go back to the controller and back to the view again and so on. I want to be able to dynamically display data according to the user selection.
Here an extract of the code just to briefly figure out what Im doing..I am not pasting the classes like Products,etc..
public class OrderCreateViewModel
{
public IEnumerable<Category> Categories { get; set; }
public IEnumerable<Branch> Branches { get; set; }
public int BranchId { get; set; }
public int CategoryId { get; set; }
}
Controller :
[HttpGet]
public IActionResult Create()
{
//Create vm
IEnumerable<Branch> branchList = _branchDataService.GetAll();
IEnumerable<Category> categoryList = _categoryDataService.GetAll();
OrderCreateViewModel vm = new OrderCreateViewModel
{
Categories = categoryList,
Branches = branchList
};
return View(vm);
}
View:
#model OrderCreateViewModel
<p>Create New Order </p>
<form asp-controller="Order" asp-action="Create" method="post">
<div class="form-group">
<label >Select Category</label>
<select class="form-control col-md-2" asp-for="CategoryId"
asp-items="#(new SelectList(Model.Categories ,"CategoryId","Name"))">
</select>
</div>
<div class="form-group">
<label>Select Branch</label>
<select class="form-control col-md-2" asp-for="BranchId"
asp-items="#(new SelectList(Model.Branches,"BranchId","Name"))">
</select>
</div>
<div >
<input type="submit" value="Save" />
</div>
</form>
Im just filling the select on the viewside and depending on what the user picks, the products I want to display. I am not passing the product list yet because I don't know where the "filter" for that particular Category takes place.
Hope you understand the idea of what i need :)
You've got two options here:
# 1 Use a Partial View And AJAX to get your data
Go have a look at the link below, it describes exactly what you want to achieve.
Populating Dropdown using partial view
# 2 Use Javascript to populate your second select:
First off you need to persist your data when the page loads
At the start of your view, add this:
#{
<text>
<script>
var data = "#Newtonsoft.Json.JsonConvert.SerializeObject(Model)";
</script>
</text>
}
Next use your on change event on the branch selector:
At the bottom of your view, do the following in the page ready event:
<script>
(function ()
{
var sltBranch = document.getElementsByName("BranchId")[0];
var sltCat = document.getElementsByName("CategoryId")[0]
sltCat.onchange = function () {
var dataAsObj = JSON.parse(data);
var branches = "";
for (i = 0; i < dataAsObj.Branches.length; i++)
{
if (dataAsObj.Branches[i].CategoryId == sltCat.value)
{
branches += "<option value='" + dataAsObj.Branches[i].BranchId + "'>" + dataAsObj.Branches[i].BranchName + "</option>"; //Whatever The branch name may be
}
}
sltBranch.innerHTML = branches;
}
}
)(document, window);
</script>
I would however advise you to follow option 1 as it is a lot more future proof strategy. This would mean that you need to change your view model etc, but if you plan on making extensive use of this strategy you need to make it more robust with something like option 1.
Good luck anyhow - happy coding.

Aurelia iterate over map where keys are strings

I'm having trouble getting Aurelia to iterate over a map where the keys are strings (UUIDs).
Here is an example of the data I'm getting from an API running somewhere else:
my_data = {
"5EI22GER7NE2XLDCPXPT5I2ABE": {
"my_property": "a value"
},
"XWBFODLN6FHGXN3TWF22RBDA7A": {
"my_property": "another value"
}
}
And I'm trying to use something like this:
<template>
<div class="my_class">
<ul class="list-group">
<li repeat.for="[key, value] of my_data" class="list-group-item">
<span>${key} - ${value.my_property}</span>
</li>
</ul>
</div>
</template>
But Aurelia is telling me that Value for 'my_data' is non-repeatable.
I've found various answer by googling, but they have not been clearly explained or incomplete. Either I'm googling wrong or a good SO question and answer is needed.
As another resource to the one supplied by ry8806, I also use a Value Converter:
export class KeysValueConverter {
toView(obj) {
if (obj !== null && typeof obj === 'object') {
return Reflect.ownKeys(obj).filter(x => x !== '__observers__');
} else {
return null;
}
}
}
It can easily be used to do what you're attempting, like this:
<template>
<div class="my_class">
<ul class="list-group">
<li repeat.for="key of my_data | keys" class="list-group-item">
<span>${key} - ${my_data[key]}</span>
</li>
</ul>
</div>
</template>
The easiest method would be to convert this into an array yourself (in the ViewModel code)
Or you could use a ValueConverter inside repeat.for as described in this article Iterating Objects
The code...
// A ValueConverter for iterating an Object's properties inside of a repeat.for in Aurelia
export class ObjectKeysValueConverter {
toView(obj) {
// Create a temporary array to populate with object keys
let temp = [];
// A basic for..in loop to get object properties
// https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Statements/for...in
for (let prop in obj) {
if (obj.hasOwnProperty(prop)) {
temp.push(obj[prop]);
}
}
return temp;
}
}
/**
* Usage
* Shows how to use the custom ValueConverter to iterate an objects properties
* aka its keys.
*
* <require from="ObjectKeys"></require>
* <li repeat.for="prop of myVmObject | objectKeys">${prop}</li>
*/
OR, you could use the Aurelia Repeat Strategies provided by an Aurelia Core Team member
You'd have to import the plugin into your app.
Then you'd use it using the pipe syntax in your repeat.for....like so....
<div repeat.for="[key, value] of data | iterable">
${key} ${value.my_property}
</div>

How to dynamically generate css class inside an each statement for an Ember View

<div>
{{#each value in controller}}
<div {{classNameBindings "col-lg-{{value}}"}}>{{value}}</div>
{{/each}}
</div>
Above is my partial view.
I want to generate classes like: col-lg-1, col-lg-2 etc
My controller is:
App.circleController = Ember.ArrayController.extend({
setupController: function(controller) {
controller.set('content', [1,2,3,4,5,6,7]);
}
});
why I get error: assertion failed: an Ember.CollectionView's content must implement Ember.Array. ?
I use a custom view to apply dynamically-named classes to items inside of an each helper. The class name is generated inside the view by a property than depends on a supplied index.
App.ItemView = Ember.View.extend({
classNameBindings: ['itemClass'],
index: null,
itemClass: function() {
return 'class-'+this.get('index');
}.property('index')
});
In the template, I supply the index through a {{view}} helper inside each iteration.
{{#each value in controller}}
{{#view App.ItemView indexBinding="value"}}
Item #{{value}}
{{/view}}
{{/each}}
For a closer look, check out this jsfiddle.