Blazor binding inconsistent behaviour - events

I am building a blazor app (WASM) where I use some <input>'s with those inputs. to both I binded a variable, for easy overview I put that in the #code part of the (index)page.
on the second <input> I also put an onclick event, and in the onclick event I fill a third variable (ThirdText) with the first (FirstText).
When I start the application, and I click on the first <input>, and fill in some characters and then click on the second <input> the value of the third variable is still empty. But when I go back to the first, change a bit, and click on the second afterwards, it directly fills the NEW value.
The last bahaviour is what I also hoped for in the first situation.
Here I have my code in index.cs:
#page "/"
<h3>#FirstText</h3> <br/>
<h3>#SecondText</h3> <br/>
<h3>#ThirdText</h3> <br/>
<input #bind="FirstText" style="width:50%" /> <br/>
<input #bind="SecondText" #onclick="OnclickHandler" style="width:50%" /> <br/>
#code{
private string FirstText { get; set; } = string.Empty;
private string SecondText { get; set; } = string.Empty;
private string ThirdText { get; set; } = string.Empty;
void OnclickHandler(MouseEventArgs mouseEventArgs)
{
ThirdText = FirstText;
}
}
This code is an easy example, the real application has a lot more logic, but this is the base of my problem.
When I make the first box empty, and click on the second box, again it does not work as expected.
I already tried to first fill the 3th variable in oninitialised() , but then the first variable is also empty so, that does not work.
I know in javascript there is a simular problem, that is because the onclick is firstly called, and the onexit or other event you want to use on the exit of the first box is then forgotten. But in that case, it goes wrong al the time. I hope that blazor has a solution for this situation. Because it goes well in Blazor if the first input was already filled.

The reason your code is not working is because of the timing of the click event:
If the button is pressed on one element and the pointer is moved
outside the element before the button is released, the event is fired
on the most specific ancestor element that contained both elements.
click fires after both the mousedown and mouseup events have fired, in
that order.
source
Long story short, by the time mouseup (or even the mousedown for that matter) occurs, the layout has changed and input element has shifted outside of the range of the mouse pointer. For example, change your code to the following and you will see it works now because the elements do not shift:
<h3>AAA #FirstText</h3> <br/>
<h3>AAA #SecondText</h3> <br/>
<h3>AAA #ThirdText</h3> <br/>
The solution to your problem would depend on what you are trying to achieve, but the basic idea here is to not rely on click events when layout changes occur.
To really prove what is happening, in Chrome Dev Tools go to Sources > Event Listener Breakpoints > Mouse > click and make sure the checkbox is checked. After that, type something in the first input and then click the second input element. The click event will fire, however notice the event target in the below screenshot! It is not the input element but the parent element <article>. This proves that the click event was never dispatched to the input element because it is not underneath the mouse pointer when the mouseup event occurs.

You describe the current behaviour but at the same time you are a bit vague about what you expect. As far as I can tell the code does what you ask for.
And what do you expect (want) to happen when a User uses the Tab key to go to the next input? Is it about mouseclicking or about completing an input?
I think you should consider one of these two changes:
<input #bind="FirstText" style="width:50%" /> <br/>
<input #bind="SecondText" #onfocus="OnUpdateHandler" style="width:50%" />
or, and this seems more logical:
<input #bind="FirstText" #onblur="OnUpdateHandler" style="width:50%" />
<input #bind="SecondText" style="width:50%" />
with
void OnUpdateHandler()
{
ThirdText = FirstText;
}
If that doesn't work then be more specific: when/why do you want ThirdText to mirror FirstText?

#Third Answer:
Here's a test page to capture the problem in it's simplest form.
I've removed most of the original code and made it a simple data entry and save form - without using EditForm.
In the current configuration SaveData isn't called when you enter your name and click on the save button.
Comment out the line <h3>#this.YourName</h3> before the form and it now works. Note there's a second <h3>#this.YourName</h3> below the form that has no impact.
Now change the line to:
<h3>Your name is: #this.YourName</h3>
and it works.
The answer lies in the answer provided by #JesseGood To quote his comment:
When you remove the single line of code, the elements do not shift down anymore so the click event occurs on the button. However when you add the line of code, the element becomes populated causing all the other elements to shift down and by the time you lift your finger off of the mouse the element is no longer under your mouse pointer causing the click event to occur on the parent element.
#page "/"
#using System.Text
<PageTitle>Index</PageTitle>
#*Comment out this line and the event triggers on first click*#
<h3>#this.YourName</h3>
<h3>Your Name is #DisplayName</h3>
<div class="m-2">
Your Name: <input value="#this.YourName" #onchange=this.NameChange style="width:50%" />
</div>
<div class="m-2">
<button class="btn btn-primary" #onclick="SaveData">Save</button> <br />
</div>
<h3>#YourName</h3>
<div>
<pre>
#log.ToString()
</pre>
</div>
#code {
private string YourName { get; set; } = string.Empty;
private StringBuilder log = new StringBuilder();
private string DisplayName = string.Empty;
void NameChange(ChangeEventArgs e)
{
this.YourName = e.Value?.ToString() ?? String.Empty;
log.AppendLine("FirstChange Called");
}
void SaveData(MouseEventArgs mouseEventArgs)
{
this.DisplayName = this.YourName;
log.AppendLine("Value Saved");
}
}
Follow on answer:
When you "bind" in Razor, the Razor compiler actually builds out the following code. In the example I've just wired it up manually so I can output some debug code when the onchange event is raised.
__builder.OpenElement(15, "input");
__builder.AddAttribute(16, "style", "width:50%");
__builder.AddAttribute(17, "value", Microsoft.AspNetCore.Components.BindConverter.FormatValue(this.FirstText));
__builder.AddAttribute(18, "onchange", Microsoft.AspNetCore.Components.EventCallback.Factory.CreateBinder(this, __value => this.FirstText = __value, this.FirstText));
__builder.SetUpdatesAttributeName("value");
__builder.CloseElement();
First and second Answer replaced

Related

meteorjs environment, getting element by id return strange value

I'm collaborating in a Meteorjs app, in wich i'm at first tries.
I builded some very simple templates to fit my needs.
In my code it happens that i have to check the value of a input text.
So i setup an event on that text box.
this is the text input:
<input type="text" name="meName" id="mockupName" />
<input type="button" {{buttonDisabled}} id="mockupCreate" value="New Mockup" />
The event check the text value and disable or enable the button. Very straight forward.
this is the event:
'keydown #mockupName': function(e) {
if (e.target.value.trim() == '') {
Session.set('buttonDisabled','disabled');
} else {
Session.set('buttonDisabled','');
}
},
It works just ok.
e.target has a reference to my text input, and value store its value.
Now i referenced this template from another page, in a big template i wrote:
{{#if mockupSelected}}
<input type="button" id="sw_product" value="switch to product view" />
{{> mockupEditor}}
{{else}}
Select product from the left
{{/if}}
And actually when mockupSelected returns true my template appears.
The event is not working anymore.
When the event fire ( and it fires ) i do a console.log(e.target)
Before i was getting: <input#mockupName> a reference to my input.
Now i m getting: Object { __impl4cf1e782hg__: <input#mockupName>, parentNode_: undefined, firstChild_: undefined, lastChild_: undefined, nextSibling_: undefined, previousSibling_: undefined, treeScope_: Object }
An object with a series of properties, one of which contains my reference.
This is the list of meteor packages installed:
meteor-platform
natestrauser:cart
http
iron:router
accounts-base
accounts-password
accounts-ui
alanning:roles
aldeed:autoform
aldeed:collection2
twbs:bootstrap
jeremy:velocity-animate
ajduke:bootstrap-tokenfield
sergeyt:typeahead
standard-app-packages
babrahams:editable-text-wysiwyg-bootstrap-3
differential:vulcanize
dburles:collection-helpers
fortawesome:fontawesome
yogiben:admin
I would like to know how i can access to that text input, considering that i do not know that key and that getElementById is returning me the same object.
I could iterate over all the object properties and testing if one of the values is actually a nodeElement of type text, but i do not think this is a solution.
Can anyone tell me how to get back to the normal behaviour?
I'm guessing here but have you tried:
.html
<input type="text" name="meName" id="mockupName" class="mockupName" />
<input type="button" {{buttonDisabled}} id="mockupCreate" value="New Mockup" />
.js
'keydown .mockupName': function(e) {
if (e.target.value.trim() == '') {
Session.set('buttonDisabled','disabled');
} else {
Session.set('buttonDisabled','');
}
},
either that, or try changing the name of the control (mockupname -> uniqueMockupName). I had strange behaviour once when there were duplicate field names.

MVC: Allow multiple click of the same button

Is it possible to allow multiple clicks on a sigle submit button of a form? I have a form where I want to perform an action on the first submission (first click) and a different action on the second submission (second click).
I am basically using ajax to populate a div in the form during the first submission and I want to submit the form on the second click.
I have tried to put by button in the div to by updated, and after the first click, I update update the div and re-creating the button in the updated div. But if I use this method, how can I set the action method of the newly created button in my controller method for Ajax?
My controller method returns something like
return Content( mystring + <input type='button' value='continue submission'/>
if i use this approach, how do I set the action method of the buttton, or is there another way of doing this?
Use two buttons with JavaScript:
Button 1 is shown initially. On click, it hides itself, shows button 2, and performs your action 1.
Button 2 is hidden initially. It is unhidden by button 1 and on click, it performs your second action.
This looks a little weird but I can tell you how to do this. Take an input type="submit" and make it hidden. Have a variable as var flag = false; When user first clicks you input type="button" call a function and do your stuff and make sure to make the flag=true; In the function itself check if flag=true; the trigger the event of your input type="submit".
Like as follows:
<input type="button" id="btn1" onclick="perfromAction()" value="submit"/>
<input type="submit" id="btn2" value="submit" style="display:none"/>
<script type="text/javascript">
var flag=false;
function performAction()
{
if(flag){
$("#btn2").trigger("click");
}
else{
//do processing
flag=true;
}

Add and remove textbox at runtime in mvc3

In my page there is one textbox by default and one add button beside it. I need to add the another textbox when user click Add button. And there should be two buttons Add and Remove beside newly added text box. And same process goes on i.e., user can add Textbox using Add button and remove it using remove button.
I am new to mvc 3 so i am confused how to proceed. Is there any way like placeholder in asp.net so that we can add control at runtime.
Any suggestion and idea will be helpful to me
MVC is a very "hands-off" framework compared to Web Forms, so you're free to add the new textboxes how you like. Note that "controls" don't exist in MVC.
Here's how I'd do it:
Model:
class MyModel {
public Boolean AddNewTextBox { get; set; }
public List<String> MultipleTextBoxes { get; set; } // this stores the values of the textboxes.
}
View (I prefer the Web Forms view engine, I'm not a fan of Razor):
<% for(int i=0;i<Model.MultipleTextBoxes.Count;i++) { %>
<%= Html.TextBoxFor( m => m.MultipleTextBoxes[i] ) /* this might look like magic to you... */ %>
<% } %>
<button type="submit" name="AddNewTextbox" value="true">Add New Textbox</button>
<button type="submit">Submit form</button>
Controller:
[HttpPost]
public ActionResult MyAction(MyModel model) {
if( model.AddNewTextBox ) model.MultipleTextBoxes.Add("Yet another");
else if( ModelState.IsValid ) {
// your regular processing
}
}
You can also add more textboxes with Javascript and it work perfectly fine. All that matters is the HTML input elements. There's no cryptic viewstate. MVC is stateless.
Note that because I used <button type="submit"> my example will not work reliably in Internet Explorer 6-8 (sucks, I know), but you can replace them with <input type="submit"> with no ill-effects.
This requires some Javascript/JQuery... The following is a sketch only, but will hopefully be useful as a general approach.
The remove button
You want to render a button that can target its own container for removal. To do that, use some markup like this:
<div class="item-container">
<input type="button" onclick="removeItem(this)" />
</div>
And the Javascript for removeItem:
<script>
function removeItem(element) {
// get the parent element with class "item-container" and remove it from the DOM
$(element).find(".item-container").remove();
}
</script>
The add button
You could either use a partial view with Ajax, or use straight Javascript; which one is best likely depends on whether you need a round-trip to the server to create a new item. Let's say you need to go the the server to generate a new ID or something.
First, create a partial view and corresponding controller action; this should contain the remove button as above, as well as the text box and add button.
Now, create an Ajax form on your main page that gets invoked when you click Add:
<script src="#Url.Content("~/Scripts/jquery.unobtrusive-ajax.min.js")" type="text/javascript"></script>
#using (Ajax.BeginForm("New", new AjaxOptions() { UpdateTargetId="ajaxTarget", HttpMethod = "GET" })) {
<input type='submit' value='Add New' />
}
<div id="ajaxTarget"></div>
This code fetches your partial view (from the action New in the current controller) and adds the result to the ajaxTarget element.
Note The Ajax form requires Unobtrusive Ajax, which you can install via Nuget: Install-Package JQuery.Ajax.Unobtrusive.

MV3 Duplicate Query String Values for CheckBox (true,false for boolean)

I've created a fairly straight forward page with a check box:
#using (Html.BeginForm("MyController", "MyAction", FormMethod.Get))
{
#Html.CheckBoxFor(x => x.MyCheckBox)
<input type="submit" value="Go!" />
}
The URL is populated with the MyCheckBox value twice!? As such:
MyAction?MyCheckBox=true&MyCheckBox=false
It only duplicates the value if the check box is true. If set to false it will only appear once in the query string.
The code above is simplified as I have a couple of drop downs and a textbox on the form which work fine. I don't think there's anything unusual about the code which I've left out from this question.
Has anyone had a similar issue with query string parameters being duplicated?
This behaviour is by design of the checkbox control. The standard HTML checkbox control passes no value if it is not checked. This is unintuitive. Instead, the ASP.Net checkbox control has 2 elements, the standard control which is visible and also a hidden control with a value of 'False'.
Therefore, if the checkbox is not checked, there will be one value passed: False.
If it is checked, there will be two values, True and False. You therefore need to use the following code to check for validity in your code:
bool checkboxChecked = Request.QueryString["MyCheckBox"].Contains("True");
Accepted answer is correct however in my case in a recent development the MVC behaviour is misleading.
The MVC Html.CheckBox(...) and Html.CheckBoxFor(...) generate an extra input of 'type=hidden' with the same ID as the checkbox control, leading to the duplicate URL parameters. I got around this problem by simply including the mark up desired as follows:
#if(checkTrue){
<input type="checkbox" id="MyCheckBox" name="MyCheckbox" checked="checked">
}else{
<input type="checkbox" id="MyCheckBox" name="MyCheckbox">
}
Would be better wrapped upin a helper to use in place of the MVC code so the value check is encapsulated.
As part of my application, the controller maintains sets of query parameters using both form injection and link injection using helpers in order to preserve state (of paging/filtering controls for example) when clicked to navigate within the same controller scope. As a result of this feature, the check box element is always set back to false if the standard MVC helpers are used. It's a good thing I noticed and did not waste much time on this bug.
In my model, I had a collection of checkboxes like so:
public class PrerequisitesViewModel
{
public List<StudentPrerequisiteStatusViewModel> PrerequisiteStatuses { get; set; }
}
public class StudentPrerequisiteStatusViewModel
{
public long Id { get; set; }
public string Name { get; set; }
public bool IsSelected { get; set; }
}
In order to get everything to bind correctly, I had to actually convert the values from the querystring and parse them manually with the following code:
// fix for how MVC binds checkboxes... it send "true,false" instead of just true, so we need to just get the true
for (int i = 0; i < model.PrerequisiteStatuses.Count(); i++)
{
model.PrerequisiteStatuses[i].IsSelected = bool.Parse((Request.QueryString[$"PrerequisiteStatuses[{i}].IsSelected"] ?? "false").Split(',')[0]);
}
Alas, it works, but I can't believe this is necessary in MVC! Hopefully, someone else knows of a better solution.
I solve this issue with use #Html.HiddenFor
<input id="checkboxId" type="checkbox" value="true" onchange="changeCheckboxValue()">
#Html.HiddenFor(m => m.MyCheckBox, new { #id = "hiddenId" } )
<script>
function changeCheckboxValue() {
document.getElementById("checkboxId").value = document.getElementById("hiddenId").checked;
}
</script>

After button disabled its value did not posted to controller

I have an controller which has check like that
if (form["submit"].ToString() == "Continue")
{
}
and i have button which is doing submit
<button name="submit" value="Continue">Continue</button>
It was all working well until i decided to disable Continue button on submit to prevent double click using this function:
$('form').submit(function () {
if ($(this).valid()) {
$(':submit', this).attr('disabled', 'disabled');
}
});
So now i don't get value form["submit"] posted on controller.
Any thoughts how may i fix that?
I want still prevent second click but be able to get form["submit"] value posted on controller.
Can you control the submit value in a hidden field in the form? I can't tell what other logic you might need, but when the form renders, you could set the hidden field's value to the submit button's value and change it when necessary using the first script below. As long as it has a name attribute and is enabled (which you'd rarely disable a hidden field) then it will post when the form is submitted.
$(function() {
// this assumes your button has id="myButton" attribute
$(':hidden[name="submit"]').val($('#myButton').val());
});
And of course in your form, you would need a hidden field with name="submit"
<input type="hidden" name="submit" value="Continue" />
Then, whenever the state of your form changes, modify the disabled state of the button and the value of the hidden field to reflect the value (if it changed at all).
There are also frameworks you may find useful for UI features like this. KnockoutJS comes to mind. It can be used to "value" bind input elements. It's probably overkill for this small example, but it could be useful if your UI expands. I've added markup, script and comments below if you're interested.
$(function () {
var viewModel = {
submitValue: ko.observable("Continue")
};
ko.applyBindings(viewModel);
$('form').submit(function() {
if($(this).valid()) {
// the following line will change the both the hidden field's value
// as well as the button's value attribute
viewModel.submitValue("some other value");
// I couldn't follow your selector here, but please note I changed
// the name of the submit button in the markup below.
$(':submit, this).attr('disabled', 'disabled');
}
});
});
KnockoutJS requires you use the data-bind attribute to setup your elements. In your case, you'd bind one property to multiple elements like this:
<button name="submitButton" data-bind="value: submitValue"/>Continue</button>
<!-- and bind the same value similarly in the hidden field-->
<input type="hidden" name="submit" data-bind="value: submitValue"/>

Resources