I'm trying to get data from database and populate different bootstrap dropdowns.
The table contains the following fields: id, ipAddress, hostName, recordNumber
The Model:
function myModel(){
var self = this;
self.id = ko.observable();
self.ipAddress = ko.observable();
self.hostName = ko.observable();
self.recordNumber = ko.observable();
}
The ViewModel
function myViewModel(){
var self = this;
self.ipAddresses = ko.observableArray();
self.hostNames = ko.observableArray();
self.recordNumbers = ko.observableArray();
self.selectedIpAddress = ko.observable("");
self.selectIpAddress = function (){
self.selectedIpAddress ();
}
}
var uri = 'api/items';
var array = [];
$.getJSON(uri)
.done(function (data) {
$.each(data, function (index, item) {
array.push(item);
self.ipAddresses(array.ipAddress);
self.hostNames(array.hostName);
self.recordNumbers(array.recordNumber);
});
});
The array contains well the data but I don't know how to access the different fields of the table, I tried this but it doesn't work:
self.ipAddresses(array.ipAddress);
self.hostNames(array.hostName);
self.recordNumbers(array.recordNumber);
Html code of one dropdown:
<div class="input-group">
<input type="text" class="form-control"data-bind="value:selectedIpAddress"
<div class="input-group-btn">
<button type="button" id="dropDownIpAddress" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
<span class="caret"></span>
</button>
<ul class="dropdown-menu dropdown-menu-right" role="menu" data-bind="foreach: ipAddresses">
<li><a role="menuitem" href="#" data-bind="text: ipAddress, click: myViewModel.selectIpAddress"></a></li>
</ul>
</div>
</div>
JSFiddle
Related
I am having problems persisting data from KO observableArray in a MVC Partialview...
I have a side navigation menu where one of the nodes is dynamically built using KOjs 'foreach', and this navigation menu should persist across the site. The menu is rendered ok on the first View page but when I navigate on other page (clicking on menu option or any way) the dynamic node is empty, although the observableArrays were fetched and loaded with the correct data.
It seems like the navigaition menu (which is a partial view) is not refreshing/reloading to render the menu nodes/options again.
Any help si much appreciated. TIA!
self.reports = ko.observableArray([]);
self.reportCategories = ko.observableArray([]);
self.getReports = function () {
App.Server.get("/.../reports/")
.then(function (vm) {
self.reports(vm.reports);
self.reportCategories(vm.categories);
});
};
self.init = function () {
self.getReports();
};
self.init();
<li>
<a class="level1" data-toggle="collapse" href="#report-submenu" aria-expanded="false" aria-controls="collapseExample">Reports</a>
<ul class="level1 collapse" id="report-submenu" data-bind="foreach: reportCategories">
<li>
<a class="level2 collapse" data-toggle="collapse" data-bind="text: label, attr: { href: '#' + value }"
aria-expanded="false" aria-controls="collapseExample"></a>
<ul class="level2 collapse" data-bind="foreach: reports, attr: { id: value }">
<li data-bind="if: category == $parent.categoryId">
<a class="level3" data-bind="text: menuName, attr: { href: reportName }"></a>
</li>
</ul>
</li>
Here's the other part of the code (a Nancy GET method):
Get["/"] = _ =>
{
var reportModel = new ReportModel();
var reports = reportService.GetList();
if (reports != null)
{
// Categories
reportModel.Categories = reports.Select(s => s.Category).Distinct().Select(c => new ReportCategoryModel()
{
CategoryId = c,
Label = c.TrimStart("Reports/".ToCharArray()),
Value = c.ToLower().Replace('/', '-')
}).ToList();
// Reports
reportModel.Reports = reports.Select(r => new ReportRecord()
{
MenuName = r.MenuName,
ReportName = r.ReportName,
Category = r.Category,
}).ToList();
}
return Response.AsJson(reportModel);
};
Right now I am populating radio button from the observable array,
<div data-bind="foreach: cars">
<div>
<input type='radio' data-bind="checked: $root.vehicle, checkedValue: id, value:id"><span data-bind="text: model"></span>
</div>
</div>
Model data,
public class MyViewModel
{
public List<Cars> cars {get; set;}
}
public class Cars
{
public int Id{get; set;}
public string Make {get; set;}
public string Model {get; set;}
}
Say if I want to populated radio button for Make 'Honda' and dropdown list for make 'Toyota'. How to do that?
Basically, I am trying populate two different list of controls from same observable array.
If I understand what exactly you want to do, here is an example through what you could do :
Example :https://jsfiddle.net/kyr6w2x3/85/
HTML :
<div data-bind="foreach: Cars">
<input type='radio' data-bind="checked :$parent.VehicleSelected ,checkedValue: Make, value:Id">
<span data-bind="text: Make"></span>
<select data-bind="foreach:Models,visible:IsSelected" >
<option data-bind="text:Name , value:Id"></option>
</select>
<br />
</div>
JS:
var data = [
{Id:1,Make :"TOYOTA",Model: [{Id:1,Name:"COROLLA"},{Id:2,Name:"CAMERY"},{Id:3,Name:"PERADO"}]},
{Id:2,Make :"HONDA",Model:[{Id:4,Name:"CIVIC"},{Id:5,Name:"ACCORD"},{Id:6,Name:"PILOT"}]},
{Id:3,Make :"NISSAN",Model:[{Id:7,Name:"SENTRA"},{Id:8,Name:"ALTIMA"},{Id:9,Name:"JUKE"}]}];
function AppViewModel() {
var self = this ;
self.Cars = ko.observableArray($.map(data, function (item) {
return new CarItemViewModel(item);
}));
self.VehicleSelected = ko.observable();
self.VehicleSelected.subscribe(function(newValue){
ko.utils.arrayForEach(self.Cars(), function(item) {
if (item.Make().toUpperCase() === newValue.toUpperCase()) {
return item.IsSelected(true);
}else{
return item.IsSelected(false);
}
});
})
}
var CarItemViewModel = function (data){
var self = this ;
self.Id = ko.observable(data.Id);
self.Make = ko.observable(data.Make);
self.Models = ko.observableArray($.map(data.Model, function (item) {
return new ModelsItemViewModel(item);
}));
self.IsSelected = ko.observable(false);
}
var ModelsItemViewModel = function (data){
var self = this ;
self.Id = data.Id;
self.Name = data.Name;
}
var appVM = new AppViewModel()
ko.applyBindings(appVM);
My interpretation of your question:
You have one variable that holds all your data
You want to render multiple UI elements that represent sub-sets of these data
A good way to do this is by using ko.computeds that return the filtered result of an observableArray. An example:
var allCars = ko.observableArray([
{"name":"CIVIC","make":"HONDA"},
{"name":"SENTRA","make":"NISSAN"}];
var hondas = ko.computed(function() {
return allCars().filter(function(car) {
return car.make === "HONDA";
});
});
Because the computed uses the allCars observable, it is notified when the source data changes. The filter method is re-evaluated. In the snippet below you can see that you can push new data to your source and is automatically put in the right subset!
var allCars = ko.observableArray([
{"name":"CIVIC","make":"HONDA"},
{"name":"ACCORD","make":"HONDA"},
{"name":"PILOT","make":"HONDA"},
{"name":"SENTRA","make":"NISSAN"},
{"name":"ALTIMA","make":"NISSAN"},
{"name":"JUKE","make":"NISSAN"}]);
var hondas = ko.computed(function() {
return allCars().filter(function(car) {
return car.make === "HONDA";
});
});
var nissans = ko.computed(function() {
return allCars().filter(function(car) {
return car.make === "NISSAN";
});
});
var otherBrands = ko.computed(function() {
return allCars().filter(function(car) {
return car.make !== "NISSAN" &&
car.make !== "HONDA";
});
});
var inputVM = {
name: ko.observable(""),
make: ko.observable(""),
add: function() {
allCars.push({
name: this.name(),
make: this.make().toUpperCase()
});
}
};
ko.applyBindings({
hondas: hondas,
nissans: nissans,
allCars: allCars,
otherBrands: otherBrands,
inputVM: inputVM
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<h2>Honda</h2>
<ul data-bind="foreach: hondas">
<li data-bind="text: name"></li>
</ul>
<h2>Nissan</h2>
<ul data-bind="foreach: nissans">
<li data-bind="text: name"></li>
</ul>
<h2>Other brands</h2>
<ul data-bind="foreach: otherBrands">
<li data-bind="text: name"></li>
</ul>
<h2>Add car</h2>
<div data-bind="with: inputVM">
<label>
Name:
<input type="text" data-bind="value: name">
</label>
<label>
Make:
<input type="text" data-bind="value: make">
</label>
<button data-bind="click: add">Add</button>
</div>
I would like to use knockout for updating dynamically the input element by the selected value from the dropdown but I don't understand how to use the code I commented here bellow with the dropdown and I'm not sure if it's the right way to do... Please help me understand and fix this problem.
View:
<div class="form-group">
<label for="dropDownRelationType">Business relation type</label>
<div class="input-group">
<input type="text" class="form-control" id="txtRelationTypeId" name="txtRelationTypeId" data-bind="value: companyModel.relationTypeId" placeholder="Business relation type"
<div class="input-group-btn">
<button type="button" id="dropDownRelationType" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true">
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" data-bind="foreach: relationTypes">
<li><a role="menuitem" href="#" data-bind="text: relationTypeId"></a></li>
</ul>
</div>
</div>
</div>
Knockout:
function companyModel() {
var self = this;
self.companyName = ko.observable("");
self.status = ko.observable("1");
self.relationTypeId = ko.observable("");
self.relationTypes = ko.observableArray();
}
function CompanyViewModel() {
var self = this;
self.companyModel = new companyModel();
self.relationTypes = ko.observableArray([
{ relationTypeId: "1" }, { relationTypeId: "2" }, { relationTypeId: "3" }, { relationTypeId: "4" }
])
//-------------------------------------------------------
//self.relationTypes.subscribe(function (selectedRelationTypeId) {
// shouter.notifySubscribers(selectedRelationTypeId, "valueToPublish");
//});
//shouter.subscribe(function (selectedRelationTypeId) {
// self.relationTypeId(selectedRelationTypeId);
//}, self, "valueToPublish");
//-------------------------------------------------------
}
var companyViewModel = new CompanyViewModel();
$(function () {
ko.applyBindings(companyViewModel);
});
You can add an observable to select the relationTypeId like
function CompanyViewModel() {
var self = this;
self.companyModel = new companyModel();
self.selectedRelationTypeId = ko.observable();
self.relationTypes = ko.observableArray([
{ relationTypeId: "1" }, { relationTypeId: "2" }, { relationTypeId: "3" }, { relationTypeId: "4" }
])
self.selectRelationTypeId = function(relationType){
self.selectedRelationTypeId(relationType.relationTypeId);
};
}
and change your html markup as
<div class="form-group">
<label for="dropDownRelationType">Business relation type</label>
<div class="input-group">
<input type="text" class="form-control" id="txtRelationTypeId" name="txtRelationTypeId" data-bind="value: selectedRelationTypeId" placeholder="Business relation type" />
<div class="input-group-btn">
<button type="button" id="dropDownRelationType" class="btn btn-default dropdown-toggle" data-toggle="dropdown" aria-haspopup="true">
<span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu" data-bind="foreach: relationTypes">
<li><a role="menuitem" href="#" data-bind="text: relationTypeId, click: $root.selectRelationTypeId"></a></li>
</ul>
</div>
</div>
</div>
You should be able to see the selection you make in the input field.
What I want to achieve is once the data got saved into database, when it goes back to client, it will automatically update the observable array. But somehow I couldn't make it happen.
This is my Server side code:
[HttpGet]
public JsonResult GetTasks()
{
var tasks = context.ToDoTasks.ToList();
return Json(tasks.Select(c => new TaskViewModel(c)).ToList(), JsonRequestBehavior.AllowGet);
}
[HttpPost]
public JsonResult AddTask(string text, string date)
{
var nTask = new ToDoTask()
{
Text = text,
Date = DateTime.ParseExact(date, "MM/dd/yyyy", System.Globalization.CultureInfo.InvariantCulture),
IsDone = false,
Order = 1,
};
context.ToDoTasks.Add(nTask);
context.SaveChanges();
return Json(new TaskViewModel(nTask), JsonRequestBehavior.AllowGet);
}
This is my cshtml file code:
<form>
<div class="controls controls-row" style="margin-top:40px;">
<input class="span7" type="text" placeholder="Task to do" style="margin-right:4px;" id="oText">
<div id="task-date" class="input-append date">
<input data-format="MM/dd/yyyy" type="text" placeholder="MM/dd/yyyy" name="taskDate" id="oDate" />
<span class="add-on">
<i data-time-icon="icon-time" data-date-icon="icon-calendar">
</i>
</span>
</div>
<button class="btn" type="submit" style="margin-top:-10px;" data-bind="click: save">+</button>
</div>
<div class="controls">
<label class="checkbox">
<input type="checkbox"> Mark all as complete
</label>
</div>
<div id="task-section" style="margin-top:20px;">
<ul data-bind="foreach: Tasks">
<!-- ko if: IsDone -->
<li>
<span><input type="checkbox" style="margin:-5px 5px 0px 0px;" data-bind="checked: IsDone" /></span>
<del><span data-bind="text: Text"></span></del>
<del><span class="task-date" data-bind="text: Date"></span></del>
</li>
<!-- /ko -->
<!-- ko ifnot: IsDone -->
<li>
<span><input type="checkbox" style="margin:-5px 5px 0px 0px;" data-bind="checked: IsDone" /></span>
<span data-bind="text: Text"></span>
<span class="task-date" data-bind="text: Date"></span>
</li>
<!-- /ko -->
</ul>
</div>
<div class="clearfix" style="margin-top:30px;">
<span class="pull-left" style="font-weight:bold;"><span data-bind="text: oItemLeft"></span> item left</span>
<span class="pull-right badge" style="cursor:pointer;" data-bind="click: remove">Clear # completed item</span>
</div>
</form>
And finally my JS:
var ViewModel = function (data) {
var self = this;
self.Tasks = ko.mapping.fromJS(data, {}, self.Tasks);
self.oItemLeft = ko.computed(function () {
var i = 0;
data.forEach(function (entry) {
if (!entry.IsDone) i++;
});
return i;
});
self.save = function () {
$.ajax({
url: "Home/AddTask",
type: "POST",
data: { text: $('#oText').val(), date: $('#oDate').val() },
success: function (response) {
ko.mapping.fromJS(response, ViewModel);
}
});
};
self.remove = function () {
alert('delete');
}
}
$(function () {
$.getJSON("/Home/GetTasks/", null, function (data) {
ko.applyBindings(new ViewModel(data));
});
// for datepicker
$('#task-date').datetimepicker({
language: 'pt-BR',
pickTime: false
});
});
self.save = function () {
$.ajax({
url: "Home/AddTask",
type: "POST",
data: { text: $('#oText').val(), date: $('#oDate').val() },
success: function (response) {
var task = ko.mapping.fromJS(response);
self.Tasks.push(task);
}
});
};
Also for oItemLeft you should be referring to self.Tasks instead of data:
self.oItemLeft = ko.computed(function () {
var i = 0;
self.Tasks().forEach(function (entry) {
if (!entry.IsDone) i++;
});
return i;
});
There are four screens in my sample application, Login,Dash Board, List Page and Details Page,Here login page, Dash Board , List Page bindings are working, but Details page details not populate as I expected, working example http://jsfiddle.net/uderox/gY9Nt/3/
Html
<div data-role="page" data-theme="a" id="loginPage" data-bind="with:loginModel">
<div data-role="content">
<p>
<input type="text" class="input" placeholder="username" data-bind="value: userName"/>
<input type="password" class="input" placeholder="Password" data-bind="value: password"/>
</p>
<p>
<button data-bind='click: submit'>Login</button>
</p>
</div>
</div>
<div data-role="page" data-theme="a" id="dashBoardPage" data-bind="with:dashboardViewModel">
<div data-role="content">
<label>User Id<span data-bind="text:uId"></span></label>
<input type="button" value="Click" data-bind="click:goToList"/>
</div>
</div>
<div data-role="page" id="listViewPage" data-bind="with:listViewModel">
<div>
<div id="listViewDiv">
<ul data-role="listview" data-bind="foreach: hospitalList">
<li data-bind="click: $parent.selectHospital">
<h2>Hospital Id:<span data-bind="text:id"></span></h2>
<p>Name <span data-bind="text:name"></span></p>
</li>
</ul>
</div>
</div>
</div>
<div data-role="page" id="detailsView">
Back
Home
<div>
<h2>Hospital Id : <span data-bind="text:id"></span></h2>
<input data-mini="true" tabindex="5" data-bind="value: name"
id="name"/>
</div>
</div>
Js
var LoginViewModel = function (parantModel) {
var self = this;
self.userName = ko.observable();
self.password = ko.observable();
self.submit = function () {
parantModel.dashboardViewModel.uId(self.userName());
navigationService.navigateTo($('#dashBoardPage'));
}
}
var DashboardViewModel = function(parentModel){
var self = this;
self.uId = ko.observable();
self.goToList = function(){
navigationService.navigateTo($('#listViewPage'));
}
}
var ListViewModel = function(parentViewModel){
var self = this;
self.selectedHospital = ko.observable();
var objList = [{"id":"001","name":"Hospital1","location":"SL"},{"id":"002","name":"Hospital2","location":"SL"}].map(function(hospital){return new HospitalViewModel(hospital);});
self.hospitalList = objList;
self.selectHospital = function(hospital){
parentViewModel.detailViewModel(hospital);
alert("000");
navigationService.navigateTo($('#detailsView'));
};
}
var HospitalViewModel = function(data){
var self = this;
self.id = data.id;
self.name = ko.observable(data.name);
}
function PageViewModel (){
var self = this;
self.loginModel = new LoginViewModel(self);
self.dashboardViewModel = new DashboardViewModel(self);
self.listViewModel = new ListViewModel(self);
self.detailViewModel = ko.observable();
}
function NavigationService(){
var self = this;
self.navigateTo = function(pageId){
$.mobile.changePage(pageId);
};
}
var navigationService = new NavigationService();
ko.applyBindings(new PageViewModel());
Why it does not populate data to Details page,I am binding it using ko.obsevable(),
You haven't bound your detailViewModel.
change
<div data-role="page" id="detailsView">
to
<div data-role="page" id="detailsView" data-bind="with:detailViewModel">