knockoutjs creating new model from input elements? - data-binding

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

Related

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

When list of search results is an object and one is chosen, ngtypeahead input shows [object][object]

Model: {
"strText": "AngularJs",
"strSectionPath": "favoriteNpmLibraries",
"strTabPath": "angularJs"
}
If user choses this search result, then input box shows [object][object].
How can we fix this?
Use inputFormatter to choose what you want to display from an object.
In view:
<input [(ngModel)]="model" [ngbTypeahead]="search" [inputFormatter]="formatter" />
In component:
formatter = (x: {strText: string}) => x.strText;
It displays value of strText property of your object.
Let me know if you need more info.

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

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).

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.

Save and retrieve checkbox values asp.net mvc

I'm new to asp.net mvc and currently using MVC 2. I'm struggling with working with checkboxes for days now. I simply need to get checked checkbox values to be saved in database and on Edit view check them back.
<input type="checkbox" id="coduit for safety near motor" name="Prepration" value="coduit for safety near motor"/><br />
<input type="checkbox" id="coduit for far side safety" name="Prepration" value="coduit for far side safety"/><br />
<input type="checkbox" id="coduit for power cable to near power point" name="Prepration" value="coduit for power cable to near power point"/><br />
On post controller method i can save the values of checked Checkboxes to the database as a comma separated string by using
strign a= = Request.Form["Prepration"];
How can i show them back on Edit view?
I don't know whether this is the way to do this any alternative solution would be great
The answer of your first question:
Need to get checked checkbox values to be saved in database
On a button click push all the values in a array and from there store them in a hidden field and when you post your form get those values from this hidden field:
<script type="text/javascript">
$(document).ready(function () {
$("input#btnSubmit").click(function () {
var id = [];
$("input[name='Prepration']:checked").each(function () {
id.push($(this).val());
});
$("#HiddenFieldId").val(id);
});
});
</script>
Now coming to your second question:
How can i show them back on Edit view?
<input type="radio" id="a" name="Prepration" checked="#Model.BoolPropertyName" />
Here you can have the value of in boolan.
Hope this will help you.
you can client side solution,
var data="";
$.each($("input:checkbox"),function(){
if($(this).is("checked")){
data+= $(this).val();
}
});
// post here

Resources