symfony 1.4 forms: Using <p> rather than <li> - symfony-1.4

When calling a form using
print $widget->render()
rather than printing
<li>
<label ...>
<input ...>
</li>
How can I have it print
<p>
<label ...>
<input ...>
</p>
PS: in my form:
public function configure()
{
$this->widgetSchema->setFormFormatterName('list');
}

You will have to create your own form schema formatter class as explained here. For example, to use a paragraph you can just redeclare the $rowFormat variable:
#lib/widget/sfWidgetFormSchemaFormatterParagraph.class.php
class sfWidgetFormSchemaFormatterParagraph extends sfWidgetFormSchemaFormatter
{
protected $rowFormat = "<p>
%label% \n %error% <br/> %field%
%help% %hidden_fields%\n</p>\n";
}
If you want to use this format for all forms of your project add it to your ProjectConfiguration class (and remove the $this->widgetSchema->setFormFormatterName('list') from your form):
class ProjectConfiguration extends sfProjectConfiguration
{
public function setup()
{
// ...
sfWidgetFormSchema::setDefaultFormFormatterName('paragraph');
}
}
If you want to use it only for one form, add this your form class:
public function setup()
{
$this->widgetSchema->addFormFormatter('paragraph', new sfWidgetFormSchemaFormatterParagraph($this->widgetSchema));
$this->widgetSchema->setFormFormatterName('paragraph');
parent::setup();
}

Just look at sfWidgetFormSchemaFormatterListand sfWidgetFormSchemaFormatter.class.php. You can make your own schema from that, let's say sfWidgetFormSchemaFormatterParagraph, and change the properties of the class to match your needs. (you probably need to change only $decoratorFormat and $rowFormat. Something like:
$rowFormat = "<p> %error%%label%\n %field%%help%\n%hidden_fields%</p>"
$decoratorFormat = "%content%"

Related

Blazor: binding to a MultiSelectList (ideally with a checkbox)

Experimenting with Blazor (Server, if that makes any difference), and I'm having difficulty getting binding to a MultiSelectList to work....
Bit of background: I'm dealing with EF Core and have a Many-to-Many relationship, let's say between people and cars. I'm currently loading a page that shows the existing details, and allowing the user to update this page.
So in my Service, I load my Person entity from the DB, and this includes the details of all the cars they currently own. I also load the list of all the available cars. My Service method then creates a MultiSelectList and adds it to my ViewModel (to be returned to the Razor Page):
Service method
vm.CarSelector = new MultiSelectList(
allCars,
nameof(Car.CarId),
nameof(Car.Name),
person.OwnedCars.Select(oc => oc.CarId));
This is fictitious code, but I hope you get the picture. When debugging this (in the Service method) I can see that this MultiSelectList has an entry for every car, and the ones that are already selected are showing as Selected. Great!
Blazor Razor Page
So, this is where I come unstuck.... I can't work out how to do the two-way data-binding of a Razor control to this object.
I'm trying to use an <InputSelect />, but that might not be the best control to use.
ideally (actually, that's more of a "must have"), each option should have CheckBox.
I'm wondering whether the use of a MultiSelectList really buys me anything
Checkboxes are a bit different in blazor. Normally you would use the bind-value attribute on an input element as shown below, however, this is not recommended as you will only be able to read the value and NOT update the UI by changing the boolean value via code:
<input type="checkbox" #bind-value="#item.Selected"/>
Instead, use the #bind syntax for checkboxes, which is much more robust and will work both ways (changing the bound boolean value from code & interacting with the checkbox on the UI). See the syntax below:
<input type="checkbox" #bind="#item.Selected"/>
The bind attribute will automatically bind your boolean value to the "checked" property of the html element.
Also make sure you are binding to the "Selected" property rather than the "Value" property.
Using the built in bind will prevent the need to manually setup events as you did in your answer. You can also get rid of the if/else block and merge your code into a single code flow since you are now binding to the boolean rather than setting the checked property manually. If you still need to tap into an event to fire off some process(maybe hiding parts of UI on checking a box), I'd suggest using the onclick event and manually passing in the multiselect Item for each line. Here is the final code:
#foreach(var item in list)
{
<input type="checkbox" #bind="item.Selected" #onclick="(()=>handleClick(item))" />
}
#foreach(var item in list.Where(x=>x.Selected))
{
<p> Item #item.Text is Selected</p>
}
#code {
MultiSelectList list = new MultiSelectList(new List<Car> { new Car { Year = 2019, Make = "Honda", Model = "Accord" }, new Car { Make = "Honda", Model = "Civic", Year = 2019 } });
private void handleClick(SelectListItem item)
{
//Do something crazy
}
}
I got this to work with a component that takes the MultiSelectList as a parameter. There may be more elegant ways to achieve this (please do update if you know of a better way).
#using Microsoft.AspNetCore.Components
#using Microsoft.AspNetCore.Mvc.Rendering
<div class="multiselect">
<div id="checkboxes">
#foreach (var item in this.Items)
{
<div>
<label for="#item.Value">
#if (item.Selected)
{
<input type="checkbox" id="#item.Value" checked="checked" #onchange="#((e) => CheckboxChanged(e, item.Value))" />
}
else
{
<input type="checkbox" id="#item.Value" #onchange="#((e) => CheckboxChanged(e, item.Value))" />
}
#item.Text
</label>
</div>
}
</div>
</div>
#code
{
[Parameter]
public MultiSelectList Items { get; set; } = null!;
private void CheckboxChanged(ChangeEventArgs e, string key)
{
var i = this.Items.FirstOrDefault(i => i.Value == key);
if (i != null)
{
i.Selected = (bool)e.Value;
}
}
}

Make field disabled using Razor in MVC Core [duplicate]

I want to enable or disable a textarea depending on a condition that evalueates from the model, and I am using the textarea tag helper.
In other words, something like this:
<textarea asp-for="Doc" #(Model.MustDisable ? "disabled" : "")></textarea>
But I got the following design-time error: The tag helper 'textarea' must not have C# in element's attribute declaration area.
Then I tried:
<textarea asp-for="Doc" disabled='#(Model.MustDisable ? "disabled" : "")'></textarea>
which did not show any design time error but it renders like this:
Model.MustDisable==true renders disabled='disabled' AND Model.MustDisable==false renders disabled.
So the text area will always be disabled.
Then I tried (removing the 's):
textarea asp-for="Doc" disabled=#(Model.MustDisable ? "disabled" : "")></textarea>
which did not show any design time error but it renders the same as the previous one.
How can I implement this the right way?
It is actually very simple, the disable attribute is already working as you want - you can pass in a boolean value:
<textarea asp-for="Doc" disabled="#Model.MustDisable"></textarea>
if false the disabled attribute is not rendered:
<textarea></textarea>
if true the disabled attribute is set to "disabled":
<textarea disabled="disabled"></textarea>
I was facing the same issue with select tag helper, i tried few things and it worked.
Try this-
<textarea asp-for="Doc" disabled="#(Model.MustDisable ? "disabled" : null)"></textarea>
The textarea tag helper does not have direct support to conditionally render a disabled text area. But you can always extend the TextAreaTagHelper and add this feature.
So create a new class which inherits from the TextAreaTagHelper class.
[HtmlTargetElement("textarea", Attributes = ForAttributeName)]
public class MyCustomTextArea : TextAreaTagHelper
{
private const string ForAttributeName = "asp-for";
[HtmlAttributeName("asp-is-disabled")]
public bool IsDisabled { set; get; }
public MyCustomTextArea(IHtmlGenerator generator) : base(generator)
{
}
public override void Process(TagHelperContext context, TagHelperOutput output)
{
if (IsDisabled)
{
output.Attributes["disabled"] = "disabled";
}
base.Process(context, output);
}
}
In your _ViewImports.cshtml file, using the #addTagHelper directive, specify the assembly where the above class is defined so that our new tag helper is available in other razor views.
#addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
#addTagHelper "*,YourAssemblyNameHere"
Now in your views, you can use it like
#model YourSomeViewModel
<textarea asp-for="Doc" asp-is-disabled="Model.MustDisable"></textarea>
where SomeViewModel has a Doc and MustDisable property.
public class YourSomeViewModel
{
public string Doc { set;get; }
public bool MustDisable { set;get; }
}
I am posting this separately since I don't have enough reputation to add a comment to Shyju's answer.
If you inherit from one of the default tag helpers and then register both the default tag helpers and your custom tag helper in _ViewImports.cshtml, then both tag helpers will be executed for the specified tags.
For the following:
[HtmlTargetElement("textarea", Attributes = ForAttributeName)]
public class MyCustomTextArea : TextAreaTagHelper
{
private const string ForAttributeName = "asp-for";
...
With the following _ViewImports.cshtml:
#addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
#addTagHelper "*,YourAssemblyNameHere"
Both MyCustomTextArea and TextAreaTagHelper will be executed for each textarea tag.
I did not notice any problems with the output generated for textareas, but I have run into problems inheriting from other default tag helpers. The solution is to remove the default tag helper in _ViewImports.cshtml.
#addTagHelper "*, Microsoft.AspNet.Mvc.TagHelpers"
#addTagHelper "*,YourAssemblyNameHere"
#removeTagHelper "Microsoft.AspNet.Mvc.TagHelpers.TextAreaTagHelper, Microsoft.AspNet.Mvc.TagHelpers"

Indexing into type 'com.ItemBean' is not supported

I have a wrapper class like so:
#NoArgsConstructor
#Data
public class ListWrapper {
public ListWrapper(List<Object> objects) {
this.objects = objects;
}
private List<Object> objects;
}
I am looking to populate the wrapper with custom beans. Let's call it an ItemBean.
So then I have:
#GetMapping("/rentSetup")
public String setupRent(#RequestParam("companyId") Integer companyId,
Model model) {
List<Object> beans = new ArrayList<>();
ItemBean bean = new Builder()
.someProperty(something)
.build();
beans.add(bean);
ListWrapper wrapper = new ListWrapper(beans);
model.addAttribute("itemBeansWrapper", wrapper);
return "setup";
}
I'd like to have the user edit the property someProperty on in the view. I'm thinking that I would do:
<form th:object="${itemBeansWrapper}"
th:action="#{/setup(companyId=${companyId})}"
th:method="post">
<div th:each="bean, iterStat : ${itemBeansWrapper.objects}">
<input type="number"
th:name="${bean[__${iterStat.index}__].someProperty}">
</div>
<button type="submit"
th:name="action"
th:value="review" value="review"> Review
</button>
</form>
But this results in:
org.springframework.expression.spel.SpelEvaluationException: EL1027E:(pos 4): Indexing into type 'com.ItemBean' is not supported
at org.springframework.expression.spel.ast.Indexer.getValueRef(Indexer.java:176)
What am I doing wrong?
Note that I also have my controller annotated with #SessionAttributes({"companyId", "itemBeansWrapper"}) since I would like to persist the wrapper across pages in the session.
If I leave off the [__${iterStat.index}__], the page compiles fine, but I am thinking that I would need something like that to differentiate the nested beans.
Caught my error. It should be:
<input type="number" th:field="*{objects[__${iterStat.index}__].someProperty}"/>

Creating reusable HTML view components using Razor in ASP.NET MVC

I have a Razor helper function that creates a re-usable HTML panel that saves me writing the same HTML over and over again.
#helper DefaultPanel(string panelTitle) {
<div class="panel">
<div class="panel-logo"><img src="/logo.png"></div>
<div class=panel-inner">
<p class="panel-title">#panelTitle</p>
<div class="panel-content">
/* Can I pass content to be rendered in here here? */
</div>
</div>
</div>
</div>
}
I'm wondering, is it possible to re-use this helper to fill .panel-content with more HTML to allow further flexibility and code reuse - similar to something like below:
#LayoutHelpers.DefaultPanel("Welcome back") {
<div class="panel-content-inner">
<p>Welcome back, please select from the following options</p>
Profile
My Defails
</div>
}
Whilst using .NET MVC I've noticed the Html.BeginForm() does a similar thing when wrapping the code within the #using statement within the Html.BeginForm declaration, like so:
#using (Html.BeginForm("Index", "Login", FormMethod.Post))
{
<div>This content gets rendered within the <form></form> markup.</div>
}
But can this done using #helper methods? If not, is it possible to create a HtmlHelper extension to do a similar thing the way the Html.BeginForm() method does?
You can do a very similar thing using the #section syntax as seen here
This seems like something that would be really useful to be able to do, and odd that there's no easy way to do it on a component level.
There are two ways to achieve the required functionality.
1. #helper
Create #helper which accepts whatever parameters you need plus a function (single object parameter, returns object):
#helper DefaultPanel(string panelTitle, Func<object, object> content)
{
<div class="panel">
<div class="panel-logo">
<img src="/logo.png" />
</div>
<div class="panel-inner">
<p class="panel-title">#panelTitle</p>
<div class="panel-content">
#content(null)
</div>
</div>
</div>
}
Usage:
#DefaultPanel("title",
#<div class="panel-content-inner">
<p>Welcome back, please select from the following options</p>
Profile
My Defails
</div>
)
Your function may also accepts parameters, example here.
2. HtmlHelper extension method
Add the following code anywhere in your project:
namespace System.Web.Mvc
{
public static class HtmlHelperExtensions
{
public static HtmlDefaultPanel DefaultPanel(this HtmlHelper html, string title)
{
html.ViewContext.Writer.Write(
"<div class=\"panel\">" +
"<div class=\"panel-inner\">" +
"<p class=\"panel-title\">" + title + "</p>" +
"<div class=\"panel-content\">"
);
return new HtmlDefaultPanel(html.ViewContext);
}
}
public class HtmlDefaultPanel : IDisposable
{
private readonly ViewContext _viewContext;
public HtmlDefaultPanel(ViewContext viewContext)
{
_viewContext = viewContext;
}
public void Dispose()
{
_viewContext.Writer.Write(
"</div>" +
"</div>" +
"</div>"
);
}
}
}
Usage:
#using (Html.DefaultPanel("title2"))
{
<div class="panel-content-inner">
<p>Welcome back, please select from the following options</p>
Profile
My Defails
</div>
}
The extension method writes directly to the context. The trick is to return a disposable object, which Dispose method will be executed at the end of using block.
I don't know if #helper methods can do this but HtmlHelper extensions certainly can. You've mentioned the Html.BeginForm() example which is probably the most well known - all that does is return an object which implements IDisposable which means that when the Dispose() method is called it just calls the complimentary Html.EndForm() method to add the appropriate closing tags.
It would be very simple to do something similar for your HTML code. You can view the source code to the ASP.NET MVC HtmlHelpers at http://aspnetwebstack.codeplex.com/ - the BeginForm() code can be specifically be viewed here.

Tapestry pagelink with dynamic css class

I try set a dynamic css class value for a pagelink in a simple custom component and can't find any way.
My component ...
<!-- my component template 'testLink' -->
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<!-- maybe I can set here something dynamic like that ...
<t:pagelink page="mytest" t:id="myLink" class="${myDynCss}">
... but in this case I need to pass the parameter what link is handled
-->
<t:pagelink page="mytest" t:id="myLink">
I want dynamic css class
</t:pagelink>
</html>
The component java code ...
public class TestLink {
#Parameter(required=true)
private int activeId;
#Component
PageLink myLink;
public int getActiveId() {
return activeId;
}
public void setupRender()
{
// I try to set some class attribute here but I find no matching function in myLink
// myLink.setCssStyle();
}
public String getMyDynCss(int currentLinkId) {
if (currentLinkId==activeId)
return "active";
else
return "xxx";
}
}
The page that includes the component ...
<html t:type="layout" title="Test" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
<p:app_navigation>
<t:testLink activeId="1000"/>
</p:app_navigation>
</html>
Maybe a silly newbie question but I have still problems to think in Tapestry way.
Every help or useful hint is welcome.
It is not quite clear from your code what the difference between currentLinkId en activeId is and where currentId comes from. I'm almost assuming you have some sort of Loop setup you are not sharing here. But given you can obtain these variables from the enclosing component, you are pretty much there in your commented out code, you just need to remove the argument from your getMyDynCss() method. Like so:
Java:
public class TestLink {
#Property
#Parameter(required=true)
private int activeId;
#Property
#Parameter(required=true)
private int currentId;
public String getMyDynCss() {
if (currentId == activeId) {
return "active";
}
else {
return "xxx";
}
}
}
Your tml:
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<t:pagelink page="mytest" t:id="myLink" class="${myDynCss}">
</html>
Your enclosing component:
<html t:type="layout" title="Test" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
<p:app_navigation>
<t:testLink activeId="1000" currentId="somePropertyFromSomewhere"/>
</p:app_navigation>
</html>
My solution use the the life cycle events. If there is any link that has a id that represents the active id (by convention) mark it as active.
My final component template ...
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<!-- convention: id == 'm' + numeric value for active entry -->
<t:pagelink page="mytest" id="m1000">
I'm active
</t:pagelink>
<t:pagelink page="mytest2" id="m1001">
I'm not active
</t:pagelink>
</html>
The java code of the component ...
public class TestLink {
#Parameter(required=true)
private int activeId;
// ... looking for a link with the active id ...
void afterRender(final MarkupWriter writer) {
// works only if the id follows the right convention :-D
String activeElemId="m"+activeId; // <--
Element activeLink=writer.getDocument().getElementById(activeElemId);
if (activeLink!=null)
activeLink.addClassName("active");
}
}
The code that includes the component ...
<html t:type="layout" title="Test" xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
<p:app_navigation>
<t:testLink activeId="1000"/>
</p:app_navigation>
</html>

Resources