I have a big DxFormLayout with many DxFormLayoutGroups and each DxFormLayoutGroup has many DxFormlauoutItems. I'm wondering if it is possible to sompress the razor code by componentizing a few DxFormlauoutItems. Here is my code:
Page:
<EditForm Model="#_model" Context="editFormContext" OnValidSubmit="#HandleValidSubmit">
<DataAnnotationsValidator />
<DxFormLayout CssClass="dxFormLayoutHeaderStyle">
<DxFormLayoutGroup Caption="Options" ColSpanMd="12">
<DxFormLayoutItem Caption="" ColSpanMd="12">
#*The below is repeated multiple times*#
<Template>
<DxStackLayout ItemSpacing="10px">
<Items>
<DxStackLayoutItem Length="95%">
<Template>
<DxTextBox ReadOnly="true" #bind-Text="#_model.TooltipTextForNegotiationsDoneBySupplyManagement"
title="#_model.TooltipTextForNegotiationsDoneBySupplyManagement" SizeMode="SizeMode.Medium"/>
</Template>
</DxStackLayoutItem>
<DxStackLayoutItem Length="5%">
<Template>
<div class="stacklayout-item">
<DxCheckBox CheckType="CheckType.Switch" style="width: 100%" #bind-Checked="#_model.IsNegotiationsDoneBySupplyManagement"
Alignment="CheckBoxContentAlignment.Center" title="#_model.TooltipTextForNegotiationsDoneBySupplyManagement"/>
</div>
</Template>
</DxStackLayoutItem>
</Items>
</DxStackLayout>
</Template >
#*The above is repeated multiple times*#
</DxFormLayoutItem >
</DxFormLayoutGroup>
</DxFormLayout>
</EditForm>
#*The below become a component with parameters and bindings*#
<EditForm Model="#_model" Context="editFormContext" OnValidSubmit="#HandleValidSubmit">
<DataAnnotationsValidator />
<DxFormLayout CssClass="dxFormLayoutHeaderStyle">
<DxFormLayoutItem Caption="" ColSpanMd="12">
<Template>
<BudgetReleaseRequestDecisionPoint DecisionText="#_model.TooltipTextForNegotiationsDoneBySupplyManagement"
DecisionResult="#_model.IsNegotiationsDoneBySupplyManagement" />
</Template >
</DxFormLayoutItem >
</DxFormLayout>
</EditForm>
Component:
<style>
.stacklayout-item {
text-align: center;
height: 100%;
padding-top: 6px;
}
</style>
<DxStackLayout ItemSpacing="10px">
<Items>
<DxStackLayoutItem Length="95%">
<Template>
<DxTextBox ReadOnly="true" #bind-Text="#DecisionText" title="#DecisionText" SizeMode="SizeMode.Medium"/>
</Template>
</DxStackLayoutItem>
<DxStackLayoutItem Length="5%">
<Template>
<div class="stacklayout-item">
<DxCheckBox CheckType="CheckType.Switch" style="width: 100%" #bind-Checked="#DecisionResult"
Alignment="CheckBoxContentAlignment.Center" title="#DecisionResult"/>
</div>
</Template>
</DxStackLayoutItem>
</Items>
</DxStackLayout>
#code {
[Parameter]
public string DecisionText
{
get => _decisionText;
set
{
if (_decisionText == value) return;
_decisionText = value;
DecisionTextChanged.InvokeAsync(value);
}
}
[Parameter]
public bool DecisionResult
{
get => _decisionResult;
set
{
if (_decisionResult == value) return;
_decisionResult = value;
DecisionResultChanged.InvokeAsync(value);
}
}
[Parameter]
public EventCallback<string> DecisionTextChanged { get; set; }
[Parameter]
public EventCallback<bool> DecisionResultChanged { get; set; }
private string _decisionText;
private bool _decisionResult;
}
Issue:
I made it a razor component but I'm having issue as the model's properties are not getting updated on the main page. I can confirm this by one property: On the page, there is a SpinEdit that get enabled once model.IsNegotiationsDoneBySupplyManagement is set to true. That is not happening anymore once I went to component-mode:
<DxSpinEdit Id="amountSavedAfterNegotiations" #bind-Value="#_model.SavingsAfterNegotiations" Enabled="#_model.IsNegotiationsDoneBySupplyManagement" title="Savings (AED) after negotiations?" />
When I had the original code (without component/top-most code I pasted), togeling this checkbox would toggle the Enabled state of the SpinEdit. After I transferred to component, the Enabled state is not longer sensing changes to the model's property leading me to believe the model's properties on the page are not getting updated.
What is wrong with the way I wired the component?
The missing magic was on the Page where I was calling the components. This is what I was doing:
<BudgetReleaseRequestDecisionPoint DecisionText="#_model.TooltipTextForNegotiationsDoneBySupplyManagement" DecisionResult="#_model.IsNegotiationsDoneBySupplyManagement" />
This is the correct syntax:
<BudgetReleaseRequestDecisionPoint DecisionText="#_model.TooltipTextForNegotiationsDoneBySupplyManagement" #bind-DecisionResult="#_model.IsNegotiationsDoneBySupplyManagement" />
I had to change the DecisionResult to #bind-DecisionResult. Now the Page's model is reflecting the changes that occur to its properties with in component.
Related
I have an EditForm as follows:
<EditForm class="container-fluid" Context="formContext" Model="#_inputModel" OnValidSubmit="() => OnSubmitInput()">
<DataAnnotationsValidator />
<!-- other stuff ordered in bootstrap cols and rows -->
<div class="col-6">
<ValidationMessage For="() => _inputModel.NumberOfInputChannels"/>
<label for="stNumberOfChannels" class="form-label">Number of Channels</label>
<input type="number" class="form-control" id="stNumberOfChannels"
#bind="_inputModel.NumberOfInputChannels">
</div>
</EditForm>
The property of the model corresponding to the form looks like this:
[Range(1, 4, ErrorMessage = "Number of Channels must be between 1 and 4!")]
[JsonPropertyName("n-channels")]
public ushort NumberOfInputChannels { get; set; } = 3;
My problem is simply, that the message appears too big, thus I want to make it smaller:
I have already tried to add class="fs-6" to the <ValidationMessage /> tag, but it doesn't seem to have any effect at all.
Either:
Modify validation-message in your CSS
Wrap ValidationMessage in a div and set whatever CSS you want on the div .
<div class="fs-6">
<ValidationMessage For="() => myData.Name" />
</div>
For reference, the ValidationMessage source BuildRenderTree looks looks this:
protected override void BuildRenderTree(RenderTreeBuilder builder)
{
foreach (var message in CurrentEditContext.GetValidationMessages(_fieldIdentifier))
{
builder.OpenElement(0, "div");
builder.AddAttribute(1, "class", "validation-message");
builder.AddMultipleAttributes(2, AdditionalAttributes);
builder.AddContent(3, message);
builder.CloseElement();
}
}
<ValidationMessage> has a parameter AdditionalAttributes which captures attributes. However it sets the class attribute itself to validation-message which can be found in wwwroot/css
<ValidationMessage For="() => _inputModel.NumberOfInputChannels"
style="font-size: 1rem;"/>
fs-6 is 1rem
Source code
I'm trying to imitate Bootstrap form validation styling with Vue and Vee-validate.
In order to have that Boostrap validation error message, when there's a validation error, the input itself must have is-invalid class presents. And in addition, the error message element must have invalid-feedback class, of course.
I'm struggling to add is-invalid class to the input when there's a validation error.
In Vee-validate 3, I was able to control the input element's classes with this guide. But it seems to be deprecated.
This is a code sandbox that you can play with. Nothing extra-ordinary, just straight out of Veevalidate example.
<template>
<div id="app">
<Form #submit="onSubmit">
<Field name="email" type="email" :rules="validateEmail" class="form-control"/>
<ErrorMessage class="invalid-feedback" name="email" />
<button class="btn btn-primary">Sign up</button>
</Form>
</div>
</template>
<script>
import {
Form,
Field,
ErrorMessage
} from "vee-validate";
export default {
components: {
Form,
Field,
ErrorMessage,
},
methods: {
onSubmit(values) {
console.log(values, null, 2);
},
validateEmail(value) {
// if the field is empty
if (!value) {
return "This field is required";
}
// if the field is not a valid email
const regex = /^[A-Z0-9._%+-]+#[A-Z0-9.-]+\.[A-Z]{2,4}$/i;
if (!regex.test(value)) {
return "This field must be a valid email";
}
// All is good
return true;
},
},
};
</script>
<style>
span {
display: block;
margin: 10px 0;
}
</style>
Versions
"vee-validate": "^4.5.11",
"vue": "^3.2.33",
You can render more complex fields, by utilizing the scoped slots of the <Field />-component.
If you replace your Field-component with the following, it should work as expected:
<Field name="email" :rules="validateEmail" v-slot="{ field, errors }">
<input v-bind="field" type="email" :class="{'is-invalid': !!errors.length }" />
</Field>
How can I change the classes/Style of a div in my controller? I tried using runat but it didnt work. here is an example
<div id="MainDiv" class="flex justify-between items-center">
<a>Done</a>
</div>
How can i add a bg-red-300 class to MainDiv if certain conditions apply and if not add a bg-green-300 class instead?
The first way,you could judge the data in client side like below:
#model Test
<div id="MainDiv" class="flex justify-between items-center #(Model.IsDone=="Yes"?"bg-red-300":"bg-green-300")">
Controller:
public IActionResult Index()
{
var model = new Test()
{
IsDone=="Yes"
};
return View(model);
}
The second way,you could use ViewData to store the class and judge in the server side:
Index.cshtml:
#{
var styleClass = ViewData["Style"];
}
<div id="MainDiv" class="flex justify-between items-center #styleClass">
<a>Done</a>
</div>
Controller:
public IActionResult Index()
{
if(YourCondition)
{
ViewData["Style"] = "bg-red-300";
}
else
{
ViewData["Style"] = "bg-green-300";
}
return View();
}
I will assume that you have a Model backing your view, with some C# property of a boolean type that will define if this field is valid, or not, let's call it Valid. Usually such validation is based on some more complex logic so I assume you have something like that.
You could edit the style inline, or replace whole blocks of HTML view depending on how much of the view you need to change based on this model value.
Both approaches are documented with example fiddle for you to eventually play with it.
I see you're using tailwindcss, so I have simply copied this style.
using System;
namespace ExampleApp
{
public class SampleViewModel
{
public bool Valid { get; set; }
}
}
Example Controller:
using System;
using System.Web.Mvc;
namespace ExampleApp
{
public class HomeController : Controller
{
[HttpGet]
public ActionResult Index()
{
var model = new SampleViewModel();
model.Valid = false;
return View(model);
}
}
}
And example view:
#model ExampleApp.SampleViewModel
#{
Layout = null;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Css based on Model</title>
<style type="text/css">
.bg-red-300 {
background-color: #feb2b2;
}
</style>
</head>
<body>
<div id="MainDiv" class="flex justify-between items-center">
<span>Done</span>
</div>
<!-- Whole block change -->
#if (Model.Valid)
{
<div id="MainDiv" class="flex justify-between items-center">
<span>Done</span>
</div>
}
else
{
<div id="MainDiv" class="flex justify-between items-center bg-red-300">
<span>Done</span>
</div>
}
<!-- inline style change -->
<div id="MainDiv2" class="flex justify-between items-center #(Model.Valid ? "" : " bg-red-300")">
<span>Done</span>
</div>
</body>
</html>
I have posted a simple .net fiddle for you to check live:
https://dotnetfiddle.net/h7JTR2
I new to blazor. I have hidden input with a specific value bind send to a parent component. I went ahead with this tutorial
Child Component is (name is MyControl):
<div>
#foreach (var result in this.Results)
{
if (result.IsFinal)
{
#(Text= result.Items[0].Transcript)
<input type="hidden" #oninput="OnTextChanged" value="Text" />
}
else
{
<img class="embed-responsive" style="height: 50px; width: auto;" src="https://gifdownload.net/wp-content/uploads/2019/01/blue-loader-gif-3.gif"/>
}
}
</div>
#code
{
[Parameter]
public string Text { get; set; }
[Parameter]
public EventCallback<string> TextChanged { get; set; }
private Task OnTextChanged(ChangeEventArgs e)
{
Text = e.Value.ToString();
return TextChanged.InvokeAsync(Text);
}
}
and Parent Comonent is:
#inherits LayoutComponentBase
<div class="sidebar">
<MyControl #bind-Text="text" />
<NavMenu />
</div>
<div class="main">
<div class="top-row px-4">
About
</div>
<div class="content px-4">
<CascadingValue Value="text">
#Body
</CascadingValue>
</div>
</div>
#code{
private string text;
}
and usages for example in index component:
#page "/"
#if (text != null)
{
<p>
#text
</p>
}
#code
{
[CascadingParameter]
public string text { get; set; }
}
note: I don't use a keyboard for wite text. input filling from #(Text= result.Items[0].Transcript).
totally I want to give text from a child and send to parent but I don't show the text (For this purpose, I used a hidden input) , after that send to all components by CascadingValue parameter, but not working and no errors, I don't know why.
This code: <input type="hidden" #oninput="OnTextChanged" value="Text" />
results in one way data-binding from the variable (Text property) to the control, but not the other way around, as no input UI is performed, and no input event is triggered to reflect this. Which is why the OnTextChanged handler is never called and the TextChanged 'delegate' is never invoked, and thus the parent component never gets a value from the Child Component( MyControl ).
To see that the rest of the code functions well, change 'hidden' to 'text', and type into the text box. This will trigger the input event, and the whole process will work.
I don't know what you're trying to do, but you should look for a solution without a hidden input that cannot get any input, and thus cannot trigger events, etc.
Hope this helps...
Just hide it with CSS.
I do the same thing in my open source project BlazorChat.
The way I do it is real simple with CSS:
CSS Class
.className
{
position: fixed;
left: -200px;
top: -200px;
}
Adjust based on the size of your object
Here is a working example:
Code
https://github.com/DataJuggler/BlazorChat
Or even a live site if you want to visit:
https://blazorchat.com
My use case is I have the MainLayout.razor with this code
#inherits LayoutComponentBase
<header><nav ....></header>
<section><h1>Page Title</h1><section>
<main class="container">
<div class="row"><div class="col-12">#Body</div></div>
</main>
Now I want to set the page title from every #Body razor fragment (maybe by inheritance)
#page "/about"
<div>....</div>
#code {
Title = "About Title";
}
I want avoid to put <section> inside the #body fragment.
Also have the same problem with the title-element from head-element. What is best practices to do this (without js interop)?
There are a couple of ways to do that...
Using CascadingValue feature
Define a property in MainLayout to get the title from child components such as
the about component.
Add a CascadingValue component to MainLayout, and pass the MainLayout component
as the value of the Value attribute.
In the child component define a CascadingParameter property which stores the
MainLayout object, and assign a title to its Title property
Here's the full code:
MainLayout
<div class="main">
<div class="top-row px-4 auth">
<h1>#Title</h1>
<LoginDisplay />
About
</div>
<div class="content px-4">
<CascadingValue Value="this">
#Body
</CascadingValue>
</div>
</div>
#code
{
string title;
public string Title
{
get => title;
set
{
if(title != value)
{
title = value;
StateHasChanged();
}
}
}
}
About.razor
#page "/about"
<div>....</div>
#code {
[CascadingParameter]
public MainLayout MainLayout { get; set; }
protected override void OnInitialized()
{
MainLayout.Title = "About Title";
}
}
Create a service class that defines a Title property that can be set by
components into which the service is injected. This service class should also provide a way to pass the title supplied by child components to the MainLayout, which should refresh itself in order to display the provided title...
Hope this helps...