knockout.js How to get values from form and get additional data ASP.NET MVC - asp.net

What i got is:
var post = function(id, message, username, date) {
this.id = id;
this.message = message;
this.username = username;
this.date = date;
this.comments = ko.observableArray([]);
this.addComment = function(context) {
var comment = $('input[name="comment"]', context).val();
if (comment.length > 0) {
$.connection.boardHub.server.addComment(this.id, comment, vm.username())
.done(function() {
$('input[name="comment"]', context).val('');
});
}
};
And View:
<form class="add-comment" data-bind="submit: addComment">
<div class="row">
<div class="col-md-9">
<input type="text" class="form-control" name="comment" placeholder="" />
</div>
<div class="col-md-3">
<button class="btn btn-default" type="submit">Answer</button>
</div>
And the question is how can i pass to addComment extra value without missing context?
Like:
<form data-bind="submit: function(){ writePost(<context>, '#ViewBag.UserN'); }">

As I mentioned in my comment, you are not using knockout correctly. Therefore I'll provide you with a generic example of how you should handle form submits using knockout.
First, you should define a viewmodel which wraps your UI data and connects to services to do any actual logic. In simple cases, many people just write the logic, such as API calls right into the viewmodel instead of a service. For simplicity, I'll just do that, because extracting it into its own 'class' should be simple enough. You should give your viewmodel a more descriptive name than post. Here is an example:
var commentViewModel = function(id, userName) {
var self = this;
this.commentText = ko.observable('');
this.addComment = function() {
var txt = self.commentText();
if (txt) {
$.connection.boardHub.server.addComment(id, txt, userName)
.done(function() {
self.commentText('');
});
}
};
};
var commentVM = new commentViewModel('#ViewBag.Id', '#ViewBag.UserN');
$(function() { ko.applyBindings(commentVM); });
HTML:
<form class="add-comment" data-bind="submit: addComment">
<div class="row">
<div class="col-md-9">
<input type="text" class="form-control" data-bind="textInput: commentText" />
</div>
<div class="col-md-3">
<button class="btn btn-default" type="submit">Answer</button>
</div>
</div>
</form>
Explaination:
The commentVM object contains a commentText member. This is a KO observable, so by exposing it by assigning it to this, you can bind to it. Then you can read the value of it by utilizing the KO binding mechanism instead of mixing jQuery and KO. Bindings exist to simplify your life not to be mixed with plain jQuery stuff, so you should use them wherever you can.
You should also expose an observable array to hold the actual comments, but since that wasn't part of your question I didn't want to introduce it to exclude any additional noise which is not related to what you asked.
In your HTML, you used the submit binding correctly. By binding the submit event to the addComment method, this method will get invoked correctly. But what's different compared to your code is that you read the comment text from the observable you've just defined, and avoid using jQuery. Since the VM itself is the context, you don't have to care about how to pass it -- that is, anything that you intend do use from within the submit method should generally be included in the viewmodel.
If, for whatever reason, you need to pass any extra parameter - such as dynamic content generated by Razor, for example, you can use the bind method. You can read more about it here, it is written just above Note 3. I'd like to note that even though it is documented in the click binding page, you can use it with submit as well (in fact, with any binding that binds to a method). Just for clarity, I'll include the relevant part here as well:
<button data-bind="click: myFunction.bind($data, 'param1', 'param2')">
Click me
</button>
So all you have to do is to specify the function to which you want to bind, and append a .bind($data, parameter1, parameter2,....) expression to it. Note that here, $data is the context; you should always include it in the .bind call. However, this will not be passed to any method parameter, it is necessary for KO to do its magic, so if you had a function like this:
function a(x,y,z)
then x would be passed param1, y would be passed param2 and z wouldn't be passed anything. If you want to force passing $data, you should include it twice in the bind call as such: .bind($data, $data, ....more params).

Related

Checking database for form input, and then responding with some specific content

I have a asp.net mvc application where I have a form in one of my views.
From that for, the user needs to be able to type in the name of another user, and if that name exists in my database, I display a specific output, and if not another.
So I want something along the lines of:
<form action="Checkifuserexists">
<input type="text" id="fname" name="fname" placeholder="Add a friend"><br>
<input type="submit" value="Submit">
</form>
#if (resultOffunction == True)
{
<p> succes, this user exists </p>
}
else
{
<p> THis user does not exist </p>
}
So, I'm aware that since this is gonna be clientside and my c# is serverside, I'm gonna have to call a function which will redirect me back to my view again.
My question is though, how do I best pass along the information about the query being a succes or not along?
My first thought was to set a cookie, return to the same view, and then do something like this:
if (Request.Cookies["response"] == "true")
{
<p> succes, this user exists </p>
} else
{
<p> THis user does not exist </p>
}
This doesnt seem best practice though, and I could end up having a hundred cookies just to keep track of the state of the site.
I could also take a query string, which might be a bit better, or somehow put the information into the model I return anyway. Both of these solutions seem a little strange though.
What would be the best way of doing this?
EDIT
I have now gotten the tip of using some js to call some c# backend function. I then found this video, and tried following it.
I now have this function in my homecointroller which is the one I want to call (for now it just returns a string):
[JSInvokable]
public string addFriend()
{
System.Security.Claims.ClaimsPrincipal currrentUser = this.User;
return "Hi there";
}
I have this javascript:
function focusonElement(element){
element.focus();
}
function setElementTextById(id, text) {
document.getElementById(id).innerText = text
}
function addFriend() {
Dotnet.invokeMethodAsync('mongoExample' ,'addFriend')
.then(result => {
setElementTextById('searchspan', result);
});
}
(The name of my project is "monoExample")
And I have this html:
<button class="btn btn-info" onclick="addFriend()"> get friend</button>
<p id ='searchNotification'> The user <span id='searchspan'></span> </p>
When I click the button, I can tell that the function gets called, but my backend function never does. Why could this be?
I also tried having the function in myt razor file:
#{
[JSInvokable]
static string DoTHis()
{
return "RETURNING THISs";
}
}
This has the same effect.
I guess the issue is that javascript cannot resolve invokeMethodAsync. But do I need to inmport something to make it work? or do I need to use blazor?
I don't understand your request but for sure no need for cookies,
if you have data before response return it with model and check in view:
if (Model.data == "true")
{
<p> succes, this user exists </p>
}
if not use ajax call to check and use jquery to show result

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;
}
}
}

How to pass data from View to Controller when data is set by JavaScript?

I have a span whose value is set via knockout.js:
<span id="total" data-bind="text: total"></span>
This is setted whenever a slider changes value. All of this is inside a BeginForm. Now my problem is how do I get this to be passed (via model binding) to the controller. How do I get this value inside the model on submit so it can be binded when I call an Action?
EDIT:
Ok, let me rephrase my question. I have cshtml file which is strongly-typed to a model. Now a property of this model (Total) is bound to a hidden field to served as the base value:
#Html.HiddenFor(model => model.Total, new { id = "base-total" })
Now in this cshtml, there is a slider in which it changes a span's value to a computation based on the slider and the base total.
<span id="total" data-bind="text: total"></span>
I need this changed via js as I couldn't have the page reload everytime I move the slider. So my problem now is the display is changed but the actual model value isn't, so when I submit, the model still has the base total value.
How can I have the value that I changed via js be passed on the model? If this is not the correct way, can you suggest what I might try?
On way you can do it is to manually send your JS model to the server on submit of your form.
For example:
<form data-bind="submit: doSomething">
... form contents go here ...
<span id="total" data-bind="text: total"></span>
<button type="submit">Submit</button>
</form>
<script type="text/javascript">
var viewModel = {
doSomething : function(formElement) {
// ... now do something
var data = JSON.stringify(
{
total: self.total // for example.
}); // prepare request data
$.post("/api/your-service", data, function(response) // sends 'post' request
{
// on success callback
self.responseJSON(response);
})
}
};
</script>

How does `event.currentTarget.INPUT.value` give me an input value in a Meteor form submit handler?

I found example code to fetch values of text inputs from a submitted form in Meteor. I wrote my own version. Works great! Here's a snippet from my form submit event handler:
'submit form': function(event, template) {
event.preventDefault();
Assets.insert({
name: event.target.inputName.value,
location: event.target.inputLocation.value,
value: event.target.inputValue.value
});
}
I'm confused about event.target.playerName. What kind of object is it? Does Meteor set up that object for us? Or is this a jQuery thing? Or something else?
Is it any better/safer to use event.currentTarget? Sounds like nesting forms is not allowed, so it might not make any difference since there wouldn't be any way for it to "bubble up" given the 'submit form' event map key.
Crossposted here.
In that case, you're not using the template object but rather the plain jQ way. From a quick look at the page containing the example code, they use function(event) as opposed to function(event, template) (I prefer the latter, but that's a matter of taste). Here's how t make use of the template object.
Suppose your form look like this
<template name='createAccount'>
<form role="form">
<input placeholder="Your Name" name="name" type="text" />
<input placeholder="E-Mail Address" name="email" type="email" />
<input placeholder="Password" name="password" type="password" />
<div id="submitForm" class="outerButton">
(SOME BUTTON)
</div>
</form>
</template>
Here's pattern using template:
Template.createAccount.events({
'click #submitForm': function (event, template) {
var displayName = template.find("input[name=name]").value;
var eMailAddress = template.find("input[name=email]").value;
var password = template.find("input[name=password]").value;
< INSERT displayName, eMailAddress, password IN COLLECTION>
}
})
Pretty new to meteor myself so I could be completely wrong, but I believe your event target is just standard javascript, not meteor or jquery specific. I've been thinking of it as a shorthand for creating your own object.addEventListener(), so your playerName is going to be a child element of the form since it's the key of the object.
Imagine if it was setup something like form.addEventListnener('submit', function(e){ ... }) maybe makes it more familiar.
As for number 2, I wouldn't see why you couldn't use currentTarget if you needed to. I don't think it'd be any safer unless you really didn't know where the event might be coming from (perhaps creating a custom package?).
event.target returns the DOM element. In your case, it's the form element, and you can use named property to get its node, see the spec
In this case it's OK to use either target or currentTarget. In other examples when there is a 'nested' node, it might be better to use currentTarget.

knockoutjs creating new model from input elements?

jsFiddle: http://jsfiddle.net/Piercy/mD4kG/
So I have recently started working with knockout.js. I have started to get my head around it but am struggling with the following scenario.
I have the following model and view model structure:
function ViewModel() {
var self = this;
self.Emails = ko.observableArray([]);
self.AddEmail = function() {
// make ajax call here
self.Emails.push(new EmailModel(data));
};
}
function EmailModel(data) {
this.Id = data.Id;
this.Name = data.Name;
this.Subject = ko.observable(data.Subject);
this.DisplayId = ko.observable(data.DisplayId);
}
I want the ViewModel.Emails array to be an array of EmailModel's and my problem is how to create a new model to insert.
I imagine something like this:
<select data-bind="options: Emaillistdata, optionsText: 'Name', optionsValue: 'Id', optionsCaption: 'Choose...', value: DisplayId"></select><br /><br />
<input type="text" data-bind="value: Name"/><br />
<input type="text" data-bind="value: Subject"/><br />
<button data-bind="click: AddEmail">Add Email</button>
However, doing this would mean the the Name,Subject and DisplayId bindings would need to be in the ViewModel (which they are not). Also, it seems odd that I would have to add these to the ViewModel. I am kind of expecting to able to click the button and have a data variable that i can just do data.Name, data.Subject and data.DisplayId and have these variables not bound to any model, they are just submitted because i've named them as such or something? Then i can add them to the array and make them part of the ViewModel.
I could do all this with standard JS and then add it to the model but that also seems odd when everything else is using knockout. However maybe this is the answer?
jsFiddle: http://jsfiddle.net/Piercy/mD4kG/
Without knowing what you are trying to achieve from a UX point of view im not sure what your trying to achieve.
Have you looked at this ko tutorial. It shows you how to structure your code to be able to add new items
http://learn.knockoutjs.com/#/?tutorial=collections

Resources