Blazor - iterate through EditForm - html

I am building a (static) website in Blazor.wasm where the users upload some number of files. My intention is then (after all the files have passed some basic checks) to iteratively present a set of fields which the users are asked to complete. Only after they have submitted all the [Required] information and press submit will the next form show up.
I have included a minimal example below.
if (valid_files == numFiles)
{
for (int counter = 0; counter < num_files; counter++)
{
paramList.Add(new ParamsForm { });
<EditForm Model="#paramList[counter]" OnValidSubmit="#SingleSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
Camera type <br>
<InputText id="cameratype" #bind-Value="#paramList[counter].CameraType" />
</p>
<button type="submit">Submit</button>
</EditForm>
}
<button #onclick="HandleValidSubmit">Upload Data </button>
}
The expected behaviour is that on each iteration, a frech instance of the onbject ParamsForm is added to the list. We then create a form based of that instance and wait for the user to complete the form. Once they press the Submit button the next stage of the for loop begins. Once all the data have been submitted and the for loop is completed, the Upload data button should appear and the users are invited to submit all their data to the server.
Instead, none of the code inside the EditForm ... section is being completed. I.e. - I see no popping up of text boxes, and any code that I put in there (for example #Console.WriteLine("This should show up) does not seem to be executed. The Upload data button does not appear and instead an error is thrown complaining that the index is out of range, which is weird because after the code at the top of the for loop there are no longer any elements being accessed by an index.
I am quite new to interacting between c# and HTML, so I think I can appreciate why what I have shouldn't work, but I don't know how I can go about writing something that will work.
Any advice would be gratefully recieved.

The ways of Blazor are a bit mysterious to me, too-- it takes a while to adjust! I do know that Blazor has an OnAfterRender event, which makes me think that it might not like to have user input in a loop like that. Or it may be that it's enumerating if (valid_files == numFiles) as false because those variables aren't initialized yet when the markup first renders.
I'd try two things:
(1) Throw StateHasChanged() at the end of your loop or after the code that sets valid_files and numFiles and see if that does anything you like.
(2) Probably this anyway: instead of looping in the markup, I'd build the entire List<ParamsForm> paramsList in the FileInput's event handler instead, move the counter to the code block, and add counter++ to the end of the SingleSubmit() method.
It's 5:00 am here, just got up to get a snack and going back to bed. Let me know if things still don't fly, and I'll try a more complete example tomorrow. :D

I don't have much information about your class, where you are getting your file list from, and so on. I recommend passing complete objects rather than individual properties. For example, I'd rather have IBrowserFile File {get; set;} in my ParamsForm class than say string FileName. That way, if I decide-- oh, I want to get this or that property-- it's already there.
Anyway, hope something in here might be useful:
#if (CurrentForm is not null)
{
<EditForm Model="CurrentForm" OnValidSubmit="#SingleSubmit">
<DataAnnotationsValidator />
<ValidationSummary />
<p>
Camera type <br>
<InputText id="cameratype" #bind-Value="CurrentForm.CameraType" />
</p>
<button type="submit">Submit</button>
</EditForm>
#if (IsComplete) // Don't show upload button until you're done
{
<button #onclick="DoUploads">Upload Data </button>
}
#DisplayMessage
}
#code {
class ParamsForm { public string FileName; public string CameraType; } // Just a placeholder
List<ParamsForm> ParamsList = new List<ParamsForm>();
ParamsForm CurrentForm { get; set; }
int counter = 0;
List<string> FileNames;
bool IsComplete = false;
string DisplayMessage = "";
void InitializeForms()
{
// I don't know your class, so just an example
foreach (var item in FileNames)
{
bool IsValid = false;
// check file validity
if (IsValid) ParamsList.Add(new ParamsForm() { FileName = item });
}
if(ParamsList.Count > 0)
CurrentForm = ParamsList[0];
}
void SingleSubmit()
{
// Do stuff with CurrentForm
if (++counter >= ParamsList.Count) IsComplete = true;
else CurrentForm = ParamsList[counter];
}
async Task DoUploads()
{
// Do stuff with your ParamsList
int UploadCounter = 0;
foreach (ParamsForm item in ParamsList){
DisplayMessage = "Uploading " + UploadCounter + " of " + ParamsList.Count;
StateHasChanged();
// Do the Upload;
}
DisplayMessage = "Finished.";
}
}

Related

How to represent a state machine with HTML elements?

On a web page I wish to display an element which depends on the state of some JavaScript. State like in a state machine. Currently the possible states are these (but I may add more):
input: display some input elements for the user to set. The user can click a button to start some JavaScript processing and move to the working state.
working: display a progress bar informing the user that the script is running. The user can cancel the computation (moving back to the input state) or the computation can end (moving to either the result or error state).
result: display the computation result. The user can go back to input with a button.
error: display the error. The user can go back to input with a button.
The JavaScript part is ready and working, but I'm unsure how to do this in HTML + CSS.
Current solution and its issue
Currently I've been doing it with classes: I set a class to a common ancestor element with the same name of the state and I display the right elements based on it. Something like this:
const parent=document.querySelector("#parent");
let timer=null;
function input(){
parent.classList.remove("working","result","error");
parent.classList.add("input");
}
function run(){
parent.classList.remove("input");
parent.classList.add("working");
timer=setTimeout(result,1500)
}
function stop(){
clearTimeout(timer);
input();
}
function result(){
parent.classList.remove("working");
if(Math.random()>0.5){parent.classList.add("result");}
else{parent.classList.add("error");}
}
input();
#input{display:none;}
#working{display:none;}
#result{display:none;}
#error{display:none;}
#parent.input #input{display:block;}
#parent.working #working{display:block;}
#parent.result #result{display:block;}
#parent.error #error{display:block;}
<div id="parent">
<div id="input">INPUT. RUN</div>
<div id="working">WORKING. STOP</div>
<div id="result">RESULT. RESTART</div>
<div id="error">ERROR. RESTART</div>
</div>
This solution works but it feels unstable: in theory it would be possible for the parent element to have no classes (in which case nothing is displayed) or multiple ones (in which case you'd see multiple states at once). This shouldn't happen, but the only thing preventing it is the correctness of my script.
Question
Are there better ways to implement this idea of states, so that the HTML elements can't end up in inconsistent states?
Let’s consider the role which HTML plays in a state machine on the web. A machine has moving parts, it is dynamic, so the core of any machine on the web must be implemented in Javascript. HTML is useful only to provide the interface between the user and the machine. It’s a subtle distinction but it fundamentally changes the way you write it.
Have you ever used React? React provides the framework to create entire web applications as “state machines”. React’s mantra is “UI is a function of state”. In a React app, you have a single variable which contains the current state, rendering code which builds the UI based on the state, and core code (mostly event handlers) which updates the state.
Even if you don’t want to build in React, you can use the same general idea:
keep the current state in a Javascript variable (typically you’d use an object, but in this case we only need a string)
write a rendering function which reads the state and then builds the appropriate HTML to represent that state
in the event handlers for your links, do any operations which are required, update the state and call the rendering function
let state = null
let timer = null
// core code
const input = () => {
state = 'input'
render()
}
const run = () => {
state = 'working'
render()
timer = setTimeout(result,1500)
}
const stop = () => {
clearTimeout(timer)
state = 'input'
render()
}
const result = () => {
if(Math.random()>0.5)
state = 'result'
else
state = 'error'
render()
}
// rendering code
const render = () => {
let x = state
switch(state) {
case 'input':
x += ' run'
break
case 'working':
x += ' stop'
break
case 'result':
x += ' restart'
break
case 'error':
x += ' restart'
break
}
document.getElementById('container').innerHTML = x
}
// initialisation code
state = 'input'
render()
<div id="container"></div>

ngOnChanges only works when it's not the same value

So basically I have a modal component with an input field that tells it which modal should be opened (coz I didn't want to make a component for each modal):
#Input() type!:string
ngOnChanges(changes: SimpleChanges): void {
this.type = changes["type"].currentValue;
this.openModal();
}
that field is binded to one in the app component:
modalType = "auth";
HTML:
<app-modal [type] = modalType></app-modal>
In the beginning it's got the type "auth" (to login or register), but when I click on an icon I want to open a different modal, I do it like so:
<h1 id="options-route"
(click) ="modalType = 'settings'"
>⚙</h1>
but this only works the first time, when modalType already has the value "settings" the event doesn't trigger even though the value has technically changed
I think the problem is that it's the same value because i tried putting a button that does the exact same thing but with the value "auth" again and with that it was clear that the settings button only worked when tha last modal opened was auth and viceversa
any ideas? I want to be able to open the settings modal more than once consecutively possibly keeping onChange because ngDoCheck gets called a whole lot of times and it slows down the app
You need to include the changeDetectorRef, in order to continue in this way.
More about it https://angular.io/api/core/ChangeDetectorRef
Although, a better and a faster alternative is the use of a behavior Subject.
All you have to do is create a service that makes use of a behavior subject to cycle through each and every value exposed and then retrieve that value in as many components as you want. To do that just check for data changes in the ngOnInit of target component.
You may modify this for implementation,
private headerData = new BehaviorSubject(new HeaderData());
headerDataCurrent = this.headerData.asObservable();
changeHeaderData(headerDataNext : HeaderData) {
this.headerData.next(headerDataNext)
console.log("subscription - changeUserData - "+headerDataNext);
}
Explanation:
HeaderData is a class that includes the various values that can be shared with respective data types.
changeHeaderData({obj: value}), is used to update the subject with multiple values.
headerDataCurrent, an observable has to be subscribed to in the target component and data can be retrieved easily.
I mean i'm too l-a-z-y to use your slightly-not-so-much-tbh complicated answers so I just did this:
I added a counter that tops to 9 then gets resetted to 0 and I add it to the value
screwYouOnChangesImTheMasterAndYouShallDoMyBidding = 0;
//gets called onClick
openSettings(){
if(this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding === 9){
this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding = 0;
}
this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding = this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding + 1;
this.modalType = "settings"+this.screwYouOnChangesImTheMasterAndYouShallDoMyBidding;
}
then in the child component I just cut that last character out:
ngOnChanges(changes: SimpleChanges): void {
let change = changes["type"].currentValue as string;
change = change.substring(0, change.length - 1);
this.type = change;
this.openModal();
}
works like a charm 😂

Wixcode "static" html iframe

Apologies if this is not the correct place to post this. I'm completely new to HTML and such, but I wanted to put a button on my website which would remember how many times it been pressed and each time someone presses it it give you a number, say for example the next prime number. With enough googleing I managed to put together some (what I expect is really bad code) which I thought could do this. This is what I have (sorry if its not formatted correctly, I had trouble with copy pasting).
<head>
<title>Space Clicker</title>
</head>
<body>
<script type="text/javascript">
function isPrime(_n)
{
var _isPrime=true;
var _sqrt=Math.sqrt(_n);
for(var _i=2;_i<=_sqrt;_i++)
if((_n%_i)==0) _isPrime=false;
return _isPrime;
}
function nextPrime(_s,_n)
{
while(_n>0)if(isPrime(_s++))_n--;
return --_s;
}
var clicks = 0;
function hello() {
clicks += 1;
v = nextPrime(2,clicks);
document.getElementById("clicks1").innerHTML = clicks ;
document.getElementById("v").innerHTML = v ;
};
</script>
<button type="button" onclick="hello()">Get your prime</button>
<p>How many primes have been claimed: <a id="clicks1">0</a></p>
<p>Your prime: <a id="v">0</a></p>
</body>
The problem is that when I put this code in a iframe on my wixsite it seems to reload the code each time you look at the site, so it starts the counter again. What I would like it say the button has been pressed 5 times, it will stay at 5 until the next visitor comes along and presses it. Is such a thing possible?
You don't actually need an iframe for that, You can use wixCode to do that. WixCode let's you have a DB collection. and all you need to do is update the collection values on every click.
Let's say you add an Events collection can have the fields:
id, eventName, clicksCount
add to it a single row with eventName = 'someButtonClickEvent' and clicksCount = 0
Then add the following code to your page:
import wixData from 'wix-data';
$w.onReady(function () {});
export function button1_click(event) {
wixData.get("Events", "the_event_id")
.then( (results) => {
let item = results;
let toSave = {
"_id": "the_event_id",
"clicksCount": item.clicksCount++
};
wixData.update("Events", toSave)
})
}
now you need to add button1_click as the onClick handler of your button (in the wixCode properties panel).

Can't post value back to action Method using #Html.BeginForm

I have some code, where when the user clicks on the "x" icon then call the CancelPendingQuote action method passing along the requestId in the requestUrl. The action method is hitting but the value is not included in the requestIdEncrypted parameter, thus the action method parameter has a null value.
Pending List
#using (#Html.BeginForm("CancelPendingQuote", "Quote", new { requestIdEncrypted = request.RequestIdEncrypted }, FormMethod.Get, new { enctype = "multipart/form-data", #id = "removeRequest" }))
{
<span data-bind="click: function(data, event){ userWarning_Dialog('#removeRequest_dialog', event); }">
<img src="~/Areas/Waybill/Content/Images/Normal_Size/posta_delete_20px.png" />
<img src="~/Areas/Waybill/Content/Images/Normal_Size/posta_delete_mouseover_20px.png" style="display:none" />
</span>
}
Knockout userWarning function that submits the form. This is called when image "x" is clicked.
removeRequest: function (model, event)
{
var form = $("#removeRequest").closest("form");
$(form).submit().error(function (messageObj) {
// if fail return error message
$(".information-bar").trigger("ErrorText", messageObj.message);
});
$("#removeRequest_dialog").dialog("close");
},
Action method
[Authorize]
public ActionResult CancelPendingQuote(string requestIdEncrypted)
{
int requestId = Convert.ToInt16(Decryption.Decrypt(requestIdEncrypted));
_controllerContent.QuoteService.Value.CancelPendingQuoteRequest(requestId);
return RedirectToAction("Dashboard");
}
Any Ideas?
There's a couple things here. For one, you need to make sure that the names of the object being posted to the server match up with the Controller's parameter. For instance, if you send this Javascript object up:
{ requestIdEncrypted: "exampleString" }
or
{ requestIdEncrypted: viewModel.requestId() }
then your Controller method should accept the input.
Secondly, from your code it's not evident to me how the data is being posted. $(form).submit().error(function (messageObj) is a little confusing: is this line responsible for submitting the form? Is it a function that would be called if the form submission is unsuccessful? Is it working? It's not clear to me what you're trying to do with this. You may have to figure out another way to attach an error handler to the form, if this is what you're trying to do - unless it's working alright.

Creating html buttons in code, for table. How to handle server callbacks?

I'm currently working on a project and got stuck. I have a Literal control, placed in my .aspx page and I use a stringbuilder like this to populate it with data :
public string CreateNewEntry(String garageName, String garageType,String garageAdress, String garagePhone, String garageId)
{
StringBuilder sb = new StringBuilder();
sb.Append(#"<tr class=""odd"">");
sb.Append(#"<td class=""v-middle"">");
sb.Append(garageName);
sb.Append(#"</td>");
sb.Append(#"<td class=""v-middle"">");
sb.Append(garageType);
sb.Append(#"</td>");
sb.Append(#"<td class=""v-middle"">");
sb.Append(garageAdress);
sb.Append(#"</td>");
sb.Append(#"<td class=""v-middle"">");
sb.Append(garagePhone);
sb.Append(#"</td>");
sb.Append(#"<td class="""">");
sb.Append(#"<a href=""#"" class=""btn btn-sm btn-icon btn-success"" id=""");
sb.Append("edit"+garageId);
sb.Append(#""" name=""");
sb.Append("edit"+garageId);
sb.Append(#"""runat=""server"" OnServerClick=""processRowButtonClick""><i class=""fa fa-edit""></i></a>");
sb.Append(#"<a href=""#"" class=""btn btn-sm btn-icon btn-danger"" id=""");
sb.Append("delete"+garageId);
sb.Append(#""" name=""");
sb.Append("delete"+garageId);
sb.Append(#"""runat=""server"" OnServerClick=""processRowButtonClick""><i class=""fa fa-ban fa-indent""></i></a></td></tr>");
return sb.ToString();
}
I then run a SQL query, to gather the data and loop trough it with a foreach loop adding all table elements dynamically to the Literal controller.
public void garageViewPopulator()
{
serviceDatabaseDataContext dbQuery = new serviceDatabaseDataContext();
var query = (from GarageDetails in dbQuery.GarageDetails
select GarageDetails);
String _constructorString = "";
NewListEntry tableEntry = new NewListEntry();
foreach (GarageDetail item in query)
{
//Creating new HTML code from the class above
_constructorString += tableEntry.CreateNewEntry(item.garageName.ToString(), item.garageType.ToString(), item.garageAdress.ToString(), item.garagePhone.ToString(), item.garageId.ToString());
}
// My litteral
garageListHolder.Text = _constructorString;
}
I then have a :
protected void processRowButtonClick (object sender, EventArgs e)
{
//Retrieve what button id was pressed, and exec action.
}
In my code-behind file. I assumed this would get called when a user pressed a button in my generated table shown under.
Two questions, is it possible to do it this way? Will the code register the buttons as clickable when i generate them from code? Because right now, clicking them does not execute the current processRowButtonClick function in the code behind.
Secondly, If this is possible, how would I get the name / id of the button pressed? Does anybody have any input?
(It may be easier methods of achieving what I'm trying to do, so I'l be happy to receive information about better solutions as well).