I'm using Knockout to create a dynamic form. I have a table and the user can add new rows to the table via a button click. My Bootstrap selectpicker appears in the first row, but not in the new rows added by the button click. Here's a screenshot:
My research indicates that when the Bootstrap select is added dynamically (as it is in this case) it needs to be rendered each time. It can be rendered like so:
$('.selectpicker').selectpicker('render');
This seems to be the case because I have tried the render in several places in my code. However, despite several hours of tinkering, I have not been able to get it to render properly on every button click. Sometimes it renders two or more select pickers on each row, for example. Here is my full Razor page code:
@model Birder2.ViewModels.CreateObservationViewModel
@using Microsoft.AspNetCore.Identity
@inject UserManager<ApplicationUser> UserManager
@using Newtonsoft.Json
@{
string data = JsonConvert.SerializeObject(Model);
}
@section scripts{
<script src="~/js/knockout-3.4.2.js"></script>
<script src="~/js/knockout.mapping-latest.js"></script>
<script src="~/js/createobservationviewmodel.js"></script>
<script type="text/javascript">
var createObservationViewModel = new CreateObservationViewModel(@Html.Raw(data));
createObservationViewModel.Observation.LocationLatitude = @UserManager.GetUserAsync(User).Result.DefaultLocationLatitude
createObservationViewModel.Observation.LocationLongitude = @UserManager.GetUserAsync(User).Result.DefaultLocationLongitude
ko.applyBindings(createObservationViewModel);
$(select).selectpicker('render');
</script>
}
<h2>Create an observation</h2>
<form>
<div class="form-group">
@* change to Partial View like Manage Index *@
<span class="control-label" name="MessageToClient" data-bind="text: MessageToClient" />
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="Observation.ObservationId">Id:</label>
<div class="col-sm-10">
<span class="form-control" name="Obervation.ObservationId" data-bind="text: Observation.ObservationId@*, event: {change: flagSalesOrderAsEdited}*@" />
</div>
</div>
<div class="form-group">
<label class="col-sm-2 control-label" for="Observation.ObservationId">Date:</label>
<div class="col-sm-10">
<input class="form-control" data-bind="datePicker : Observation.ObservationDateTime" type="date" />
</div>
</div>
<div class="form-group">
<input class="form-control" name="Observation.LocationLatitude" data-bind="value: Observation.LocationLatitude" />
<input class="form-control" name="Observation.LocationLongitude" data-bind="value: Observation.LocationLongitude" />
</div>
<div class="form-group" spellcheck="true">
<label class="col-sm-2 control-label" for="Observation.Note">Note:</label>
<div class="col-sm-10">
<input class="form-control" name="Observation.Note" data-bind="value: Observation.Note" />
</div>
</div>
<hr />
<hr />
<table class="table table-striped">
<tr>
<th class="text-right">Quantity</th>
<th class="text-right">Bird</th>
<th><button class="btn btn-info btn-xs" data-bind="click: addObservedSpecies">Add</button></th>
</tr>
<tbody data-bind="foreach: ObservedSpecies">
<tr>
<td class="form-group"><input name="ObservedSpecies.Quantity" class="form-control input-sm text-right" data-bind="@*attr: {'id': 'Quantity_' + $index()},*@ value: Quantity@*, event: {change: flagSalesOrderItemAsEdited}*@" /></td>
<td class="form-group">
@Html.DropDownListFor(m => m.Observation.BirdId,
new SelectList(Model.Birds, "BirdId", "EnglishName"),
new
{
@class = "form-control selectpicker show-tick",
data_bind = "value: BirdId",
title = "Choose a bird",
data_live_search = "true",
data_show_subtext = "true"
})
</td>
<td class="form-group">delete</td>
</tr>
</tbody>
</table>
</form>
Can anyone offer any help on how I can render each selectpicker?
Update:
If I remove the bootstrap select, leaving it as an ordinary select list, everything works as expected. So it is just a matter of getting the Bootstrap select picker to render properly.
Update 2:
The code for adding another row is in a knockout model:
CreateObservationViewModel = function (data) {
var self = this;
ko.mapping.fromJS(data, observedSpeciesMapping, self);
self.addObservedSpecies = function () {
var observedSpecies = new ObservedSpeciesViewModel({ Id: 0, BirdId: 0, Quantity: 1 });
self.ObservedSpecies.push(observedSpecies);
}
To make the selectpicker function get called whenever knockout dynamically adds the new select element you'll need to use a custom binding. The simplest case might look like this:
ko.bindingHandlers.selectPicker = {
init: function (element, valueAccessor, allBindings) {
$(element).selectpicker('render');
}
}
Add that to your razor droplist arguments. It doesn't need a meaningful value since it's not doing anything to the data.
data_bind = "selectPicker: {}, value: BirdId",