Blazor - change text inside input - input

I am building the number input component in blazor an i just can't figure it out how to prevent the input to change on mousewheeel up/down. I have parameter 'DisableMouseWheel' in if true i want to prevent the number input for going up or down if mouse wheel is turned. If 'DisableMouseWheel' is true and the mouse wheel is turned it does skip the StepUp/StepDown methods but still change the value. Is there any options to solve this without javascript
I use also the 'Disabled' and 'Readonly' parameters for the input
My code
<input type="number"
step="1"
disabled="#Disabled"
readonly="#Readonly"
#onmousewheel="#OnMouseWheel"
#onwheel="#OnMouseWheel"
#bind-value="#_value" />
protected async Task OnMouseWheel(WheelEventArgs args)
{
if (DisableMouseWheel == false)
{
if (args.ShiftKey || Disabled || Readonly)
return;
if (args.DeltaY > 0)
{
await StepUp();
}
else
{
await StepDown();
}
} else
{
args.DeltaY = 0;
}
}
EDIT --> SOLUTION:
In this case i need to disable the #onkeydown:preventDefault and then handle everything in onkeydown event

You can use #oninput event of the checkbox. I implemented your code with that, and it works correctly.
<input type="checkbox" #oninput="ChangeMouseWheel"/>prevent the input to change on mouse wheel
<br/>
<input type="number"
step="1"
disabled="#Disabled"
readonly="#Readonly"
#onmousewheel="#OnMouseWheel"
#onwheel="#OnMouseWheel"
#bind-value="#_value"/>
#code section:
#code
{
bool DisableMouseWheel,Disabled,Readonly;
int _value=0;
protected async Task ChangeMouseWheel(ChangeEventArgs e)
{
DisableMouseWheel = (bool)e.Value;
//StateHasChanged();
}
protected async Task OnMouseWheel(WheelEventArgs args)
{
if (DisableMouseWheel == false)
{
if (args.ShiftKey || Disabled || Readonly)
return;
if (args.DeltaY > 0)
{
await StepDown();
}
else
{
await StepUp();
}
} else
{
args.DeltaY = 0;
}
}
protected async Task StepUp()
{
_value++;
}
protected async Task StepDown()
{
_value--;
}
}

Related

Fire event when the textbox changes in Blazor

I'm using EditForm and InputText to achieve data binding and forms validation. (Using <input #bind="SomeProperty" /> doesn't seem to fire component validation.)
I want to attach an event handler that gets fired whenever the value of the textbox changes:
<InputText #bind-Value="#model.ZipCode" #onkeypress="#model.InvokeThisMethod)"></InputText>
This sort of works, but when I'm in the event handler it's apparently firing before ZipCode has updated. The goal here is to do a thing when the zip code reaches 5 characters, not when the text input loses focus. I've also tried just forcing the change event to fire oninput but that just creates an error on the console about ChangedEventArgs to string not working.
How can I fire an event after the input field has updated the data binding?
After trying everything under the sun to get the grid to reload via the search string after inserting a new record, and failing, I found that I can just reload the grid by setting the search string to empty and displaying all the results (simply having a value in the search box prevents the grid from refreshing after an insert - there is no solution I could get to work for this), but at least it shows the record as being inserted. While this isn't the answer I was looking for, it will suffice for now.
Here's the index.razor page (final):
#page "/"
#inject IConfiguration config
#inject DialogService dialog
#inject NotificationService notification
<PageTitle>Memo Master</PageTitle>
<RadzenButton Click="() => GetMemos()" Text="Get Memos" ButtonStyle="ButtonStyle.Primary" ButtonType="ButtonType.Submit" />
<RadzenTextBox #ref="searchBox" Name="SearchPhrase" #bind-Value="#SearchString" MaxLength="400" #oninput="#(args => SearchString = args.Value?.ToString())" #onkeydown="#Enter" />
<RadzenButton Click="() => OpenMemo(0)" Text="New Memo" Icon="add_circle_outline" ButtonStyle="ButtonStyle.Secondary" />
<br />
<br />
#if (FoundMemos != null && !busy)
{
<RadzenDataGrid #ref=this.grid Data="#FoundMemos" TItem="MemoSearch" AllowFiltering="true" AllowSorting="true" AllowColumnResize="true" AllowPaging="true" PageSize=20
FilterCaseSensitivity="FilterCaseSensitivity.CaseInsensitive" SelectionMode="DataGridSelectionMode.Single" #bind-Value="#SelectedMemos" RowClick="#OnRowClicked">
<Columns>
<RadzenDataGridColumn TItem="MemoSearch" Title="Index" Width="70px" Filterable="false" TextAlign="TextAlign.Left">
<Template Context="m">
<RadzenText TextStyle="TextStyle.Caption">#m.Idx.ToString()</RadzenText>
</Template>
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="MemoSearch" Property="Title" Title="Title">
</RadzenDataGridColumn>
<RadzenDataGridColumn TItem="MemoSearch" Title="Modified" Width="140px" TextAlign="TextAlign.Right">
<Template Context="m">
<RadzenText TextStyle="TextStyle.Caption">#m.ModifiedOn.ToString("MM/dd/yyyy hh:mm tt")</RadzenText>
</Template>
</RadzenDataGridColumn>
</Columns>
</RadzenDataGrid>
}
else
{
<DisplaySpinner />
}
<br />
<br />
<RadzenButton Click="Reset" Text="Reset" ButtonStyle="ButtonStyle.Secondary" />
#code {
[Parameter]
public string? SearchString { get; set; }
List<MemoSearch> FoundMemos = new();
private string DBConnStr { get; set; } = "";
public DB dB = new();
IList<MemoSearch>? SelectedMemos;
RadzenTextBox? searchBox;
private bool busy;
private RadzenDataGrid<MemoSearch>? grid; //reference to grid, so forced reloading can happen - though it doesn't actually work
async Task OpenMemo(int Idx)
{
string DialogTitle = (Idx == 0) ? "Create New Memo" : $"Edit Memo {Idx.xToStr()}";
object? RefreshResults = await dialog.OpenAsync<MemoDetails>(DialogTitle, new Dictionary<string, object>() { { "Idx", Idx } });
RefreshResults = (RefreshResults == null) ? false : RefreshResults;
if (RefreshResults.xToBoo())
{
if (Idx == 0) SearchString = ""; //force setting to empty reloads grid but only w/o search entry
await GetMemos();
}
await ReturnFocus();
}
protected override async Task OnInitializedAsync()
{
dB.DBConnStr = await Task<string>.Run(()=> config.GetConnectionString("DBConnStr"));
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender) await ReturnFocus(); //NOTE: this is for Radzen "elements"
}
public async Task GetMemos()
{
busy = true;
FoundMemos = await dB.MemoSearchByPageFilterSortAsync(SearchString, PageSize: 9999);
busy = false;
}
public async Task Reset()
{
FoundMemos = new();
SearchString = "";
await ReturnFocus();
}
public async void Enter(KeyboardEventArgs e)
{
if (e.Code == "Enter" || e.Code == "NumpadEnter" || e.Key == "Enter")
{
await GetMemos();
StateHasChanged(); //need to call this here after keypress, lest you get a continual spinner
}
}
async Task OnRowClicked(Radzen.DataGridRowMouseEventArgs<MemoSearch> args)
{
if (args != null)
{
await OpenMemo(args.Data.Idx);
}
}
async Task ReturnFocus()
{
if (searchBox != null) await searchBox.Element.FocusAsync();
}
}
and the MemoDetails.razor page:
#inject IConfiguration config
#inject DialogService dialog
#inject NotificationService notification
#if (memo != null)
{
<RadzenTemplateForm TItem="Memo" Data=#memo Submit=#OnSubmit>
<p>
<RadzenLabel Component="Title" Text="Title" />
<RadzenTextBox id="MemoTitle" Name="Title" #bind-Value=#memo.Title MaxLength="400" />
<RadzenRequiredValidator Component="Title" Text="Title is required!" />
</p>
<p>
<RadzenLabel Component="Body" Text="Memo" />
<RadzenTextArea id="MemoBody" Name="Body" #bind-Value=#memo.Body Rows="18" />
</p>
<p>
<RadzenLabel Component="Keywords" Text="Key Words" />
<RadzenTextBox id="MemoKeywords" Name="Keywords" #bind-Value=#memo.Keywords MaxLength="400" />
</p>
<RadzenButton ButtonType="ButtonType.Submit" ButtonStyle="ButtonStyle.Success" Icon="save" Text="Save" BusyText="Saving ..." IsBusy=#busy />
#if (Idx > 0)
{
<RadzenButton ButtonType="ButtonType.Button" ButtonStyle="ButtonStyle.Danger" Icon="delete" Text="Delete" Click="#((args) => DeleteMemo(memo.Idx))" #onclick:stopPropagation="true"></RadzenButton>
}
<RadzenButton Text="Close" Click="() => dialog.Close(false)" ButtonStyle="ButtonStyle.Light" />
</RadzenTemplateForm>
}
#code {
[Parameter]
public int Idx { get; set; } = 0;
public DB dB = new();
Memo? memo;
bool busy;
protected override async void OnInitialized()
{
dB.DBConnStr = config.GetConnectionString("DBConnStr");
memo = (Idx == 0) ? new Memo() : await GetMemoByIdx(Idx);
await InvokeAsync(() => StateHasChanged()).ConfigureAwait(false); //IMPORTANT!!
}
public async Task<Memo> GetMemoByIdx(int Idx) => await dB.MemoSelectByIdxAsync(Idx);
async Task OnSubmit(Memo memo)
{
int Result;
bool RefreshResults = false;
if (memo.ModifiedOn == DateTime.MinValue) memo.ModifiedOn = DateTime.Now;
string NotificationDetailMessage = memo.Idx == 0 ? "New Memo has been created." : $"Memo {memo.Idx} has been saved.";
busy = true;
Result = await dB.MemoUpsertAsync(memo);
if (Result < -1)
{
notification.Notify(new NotificationMessage { Severity = NotificationSeverity.Error, Summary = "Error Saving", Detail = "An error saving this record has occured!\n" + dB.LastErrorMsg, Duration = 4000 });
}
else
{
notification.Notify(new NotificationMessage { Severity = NotificationSeverity.Success, Summary = "Save Success", Detail = NotificationDetailMessage, Duration = 2000 });
RefreshResults = true;
}
busy = false;
dialog.Close(RefreshResults);
}
async Task DeleteMemo(int Idx)
{
int Result;
bool RefreshResults = false;
var confirmResult = await dialog.Confirm("Are you sure?", "Confirm Memo Deletion");
if (confirmResult.HasValue && confirmResult.Value)
{
busy = true;
Result = await dB.MemoDeleteByIdxAsync(Idx);
if (Result < -1)
{
notification.Notify(new NotificationMessage { Severity = NotificationSeverity.Error, Summary = "Error Deleting", Detail = "An error deleting this record has occured!\n" + dB.LastErrorMsg, Duration = 4000 });
}
else
{
notification.Notify(new NotificationMessage { Severity = NotificationSeverity.Success, Summary = "Deletion Success", Detail = $"Memo {Idx} has been deleted.", Duration = 2000 });
RefreshResults = true;
}
}
busy = false;
dialog.Close(RefreshResults);
}
}
Good enough, for now, but still baffled as to what Radzen is doing behind the scenes.
<InputText class="form-control" Value="#model.ZipCode" ValueExpression="#( ()=> model.ZipCode )" ValueChanged="#( (string s) => model.InvokeThisMethod(s) )"></InputText>
I didn't find this in the Microsoft documentation. I found it on the Telerik site for their Blazor controls (which apparently works for the non Telerik controls).
Dealing with form fields in Blazor really sucks right now.
<input #bind="model.Property" />
or
<InputText #bind-Value="model.Property" />
and then the ValueExpression attributes shown above. The intellisense in .razor files for VS2019 is poorly implemented. This sucks.

Is there a way to update a binding variable attached to an Input text Item in Blazor when using Ctrl +V combination keys?

I have this input which is to capture a phone number.
When the user enters a number and press the "Enter" Key the Method "KeyWasPressed" is triggered and some validation happens. this works as expected BUT...
When the user copies and pastes the number from excel, for example, the variable #Phone doesn't updates its value so when the user presses the "Enter" key the validation sends and empty value.
Is there a way to refresh/update #Phone variable when some text is pasted to the input control?
Here is a snipped of my code:
<input type="number" class="form-control" #bind="#Phone" #onkeypress="#(async e => await KeyWasPressed(e))" placeholder="Client Phone Number" />
#code {
string Phone { get; set; }
private async Task GetClientInfo()
{
if(String.IsNullOrWhiteSpace(Phone))
{
notificationMessages = $"Add a phone number";
}
else
{
showSpinner = true;
clientInfo = await ApiHelper.GetClientInfoByPhone(Phone);
if(clientInfo != null)
{
var singleViewId = clientInfo?.SingleViewId;
var customerNumber = clientInfo?.Accounts?.FirstOrDefault().CustomerNumber;
var status = clientInfo?.Accounts?.FirstOrDefault().Status;
showClientInformation = true;
var CrossSell = clientInfo?.Accounts[0]?.CrossSell;
}
else
{
showClientInformation = false;
notificationMessages = $"No client data for this phone ({Phone})";
}
showSpinner = false;
}
}
private async Task KeyWasPressed(KeyboardEventArgs args)
{
if(args.Key == "Enter")
{
//await GetClientInfo();
}
}
}
Direct solution:
Just use #bind-value="#Phone" #bind-value:event="oninput":
<input type="number" #bind-value="#Phone" #bind-value:event="oninput"
#onkeyup="#OnUserFinish"/>
<p>#clientInfo</p>
#code {
protected string Phone { get; set; }
protected string clientInfo {get; set;}
private async Task OnUserFinish(KeyboardEventArgs e)
{
if (e.Key == "Enter")
clientInfo = await Fake_ApiHelper_GetClientInfoByPhone(Phone);
}
private async Task<string> Fake_ApiHelper_GetClientInfoByPhone(string phone)
{
await Task.CompletedTask;
return $"Client phone: {phone}";
}
}
Bonus track:
Move to a user friendly debounce version:
#using System.Timers;
<input type="number" #bind-value="#Phone" #bind-value:event="oninput"
#onkeyup="#HandleKeyUp"/>
<p>#clientInfo</p>
#code {
protected string Phone { get; set; }
protected string clientInfo {get; set;}
private System.Timers.Timer aTimer;
protected override void OnInitialized()
{
aTimer = new System.Timers.Timer(250);
aTimer.Elapsed += OnUserFinish;
aTimer.AutoReset = false;
}
void HandleKeyUp(KeyboardEventArgs e)
{
// remove previous one
aTimer.Stop();
// new timer
aTimer.Start();
}
private void OnUserFinish(Object source, ElapsedEventArgs e)
{
InvokeAsync( async () =>
{
clientInfo = await Fake_ApiHelper_GetClientInfoByPhone(Phone);
StateHasChanged();
});
}
private async Task<string> Fake_ApiHelper_GetClientInfoByPhone(string phone)
{
await Task.CompletedTask;
return $"Client phone: {phone}";
}
}
The Reason
I could reproduce the same issue. Turns out the reason is when we copy sth & paste into the input, and then press the Enter key, the enter key event is triggered before the change event.
See the event sequence:
Because the Enter KeyPress event is triggered before the change event, the Phone property has not been updated yet.
How to fix
One possible walkaround is to listen the paste event. But unfortunately, there's currently a limitation when using Blaozr's native onpaste (See https://github.com/aspnet/AspNetCore/issues/14133#issuecomment-533198522).
Since the Team member suggests that we should use jsinterop, we can add a interHelper.handlePaste function:
<script>
var interHelper={
handlePaste: function (){
var node = document.getElementById('phoneInput');
return node.value;
},
}
</script>
<script src="_framework/blazor.server.js"></script>
And then refresh the latest value manually when pasting:
<input id='phoneInput' type="number" class="form-control" #bind="#Phone"
#onpaste="HandlePaste"
#onkeypress="#(async e => await KeyWasPressed(e))"
placeholder="Client Phone Number" />
#code {
...
private async Task HandlePaste(ClipboardEventArgs e){
var str = await jsRuntime.InvokeAsync<string>("interHelper.handlePaste");
this.Phone= str;
}
}
Demo
Copy "107783" & Paste into the input & Press the Enter Key:

Aurelia Custom element with dialog

I am having trouble with creating a custom element that will be used like
<shimmy-dialog type="video" href="/test">Hi</shimmy-dialog>
The custim element will replace this code with a href that when clicked should popup a dialog of a particular type.
Everything seems to work up until the point I try to open the dialog.
This is when I get the error
Unhandled rejection TypeError: Cannot set property 'bindingContext' of null
I do sometimes find the Aurelia errors a little cyptic.
I suspect it has something todo with the element not having a view.
The code is as follows
enum DialogType {
video = 1,
iframe
};
#inject(Bcp, DialogController)
export class ShimmyDialogModel {
private type : DialogType;
constructor(private bcp: Bcp, private controller : DialogController){
console.log("here");
}
async activate(state){
this.type = state['type'];
}
get isVideo() : boolean {
return this.type == DialogType.video;
}
get isIframe() : boolean {
return this.type == DialogType.iframe;
}
}
#noView
#processContent(false)
#customElement('shimmy-dialog')
#inject(Element, App, Bcp, DialogService)
export class ShimmyDialog {
#bindable public type : string;
#bindable public href;
#bindable public name;
private originalContent : string;
constructor(private element: Element, private app: App, private bcp: Bcp,
private dialogService: DialogService) {
this.originalContent = this.element.innerHTML;
}
bind() {
this.element.innerHTML = '' + this.originalContent + '';
}
attached() {
let self = this;
this.type = this.element.getAttribute("type");
let dialogType = DialogType[this.type];
this.element.children[0].addEventListener("click", function(){
if(dialogType == DialogType.iframe) {
self.dialogService.open({ viewModel: ShimmyDialogModel, model: {'type' : dialogType}}).then(response => {
});
}
else if(dialogType == DialogType.video) {
self.dialogService.open({ viewModel: ShimmyDialogModel, model: {'type' : dialogType}}).then(response => {
});
}
return false;
});
}
async typeChanged(newValue) {
this.type = newValue;
}
async hrefChanged(newValue) {
this.href = newValue;
}
}
The template for the dialog is below.
<template>
<require from="materialize-css/bin/materialize.css"></require>
<ai-dialog>
<ai-dialog-header>
</ai-dialog-header>
<ai-dialog-body>
<div if.bind="isVideo">
Video
</div>
<div if.bind="isIframe">
IFrame
</div>
</ai-dialog-body>
<ai-dialog-footer>
<button click.trigger="controller.cancel()">Close</button>
</ai-dialog-footer>
</ai-dialog>
</template>
Thanks for any help.
I solved this by seperating the classes into their own files.
Aurelia did no like having two export classes there.

Razor can't see my closing brace

I am experiencing a very strange issue with Razor2 views. The code below is the actual code on which intellisense is throwing down the red squiggly, just names have been changed.
#model Space.ViewModels.PropertyAdminViewModel
#{
ViewBag.Title = "Create";
ViewBag.UmbracoTitle = "Create Property";
Layout = "../Shared/_Layout.cshtml";
InitView();
AnalysePreviousState();
SortErrorsPerStep();
GetStepToDisplay();
}
<script src="../../Scripts/jquery-1.8.2.min.js"></script>
<script src="../../Scripts/jquery.validate.min.js"></script>
<script src="../../Scripts/jquery.validate.unobtrusive.min.js"></script>
<script src="../../Scripts/AdminScripts.js"></script>
#Html.ValidationSummary(ErrorMessage)
#using (Html.BeginForm())
{
#Html.Hidden("CurrentStep", StepToDisplay.ToString(), new { id="CurrentStep"})
<input id="AddressButton" type="submit" name="#Button.AddressButton.ToString()" value="Address Details" />
<input id="DetailsButton" type="submit" name="#Button.DetailsButton.ToString()" value="Details & Description" />
<input id="MediaButton" type="submit" name="#Button.MediaButton.ToString()" value="Images & Documents" />
switch (StepToDisplay)
{
case Step.Address:
Html.RenderPartial("_AddressDetailsForm", ViewData.Model.Address);
break;
case Step.Details:
Html.RenderPartial("_PropertyDetailsForm", ViewData.Model);
break;
case Step.Media:
Html.RenderPartial("_MediaUploadEditForm", ViewData.Model.MediaItems);
break;
}
<input id="BackButton" type="submit" name="#Button.BackButton.ToString()" value="Back" />
<input id="NextButton" type="submit" name="#Button.NextButton.ToString()" value="Next" />
<input id="FinishButton" type="submit" name="#Button.FinishButton.ToString()" value="Finish" />
} <-- `SQUIGGLY`
#{
private enum Button
{
None,
AddressButton,
DetailsButton,
MediaButton,
NextButton,
BackButton,
FinishButton,
CancelButton
}
public enum Step
{
Address,
Details,
Media,
UpperBound
}
private const Step AddressStep = Step.Address;
private const Step DetailsStep = Step.Details;
private const Step MediaStep = Step.Media;
private const Step First_Step = AddressStep;
private const Step Last_Step = MediaStep;
private const string CurrentStep = "CurrentStep";
private const string DisabledAttribute = "disabled='disabled'";
private string BackButtonState = string.Empty;
private string NextButtonState = string.Empty;
private string ErrorMessage = "Please correct the errors and try again.";
private Button ButtonPressed = Button.None;
private Step PreviousStepDisplayed = AddressStep;
private Step StepToDisplay = AddressStep;
private ModelStateDictionary[] StepModelState = new ModelStateDictionary[(int)Step.UpperBound];
private void InitView()
{
// Create a ModelState for each individual step
for (int key = (int)First_Step; key <= (int)Last_Step; key++)
{
StepModelState[key] = new ModelStateDictionary();
}
}
/// <summary>
/// Check form variables from the last HTTP_POST to figure where the wizard needs to be
/// Grab the current step string along with which button was pressed
/// </summary>
private void AnalysePreviousState()
{
if (!string.IsNullOrEmpty(Request[CurrentStep]))
{
PreviousStepDisplayed = (Step)Enum.Parse(typeof(Step), Request[CurrentStep], true);
}
if (!string.IsNullOrEmpty(Request[Button.FinishButton.ToString()]))
{
ButtonPressed = Button.FinishButton;
}
if (!string.IsNullOrEmpty(Request[Button.CancelButton.ToString()]))
{
ButtonPressed = Button.CancelButton;
}
if (!string.IsNullOrEmpty(Request[Button.NextButton.ToString()]))
{
ButtonPressed = Button.NextButton;
}
if (!string.IsNullOrEmpty(Request[Button.BackButton.ToString()]))
{
ButtonPressed = Button.BackButton;
}
if (!string.IsNullOrEmpty(Request[Button.AddressButton.ToString()]))
{
ButtonPressed = Button.AddressButton;
}
if (!string.IsNullOrEmpty(Request[Button.DetailsButton.ToString()]))
{
ButtonPressed = Button.DetailsButton;
}
if (!string.IsNullOrEmpty(Request[Button.MediaButton.ToString()]))
{
ButtonPressed = Button.MediaButton;
}
}
/// <summary>
/// Sort all modelstate errors into the right step
/// </summary>
private void SortErrorsPerStep()
{
foreach (KeyValuePair<string, ModelState> entry in ViewData.ModelState)
{
foreach (int key in Enum.GetValues(typeof(Step)))
{
//Compare the start of each error's key with the name of a step
//if they match then that error belongs to that step
if (entry.Key.StartsWith(((Step)key).ToString()))
{
StepModelState[key].Add(entry);
break;
}
}
}
ViewData.ModelState.Clear();
}
/// <summary>
/// Look at the previous step to get any errors and which button was clicked
/// and decide which step needs to be displayed now
/// </summary>
private void GetStepToDisplay()
{
//if the user tried to jump steps or finish, display the first step that has errors
//this ensures that the wizard is completed in the intended sequence
if (ButtonPressed != Button.NextButton && ButtonPressed != Button.BackButton)
{
ErrorMessage = "There are errors in the data provided. Please correct the errors and try again.";
for (Step key = First_Step; key <= Last_Step; key++)
{
if (!StepModelState[(int)key].IsValid)
{
DisplayStep(key, true);
return;
}
}
}
//if the last step has errors and the user has not hit the back button then stay on page and show the errors
//user can go back through steps but not forward until errors are resolved
if (!StepModelState[(int)PreviousStepDisplayed].IsValid && ButtonPressed != Button.BackButton)
{
DisplayStep(PreviousStepDisplayed, true);
return;
}
//Otherwise move as per user request
Step stepToDisplay = PreviousStepDisplayed;
switch (ButtonPressed)
{
case Button.BackButton:
stepToDisplay--;
break;
case Button.NextButton:
stepToDisplay++;
break;
case Button.AddressButton:
stepToDisplay = AddressStep;
break;
case Button.DetailsButton:
stepToDisplay = DetailsStep;
break;
case Button.MediaButton:
stepToDisplay = MediaStep;
break;
}
stepToDisplay = (Step)Math.Max((int)stepToDisplay, (int)First_Step);
stepToDisplay = (Step)Math.Min((int)stepToDisplay, (int)Last_Step);
DisplayStep(stepToDisplay, false);
}
private void DisplayStep(Step stepToDisplay, bool displayErrors)
{
StepToDisplay = stepToDisplay;
BackButtonState = stepToDisplay == First_Step ? DisabledAttribute : string.Empty;
NextButtonState = stepToDisplay >= Last_Step ? DisabledAttribute : string.Empty;
//page number
if (displayErrors)
{
foreach (KeyValuePair<string, ModelState> entry in StepModelState[(int)stepToDisplay])
{
ViewData.ModelState.Add(entry.Key, entry.Value);
}
}
}
}
When I hover over Viewbag I get the usual intellisense popup, and hovering on Title explains the normal 'evaluated at runtime'.
Has anyone run into this issue? I have looked over other questions but they all had code typos or genuine mistakes. I know it's not an assembly reference issue otherwise Intellisense wouldn't know what the Viewbag is.
UPDATE
It looks like Intellisense is having a problem specifically dynamic assignments. Normal server code does not experience the above issue, whereas anything like Viewbag assignments or specifying the layout within #{//code here} seems broken. Also note that this only occurs on one of my .cshtml files and the others are unaffected.
UPDATE 2
This is the result of testing the view. Source file is the generated code file in ASP.NET temp files.
Compiler Error Message: CS1513: } expected
Source Error:
Line 259: #line default
Line 260: #line hidden
Line 261:WriteLiteral("\r\n");
Line 262:
And this is the code relating to the above error in the compiled temp file:
WriteLiteral(" value=\"Finish\"");
WriteLiteral(" /> \r\n");
#line 46 "F:....Create.cshtml"
} <-- see the brace!? It's right there so why does the compiler freak out?
#line default
#line hidden
WriteLiteral("\r\n");
You need this:
#switch (StepToDisplay)
It's seeing the closing switch brace as the close to your earlier one. You may even need to enclose the switch in a razor code block #{ switch(){} }
As an aside, if you were using ReSharper, it would highlight the actual block of code it things it is, and you would see where Razor is thinking the end of the block would be.
EDIT:
It should look like this:
#using (Html.BeginForm())
{
// remove the # from before the Html on this line
Html.Hidden("CurrentStep", StepToDisplay.ToString(), new { id="CurrentStep"})
// This html causes razor to switch back to "html mode"
<input id="AddressButton" type="submit" name="#Button.Step1Button.ToString()"...
...
// Add an # here, because razor is now in Html mode
#switch (StepToDisplay)
{
...
}
// Back in html mode again, so we need #'s
<input id="BackButton" type="submit" name="#Button.BackButton.ToString()"...
...
}
I had no idea that you can't write normal c# code into Razor views.

Custom control child controls not persisted to ViewState in ASP.NET 4.0

We just switched target framework in our ASP.NET web application from 3.5 to 4.0. We ran into the following problem:
We have a couple of custom controls that worked fine in 3.5 but now with 4.0 they are not persisted in ViewState, one of them is basically a wrapper for other controls that inherits the Label class and the aspx-code looks like this:
<fsc:FormLabel ID="l_purchaserNo" runat="server" CssClass="label" Text="Purchaser">
<asp:TextBox ID="tb_purchaserNo" runat="server" CssClass="textBox" MaxLength="50" />
</fsc:FormLabel>
and the resulting html is:
<span id="l_purchaserNo" class="label">
<label class="header" for="tb_purchaserNo">Purchaser</label>
<span class="valueContainer">
<input name="tb_purchaserNo" type="text" id="tb_purchaserNo" class="textBox" />
</span>
</span>
So the control basically just adds a few span-tags and a label that is connected to the textbox.
After postback the html-code in 4.0 was:
<span id="l_purchaserNo" class="label"></span>
i.e. everything within the outer wrapper was gone and anything entered in the textbox could not be retreived from code behind.
Below you find the code for our FormLabel class.
We found that by setting ViewStateMode=Disabled on our custom control fsc:FormLabel and ViewStateMode=Enabled on the asp:TextBox the inner controls where persisted to ViewState but at the same time we lost ViewState on the wrapper and since we translate the text on the wrapper label we need viewstate for this as well (actually we tried every combination and setting ViewStateMode=Enabled on fsc:FormLabel did not help, regardless of how we set the ViewStateMode on the page). EnableViewState is true on all levels.
Could someone tell us how to get ViewState to work as before in 3.5, on ALL controls - wrapper as well as wrapped controls?
Thanks
Christian, Zeljko, Jonas
using System;
using System.Collections.Generic;
using System.Web.UI;
using System.Web.UI.WebControls;
namespace FormLabel
{
public class FormLabel : Label
{
private bool HasChildren
{
get
{
return Controls.Count > 0;
}
}
public override string Text
{
get
{
if (!HasChildren)
{
return base.Text;
}
var text = ViewState[ID + "Text"] as String;
if (text != null)
{
((LiteralControl)Controls[0]).Text = text;
}
return ((LiteralControl)Controls[0]).Text;
}
set
{
if (!HasChildren)
{
base.Text = value;
return;
}
((LiteralControl)Controls[0]).Text = value;
}
}
public void SetText(string text)
{
((LiteralControl)Controls[0]).Text = text;
ViewState[ID + "Text"] = text;
}
public bool IndicateRequired
{
get
{
object state = ViewState[String.Format("{0}_IR", ID)];
return state != null && (bool)state;
}
set
{
ViewState[String.Format("{0}_IR", ID)] = value;
}
}
protected override void OnLoad(EventArgs e)
{
ViewState[ID + "Text"] = Text;
base.OnLoad(e);
}
protected override void Render(HtmlTextWriter writer)
{
if (HasChildren)
{
List<Control> controls = Controls.GetControls();
List<BaseValidator> validators = Controls.GetValidators();
WriteLabelControl(writer);
WriteRequiredIndicator(writer);
WriteControlHeader(writer, validators, this.GetFirstControl());
WriteInnerControls(writer, controls);
WriteLabelEndControl(writer);
return;
}
base.Render(writer);
}
private void WriteLabelControl(HtmlTextWriter writer)
{
writer.WriteBeginTag("span");
writer.WriteAttribute("id", ClientID);
writer.WriteAttribute("class", CssClass);
foreach (string attributeKey in Attributes.Keys)
{
writer.WriteAttribute(attributeKey, Attributes[attributeKey]);
}
writer.Write(HtmlTextWriter.TagRightChar);
}
private void WriteRequiredIndicator(HtmlTextWriter writer)
{
if (IndicateRequired)
{
writer.WriteBeginTag("span");
writer.WriteAttribute("class", "requiredIndicator");
writer.Write(HtmlTextWriter.TagRightChar);
writer.WriteEndTag("span");
}
}
private void WriteControlHeader(HtmlTextWriter writer, IEnumerable<BaseValidator> validators, Control userControl)
{
writer.WriteBeginTag("label");
writer.WriteAttribute("class", "header");
if (userControl != null)
{
writer.WriteAttribute("for", userControl.ClientID);
}
writer.Write(HtmlTextWriter.TagRightChar);
writer.Write(Text);
foreach (BaseValidator validator in validators)
{
validator.CssClass = "Error";
validator.RenderControl(writer);
}
writer.WriteEndTag("label");
}
private static void WriteInnerControls(HtmlTextWriter writer, IList<Control> controls)
{
writer.WriteBeginTag("span");
writer.WriteAttribute("class", "valueContainer");
writer.Write(HtmlTextWriter.TagRightChar);
for (int i = 1; i < controls.Count; i++)
{
controls[i].RenderControl(writer);
}
writer.WriteEndTag("span");
}
private static void WriteLabelEndControl(HtmlTextWriter writer)
{
writer.WriteEndTag("span");
}
}
#region Nested type: Extensions
public static class Extensions
{
public static List<BaseValidator> GetValidators(this ControlCollection controls)
{
var validators = new List<BaseValidator>();
foreach (Control c in controls)
{
if (c is BaseValidator)
{
validators.Add(c as BaseValidator);
}
}
return validators;
}
public static List<Control> GetControls(this ControlCollection controls)
{
var listcontrols = new List<Control>();
foreach (Control c in controls)
{
if (!(c is BaseValidator))
{
listcontrols.Add(c as Control);
}
}
return listcontrols;
}
public static Control GetFirstControl(this Control container)
{
return (new InputControlFinder().FindFirstInputControl(container));
}
private class InputControlFinder
{
public Control FindFirstInputControl(Control control)
{
Control input = null;
foreach (Control child in control.Controls)
{
if (child is TextBox || child is DropDownList || child is CheckBox)
{
input = child;
}
else
{
input = FindFirstInputControl(child);
}
if (input != null)
{
return input;
}
}
return input;
}
}
}
#endregion
}