I want to use Html helpers and Knockout to create a seamless transition of data from server to client, and then back to server again on postback.
My ViewModel has a few properties and then an array of items. I need to know how I can iterate over my array in ASP.NET Razor while assigning my knockout bindings to individual elements in the array.
Here's what I have so far:
#for (int i = 0; i < Model.Fields.Count; i++)
{
<div name="customFormField" data-bind="with: Fields[#i]">
<div class="form-group">
#Html.LabelFor(m => m.Fields[i].SqlDataType)
#Html.ListBoxFor(m => m.Fields[i].SqlDataType, new SelectList(selectDataTypeOptions), new { #class = "form-control", data_bind = "value: SqlDataType" })
</div>
</div>
}
My ko javascript:
<script>
function viewModel() {
this.addField = function() {
alert("wut");
}
}
$(function (){
var jsonModel = #Html.Raw(Json.Encode(Model));
var mvcModel = ko.mapping.fromJS(jsonModel);
var customFormTemplateViewModel = new viewModel();
var g = ko.mapping.fromJS(customFormTemplateViewModel, mvcModel);
ko.applyBindings(g);
});
</script>
When I browse the page, the only elements rendered are the <div name="customFormField" data-bind="with: Fields[x]"></div> elements, neither the form-group div inside it nor the label/listbox are created on the page.
There are no binding errors from KnockoutJS.
If I remove the data-bind="with: Fields[#i]", the other elements render properly, so it must be something about trying to bind to an element in an array that's making this go sideways, but I haven't been able to figure out what.
Edit: Here's the Html that is output from this:
It's interesting because if I inspect the DOM with chrome's inspector, it doesn't show anything inside each customFormField div, but in the source it does have the elements inside. It's outputting the System.Web.Mvc.SelectListItem in each item, but I think I just made my SelectList wrong.
<div name="customFormField" data-bind="with: Fields[0]">
<div class="form-group">
<label for="Fields_0__SqlDataType">SqlDataType</label>
<select class="form-control" data-bind="text: SqlDataType" id="Fields_0__SqlDataType" multiple="multiple" name="Fields[0].SqlDataType"><option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
</select>
</div>
</div>
<div name="customFormField" data-bind="with: Fields[1]">
<div class="form-group">
<label for="Fields_1__SqlDataType">SqlDataType</label>
<select class="form-control" data-bind="text: SqlDataType" id="Fields_1__SqlDataType" multiple="multiple" name="Fields[1].SqlDataType"><option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
</select>
</div>
</div>
<div name="customFormField" data-bind="with: Fields[2]">
<div class="form-group">
<label for="Fields_2__SqlDataType">SqlDataType</label>
<select class="form-control" data-bind="text: SqlDataType" id="Fields_2__SqlDataType" multiple="multiple" name="Fields[2].SqlDataType"><option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
</select>
</div>
</div>
<div name="customFormField" data-bind="with: Fields[3]">
<div class="form-group">
<label for="Fields_3__SqlDataType">SqlDataType</label>
<select class="form-control" data-bind="text: SqlDataType" id="Fields_3__SqlDataType" multiple="multiple" name="Fields[3].SqlDataType"><option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
</select>
</div>
</div>
<div name="customFormField" data-bind="with: Fields[4]">
<div class="form-group">
<label for="Fields_4__SqlDataType">SqlDataType</label>
<select class="form-control" data-bind="text: SqlDataType" id="Fields_4__SqlDataType" multiple="multiple" name="Fields[4].SqlDataType"><option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
<option>System.Web.Mvc.SelectListItem</option>
</select>
</div>
</div>
Edit 2: This example is showing when I tried the text binding, but I've tried it both ways.
Edit 3: I copied the output Html into another place on the page, and it didn't render that either, until I removed the data-bind="with: Fields[n]" from the containing div, so the problem seems to be there. However, if I remove that from the parent in the actual file, I get a binding error from data-bind="value: SqlDataType"
Related
I'm trying to bind multiple single select lists to one list of strings in my view model I have the property: public List<string> Items {get; set;} and in my view I have 3 select lists with single select option, but when I try to model bind my select list to Items property using asp-for="#Model.Items" all my single select lists converts to multi select lists. Why?
My view:
<form asp-controller="Admin" asp-action="Index" method="post">
<div class="col-12">
<div class="row">
<div class="form-group col-4">
<div class="d-inline-block">
<label>Order By Price:</label>
</div>
<div class="d-inline-block">
<select class="form-control" asp-for="#Model.Items" style="width:150px">
<option value="Ascending">Ascending</option>
<option value="Descending">Descending</option>
</select>
</div>
</div>
<div class="form-group col-4">
<div class="d-inline-block">
<label>Order By Name:</label>
</div>
<div class="d-inline-block">
<select class="form-control" asp-for="#Model.Items" style="width:150px">
<option value="A-Z">A-Z</option>
<option value="Z-A">Z-A</option>
</select>
</div>
</div>
<div class="form-group">
<div class="d-inline-block">
<button type="submit" class="btn btn-secondary">Confirm</button>
</div>
</div>
</div>
</div>
</form>
Controller:
[HttpPost]
public IActionResult Index(MangeProductsViewModel vm)
{
return View(new MangeProductsViewModel
{
Products = productRepository.Products.ToList().SortProducts(vm)
});
}
My viewmodel
public IEnumerable<Product> Products { get; set; }
public List<string> Items { get; set; }
And when I select items from lists it adds them correctly the problem is dat user can select multiple options from one list
That's because asp-for invokes a tag helper, and for a prop of type List<string>, the HTML that is generated is going to be a select multiple. However, this wouldn't work for you anyways as it would give all the selects the same name. As a result, they'll simply overwrite each other, not add to each other.
For this, you're going to have to handle things manually via:
<select name="Items[]">
#foreach (var option in Model.SelectList1Options)
{
<option value="#option.Value">#option.Text</option>
}
</select>
For each drop down.
Try this!
In your HttpGet method create a new list with length 3 and assign it to Items property of model and pass the model to view.
And in view write asp-for as follows
asp-for=“#Model.Items[0]”
asp-for=“#Model.Items[1]”
asp-for=“#Model.Items[2]”
The reason is that you have used asp-for="#Model.Items", sicne your item is type of List<string>, so the dropdown list is set as multiple select.
Just use name="Items" instead:
<form asp-controller="Managers" asp-action="SelectTest" method="post">
<div class="col-12">
<div class="row">
<div class="form-group col-4">
<div class="d-inline-block">
<label>Order By Price:</label>
</div>
<div class="d-inline-block">
<select class="form-control" name="Items" style="width:150px">
<option value="Ascending">Ascending</option>
<option value="Descending">Descending</option>
</select>
</div>
</div>
<div class="form-group col-4">
<div class="d-inline-block">
<label>Order By Name:</label>
</div>
<div class="d-inline-block">
<select class="form-control" name="Items" style="width:150px">
<option value="A-Z">A-Z</option>
<option value="Z-A">Z-A</option>
</select>
</div>
</div>
<div class="form-group">
<div class="d-inline-block">
<button type="submit" class="btn btn-secondary">Confirm</button>
</div>
</div>
</div>
</div>
</form>
Result:
I am using an array of objects
({"code":"id1","color":"red","description":"eg1"})
where each object contains input fields.User can dynamically add and remove those objects.
Now I want that when he enters code rest of the field should be filled automatically.
For that I have developed an algorithm which will take the code and will give me the color and description but for that I need the POSITION of the object which is changed so that I can update the array on that index itself.
Below is my share of code:
<div v-for="(x,i) in fabric_arr">
<!--<pre>{{x}}</pre>-->
<div class="row">
<div class="col-md-4">
<span>
<input type=”text” list="idOfDatalist" class="form-control border-input" placeholder="Fabric Code" v-model="x._id">
<datalist id="idOfDatalist">
<option v-for="y in all_fabrics">{{y._id}}</option>
</datalist>
</span>
</div>
<div class="col-md-4">
<span>
<input type=”text” class="form-control border-input" placeholder="Fabric Color" v-model="x.color"></span>
</div>
<div class="col-md-4">
<span>
<input type=”text” class="form-control border-input" placeholder="Fabric Description" v-model="x.description"></span>
</div>
</div>
</div>
And my controller:
watch: {
fabric_arr: {
handler : function (val) {
console.log("val");
console.log(val);
//val.color="red always";
this.fabric_arr[0].color="fghjkl"
},
deep: true
}
}
For all those who are looking for the answer,use v-bind:change() to trigger the changes and manipulate index there.
Below is the code:
<input type=”text” list="idOfDatalist" class="form-control border-input" placeholder="Fabric Code" v-model="x._id" v-bind:change="inpChangedForCode(x)">
<datalist id="idOfDatalist">
<option v-for="y in all_fabrics">{{y._id}}</option>
</datalist>
I'm trying to validate a changed password partial view in my Index page of my Manage section. Like so,
#inject UserManager<ApplicationUser> UserManager
#model IndexViewModel
#{
ViewData["Title"] = "Manage your account";
}
<div class="row settings">
<div class="col-sm-6">
<h4>Basic information</h4>
#await Html.PartialAsync("BasicInformation", new BasicInformationViewModel(UserManager))
</div>
<div class="col-sm-6">
<h4>Change Password</h4>
#await Html.PartialAsync("ChangePassword", new ChangePasswordViewModel())
</div>
Controller code
[HttpPost("ChangePassword")]
[ValidateAntiForgeryToken]
public async Task<IActionResult> ChangePassword(ChangePasswordViewModel model)
{
if (!ModelState.IsValid)
{
return PartialView(model);
}
var user = await GetCurrentUserAsync();
if (user != null)
{
var result = await _userManager.ChangePasswordAsync(user, model.OldPassword, model.NewPassword);
if (result.Succeeded)
{
await _signInManager.SignInAsync(user, isPersistent: false);
_logger.LogInformation(3, "User changed their password successfully.");
ViewData["StatusMessage"] = TranslationService.TranslateManageMessage(_context, ManageMessageId.ChangePasswordSuccess, "Your password has been changed.");
return PartialView();
}
AddErrors(result);
return PartialView(model);
}
ViewData["StatusMessage"] = TranslationService.TranslateManageMessage(_context, ManageMessageId.Error, "An error has occured.");
return PartialView();
}
So when the user changes his password, the thing I actually want to happen is that the Action get completed and the statusmessage of the partialview ges updated with what happend (succes, error, etc..). So how i have implemented it now it's work as I want it to but the only thing that is bad about this setup is that this happens.
My layout just disappears and I have no idea why. I thought returning a partial view would just reload the page but seems like it's not that.
Could anyone point me in the right direction ? Or give a suggestion how to do this better ?
EDIT:
ChangePassword Partial
#model ChangePasswordViewModel
<form asp-controller="Manage" asp-action="ChangePassword" method="post" class="form-horizontal">
<p class="text-success">#ViewData["StatusMessage"]</p>
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="OldPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="OldPassword" class="form-control" />
<span asp-validation-for="OldPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="NewPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="NewPassword" class="form-control" />
<span asp-validation-for="NewPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Change password</button>
</div>
</div>
#section Scripts {
#{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
}
The thing is you are initially loading the partialview inside index view on a get request, when you post to changepassword action it is not an ajax partial post it is a full post and it is not the Index action so it is not using the index view and you are returning a partialview that is not inside any other view so there is no outer view and no layout
UPDATE: since you requested in comments to show how to make it use ajax:
In your index view wrap a div with an id around the partial like this:
<div id="changepassword">
#await Html.PartialAsync("ChangePassword", new ChangePasswordViewModel())
</div>
and you need to include jquery unobtrusive ajax
in the Scripts section which should be in the index view not in the partial
#section Scripts {
#{ await Html.RenderPartialAsync("_ValidationScriptsPartial"); }
<script src="~/path-to-jqueryajaxunobtrusive"></script>
}
In your ChangePassword view change like this:
#model ChangePasswordViewModel
<form asp-controller="Manage" asp-action="ChangePassword" method="post" class="form-horizontal"
data-ajax="true"
data-ajax-method="POST"
data-ajax-mode="replace"
data-ajax-update="#changepassword">
<p class="text-success">#ViewData["StatusMessage"]</p>
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="OldPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="OldPassword" class="form-control" />
<span asp-validation-for="OldPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="NewPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="NewPassword" class="form-control" />
<span asp-validation-for="NewPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<label asp-for="ConfirmPassword" class="col-md-2 control-label"></label>
<div class="col-md-10">
<input asp-for="ConfirmPassword" class="form-control" />
<span asp-validation-for="ConfirmPassword" class="text-danger"></span>
</div>
</div>
<div class="form-group">
<div class="col-md-offset-2 col-md-10">
<button type="submit" class="btn btn-default">Change password</button>
</div>
</div>
by including the jquery unobtrusive ajax script and adding the data-* attributes as shown that should wire up the form to do an ajax post and the result should update the contents of the div with the id indicated
I'm extending laravel spark and wanted to try to validate registration before actually sending it off.
All data is handled nicely and correctly from the server but the errors do not pop-up.
The code being used for validation is as following:
module.exports = {
...
methods: {
...
validate(field, value) {
var formData = {
field: field,
value: value
};
var form = new SparkForm(formData);
Spark.post('/register_validate', form).then(response => {
this.registerForm.addSuccess(field);
}).catch(errors => {
this.registerForm.addError(field, errors[field]);
});
}
}
};
After, i extended SparkForm and added these methods (successes is just a copy of errors, used to display validation success messages):
/**
* SparkForm helper class. Used to set common properties on all forms.
*/
window.SparkForm = function (data) {
...
this.addError = function(field, errors) {
form.successes.remove(field);
form.errors.add(field, errors);
},
this.addSuccess = function(field) {
form.errors.remove(field);
form.successes.add(field, true);
}
};
And finally i added some methods on the SparkFormErrors.
/**
* Spark form error collection class.
*/
window.SparkFormErrors = function () {
...
this.add = function(field, errors) {
this.errors[field] = errors;
};
this.remove = function(field) {
delete this.errors[field];
}
};
In the console no errors are shown and in the network tab i can see the correct messages coming true, also when i add a console log in for example the response callback i can see the actual errors or success messages. But they are not drawn on screen.
For completeness i'll include the important content of the template blade file:
#extends('spark::layouts.app')
#section('content')
<spark-register-stripe inline-template>
<!-- Basic Profile -->
<div class="row">
<div class="col-md-8 col-md-offset-2">
<div class="panel panel-default">
<div class="panel-heading">
{{ trans('auth.register.title') }}
</div>
<div class="panel-body">
<!-- Generic Error Message -->
<div class="alert alert-danger" v-if="registerForm.errors.has('form')">
#{{ registerForm.errors.get('form') }}
</div>
<!-- Registration Form -->
<form class="form-horizontal" role="form">
<!-- Name -->
<div class="form-group" :class="{'has-error': registerForm.errors.has('name')}">
<label class="col-md-4 control-label">{{ trans('user.name') }}</label>
<div class="col-md-6">
<input type="name" class="form-control" name="name" v-model="registerForm.name" autofocus #blur="validate('name', registerForm.name)">
<span class="help-block" v-show="registerForm.errors.has('name')">
#{{ registerForm.errors.get('name') }}
</span>
</div>
</div>
<!-- E-Mail Address -->
<div class="form-group" :class="{'has-error': registerForm.errors.has('email')}">
<label class="col-md-4 control-label">{{ trans('user.email') }}</label>
<div class="col-md-6">
<input type="email" class="form-control" name="email" v-model="registerForm.email" #blur="validate('email', registerForm.email)">
<span class="help-block" v-show="registerForm.errors.has('email')">
#{{ registerForm.errors.get('email') }}
</span>
</div>
</div>
<!-- Password -->
<div class="form-group" :class="{'has-error': registerForm.errors.has('password')}">
<label class="col-md-4 control-label">{{ trans('user.password') }}</label>
<div class="col-md-6">
<input type="password" class="form-control" name="password" v-model="registerForm.password" #blur="validate('password', registerForm.password)">
<span class="help-block" v-show="registerForm.errors.has('password')">
#{{ registerForm.errors.get('password') }}
</span>
</div>
</div>
<div class="form-group">
<div class="col-md-6 col-md-offset-4">
<button class="btn btn-primary" #click.prevent="register" :disabled="registerForm.busy">
<span v-if="registerForm.busy">
<i class="fa fa-btn fa-spinner fa-spin"></i>{{ trans('auth.register.submitting') }}
</span>
<span v-else>
<i class="fa fa-btn fa-check-circle"></i>{{ trans('auth.register.submit') }}
</span>
</button>
</div>
</div>
</form>
</div>
</div>
</div>
</div>
</div>
</spark-register-stripe>
#endsection
Any bright mind seeing what i'm missing/forgetting?
I can't seem to get my checkboxes to align with the labels and select options inside a well using bootstrap 3.3.5. My code is:
<div class="well">
<div class="row">
<div class="col-md-6">
<label class="control-label">Checkboxes</label>
</div>
</div>
<div class="row">
<div class="col-md-6">
<div class="checkbox">
<input type="checkbox" id="1">1
</div>
<div class="checkbox">
<input type="checkbox" id="2">2
</div>
</div>
</div>
<div class="row">
<div class="col-md-6">
<label class="control-label">Select</label>
</div>
</div>
<div class="row">
<div class="col-md-6">
<select id="2" class="form_control">
<option value="A">A</option>
<option value="B">B</option>
</select>
</div>
</div>
</div>
Fiddle illustrating misalignment:
https://jsfiddle.net/dwzvsoax/
I'm pretty new to bootstrap, so I'm probably doing something dumb! Thank you in advance.
Wrap your form elements and labels together with a .form-group element.
jsFiddle
Also, you should read the Boostrap Documentation on Forms
<div class="well">
<div class="form-group">
<label>Check Boxes</label>
<div class="checkbox">
<label>
<input type="checkbox" /> 1
</label>
</div>
<div class="checkbox">
<label>
<input type="checkbox" /> 2
</label>
</div>
</div>
<div class="form-group">
<label>select</label>
<select id="2" class="form-control">
<option value="A">A</option>
<option value="B">B</option>
</select>
</div>
</div>
you could add in to the .checkbox class an attribute margin-left:22px; in the css file and that should give you the desired effect. If it's just on one page though, just add it in style tags at the top of the page, as it may not be desirable everywhere.
I updated your fiddle